From 36da1617e8dc27ced3cb8e28dd293944e70cd74d Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Wed, 13 Nov 2013 17:10:56 +0200 Subject: [PATCH] "yii\sphinx\Query" has been composed, unit test for it added. --- extensions/sphinx/Query.php | 74 +++++++++++++++- extensions/sphinx/QueryBuilder.php | 25 ++++-- tests/unit/extensions/sphinx/QueryTest.php | 138 +++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+), 11 deletions(-) create mode 100644 tests/unit/extensions/sphinx/QueryTest.php diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index 0d76355..dceddb0 100644 --- a/extensions/sphinx/Query.php +++ b/extensions/sphinx/Query.php @@ -30,10 +30,14 @@ class Query extends Component */ const SORT_DESC = true; + /** + * @var array the columns being selected. For example, `['id', 'group_id']`. + * This is used to construct the SELECT clause in a SQL statement. If not set, if means selecting all columns. + * @see select() + */ public $select; /** - * @var string additional option that should be appended to the 'SELECT' keyword. For example, - * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. + * @var string additional option that should be appended to the 'SELECT' keyword. */ public $selectOption; /** @@ -41,15 +45,45 @@ class Query extends Component * the SELECT clause would be changed to SELECT DISTINCT. */ public $distinct; + /** + * @var array the index(es) to be selected from. For example, `['idx_user', 'idx_post']`. + * This is used to construct the FROM clause in a SQL statement. + * @see from() + */ public $from; + /** + * @var string|array query condition. This refers to the WHERE clause in a SQL statement. + * For example, `MATCH('ipod') AND team = 1`. + * @see where() + */ public $where; + /** + * @var integer maximum number of records to be returned. + * Note: if not set implicit LIMIT 0,20 is present by default. + */ public $limit; + /** + * @var integer zero-based offset from where the records are to be returned. If not set or + * less than 0, it means starting from the beginning. + * Note: implicit LIMIT 0,20 is present by default. + */ public $offset; + /** + * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement. + * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which + * can be either [[Query::SORT_ASC]] or [[Query::SORT_DESC]]. The array may also contain [[Expression]] objects. + * If that is the case, the expressions will be converted into strings without any change. + */ public $orderBy; + /** + * @var array how to group the query results. For example, `['company', 'department']`. + * This is used to construct the GROUP BY clause in a SQL statement. + */ public $groupBy; /** * @var string WITHIN GROUP ORDER BY clause. This is a Sphinx specific extension * that lets you control how the best row within a group will to be selected. + * The possible value matches the [[orderBy]] one. */ public $within; /** @@ -502,13 +536,25 @@ class Query extends Component return $this; } - public function options(array $options) + /** + * Sets the query options. + * @param array $options query options in format: optionName => optionValue + * @return static the query object itself + * @see addOptions() + */ + public function options($options) { $this->options = $options; return $this; } - public function addOptions(array $options) + /** + * Adds additional query options. + * @param array $options query options in format: optionName => optionValue + * @return static the query object itself + * @see options() + */ + public function addOptions($options) { if (is_array($this->options)) { $this->options = array_merge($this->options, $options); @@ -518,12 +564,32 @@ class Query extends Component return $this; } + /** + * Sets the WITHIN GROUP ORDER BY part of the query. + * @param string|array $columns the columns (and the directions) to find best row within a group. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array + * (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return static the query object itself + * @see addWithin() + */ public function within($columns) { $this->within = $this->normalizeOrderBy($columns); return $this; } + /** + * Adds additional WITHIN GROUP ORDER BY columns to the query. + * @param string|array $columns the columns (and the directions) to find best row within a group. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array + * (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return static the query object itself + * @see within() + */ public function addWithin($columns) { $columns = $this->normalizeOrderBy($columns); diff --git a/extensions/sphinx/QueryBuilder.php b/extensions/sphinx/QueryBuilder.php index e6d110e..91e3b62 100644 --- a/extensions/sphinx/QueryBuilder.php +++ b/extensions/sphinx/QueryBuilder.php @@ -62,7 +62,7 @@ class QueryBuilder extends Object $this->buildWithin($query->within), $this->buildOrderBy($query->orderBy), $this->buildLimit($query->limit, $query->offset), - $this->buildOption($query->options), + $this->buildOption($query->options, $params), ]; return [implode($this->separator, array_filter($clauses)), $params]; } @@ -311,9 +311,13 @@ class QueryBuilder extends Object if (!empty($options)) { $optionParts = []; foreach ($options as $name => $value) { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $value; - $optionParts[] = $phName . ' AS ' . $name; + if ($value instanceof Expression) { + $actualValue = $value->expression; + } else { + $actualValue = self::PARAM_PREFIX . count($params); + $params[$actualValue] = $value; + } + $optionParts[] = $actualValue . ' AS ' . $name; } $optionSql = ', ' . implode(', ', $optionParts); } else { @@ -768,17 +772,24 @@ class QueryBuilder extends Object } /** - * @param array $options + * @param array $options query options in format: optionName => optionValue + * @param array $params the binding parameters to be populated * @return string the OPTION clause build from [[query]] */ - public function buildOption(array $options) + public function buildOption($options, &$params) { if (empty($options)) { return ''; } $optionLines = []; foreach ($options as $name => $value) { - $optionLines[] = $name . ' = ' . $value; + if ($value instanceof Expression) { + $actualValue = $value->expression; + } else { + $actualValue = self::PARAM_PREFIX . count($params); + $params[$actualValue] = $value; + } + $optionLines[] = $name . ' = ' . $actualValue; } return 'OPTION ' . implode(', ', $optionLines); } diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php new file mode 100644 index 0000000..36bf95d --- /dev/null +++ b/tests/unit/extensions/sphinx/QueryTest.php @@ -0,0 +1,138 @@ +select('*'); + $this->assertEquals(['*'], $query->select); + $this->assertNull($query->distinct); + $this->assertEquals(null, $query->selectOption); + + $query = new Query; + $query->select('id, name', 'something')->distinct(true); + $this->assertEquals(['id', 'name'], $query->select); + $this->assertTrue($query->distinct); + $this->assertEquals('something', $query->selectOption); + } + + public function testFrom() + { + $query = new Query; + $query->from('tbl_user'); + $this->assertEquals(['tbl_user'], $query->from); + } + + public function testWhere() + { + $query = new Query; + $query->where('id = :id', [':id' => 1]); + $this->assertEquals('id = :id', $query->where); + $this->assertEquals([':id' => 1], $query->params); + + $query->andWhere('name = :name', [':name' => 'something']); + $this->assertEquals(['and', 'id = :id', 'name = :name'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something'], $query->params); + + $query->orWhere('age = :age', [':age' => '30']); + $this->assertEquals(['or', ['and', 'id = :id', 'name = :name'], 'age = :age'], $query->where); + $this->assertEquals([':id' => 1, ':name' => 'something', ':age' => '30'], $query->params); + } + + public function testGroup() + { + $query = new Query; + $query->groupBy('team'); + $this->assertEquals(['team'], $query->groupBy); + + $query->addGroupBy('company'); + $this->assertEquals(['team', 'company'], $query->groupBy); + + $query->addGroupBy('age'); + $this->assertEquals(['team', 'company', 'age'], $query->groupBy); + } + + public function testOrder() + { + $query = new Query; + $query->orderBy('team'); + $this->assertEquals(['team' => false], $query->orderBy); + + $query->addOrderBy('company'); + $this->assertEquals(['team' => false, 'company' => false], $query->orderBy); + + $query->addOrderBy('age'); + $this->assertEquals(['team' => false, 'company' => false, 'age' => false], $query->orderBy); + + $query->addOrderBy(['age' => true]); + $this->assertEquals(['team' => false, 'company' => false, 'age' => true], $query->orderBy); + + $query->addOrderBy('age ASC, company DESC'); + $this->assertEquals(['team' => false, 'company' => true, 'age' => false], $query->orderBy); + } + + public function testLimitOffset() + { + $query = new Query; + $query->limit(10)->offset(5); + $this->assertEquals(10, $query->limit); + $this->assertEquals(5, $query->offset); + } + + public function testWithin() + { + $query = new Query; + $query->within('team'); + $this->assertEquals(['team' => false], $query->within); + + $query->addWithin('company'); + $this->assertEquals(['team' => false, 'company' => false], $query->within); + + $query->addWithin('age'); + $this->assertEquals(['team' => false, 'company' => false, 'age' => false], $query->within); + + $query->addWithin(['age' => true]); + $this->assertEquals(['team' => false, 'company' => false, 'age' => true], $query->within); + + $query->addWithin('age ASC, company DESC'); + $this->assertEquals(['team' => false, 'company' => true, 'age' => false], $query->within); + } + + public function testOptions() + { + $query = new Query; + $options = [ + 'cutoff' => 50, + 'max_matches' => 50, + ]; + $query->options($options); + $this->assertEquals($options, $query->options); + + $newMaxMatches = $options['max_matches'] + 10; + $query->addOptions(['max_matches' => $newMaxMatches]); + $this->assertEquals($newMaxMatches, $query->options['max_matches']); + } + + public function testRun() + { + $connection = $this->getConnection(); + + $query = new Query; + $rows = $query->from('yii2_test_article_index') + ->where("MATCH('about')") + ->options([ + 'cutoff' => 50, + ]) + ->all($connection); + $this->assertNotEmpty($rows); + } +} \ No newline at end of file