Browse Source

...

tags/2.0.0-beta
Qiang Xue 13 years ago
parent
commit
c5cdcab6c6
  1. 14
      framework/db/ar/ActiveMetaData.php
  2. 98
      framework/db/ar/ActiveQuery.php
  3. 165
      framework/db/ar/ActiveRecord.php
  4. 93
      framework/db/ar/JoinElement.php
  5. 2
      framework/db/dao/QueryBuilder.php
  6. 9
      tests/unit/data/ar/Customer.php
  7. 9
      tests/unit/data/ar/Order.php
  8. 2
      tests/unit/data/mysql.sql
  9. 12
      tests/unit/framework/db/ar/ActiveRecordTest.php

14
framework/db/ar/ActiveMetaData.php

@ -18,6 +18,10 @@ class ActiveMetaData
*/ */
public $table; public $table;
/** /**
* @var string the model class name
*/
public $modelClass;
/**
* @var array list of relations * @var array list of relations
*/ */
public $relations = array(); public $relations = array();
@ -30,6 +34,7 @@ class ActiveMetaData
{ {
$tableName = $modelClass::tableName(); $tableName = $modelClass::tableName();
$this->table = $modelClass::getDbConnection()->getDriver()->getTableSchema($tableName); $this->table = $modelClass::getDbConnection()->getDriver()->getTableSchema($tableName);
$this->modelClass = $modelClass;
if ($this->table === null) { if ($this->table === null) {
throw new Exception("Unable to find table '$tableName' for ActiveRecord class '$modelClass'."); throw new Exception("Unable to find table '$tableName' for ActiveRecord class '$modelClass'.");
} }
@ -64,11 +69,16 @@ class ActiveMetaData
} }
$relation = ActiveRelation::newInstance($config); $relation = ActiveRelation::newInstance($config);
$relation->name = $matches[1]; $relation->name = $matches[1];
$relation->modelClass = '\\' . $matches[2]; $modelClass = $matches[2];
if (strpos($modelClass, '\\') !== false) {
$relation->modelClass = '\\' . ltrim($modelClass, '\\');
} else {
$relation->modelClass = dirname($this->modelClass) . '\\' . $modelClass;
}
$relation->hasMany = isset($matches[3]); $relation->hasMany = isset($matches[3]);
$this->relations[$relation->name] = $relation; $this->relations[$relation->name] = $relation;
} else { } else {
throw new Exception("Relation name in bad format: $name"); throw new Exception("{$this->modelClass} has an invalid relation: $name");
} }
} }
} }

98
framework/db/ar/ActiveQuery.php

@ -18,6 +18,18 @@ use yii\db\Exception;
* ActiveFinder.php is ... * ActiveFinder.php is ...
* todo: add SQL monitor * todo: add SQL monitor
* *
* todo: add ActiveQueryBuilder
* todo: quote join/on part of the relational query
* todo: modify QueryBuilder about join() methods
* todo: unify ActiveQuery and ActiveRelation in query building process
* todo: intelligent table aliasing (first table name, then relation name, finally t?)
* todo: allow using tokens in primary query fragments
* todo: findBySql
* todo: base limited
* todo: lazy loading
* todo: scope
* todo: test via option
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
@ -692,7 +704,7 @@ class ActiveQuery extends \yii\base\Object implements \IteratorAggregate, \Array
{ {
if (!empty($this->with)) { if (!empty($this->with)) {
// todo: handle findBySql() and limit cases // todo: handle findBySql() and limit cases
$this->initRelationalQuery(); $joinTree = $this->buildRelationalQuery();
} }
if ($this->sql === null) { if ($this->sql === null) {
@ -703,9 +715,16 @@ class ActiveQuery extends \yii\base\Object implements \IteratorAggregate, \Array
$command = $this->getDbConnection()->createCommand($this->sql); $command = $this->getDbConnection()->createCommand($this->sql);
$command->bindValues($this->query->params); $command->bindValues($this->query->params);
} }
echo $command->sql;
$rows = $command->queryAll(); $rows = $command->queryAll();
if (!empty($this->with)) {
foreach ($rows as $row) {
$joinTree->populateData($row);
}
return array_values($joinTree->records);
}
if ($this->asArray) { if ($this->asArray) {
if ($this->indexBy === null) { if ($this->indexBy === null) {
return $rows; return $rows;
@ -759,45 +778,76 @@ class ActiveQuery extends \yii\base\Object implements \IteratorAggregate, \Array
} }
} }
protected function initRelationalQuery() protected function buildRelationalQuery()
{ {
$joinTree = new JoinElement(null, null); $joinTree = new JoinElement($this, null, null);
$joinCount = 0; $this->buildJoinTree($joinTree, $this->with);
$this->buildJoinTree($joinTree, $this->with, $joinCount); $this->buildTableAlias($joinTree);
$query = new Query; $query = new Query;
foreach ($joinTree->children as $child) { foreach ($joinTree->children as $child) {
$child->buildQuery($query); $child->buildQuery($query);
} }
$select = $joinTree->buildSelect($this->query->select);
if (!empty($query->select)) {
$this->query->select = array_merge($select, $query->select);
} else {
$this->query->select = $select;
}
if (!empty($query->where)) {
$this->query->andWhere('(' . implode(') AND (', $query->where) . ')');
}
if (!empty($query->having)) {
$this->query->andHaving('(' . implode(') AND (', $query->having) . ')');
}
if (!empty($query->join)) {
if ($this->query->join === null) {
$this->query->join = $query->join;
} else {
$this->query->join = array_merge($this->query->join, $query->join);
}
}
if (!empty($query->orderBy)) {
$this->query->addOrderBy($query->orderBy);
}
if (!empty($query->groupBy)) {
$this->query->addGroupBy($query->groupBy);
}
if (!empty($query->params)) {
$this->query->addParams($query->params);
}
return $joinTree;
} }
/** /**
* @param JoinElement $parent * @param JoinElement $parent
* @param array|string $with * @param array|string $with
* @param integer $joinCount
* @param array $config * @param array $config
* @return null|JoinElement * @return null|JoinElement
* @throws \yii\db\Exception * @throws \yii\db\Exception
*/ */
protected function buildJoinTree($parent, $with, &$joinCount, $config = array()) protected function buildJoinTree($parent, $with, $config = array())
{ {
if (is_array($with)) { if (is_array($with)) {
foreach ($with as $name => $value) { foreach ($with as $name => $value) {
if (is_string($value)) { if (is_string($value)) {
$this->buildJoinTree($parent, $value, $joinCount); $this->buildJoinTree($parent, $value);
} elseif (is_string($name) && is_array($value)) { } elseif (is_string($name) && is_array($value)) {
$this->buildJoinTree($parent, $name, $joinCount, $value); $this->buildJoinTree($parent, $name, $value);
} }
} }
return null; return null;
} }
if (($pos = strrpos($with, '.')) !== false) { if (($pos = strrpos($with, '.')) !== false) {
$parent = $this->buildJoinTree($parent, substr($with, 0, $pos), $joinCount); $parent = $this->buildJoinTree($parent, substr($with, 0, $pos));
$with = substr($with, $pos + 1); $with = substr($with, $pos + 1);
} }
if (isset($parent->children[$with])) { if (isset($parent->children[$with])) {
$child = $parent->children[$with]; $child = $parent->children[$with];
$child->joinOnly = false;
} else { } else {
$modelClass = $parent->relation->modelClass; $modelClass = $parent->relation->modelClass;
$relations = $modelClass::getMetaData()->relations; $relations = $modelClass::getMetaData()->relations;
@ -805,20 +855,32 @@ class ActiveQuery extends \yii\base\Object implements \IteratorAggregate, \Array
throw new Exception("$modelClass has no relation named '$with'."); throw new Exception("$modelClass has no relation named '$with'.");
} }
$relation = clone $relations[$with]; $relation = clone $relations[$with];
if ($relation->tableAlias === null) { if ($relation->via !== null && isset($relations[$relation->via])) {
$relation->tableAlias = 't' . ($joinCount++); $relation->via = null;
$parent2 = $this->buildJoinTree($parent, $relation->via);
if ($parent2->joinOnly === null) {
$parent2->joinOnly = true;
}
$child = new JoinElement($relation, $parent2, $parent);
} else {
$child = new JoinElement($relation, $parent, $parent);
} }
$child = new JoinElement($parent, $relation);
} }
foreach ($config as $name => $value) { foreach ($config as $name => $value) {
$child->relation->$name = $value; $child->relation->$name = $value;
} }
if (!empty($child->relation->with)) {
$this->buildJoinTree($child, $child->relation->with, $joinCount);
}
return $child; return $child;
} }
protected function buildTableAlias($element, &$count = 0)
{
if ($element->relation->tableAlias === null) {
$element->relation->tableAlias = 't' . ($count++);
}
foreach ($element->children as $child) {
$this->buildTableAlias($child, $count);
}
}
} }

165
framework/db/ar/ActiveRecord.php

@ -167,15 +167,15 @@ abstract class ActiveRecord extends \yii\base\Model
* *
* ~~~ * ~~~
* return array( * return array(
* 'manager:Manager' => '?.manager_id = manager.id', * 'manager:Manager' => '?.manager_id = manager.id',
* 'assignments:Assignment[]' => array( * 'assignments:Assignment[]' => array(
* 'on' => '?.id = assignments.owner_id AND assignments.status=1', * 'on' => '?.id = assignments.owner_id AND assignments.status=1',
* 'orderBy' => 'assignments.create_time DESC', * 'orderBy' => 'assignments.create_time DESC',
* ), * ),
* 'projects:Project[]' => array( * 'projects:Project[]' => array(
* 'via' => 'assignments', * 'via' => 'assignments',
* 'on' => 'projects.id = assignments.project_id', * 'on' => 'projects.id = assignments.project_id',
* ), * ),
* ); * );
* ~~~ * ~~~
* *
@ -373,21 +373,13 @@ abstract class ActiveRecord extends \yii\base\Model
{ {
if (isset($this->_attributes[$name])) { if (isset($this->_attributes[$name])) {
return true; return true;
} } elseif (isset($this->getMetaData()->columns[$name])) {
elseif (isset($this->getMetaData()->columns[$name]))
{
return false; return false;
} } elseif (isset($this->_related[$name])) {
elseif (isset($this->_related[$name]))
{
return true; return true;
} } elseif (isset($this->getMetaData()->relations[$name])) {
elseif (isset($this->getMetaData()->relations[$name]))
{
return $this->getRelatedRecord($name) !== null; return $this->getRelatedRecord($name) !== null;
} } else {
else
{
return parent::__isset($name); return parent::__isset($name);
} }
} }
@ -402,13 +394,9 @@ abstract class ActiveRecord extends \yii\base\Model
{ {
if (isset($this->getMetaData()->columns[$name])) { if (isset($this->getMetaData()->columns[$name])) {
unset($this->_attributes[$name]); unset($this->_attributes[$name]);
} } elseif (isset($this->getMetaData()->relations[$name])) {
elseif (isset($this->getMetaData()->relations[$name]))
{
unset($this->_related[$name]); unset($this->_related[$name]);
} } else {
else
{
parent::__unset($name); parent::__unset($name);
} }
} }
@ -426,9 +414,7 @@ abstract class ActiveRecord extends \yii\base\Model
if (isset($this->getMetaData()->relations[$name])) { if (isset($this->getMetaData()->relations[$name])) {
if (empty($parameters)) { if (empty($parameters)) {
return $this->getRelatedRecord($name, false); return $this->getRelatedRecord($name, false);
} } else {
else
{
return $this->getRelatedRecord($name, false, $parameters[0]); return $this->getRelatedRecord($name, false, $parameters[0]);
} }
} }
@ -442,6 +428,20 @@ abstract class ActiveRecord extends \yii\base\Model
return parent::__call($name, $parameters); return parent::__call($name, $parameters);
} }
public function initRelatedRecord($relation)
{
$this->_related[$relation->name] = $relation->hasMany ? array() : null;
}
public function addRelatedRecord($relation, $record)
{
if ($relation->hasMany) {
$this->_related[$relation->name][] = $record;
} else {
$this->_related[$relation->name] = $record;
}
}
/** /**
* Returns the related record(s). * Returns the related record(s).
* This method will return the related record(s) of the current record. * This method will return the related record(s) of the current record.
@ -464,8 +464,7 @@ abstract class ActiveRecord extends \yii\base\Model
$md = $this->getMetaData(); $md = $this->getMetaData();
if (!isset($md->relations[$name])) { if (!isset($md->relations[$name])) {
throw new Exception(Yii::t('yii', '{class} does not have relation "{name}".', throw new Exception(Yii::t('yii', '{class} does not have relation "{name}".', array('{class}' => get_class($this), '{name}' => $name)));
array('{class}' => get_class($this), '{name}' => $name)));
} }
Yii::trace('lazy loading ' . get_class($this) . '.' . $name, 'system.db.ar.ActiveRecord'); Yii::trace('lazy loading ' . get_class($this) . '.' . $name, 'system.db.ar.ActiveRecord');
@ -481,8 +480,7 @@ abstract class ActiveRecord extends \yii\base\Model
$save = $this->_related[$name]; $save = $this->_related[$name];
} }
$r = array($name => $params); $r = array($name => $params);
} else } else {
{
$r = $name; $r = $name;
} }
unset($this->_related[$name]); unset($this->_related[$name]);
@ -493,13 +491,9 @@ abstract class ActiveRecord extends \yii\base\Model
if (!isset($this->_related[$name])) { if (!isset($this->_related[$name])) {
if ($relation instanceof CHasManyRelation) { if ($relation instanceof CHasManyRelation) {
$this->_related[$name] = array(); $this->_related[$name] = array();
} } elseif ($relation instanceof CStatRelation) {
elseif ($relation instanceof CStatRelation)
{
$this->_related[$name] = $relation->defaultValue; $this->_related[$name] = $relation->defaultValue;
} } else {
else
{
$this->_related[$name] = null; $this->_related[$name] = null;
} }
} }
@ -508,14 +502,11 @@ abstract class ActiveRecord extends \yii\base\Model
$results = $this->_related[$name]; $results = $this->_related[$name];
if ($exists) { if ($exists) {
$this->_related[$name] = $save; $this->_related[$name] = $save;
} } else {
else
{
unset($this->_related[$name]); unset($this->_related[$name]);
} }
return $results; return $results;
} else } else {
{
return $this->_related[$name]; return $this->_related[$name];
} }
} }
@ -617,9 +608,7 @@ abstract class ActiveRecord extends \yii\base\Model
{ {
if (property_exists($this, $name)) { if (property_exists($this, $name)) {
return $this->$name; return $this->$name;
} } elseif (isset($this->_attributes[$name])) {
elseif (isset($this->_attributes[$name]))
{
return $this->_attributes[$name]; return $this->_attributes[$name];
} }
} }
@ -636,13 +625,9 @@ abstract class ActiveRecord extends \yii\base\Model
{ {
if (property_exists($this, $name)) { if (property_exists($this, $name)) {
$this->$name = $value; $this->$name = $value;
} } elseif (isset($this->getMetaData()->table->columns[$name])) {
elseif (isset($this->getMetaData()->table->columns[$name]))
{
$this->_attributes[$name] = $value; $this->_attributes[$name] = $value;
} } else {
else
{
return false; return false;
} }
return true; return true;
@ -660,31 +645,24 @@ abstract class ActiveRecord extends \yii\base\Model
public function getAttributes($names = true) public function getAttributes($names = true)
{ {
$attributes = $this->_attributes; $attributes = $this->_attributes;
foreach ($this->getMetaData()->columns as $name => $column) foreach ($this->getMetaData()->columns as $name => $column) {
{
if (property_exists($this, $name)) { if (property_exists($this, $name)) {
$attributes[$name] = $this->$name; $attributes[$name] = $this->$name;
} } elseif ($names === true && !isset($attributes[$name])) {
elseif ($names === true && !isset($attributes[$name]))
{
$attributes[$name] = null; $attributes[$name] = null;
} }
} }
if (is_array($names)) { if (is_array($names)) {
$attrs = array(); $attrs = array();
foreach ($names as $name) foreach ($names as $name) {
{
if (property_exists($this, $name)) { if (property_exists($this, $name)) {
$attrs[$name] = $this->$name; $attrs[$name] = $this->$name;
} } else {
else
{
$attrs[$name] = isset($attributes[$name]) ? $attributes[$name] : null; $attrs[$name] = isset($attributes[$name]) ? $attributes[$name] : null;
} }
} }
return $attrs; return $attrs;
} else } else {
{
return $attributes; return $attributes;
} }
} }
@ -716,9 +694,7 @@ abstract class ActiveRecord extends \yii\base\Model
{ {
if (!$runValidation || $this->validate($attributes)) { if (!$runValidation || $this->validate($attributes)) {
return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes); return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
} } else {
else
{
return false; return false;
} }
} }
@ -798,8 +774,7 @@ abstract class ActiveRecord extends \yii\base\Model
$event = new CModelEvent($this); $event = new CModelEvent($this);
$this->onBeforeSave($event); $this->onBeforeSave($event);
return $event->isValid; return $event->isValid;
} else } else {
{
return true; return true;
} }
} }
@ -830,8 +805,7 @@ abstract class ActiveRecord extends \yii\base\Model
$event = new CModelEvent($this); $event = new CModelEvent($this);
$this->onBeforeDelete($event); $this->onBeforeDelete($event);
return $event->isValid; return $event->isValid;
} else } else {
{
return true; return true;
} }
} }
@ -910,11 +884,8 @@ abstract class ActiveRecord extends \yii\base\Model
if ($table->sequenceName !== null) { if ($table->sequenceName !== null) {
if (is_string($primaryKey) && $this->$primaryKey === null) { if (is_string($primaryKey) && $this->$primaryKey === null) {
$this->$primaryKey = $builder->getLastInsertID($table); $this->$primaryKey = $builder->getLastInsertID($table);
} } elseif (is_array($primaryKey)) {
elseif (is_array($primaryKey)) foreach ($primaryKey as $pk) {
{
foreach ($primaryKey as $pk)
{
if ($this->$pk === null) { if ($this->$pk === null) {
$this->$pk = $builder->getLastInsertID($table); $this->$pk = $builder->getLastInsertID($table);
break; break;
@ -955,8 +926,7 @@ abstract class ActiveRecord extends \yii\base\Model
$this->_pk = $this->getPrimaryKey(); $this->_pk = $this->getPrimaryKey();
$this->afterSave(); $this->afterSave();
return true; return true;
} else } else {
{
return false; return false;
} }
} }
@ -984,13 +954,10 @@ abstract class ActiveRecord extends \yii\base\Model
if (!$this->getIsNewRecord()) { if (!$this->getIsNewRecord()) {
Yii::trace(get_class($this) . '.saveAttributes()', 'system.db.ar.ActiveRecord'); Yii::trace(get_class($this) . '.saveAttributes()', 'system.db.ar.ActiveRecord');
$values = array(); $values = array();
foreach ($attributes as $name => $value) foreach ($attributes as $name => $value) {
{
if (is_integer($name)) { if (is_integer($name)) {
$values[$value] = $this->$value; $values[$value] = $this->$value;
} } else {
else
{
$values[$name] = $this->$name = $value; $values[$name] = $this->$name = $value;
} }
} }
@ -1000,12 +967,10 @@ abstract class ActiveRecord extends \yii\base\Model
if ($this->updateByPk($this->getOldPrimaryKey(), $values) > 0) { if ($this->updateByPk($this->getOldPrimaryKey(), $values) > 0) {
$this->_pk = $this->getPrimaryKey(); $this->_pk = $this->getPrimaryKey();
return true; return true;
} else } else {
{
return false; return false;
} }
} else } else {
{
throw new Exception(Yii::t('yii', 'The active record cannot be updated because it is new.')); throw new Exception(Yii::t('yii', 'The active record cannot be updated because it is new.'));
} }
} }
@ -1032,13 +997,11 @@ abstract class ActiveRecord extends \yii\base\Model
$criteria = $builder->createPkCriteria($table, $this->getOldPrimaryKey()); $criteria = $builder->createPkCriteria($table, $this->getOldPrimaryKey());
$command = $builder->createUpdateCounterCommand($this->getTableSchema(), $counters, $criteria); $command = $builder->createUpdateCounterCommand($this->getTableSchema(), $counters, $criteria);
if ($command->execute()) { if ($command->execute()) {
foreach ($counters as $name => $value) foreach ($counters as $name => $value) {
{
$this->$name = $this->$name + $value; $this->$name = $this->$name + $value;
} }
return true; return true;
} else } else {
{
return false; return false;
} }
} }
@ -1056,12 +1019,10 @@ abstract class ActiveRecord extends \yii\base\Model
$result = $this->deleteByPk($this->getPrimaryKey()) > 0; $result = $this->deleteByPk($this->getPrimaryKey()) > 0;
$this->afterDelete(); $this->afterDelete();
return $result; return $result;
} else } else {
{
return false; return false;
} }
} else } else {
{
throw new Exception(Yii::t('yii', 'The active record cannot be deleted because it is new.')); throw new Exception(Yii::t('yii', 'The active record cannot be deleted because it is new.'));
} }
} }
@ -1076,19 +1037,15 @@ abstract class ActiveRecord extends \yii\base\Model
if (!$this->getIsNewRecord() && ($record = $this->findByPk($this->getPrimaryKey())) !== null) { if (!$this->getIsNewRecord() && ($record = $this->findByPk($this->getPrimaryKey())) !== null) {
$this->_attributes = array(); $this->_attributes = array();
$this->_related = array(); $this->_related = array();
foreach ($this->getMetaData()->columns as $name => $column) foreach ($this->getMetaData()->columns as $name => $column) {
{
if (property_exists($this, $name)) { if (property_exists($this, $name)) {
$this->$name = $record->$name; $this->$name = $record->$name;
} } else {
else
{
$this->_attributes[$name] = $record->$name; $this->_attributes[$name] = $record->$name;
} }
} }
return true; return true;
} else } else {
{
return false; return false;
} }
} }

93
framework/db/ar/JoinElement.php

@ -22,13 +22,21 @@ class JoinElement extends \yii\base\Object
*/ */
public $relation; public $relation;
/** /**
* @var JoinElement * @var JoinElement the parent element that this element needs to join with
*/ */
public $parent; public $parent;
/** /**
* @var JoinElement[] * @var JoinElement[] the child elements that need to join with this element
*/ */
public $children = array(); public $children = array();
/**
* @var JoinElement[] the child elements that have relations declared in the AR class of this element
*/
public $relatedChildren = array();
/**
* @var boolean whether this element is only for join purpose. If true, data will also be populated into the AR of this element.
*/
public $joinOnly;
public $columnAliases = array(); // alias => original name public $columnAliases = array(); // alias => original name
public $pkAlias = array(); // original name => alias public $pkAlias = array(); // original name => alias
@ -36,12 +44,13 @@ class JoinElement extends \yii\base\Object
public $records; public $records;
public $relatedRecords; public $relatedRecords;
public function __construct($parent, $relation) public function __construct($relation, $parent, $relatedParent)
{ {
$this->relation = $relation;
if ($parent !== null) { if ($parent !== null) {
$this->parent = $parent; $this->parent = $parent;
$this->relation = $relation;
$parent->children[$relation->name] = $this; $parent->children[$relation->name] = $this;
$relatedParent->relatedChildren[$relation->name] = $this;
} }
} }
@ -68,18 +77,18 @@ class JoinElement extends \yii\base\Object
} }
} }
$modelClass = $this->relation->modelClass; $modelClass = $this->relation->modelClass;
$record = $modelClass::populateRecord($attributes); $record = $modelClass::populateData($attributes);
foreach ($this->children as $child) { foreach ($this->children as $child) {
if ($child->relation->select !== false) { if ($child->relation->select !== false) {
$record->initRelation($child->relation); $record->initRelatedRecord($child->relation);
} }
} }
$this->records[$pk] = $record; $this->records[$pk] = $record;
} }
// populate child records // populate child records
foreach ($this->children as $child) { foreach ($this->relatedChildren as $child) {
if ($child->relation->select === false) { if ($child->relation->select === false || $child->joinOnly) {
continue; continue;
} }
$childRecord = $child->populateData($row); $childRecord = $child->populateData($row);
@ -102,10 +111,10 @@ class JoinElement extends \yii\base\Object
public function buildQuery($query) public function buildQuery($query)
{ {
$tokens = array( $tokens = array(
'@.' => $this->relation->tableAlias, '@.' => $this->relation->tableAlias . '.',
'?.' => $this->parent->relation->tableAlias, '?.' => $this->parent->relation->tableAlias . '.',
); );
foreach ($this->buildSelect() as $column) { foreach ($this->buildSelect($this->relation->select) as $column) {
$query->select[] = strtr($column, $tokens); $query->select[] = strtr($column, $tokens);
} }
@ -117,32 +126,60 @@ class JoinElement extends \yii\base\Object
$query->having[] = strtr($this->relation->having, $tokens); $query->having[] = strtr($this->relation->having, $tokens);
} }
/* if ($this->relation->via !== null) {
* joinType; $query->join[] = $this->relation->via;
on; }
via;
orderby $modelClass = $this->relation->modelClass;
groupby $tableName = $modelClass::tableName();
join $joinType = $this->relation->joinType === null ? 'LEFT JOIN' : $this->relation->joinType;
params $join = "$joinType $tableName {$this->relation->tableAlias}";
*/ if ($this->relation->on !== null) {
$join .= ' ON ' . strtr($this->relation->on, $tokens);
}
$query->join[] = $join;
if ($this->relation->join !== null) {
$query->join[] = strtr($this->relation->join, $tokens);
}
// todo: convert orderBy to array first
if ($this->relation->orderBy !== null) {
$query->orderBy[] = strtr($this->relation->orderBy, $tokens);
}
// todo: convert groupBy to array first
if ($this->relation->groupBy !== null) {
$query->groupBy[] = strtr($this->relation->groupBy, $tokens);
}
if ($this->relation->params !== null) {
foreach ($this->relation->params as $name => $value) {
if (is_integer($name)) {
$query->params[] = $value;
} else {
$query->params[$name] = $value;
}
}
}
foreach ($this->children as $child) { foreach ($this->children as $child) {
$child->buildQuery($query); $child->buildQuery($query);
} }
} }
public function buildSelect() public function buildSelect($select)
{ {
$modelClass = $this->relation->modelClass; $modelClass = $this->relation->modelClass;
$tableSchema = $modelClass::getMetaData()->table; $tableSchema = $modelClass::getMetaData()->table;
$select = $this->relation->select;
$columns = array(); $columns = array();
$columnCount = 0; $columnCount = 0;
$prefix = $this->relation->tableAlias;
if (empty($select) || $select === '*') { if (empty($select) || $select === '*') {
foreach ($tableSchema->columns as $column) { foreach ($tableSchema->columns as $column) {
$alias = $this->tableAlias . '_' . ($columnCount++); $alias = $this->relation->tableAlias . '_' . ($columnCount++);
$columns[] = "{$column->name} AS $alias"; $columns[] = "$prefix.{$column->name} AS $alias";
$this->columnAliases[$alias] = $column->name; $this->columnAliases[$alias] = $column->name;
if ($column->isPrimaryKey) { if ($column->isPrimaryKey) {
$this->pkAlias[$column->name] = $alias; $this->pkAlias[$column->name] = $alias;
@ -153,8 +190,8 @@ class JoinElement extends \yii\base\Object
$select = explode(',', $select); $select = explode(',', $select);
} }
foreach ($tableSchema->primaryKey as $column) { foreach ($tableSchema->primaryKey as $column) {
$alias = $this->tableAlias . '_' . ($columnCount++); $alias = $this->relation->tableAlias . '_' . ($columnCount++);
$columns[] = "$column AS $alias"; $columns[] = "$prefix.$column AS $alias";
$this->pkAlias[$column] = $alias; $this->pkAlias[$column] = $alias;
} }
foreach ($select as $column) { foreach ($select as $column) {
@ -164,8 +201,8 @@ class JoinElement extends \yii\base\Object
$this->columnAliases[$matches[2]] = $matches[2]; $this->columnAliases[$matches[2]] = $matches[2];
$columns[] = $column; $columns[] = $column;
} elseif (!isset($this->pkAlias[$column])) { } elseif (!isset($this->pkAlias[$column])) {
$alias = $this->tableAlias . '_' . ($columnCount++); $alias = $this->relation->tableAlias . '_' . ($columnCount++);
$columns[] = "$column AS $alias"; $columns[] = "$prefix.$column AS $alias";
$this->columnAliases[$alias] = $column; $this->columnAliases[$alias] = $column;
} }
} }

2
framework/db/dao/QueryBuilder.php

@ -656,7 +656,7 @@ class QueryBuilder extends \yii\base\Object
if (is_object($column)) { if (is_object($column)) {
$columns[$i] = (string)$column; $columns[$i] = (string)$column;
} elseif (strpos($column, '(') === false) { } elseif (strpos($column, '(') === false) {
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-\.])$/', $column, $matches)) { if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
$columns[$i] = $driver->quoteColumnName($matches[1]) . ' AS ' . $driver->quoteSimpleColumnName($matches[2]); $columns[$i] = $driver->quoteColumnName($matches[1]) . ' AS ' . $driver->quoteSimpleColumnName($matches[2]);
} else { } else {
$columns[$i] = $driver->quoteColumnName($column); $columns[$i] = $driver->quoteColumnName($column);

9
tests/unit/data/ar/Customer.php

@ -8,4 +8,13 @@ class Customer extends ActiveRecord
{ {
return 'tbl_customer'; return 'tbl_customer';
} }
public static function relations()
{
return array(
'orders:Order[]' => array(
'on' => '@.customer_id = ?.id',
),
);
}
} }

9
tests/unit/data/ar/Order.php

@ -8,4 +8,13 @@ class Order extends ActiveRecord
{ {
return 'tbl_order'; return 'tbl_order';
} }
public static function relations()
{
return array(
'customer:Customer' => array(
'on' => '@.id = ?.customer_id',
),
);
}
} }

2
tests/unit/data/mysql.sql

@ -86,7 +86,7 @@ INSERT INTO tbl_item (name, category_id) VALUES ('Cars', 2);
INSERT INTO tbl_order (customer_id, create_time, total) VALUES (1, 1325282384, 110.0); INSERT INTO tbl_order (customer_id, create_time, total) VALUES (1, 1325282384, 110.0);
INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325334482, 33.0); INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325334482, 33.0);
INSERT INTO tbl_order (customer_id, create_time, total) VALUES (3, 1325502201, 40.0); INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325502201, 40.0);
INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0);
INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0); INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0);

12
tests/unit/framework/db/ar/ActiveRecordTest.php

@ -116,6 +116,18 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase
$this->assertEquals(3, $customer->id); $this->assertEquals(3, $customer->id);
$this->assertEquals(null, $customer->name); $this->assertEquals(null, $customer->name);
} }
public function testEagerLoading()
{
$customers = Customer::find()->with('orders')->orderBy('t0.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('t0.id')->all();
}
/* /*
public function testGetSql() public function testGetSql()
{ {

Loading…
Cancel
Save