diff --git a/framework/db/ar/ActiveFinder.php b/framework/db/ar/ActiveFinder.php index a7daf3d..c30c342 100644 --- a/framework/db/ar/ActiveFinder.php +++ b/framework/db/ar/ActiveFinder.php @@ -103,6 +103,7 @@ class ActiveFinder extends \yii\base\Object private $_joinCount; private $_tableAliases; + private $_hasMany; /** * @param ActiveQuery $query @@ -112,6 +113,7 @@ class ActiveFinder extends \yii\base\Object { $this->_joinCount = 0; $this->_tableAliases = array(); + $this->_hasMany = false; $joinTree = new JoinElement($this->_joinCount++, $query, null, null); $this->buildJoinTree($joinTree, $query->with); $this->initJoinTree($joinTree); @@ -123,7 +125,15 @@ class ActiveFinder extends \yii\base\Object $joinTree->createRecord($row); } - return $query->indexBy !== null ? $joinTree->records : array_values($joinTree->records); + if ($query->indexBy !== null) { + $records = array(); + foreach ($joinTree->records as $record) { + $records[$record[$query->indexBy]] = $record; + } + return $records; + } else { + return array_values($joinTree->records); + } } protected function applyScopes($query) @@ -182,7 +192,8 @@ class ActiveFinder extends \yii\base\Object throw new Exception("$modelClass has no relation named '$with'."); } $relation = clone $relations[$with]; - if ($relation->via !== null && isset($relations[$relation->via])) { + if (is_string($relation->via) && isset($relations[$relation->via])) { + // join via an existing relation $parent2 = $this->buildJoinTree($parent, $relation->via); $relation->via = null; if ($parent2->joinOnly === null) { @@ -213,6 +224,14 @@ class ActiveFinder extends \yii\base\Object } else { $alias = 't'; } + if ($element->query instanceof ActiveRelation) { + if ($element->query->hasMany) { + $this->_hasMany = true; + } + if ($element->parent->query->asArray !== null && $element->query->asArray === null) { + $element->query->asArray = $element->parent->query->asArray; + } + } $count = 0; while (isset($this->_tableAliases[$alias])) { $alias = 't' . $count++; @@ -279,8 +298,9 @@ class ActiveFinder extends \yii\base\Object } if ($element->query instanceof ActiveRelation) { - if ($element->query->via !== null) { - $query->join[] = strtr($element->query->via, $quotedPrefixes); + if (is_array($element->query->via)) { + // todo: join via a pivot table + // $query->join[] = strtr($element->query->via, $quotedPrefixes); } if ($element->query->joinType === null) { @@ -311,21 +331,21 @@ class ActiveFinder extends \yii\base\Object } } - if ($element->query->orderBy !== null) { - if (!is_array($element->query->orderBy)) { - $element->query->orderBy = preg_split('/\s*,\s*/', trim($element->query->orderBy), -1, PREG_SPLIT_NO_EMPTY); + if ($element->query->order !== null) { + if (!is_array($element->query->order)) { + $element->query->order = preg_split('/\s*,\s*/', trim($element->query->order), -1, PREG_SPLIT_NO_EMPTY); } - foreach ($element->query->orderBy as $orderBy) { - $query->orderBy[] = strtr($orderBy, $prefixes); + foreach ($element->query->order as $order) { + $query->order[] = strtr($order, $prefixes); } } - if ($element->query->groupBy !== null) { - if (!is_array($element->query->groupBy)) { - $element->query->groupBy = preg_split('/\s*,\s*/', trim($element->query->groupBy), -1, PREG_SPLIT_NO_EMPTY); + if ($element->query->group !== null) { + if (!is_array($element->query->group)) { + $element->query->group = preg_split('/\s*,\s*/', trim($element->query->group), -1, PREG_SPLIT_NO_EMPTY); } - foreach ($element->query->groupBy as $groupBy) { - $query->groupBy[] = strtr($groupBy, $prefixes); + foreach ($element->query->group as $group) { + $query->group[] = strtr($group, $prefixes); } } diff --git a/framework/db/ar/ActiveRecord.php b/framework/db/ar/ActiveRecord.php index ce5e00f..4ed4736 100644 --- a/framework/db/ar/ActiveRecord.php +++ b/framework/db/ar/ActiveRecord.php @@ -80,12 +80,12 @@ abstract class ActiveRecord extends Model * // find all active customers and order them by their age: * $customers = Customer::find() * ->where(array('status' => 1)) - * ->orderBy('age') + * ->order('age') * ->all(); * // or alternatively: * $customers = Customer::find(array( * 'where' => array('status' => 1), - * 'orderBy' => 'age', + * 'order' => 'age', * ))->all(); * ~~~ * @@ -114,7 +114,7 @@ abstract class ActiveRecord extends Model /** * Creates an [[ActiveQuery]] instance and query by a given SQL statement. * Note that because the SQL statement is already specified, calling further - * query methods (such as `where()`, `orderBy()`) on [[ActiveQuery]] will have no effect. + * query methods (such as `where()`, `order()`) on [[ActiveQuery]] will have no effect. * Methods such as `with()`, `asArray()` can still be called though. * @param string $sql the SQL statement to be executed * @param array $params parameters to be bound to the SQL statement during execution. @@ -267,7 +267,7 @@ abstract class ActiveRecord extends Model * 'manager:Manager' => '@.id = ?.manager_id', * 'assignments:Assignment[]' => array( * 'on' => '@.owner_id = ?.id AND @.status = 1', - * 'orderBy' => '@.create_time DESC', + * 'order' => '@.create_time DESC', * ), * 'projects:Project[]' => array( * 'via' => 'assignments', diff --git a/framework/db/ar/ActiveRelation.php b/framework/db/ar/ActiveRelation.php index a92b509..72b242c 100644 --- a/framework/db/ar/ActiveRelation.php +++ b/framework/db/ar/ActiveRelation.php @@ -23,6 +23,13 @@ class ActiveRelation extends BaseActiveQuery */ public $name; /** + * @var array the columns of the primary and foreign tables that establish the relation. + * The array keys must be columns of the table for this relation, and the array values + * must be the corresponding columns from the primary table. Do not prefix or quote the column names. + * They will be done automatically by Yii. + */ + public $link; + /** * @var boolean whether this relation is a one-many relation */ public $hasMany; @@ -36,7 +43,7 @@ class ActiveRelation extends BaseActiveQuery */ public $on; /** - * @var string + * @var string|array */ public $via; } diff --git a/framework/db/ar/JoinElement.php b/framework/db/ar/JoinElement.php index ed2c1a6..72b740a 100644 --- a/framework/db/ar/JoinElement.php +++ b/framework/db/ar/JoinElement.php @@ -71,26 +71,18 @@ class JoinElement extends \yii\base\Object */ public function createRecord($row) { - if ($this->query->indexBy === null) { - $pk = array(); - foreach ($this->pkAlias as $alias) { - if (isset($row[$alias])) { - $pk[] = $row[$alias]; - } else { - return null; - } - } - $pk = count($pk) === 1 ? $pk[0] : serialize($pk); - } else { - $pk = array_search($this->query->indexBy, $this->columnAliases); - if ($pk !== false) { - $pk = $row[$pk]; + $pk = array(); + foreach ($this->pkAlias as $alias) { + if (isset($row[$alias])) { + $pk[] = $row[$alias]; } else { - throw new Exception("Invalid indexBy: {$this->query->modelClass} has no attribute named '{$this->query->indexBy}'."); + return null; } } + $pk = count($pk) === 1 ? $pk[0] : serialize($pk); // create record + // todo: asArray if (isset($this->records[$pk])) { $record = $this->records[$pk]; } else { @@ -120,7 +112,7 @@ class JoinElement extends \yii\base\Object } if ($child->query->hasMany) { if ($child->query->indexBy !== null) { - $hash = $childRecord->{$child->query->indexBy}; + $hash = $childRecord[$child->query->indexBy]; } else { $hash = serialize($childRecord->getPrimaryKey()); } diff --git a/framework/db/dao/BaseQuery.php b/framework/db/dao/BaseQuery.php index cf7daad..2969490 100644 --- a/framework/db/dao/BaseQuery.php +++ b/framework/db/dao/BaseQuery.php @@ -60,12 +60,12 @@ class BaseQuery extends \yii\base\Object * @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement. * It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`). */ - public $orderBy; + public $order; /** * @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement. * It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`). */ - public $groupBy; + public $group; /** * @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement. * It can either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g. @@ -329,11 +329,11 @@ class BaseQuery extends \yii\base\Object * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). * @return BaseQuery the query object itself - * @see addGroupBy() + * @see addGroup() */ - public function groupBy($columns) + public function group($columns) { - $this->groupBy = $columns; + $this->group = $columns; return $this; } @@ -344,20 +344,20 @@ class BaseQuery extends \yii\base\Object * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). * @return BaseQuery the query object itself - * @see groupBy() + * @see group() */ - public function addGroupBy($columns) + public function addGroup($columns) { - if (empty($this->groupBy)) { - $this->groupBy = $columns; + if (empty($this->group)) { + $this->group = $columns; } else { - if (!is_array($this->groupBy)) { - $this->groupBy = preg_split('/\s*,\s*/', trim($this->groupBy), -1, PREG_SPLIT_NO_EMPTY); + if (!is_array($this->group)) { + $this->group = preg_split('/\s*,\s*/', trim($this->group), -1, PREG_SPLIT_NO_EMPTY); } if (!is_array($columns)) { $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); } - $this->groupBy = array_merge($this->groupBy, $columns); + $this->group = array_merge($this->group, $columns); } return $this; } @@ -427,11 +427,11 @@ class BaseQuery extends \yii\base\Object * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). * @return BaseQuery the query object itself - * @see addOrderBy() + * @see addOrder() */ - public function orderBy($columns) + public function order($columns) { - $this->orderBy = $columns; + $this->order = $columns; return $this; } @@ -442,20 +442,20 @@ class BaseQuery extends \yii\base\Object * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). * @return BaseQuery the query object itself - * @see orderBy() + * @see order() */ - public function addOrderBy($columns) + public function addOrder($columns) { - if (empty($this->orderBy)) { - $this->orderBy = $columns; + if (empty($this->order)) { + $this->order = $columns; } else { - if (!is_array($this->orderBy)) { - $this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY); + if (!is_array($this->order)) { + $this->order = preg_split('/\s*,\s*/', trim($this->order), -1, PREG_SPLIT_NO_EMPTY); } if (!is_array($columns)) { $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); } - $this->orderBy = array_merge($this->orderBy, $columns); + $this->order = array_merge($this->order, $columns); } return $this; } @@ -541,7 +541,7 @@ class BaseQuery extends \yii\base\Object * takes precedence over this query. * - [[where]], [[having]]: the new query's corresponding property value * will be 'AND' together with the existing one. - * - [[params]], [[orderBy]], [[groupBy]], [[join]], [[union]]: the new query's + * - [[params]], [[order]], [[group]], [[join]], [[union]]: the new query's * corresponding property value will be appended to the existing one. * * In general, the merging makes the resulting query more restrictive and specific. @@ -592,12 +592,12 @@ class BaseQuery extends \yii\base\Object $this->addParams($query->params); } - if ($query->orderBy !== null) { - $this->addOrderBy($query->orderBy); + if ($query->order !== null) { + $this->addOrder($query->order); } - if ($query->groupBy !== null) { - $this->addGroupBy($query->groupBy); + if ($query->group !== null) { + $this->addGroup($query->group); } if ($query->join !== null) { diff --git a/framework/db/dao/QueryBuilder.php b/framework/db/dao/QueryBuilder.php index 74d9904..986e0de 100644 --- a/framework/db/dao/QueryBuilder.php +++ b/framework/db/dao/QueryBuilder.php @@ -68,10 +68,10 @@ class QueryBuilder extends \yii\base\Object $this->buildFrom($query->from), $this->buildJoin($query->join), $this->buildWhere($query->where), - $this->buildGroupBy($query->groupBy), + $this->buildGroup($query->group), $this->buildHaving($query->having), $this->buildUnion($query->union), - $this->buildOrderBy($query->orderBy), + $this->buildOrder($query->order), $this->buildLimit($query->limit, $query->offset), ); return implode($this->separator, array_filter($clauses)); @@ -756,7 +756,7 @@ class QueryBuilder extends \yii\base\Object * @param string|array $columns * @return string the GROUP BY clause */ - public function buildGroupBy($columns) + public function buildGroup($columns) { if (empty($columns)) { return ''; @@ -779,7 +779,7 @@ class QueryBuilder extends \yii\base\Object * @param string|array $columns * @return string the ORDER BY clause built from [[query]]. */ - public function buildOrderBy($columns) + public function buildOrder($columns) { if (empty($columns)) { return ''; diff --git a/tests/unit/data/ar/Customer.php b/tests/unit/data/ar/Customer.php index 0a2e121..5c108da 100644 --- a/tests/unit/data/ar/Customer.php +++ b/tests/unit/data/ar/Customer.php @@ -25,7 +25,7 @@ class Customer extends ActiveRecord { return array( 'active' => function($q) { - return $q->andWhere('@.status = 1'); + return $q->andWhere('@.status = ' . self::STATUS_ACTIVE); }, ); } diff --git a/tests/unit/framework/db/ar/ActiveRecordTest.php b/tests/unit/framework/db/ar/ActiveRecordTest.php index b3449f3..e9bbb67 100644 --- a/tests/unit/framework/db/ar/ActiveRecordTest.php +++ b/tests/unit/framework/db/ar/ActiveRecordTest.php @@ -200,7 +200,7 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase $this->assertTrue($customer instanceof Customer); $this->assertEquals('user3', $customer->name); - $customer = Customer::find()->select('id')->orderBy('id DESC')->one(); + $customer = Customer::find()->select('id')->order('id DESC')->one(); $this->assertTrue($customer instanceof Customer); $this->assertEquals(3, $customer->id); $this->assertEquals(null, $customer->name); @@ -214,23 +214,27 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase $this->assertEquals(2, count($customers)); // asArray - $customers = Customer::find()->orderBy('id')->asArray()->all(); + $customers = Customer::find()->order('id')->asArray()->all(); $this->assertEquals('user2', $customers[1]['name']); // indexBy - $customers = Customer::find()->orderBy('id')->indexBy('name')->all(); + $customers = Customer::find()->order('id')->indexBy('name')->all(); $this->assertEquals(2, $customers['user2']['id']); } public function testEagerLoading() { - $customers = Customer::find()->with('orders')->orderBy('@.id')->all(); + $customers = Customer::find()->with('orders')->order('@.id')->all(); $this->assertEquals(3, count($customers)); $this->assertEquals(1, count($customers[0]->orders)); $this->assertEquals(2, count($customers[1]->orders)); $this->assertEquals(0, count($customers[2]->orders)); - $customers = Customer::find()->with('orders.customer')->orderBy('@.id')->all(); + $customers = Customer::find()->with('orders.customer')->order('@.id')->all(); + $this->assertEquals(3, count($customers)); + $this->assertEquals(1, $customers[0]->orders[0]->customer->id); + $this->assertEquals(2, $customers[1]->orders[0]->customer->id); + $this->assertEquals(2, $customers[1]->orders[1]->customer->id); } /* diff --git a/tests/unit/framework/db/dao/CommandTest.php b/tests/unit/framework/db/dao/CommandTest.php index 4971311..0c4ca12 100644 --- a/tests/unit/framework/db/dao/CommandTest.php +++ b/tests/unit/framework/db/dao/CommandTest.php @@ -21,19 +21,6 @@ class CommandTest extends \yiiunit\MysqlTestCase $sql = 'SELECT * FROM tbl_customer'; $command = $db->createCommand($sql); $this->assertEquals($sql, $command->sql); - - // Query object - $query = new Query; - $query->select('id')->from('tbl_customer'); - $command = $db->createCommand($query); - $this->assertEquals("SELECT `id` FROM `tbl_customer`", $command->sql); - - // array - $command = $db->createCommand(array( - 'select' => 'name', - 'from' => 'tbl_customer', - )); - $this->assertEquals("SELECT `name` FROM `tbl_customer`", $command->sql); } function testGetSetSql() diff --git a/tests/unit/framework/db/dao/QueryTest.php b/tests/unit/framework/db/dao/QueryTest.php index cbfd2ff..8779cd1 100644 --- a/tests/unit/framework/db/dao/QueryTest.php +++ b/tests/unit/framework/db/dao/QueryTest.php @@ -13,7 +13,7 @@ class QueryTest extends \yiiunit\MysqlTestCase { // default $query = new Query; - $query->select(); + $query->select('*'); $this->assertEquals('*', $query->select); $this->assertNull($query->distinct); $this->assertEquals(null, $query->selectOption); @@ -53,17 +53,17 @@ class QueryTest extends \yiiunit\MysqlTestCase } - function testGroupBy() + function testGroup() { $query = new Query; - $query->groupBy('team'); - $this->assertEquals('team', $query->groupBy); + $query->group('team'); + $this->assertEquals('team', $query->group); - $query->addGroupBy('company'); - $this->assertEquals(array('team', 'company'), $query->groupBy); + $query->addGroup('company'); + $this->assertEquals(array('team', 'company'), $query->group); - $query->addGroupBy('age'); - $this->assertEquals(array('team', 'company', 'age'), $query->groupBy); + $query->addGroup('age'); + $this->assertEquals(array('team', 'company', 'age'), $query->group); } function testHaving() @@ -82,17 +82,17 @@ class QueryTest extends \yiiunit\MysqlTestCase $this->assertEquals(array(':id' => 1, ':name' => 'something', ':age' => '30'), $query->params); } - function testOrderBy() + function testOrder() { $query = new Query; - $query->orderBy('team'); - $this->assertEquals('team', $query->orderBy); + $query->order('team'); + $this->assertEquals('team', $query->order); - $query->addOrderBy('company'); - $this->assertEquals(array('team', 'company'), $query->orderBy); + $query->addOrder('company'); + $this->assertEquals(array('team', 'company'), $query->order); - $query->addOrderBy('age'); - $this->assertEquals(array('team', 'company', 'age'), $query->orderBy); + $query->addOrder('age'); + $this->assertEquals(array('team', 'company', 'age'), $query->order); } function testLimitOffset()