diff options
Diffstat (limited to 'framework/Db/ActiveRecord/TActiveFinder.php')
-rw-r--r-- | framework/Db/ActiveRecord/TActiveFinder.php | 1279 |
1 files changed, 0 insertions, 1279 deletions
diff --git a/framework/Db/ActiveRecord/TActiveFinder.php b/framework/Db/ActiveRecord/TActiveFinder.php deleted file mode 100644 index d55074c0..00000000 --- a/framework/Db/ActiveRecord/TActiveFinder.php +++ /dev/null @@ -1,1279 +0,0 @@ -<?php -/** - * CActiveRecord class file. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2009 Yii Software LLC - * @license http://www.yiiframework.com/license/CBaseActiveRelation*/ - -/** - * CActiveFinder implements eager loading and lazy loading of related active records. - * - * When used in eager loading, this class provides the same set of find methods as - * {@link CActiveRecord}. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @version $Id: CActiveFinder.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Db.ActiveRecord - * @since 1.0 - */ -class TActiveFinder extends TComponent -{ - /** - * @var boolean join all tables all at once. Defaults to false. - * This property is internally used. - * @since 1.0.2 - */ - public $joinAll=false; - /** - * @var boolean whether the base model has limit or offset. - * This property is internally used. - * @since 1.0.2 - */ - public $baseLimited=false; - - private $_joinCount=0; - private $_joinTree; - private $_builder; - private $_criteria; // the criteria generated via named scope - - /** - * Constructor. - * A join tree is built up based on the declared relationships between active record classes. - * @param CActiveRecord the model that initiates the active finding process - * @param mixed the relation names to be actively looked for - * @param CDbCriteria the criteria associated with the named scopes (since version 1.0.5) - */ - public function __construct($model,$with,$criteria=null) - { - $this->_criteria=$criteria; - $this->_builder=$model->getCommandBuilder(); - $this->_joinTree=new TJoinElement($this,$model); - $this->buildJoinTree($this->_joinTree,$with); - } - - /** - * Uses the most aggressive join approach. - * By calling this method, even if there is LIMIT/OFFSET option set for - * the primary table query, we will still use a single SQL statement. - * By default (without calling this method), the primary table will be queried - * by itself so that LIMIT/OFFSET can be correctly applied. - * @return CActiveFinder the finder object - * @since 1.0.2 - */ - public function together() - { - $this->joinAll=true; - return $this; - } - - private function query($criteria,$all=false) - { - if($this->_criteria!==null) - { - $this->_criteria->mergeWith($criteria); - $criteria=$this->_criteria; - } - - $this->_joinTree->find($criteria); - $this->_joinTree->afterFind(); - - if($all) - return array_values($this->_joinTree->records); - else if(count($this->_joinTree->records)) - return reset($this->_joinTree->records); - else - return null; - } - - /** - * This is the relational version of {@link CActiveRecord::find()}. - */ - public function find($condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.find() eagerly','System.Db.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createCriteria($condition,$params); - return $this->query($criteria); - } - - /** - * This is the relational version of {@link CActiveRecord::findAll()}. - */ - public function findAll($condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findAll() eagerly','System.Db.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createCriteria($condition,$params); - return $this->query($criteria,true); - } - - /** - * This is the relational version of {@link CActiveRecord::findByPk()}. - */ - public function findByPk($pk,$condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findByPk() eagerly','System.Db.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createPkCriteria($this->_joinTree->model->getTableSchema(),$pk,$condition,$params); - return $this->query($criteria); - } - - /** - * This is the relational version of {@link CActiveRecord::findAllByPk()}. - */ - public function findAllByPk($pk,$condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findAllByPk() eagerly','System.Db.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createPkCriteria($this->_joinTree->model->getTableSchema(),$pk,$condition,$params); - return $this->query($criteria,true); - } - - /** - * This is the relational version of {@link CActiveRecord::findByAttributes()}. - */ - public function findByAttributes($attributes,$condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findByAttributes() eagerly','System.Db.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createColumnCriteria($this->_joinTree->model->getTableSchema(),$attributes,$condition,$params); - return $this->query($criteria); - } - - /** - * This is the relational version of {@link CActiveRecord::findAllByAttributes()}. - */ - public function findAllByAttributes($attributes,$condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findAllByAttributes() eagerly','System.Db.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createColumnCriteria($this->_joinTree->model->getTableSchema(),$attributes,$condition,$params); - return $this->query($criteria,true); - } - - /** - * This is the relational version of {@link CActiveRecord::findBySql()}. - */ - public function findBySql($sql,$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findBySql() eagerly','System.Db.ActiveRecord.TActiveRecord'); - if(($row=$this->_builder->createSqlCommand($sql,$params)->queryRow())!==false) - { - $baseRecord=$this->_joinTree->model->populateRecord($row,false); - $this->_joinTree->findWithBase($baseRecord); - $this->_joinTree->afterFind(); - return $baseRecord; - } - } - - /** - * This is the relational version of {@link CActiveRecord::findAllBySql()}. - */ - public function findAllBySql($sql,$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findAllBySql() eagerly','System.Db.ActiveRecord.TActiveRecord'); - if(($rows=$this->_builder->createSqlCommand($sql,$params)->queryAll())!==array()) - { - $baseRecords=$this->_joinTree->model->populateRecords($rows,false); - $this->_joinTree->findWithBase($baseRecords); - $this->_joinTree->afterFind(); - return $baseRecords; - } - else - return array(); - } - - /** - * This is the relational version of {@link CActiveRecord::count()}. - * @since 1.0.3 - */ - public function count($condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.count() eagerly','System.Db.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createCriteria($condition,$params); - if($this->_criteria!==null) - { - $this->_criteria->mergeWith($criteria); - $criteria=$this->_criteria; - } - return $this->_joinTree->count($criteria); - } - - /** - * Finds the related objects for the specified active record. - * This method is internally invoked by {@link CActiveRecord} to support lazy loading. - * @param CActiveRecord the base record whose related objects are to be loaded - */ - public function lazyFind($baseRecord) - { - $this->_joinTree->lazyFind($baseRecord); - if(!empty($this->_joinTree->children)) - { - $child=reset($this->_joinTree->children); - $child->afterFind(); - } - } - - /** - * Builds up the join tree representing the relationships involved in this query. - * @param CJoinElement the parent tree node - * @param mixed the names of the related objects relative to the parent tree node - * @param array additional query options to be merged with the relation - */ - private function buildJoinTree($parent,$with,$options=null) - { - if($parent instanceof CStatElement) - throw new TDbException('The STAT relation "'.($parent->relation->name).'" cannot have child relations.'); - - if(is_string($with)) - { - if(($pos=strrpos($with,'.'))!==false) - { - $parent=$this->buildJoinTree($parent,substr($with,0,$pos)); - $with=substr($with,$pos+1); - } - - // named scope - if(($pos=strpos($with,':'))!==false) - { - $scopes=explode(':',substr($with,$pos+1)); - $with=substr($with,0,$pos); - } - - if(isset($parent->children[$with])) - return $parent->children[$with]; - - if(($relation=$parent->model->getActiveRelation($with))===null) - throw new TDbException('Relation "'.$with.'" is not defined in active record class "'.get_class($parent->model).'".'); - - $relation=clone $relation; - $model=TActiveRecord::model($relation->className); - if(($scope=$model->defaultScope())!==array()) - $relation->mergeWith($scope); - if(isset($scopes) && !empty($scopes)) - { - $scs=$model->scopes(); - foreach($scopes as $scope) - { - if(isset($scs[$scope])) - $relation->mergeWith($scs[$scope]); - else - throw new TDbException('Active record class "'.get_class($model).'" does not have a scope named "'.$scope.'".'); - } - } - - // dynamic options - if($options!==null) - $relation->mergeWith($options); - - if($relation instanceof TStatRelation) - return new TStatElement($this,$relation,$parent); - else - { - $element=$parent->children[$with]=new TJoinElement($this,$relation,$parent,++$this->_joinCount); - if(!empty($relation->with)) - $this->buildJoinTree($element,$relation->with); - return $element; - } - } - - // $with is an array, keys are relation name, values are relation spec - foreach($with as $key=>$value) - { - if(is_string($value)) // the value is a relation name - $this->buildJoinTree($parent,$value); - else if(is_string($key) && is_array($value)) - $element=$this->buildJoinTree($parent,$key,$value); - } - } -} - - -/** - * CJoinElement represents a tree node in the join tree created by {@link CActiveFinder}. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @version $Id: CActiveFinder.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Db.ActiveRecord - * @since 1.0 - */ -class TJoinElement -{ - /** - * @var integer the unique ID of this tree node - */ - public $id; - /** - * @var CActiveRelation the relation represented by this tree node - */ - public $relation; - /** - * @var CActiveRecord the model associated with this tree node - */ - public $model; - /** - * @var array list of active records found by the queries. They are indexed by primary key values. - */ - public $records=array(); - /** - * @var array list of child join elements - */ - public $children=array(); - /** - * @var array list of stat elements - * @since 1.0.4 - */ - public $stats=array(); - /** - * @var string table alias for this join element - */ - public $tableAlias; - - private $_finder; - private $_builder; - private $_parent; - private $_pkAlias; // string or name=>alias - private $_columnAliases=array(); // name=>alias - private $_joined=false; - private $_table; - private $_related=array(); // PK, relation name, related PK => true - - /** - * Constructor. - * @param CActiveFinder the finder - * @param mixed the relation (if the third parameter is not null) - * or the model (if the third parameter is null) associated with this tree node. - * @param CJoinElement the parent tree node - * @param integer the ID of this tree node that is unique among all the tree nodes - */ - public function __construct($finder,$relation,$parent=null,$id=0) - { - $this->_finder=$finder; - $this->id=$id; - if($parent!==null) - { - $this->relation=$relation; - $this->_parent=$parent; - $this->_builder=$parent->_builder; - $this->tableAlias=$relation->alias===null?$relation->name:$relation->alias; - $this->model=TActiveRecord::model($relation->className); - $this->_table=$this->model->getTableSchema(); - } - else // root element, the first parameter is the model. - { - $this->model=$relation; - $this->_builder=$relation->getCommandBuilder(); - $this->_table=$relation->getTableSchema(); - } - - // set up column aliases, such as t1_c2 - $table=$this->_table; - $prefix='t'.$id.'_c'; - foreach($table->getColumnNames() as $key=>$name) - { - $alias=$prefix.$key; - $this->_columnAliases[$name]=$alias; - if($table->primaryKey===$name) - $this->_pkAlias=$alias; - else if(is_array($table->primaryKey) && in_array($name,$table->primaryKey)) - $this->_pkAlias[$name]=$alias; - } - } - - /** - * Performs the recursive finding with the criteria. - * @param CDbCriteria the query criteria - */ - public function find($criteria=null) - { - if($this->_parent===null) // root element - { - $query=new TJoinQuery($this,$criteria); - $this->_finder->baseLimited=($criteria->offset>=0 || $criteria->limit>=0); - $this->buildQuery($query); - $this->_finder->baseLimited=false; - $this->runQuery($query); - } - else if(!$this->_joined && !empty($this->_parent->records)) // not joined before - { - $query=new TJoinQuery($this->_parent); - $this->_joined=true; - $query->join($this); - $this->buildQuery($query); - $this->_parent->runQuery($query); - } - - foreach($this->children as $child) // find recursively - $child->find(); - - foreach($this->stats as $stat) - $stat->query(); - } - - /** - * Performs lazy find with the specified base record. - * @param CActiveRecord the active record whose related object is to be fetched. - */ - public function lazyFind($baseRecord) - { - if(is_string($this->_table->primaryKey)) - $this->records[$baseRecord->{$this->_table->primaryKey}]=$baseRecord; - else - { - $pk=array(); - foreach($this->_table->primaryKey as $name) - $pk[$name]=$baseRecord->$name; - $this->records[serialize($pk)]=$baseRecord; - } - - foreach($this->stats as $stat) - $stat->query(); - - if(empty($this->children)) - return; - - $child=reset($this->children); - $query=new TJoinQuery($this); - $this->_joined=true; - $child->_joined=true; - $query->join($child); - if($child->relation instanceof THasManyRelation) - { - $query->limit=$child->relation->limit; - $query->offset=$child->relation->offset; - $this->_finder->baseLimited=($query->offset>=0 || $query->limit>=0); - $query->groups[]=$child->relation->group; - $query->havings[]=$child->relation->having; - } - $child->buildQuery($query); - $this->_finder->baseLimited=false; - $this->runQuery($query); - foreach($child->children as $c) - $c->find(); - } - - /** - * Performs the eager loading with the base records ready. - * @param mixed the available base record(s). - */ - public function findWithBase($baseRecords) - { - if(!is_array($baseRecords)) - $baseRecords=array($baseRecords); - if(is_string($this->_table->primaryKey)) - { - foreach($baseRecords as $baseRecord) - $this->records[$baseRecord->{$this->_table->primaryKey}]=$baseRecord; - } - else - { - foreach($baseRecords as $baseRecord) - { - $pk=array(); - foreach($this->_table->primaryKey as $name) - $pk[$name]=$baseRecord->$name; - $this->records[serialize($pk)]=$baseRecord; - } - } - - $query=new TJoinQuery($this); - $this->buildQuery($query); - if(count($query->joins)>1) - $this->runQuery($query); - foreach($this->children as $child) - $child->find(); - - foreach($this->stats as $stat) - $stat->query(); - } - - /** - * Count the number of primary records returned by the join statement. - * @param CDbCriteria the query criteria - * @return integer number of primary records. - * @since 1.0.3 - */ - public function count($criteria=null) - { - $query=new TJoinQuery($this,$criteria); - // ensure only one big join statement is used - $this->_finder->baseLimited=false; - $this->_finder->joinAll=true; - $this->buildQuery($query); - - if(is_string($this->_table->primaryKey)) - { - $prefix=$this->getColumnPrefix(); - $schema=$this->_builder->getSchema(); - $column=$prefix.$schema->quoteColumnName($this->_table->primaryKey); - } - else if($criteria->select!=='*') - $column=$criteria->select; - else - throw new TDbException('Unable to count records with composite primary keys. Please explicitly specify the SELECT option in the query criteria.'); - - $query->selects=array("COUNT(DISTINCT $column)"); - $query->orders=$query->groups=$query->havings=array(); - $command=$query->createCommand($this->_builder); - return $command->queryScalar(); - } - - /** - * Calls {@link CActiveRecord::afterFind} of all the records. - * @since 1.0.3 - */ - public function afterFind() - { - foreach($this->records as $record) - $record->afterFindInternal(); - foreach($this->children as $child) - $child->afterFind(); - } - - /** - * Builds the join query with all descendant HAS_ONE and BELONGS_TO nodes. - * @param CJoinQuery the query being built up - */ - public function buildQuery($query) - { - foreach($this->children as $child) - { - if($child->relation instanceof THasOneRelation || $child->relation instanceof TBelongsToRelation - || $this->_finder->joinAll || !$this->_finder->baseLimited && $child->relation->together) - { - $child->_joined=true; - $query->join($child); - $child->buildQuery($query); - } - } - } - - /** - * Executes the join query and populates the query results. - * @param CJoinQuery the query to be executed. - */ - public function runQuery($query) - { - $command=$query->createCommand($this->_builder); - foreach($command->queryAll() as $row) - $this->populateRecord($query,$row); - } - - /** - * Populates the active records with the query data. - * @param CJoinQuery the query executed - * @param array a row of data - * @return CActiveRecord the populated record - */ - private function populateRecord($query,$row) - { - // determine the primary key value - if(is_string($this->_pkAlias)) // single key - { - if(isset($row[$this->_pkAlias])) - $pk=$row[$this->_pkAlias]; - else // no matching related objects - return null; - } - else // is_array, composite key - { - $pk=array(); - foreach($this->_pkAlias as $name=>$alias) - { - if(isset($row[$alias])) - $pk[$name]=$row[$alias]; - else // no matching related objects - return null; - } - $pk=serialize($pk); - } - - // retrieve or populate the record according to the primary key value - if(isset($this->records[$pk])) - $record=$this->records[$pk]; - else - { - $attributes=array(); - $aliases=array_flip($this->_columnAliases); - foreach($row as $alias=>$value) - { - if(isset($aliases[$alias])) - $attributes[$aliases[$alias]]=$value; - } - $record=$this->model->populateRecord($attributes,false); - foreach($this->children as $child) - $record->addRelatedRecord($child->relation->name,null,$child->relation instanceof THasManyRelation); - $this->records[$pk]=$record; - } - - // populate child records recursively - foreach($this->children as $child) - { - if(!isset($query->elements[$child->id])) - continue; - $childRecord=$child->populateRecord($query,$row); - if($child->relation instanceof THasOneRelation || $child->relation instanceof TBelongsToRelation) - $record->addRelatedRecord($child->relation->name,$childRecord,false); - else // has_many and many_many - { - // need to double check to avoid adding duplicated related objects - if($childRecord instanceof TActiveRecord) - $fpk=serialize($childRecord->getPrimaryKey()); - else - $fpk=0; - if(!isset($this->_related[$pk][$child->relation->name][$fpk])) - { - $record->addRelatedRecord($child->relation->name,$childRecord,true); - $this->_related[$pk][$child->relation->name][$fpk]=true; - } - } - } - - return $record; - } - - /** - * @return string the table name and the table alias (if any). This can be used directly in SQL query without escaping. - */ - public function getTableNameWithAlias() - { - if($this->tableAlias!==null) - return $this->_table->rawName . ' ' . $this->tableAlias; - else - return $this->_table->rawName; - } - - /** - * Generates the list of columns to be selected. - * Columns will be properly aliased and primary keys will be added to selection if they are not specified. - * @param mixed columns to be selected. Defaults to '*', indicating all columns. - * @return string the column selection - */ - public function getColumnSelect($select='*') - { - $schema=$this->_builder->getSchema(); - $prefix=$this->getColumnPrefix(); - $columns=array(); - if($select==='*') - { - foreach($this->_table->getColumnNames() as $name) - $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($this->_columnAliases[$name]); - } - else - { - if(is_string($select)) - $select=explode(',',$select); - $selected=array(); - foreach($select as $name) - { - $name=trim($name); - $matches=array(); - if(($pos=strrpos($name,'.'))!==false) - $key=substr($name,$pos+1); - else - $key=$name; - if(isset($this->_columnAliases[$key])) // simple column names - { - $columns[]=$prefix.$schema->quoteColumnName($key).' AS '.$schema->quoteColumnName($this->_columnAliases[$key]); - $selected[$this->_columnAliases[$key]]=1; - } - else if(preg_match('/^(.*?)\s+AS\s+(\w+)$/i',$name,$matches)) // if the column is already aliased - { - $alias=$matches[2]; - if(!isset($this->_columnAliases[$alias]) || $this->_columnAliases[$alias]!==$alias) - { - $this->_columnAliases[$alias]=$alias; - $columns[]=$name; - $selected[$alias]=1; - } - } - else - throw new TDbException('Active record "'.get_class($this->model).'" is trying to select an invalid column "'.$name.'". Note, the column must exist in the table or be an expression with alias.'); - } - // add primary key selection if they are not selected - if(is_string($this->_pkAlias) && !isset($selected[$this->_pkAlias])) - $columns[]=$prefix.$schema->quoteColumnName($this->_table->primaryKey).' AS '.$schema->quoteColumnName($this->_pkAlias); - else if(is_array($this->_pkAlias)) - { - foreach($this->_table->primaryKey as $name) - if(!isset($selected[$name])) - $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($this->_pkAlias[$name]); - } - } - - return implode(', ',$columns); - } - - /** - * @return string the primary key selection - */ - public function getPrimaryKeySelect() - { - $schema=$this->_builder->getSchema(); - $prefix=$this->getColumnPrefix(); - $columns=array(); - if(is_string($this->_pkAlias)) - $columns[]=$prefix.$schema->quoteColumnName($this->_table->primaryKey).' AS '.$schema->quoteColumnName($this->_pkAlias); - else if(is_array($this->_pkAlias)) - { - foreach($this->_pkAlias as $name=>$alias) - $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($alias); - } - return implode(', ',$columns); - } - - /** - * @return string the condition that specifies only the rows with the selected primary key values. - */ - public function getPrimaryKeyRange() - { - if(empty($this->records)) - return ''; - $values=array_keys($this->records); - if(is_array($this->_table->primaryKey)) - { - foreach($values as &$value) - $value=unserialize($value); - } - return $this->_builder->createInCondition($this->_table,$this->_table->primaryKey,$values,$this->getColumnPrefix()); - } - - /** - * @return string the column prefix for column reference disambiguation - */ - public function getColumnPrefix() - { - if($this->tableAlias!==null) - return $this->tableAlias.'.'; - else - return $this->_table->rawName.'.'; - } - - /** - * @return string the join statement (this node joins with its parent) - */ - public function getJoinCondition() - { - $parent=$this->_parent; - $relation=$this->relation; - if($this->relation instanceof TManyManyRelation) - { - if(!preg_match('/^\s*(.*?)\((.*)\)\s*$/',$this->relation->foreignKey,$matches)) - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key. The format of the foreign key must be "joinTable(fk1,fk2,...)".'); - - $schema=$this->_builder->getSchema(); - if(($joinTable=$schema->getTable($matches[1]))===null) - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is not specified correctly: the join table "'.($matches[1]).'" given in the foreign key cannot be found in the database.'); - - $fks=preg_split('/[\s,]+/',$matches[2],-1,PREG_SPLIT_NO_EMPTY); - - return $this->joinManyMany($joinTable,$fks,$parent); - } - else - { - $fks=preg_split('/[\s,]+/',$relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY); - if($this->relation instanceof TBelongsToRelation) - { - $pke=$this; - $fke=$parent; - } - else - { - $pke=$parent; - $fke=$this; - } - return $this->joinOneMany($fke,$fks,$pke,$parent); - } - } - - /** - * Generates the join statement for one-many relationship. - * This works for HAS_ONE, HAS_MANY and BELONGS_TO. - * @param CJoinElement the join element containing foreign keys - * @param array the foreign keys - * @param CJoinElement the join element containg primary keys - * @param CJoinElement the parent join element - * @return string the join statement - * @throws CDbException if a foreign key is invalid - */ - private function joinOneMany($fke,$fks,$pke,$parent) - { - $schema=$this->_builder->getSchema(); - $joins=array(); - foreach($fks as $i=>$fk) - { - if(!isset($fke->_table->columns[$fk])) - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key "'.$fk.'". There is no such column in the table "'.($fke->_table->name).'".'); - - if(isset($fke->_table->foreignKeys[$fk])) - $pk=$fke->_table->foreignKeys[$fk][1]; - else // FK constraints undefined - { - if(is_array($pke->_table->primaryKey)) // composite PK - $pk=$pke->_table->primaryKey[$i]; - else - $pk=$pke->_table->primaryKey; - } - $joins[]=$fke->getColumnPrefix().$schema->quoteColumnName($fk) . '=' . $pke->getColumnPrefix().$schema->quoteColumnName($pk); - } - if(!empty($this->relation->on)) - $joins[]=$this->relation->on; - return $this->relation->joinType . ' ' . $this->getTableNameWithAlias() . ' ON (' . implode(') AND (',$joins).')'; - } - - /** - * Generates the join statement for many-many relationship. - * @param CDbTableSchema the join table - * @param array the foreign keys - * @param CJoinElement the parent join element - * @return string the join statement - * @throws CDbException if a foreign key is invalid - */ - private function joinManyMany($joinTable,$fks,$parent) - { - $schema=$this->_builder->getSchema(); - $joinAlias=$this->relation->name.'_'.$this->tableAlias; - $parentCondition=array(); - $childCondition=array(); - foreach($fks as $i=>$fk) - { - if(!isset($joinTable->columns[$fk])) - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key "'.$fk.'". There is no such column in the table "'.($joinTable->name).'".'); - - if(isset($joinTable->foreignKeys[$fk])) - { - list($tableName,$pk)=$joinTable->foreignKeys[$fk]; - if(!isset($parentCondition[$pk]) && $schema->compareTableNames($parent->_table->rawName,$tableName)) - $parentCondition[$pk]=$parent->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk); - else if(!isset($childCondition[$pk]) && $schema->compareTableNames($this->_table->rawName,$tableName)) - $childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk); - else - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key "'.$fk.'". The foreign key does not point to either joining table.'); - } - else // FK constraints not defined - { - if($i<count($parent->_table->primaryKey)) - { - $pk=is_array($parent->_table->primaryKey) ? $parent->_table->primaryKey[$i] : $parent->_table->primaryKey; - $parentCondition[$pk]=$parent->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk); - } - else - { - $j=$i-count($parent->_table->primaryKey); - $pk=is_array($this->_table->primaryKey) ? $this->_table->primaryKey[$j] : $this->_table->primaryKey; - $childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk); - } - } - } - if($parentCondition!==array() && $childCondition!==array()) - { - $join=$this->relation->joinType.' '.$joinTable->rawName.' '.$joinAlias; - $join.=' ON ('.implode(') AND (',$parentCondition).')'; - $join.=' '.$this->relation->joinType.' '.$this->getTableNameWithAlias(); - $join.=' ON ('.implode(') AND (',$childCondition).')'; - if(!empty($this->relation->on)) - $join.=' AND ('.$this->relation->on.')'; - return $join; - } - else - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.'); - } -} - - -/** - * CJoinQuery represents a JOIN SQL statement. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @version $Id: CActiveFinder.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Db.ActiveRecord - * @since 1.0 - */ -class TJoinQuery -{ - /** - * @var array list of column selections - */ - public $selects=array(); - /** - * @var array list of join statement - */ - public $joins=array(); - /** - * @var array list of WHERE clauses - */ - public $conditions=array(); - /** - * @var array list of ORDER BY clauses - */ - public $orders=array(); - /** - * @var array list of GROUP BY clauses - */ - public $groups=array(); - /** - * @var array list of HAVING clauses - */ - public $havings=array(); - /** - * @var integer row limit - */ - public $limit=-1; - /** - * @var integer row offset - */ - public $offset=-1; - /** - * @var array list of query parameters - */ - public $params=array(); - /** - * @var array list of join element IDs (id=>true) - */ - public $elements=array(); - - /** - * Constructor. - * @param CJoinElement The root join tree. - * @param CDbCriteria the query criteria - */ - public function __construct($joinElement,$criteria=null) - { - if($criteria!==null) - { - $this->selects[]=$joinElement->getColumnSelect($criteria->select); - $this->joins[]=$joinElement->getTableNameWithAlias(); - $this->joins[]=$criteria->join; - $this->conditions[]=$criteria->condition; - $this->orders[]=$criteria->order; - $this->groups[]=$criteria->group; - $this->havings[]=$criteria->having; - $this->limit=$criteria->limit; - $this->offset=$criteria->offset; - $this->params=$criteria->params; - } - else - { - $this->selects[]=$joinElement->getPrimaryKeySelect(); - $this->joins[]=$joinElement->getTableNameWithAlias(); - $this->conditions[]=$joinElement->getPrimaryKeyRange(); - } - $this->elements[$joinElement->id]=true; - } - - /** - * Joins with another join element - * @param CJoinElement the element to be joined - */ - public function join($element) - { - $this->selects[]=$element->getColumnSelect($element->relation->select); - $this->conditions[]=$element->relation->condition; - $this->orders[]=$element->relation->order; - $this->joins[]=$element->getJoinCondition(); - $this->groups[]=$element->relation->group; - $this->havings[]=$element->relation->having; - - if(is_array($element->relation->params)) - { - if(is_array($this->params)) - $this->params=array_merge($this->params,$element->relation->params); - else - $this->params=$element->relation->params; - } - $this->elements[$element->id]=true; - } - - /** - * Creates the SQL statement. - * @param CDbCommandBuilder the command builder - * @return string the SQL statement - */ - public function createCommand($builder) - { - $sql='SELECT ' . implode(', ',$this->selects); - $sql.=' FROM ' . implode(' ',$this->joins); - - $conditions=array(); - foreach($this->conditions as $condition) - if($condition!=='') - $conditions[]=$condition; - if($conditions!==array()) - $sql.=' WHERE (' . implode(') AND (',$conditions).')'; - - $groups=array(); - foreach($this->groups as $group) - if($group!=='') - $groups[]=$group; - if($groups!==array()) - $sql.=' GROUP BY ' . implode(', ',$groups); - - $havings=array(); - foreach($this->havings as $having) - if($having!=='') - $havings[]=$having; - if($havings!==array()) - $sql.=' HAVING (' . implode(') AND (',$havings).')'; - - $orders=array(); - foreach($this->orders as $order) - if($order!=='') - $orders[]=$order; - if($orders!==array()) - $sql.=' ORDER BY ' . implode(', ',$orders); - - $sql=$builder->applyLimit($sql,$this->limit,$this->offset); - $command=$builder->getDbConnection()->createCommand($sql); - $builder->bindValues($command,$this->params); - return $command; - } -} - - -/** - * CStatElement represents STAT join element for {@link CActiveFinder}. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @version $Id: CActiveFinder.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Db.ActiveRecord - * @since 1.0.4 - */ -class TStatElement -{ - /** - * @var CActiveRelation the relation represented by this tree node - */ - public $relation; - - private $_finder; - private $_parent; - - /** - * Constructor. - * @param CActiveFinder the finder - * @param CStatRelation the STAT relation - * @param CJoinElement the join element owning this STAT element - */ - public function __construct($finder,$relation,$parent) - { - $this->_finder=$finder; - $this->_parent=$parent; - $this->relation=$relation; - $parent->stats[]=$this; - } - - /** - * Performs the STAT query. - */ - public function query() - { - if(preg_match('/^\s*(.*?)\((.*)\)\s*$/',$this->relation->foreignKey,$matches)) - $this->queryManyMany($matches[1],$matches[2]); - else - $this->queryOneMany(); - } - - private function queryOneMany() - { - $relation=$this->relation; - $model=TActiveRecord::model($relation->className); - $builder=$model->getCommandBuilder(); - $schema=$builder->getSchema(); - $table=$model->getTableSchema(); - $pkTable=$this->_parent->model->getTableSchema(); - - $fks=preg_split('/[\s,]+/',$relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY); - if(count($fks)!==count($pkTable->primaryKey)) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key. The columns in the key must match the primary keys of the table "'.($pkTable->name).'".'); - - // set up mapping between fk and pk columns - $map=array(); // pk=>fk - foreach($fks as $i=>$fk) - { - if(!isset($table->columns[$fk])) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key "'.$fk.'". There is no such column in the table "'.($table->name).'".'); - - if(isset($table->foreignKeys[$fk])) - { - list($tableName,$pk)=$table->foreignKeys[$fk]; - if($schema->compareTableNames($pkTable->rawName,$tableName)) - $map[$pk]=$fk; - else - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($parent->model).'" is specified with a foreign key "'.$fk.'" that does not point to the parent table "'.($pkTable->name).'".'); - } - else // FK constraints undefined - { - if(is_array($table->primaryKey)) // composite PK - $map[$table->primaryKey[$i]]=$fk; - } - } - - $records=$this->_parent->records; - - $where=empty($relation->condition)?'' : ' WHERE ('.$relation->condition.')'; - $group=empty($relation->group)?'' : ', '.$relation->group; - $having=empty($relation->having)?'' : ' AND ('.$relation->having.')'; - $order=empty($relation->order)?'' : ' ORDER BY '.$relation->order; - - $c=$schema->quoteColumnName('c'); - $s=$schema->quoteColumnName('s'); - - // generate and perform query - if(count($fks)===1) // single column FK - { - $col=$table->columns[$fks[0]]->rawName; - $sql="SELECT $col AS $c, {$relation->select} AS $s FROM {$table->rawName}" - .$where - ." GROUP BY $col".$group - ." HAVING ".$builder->createInCondition($table,$fks[0],array_keys($records)) - .$having.$order; - $command=$builder->getDbConnection()->createCommand($sql); - if(is_array($relation->params)) - $builder->bindValues($command,$relation->params); - $stats=array(); - foreach($command->queryAll() as $row) - $stats[$row['c']]=$row['s']; - } - else // composite FK - { - $keys=array_keys($records); - foreach($keys as &$key) - { - $key2=unserialize($key); - $key=array(); - foreach($pkTable->primaryKey as $pk) - $key[$map[$pk]]=$key2[$pk]; - } - $cols=array(); - foreach($pkTable->primaryKey as $n=>$pk) - { - $name=$table->columns[$map[$pk]]->rawName; - $cols[$name]=$name.' AS '.$schema->quoteColumnName('c'.$n); - } - $sql='SELECT '.implode(', ',$cols).", {$relation->select} AS $s FROM {$table->rawName}" - .$where - .' GROUP BY '.implode(', ',array_keys($cols)).$group - .' HAVING '.$builder->createInCondition($table,$fks,$keys) - .$having.$order; - $command=$builder->getDbConnection()->createCommand($sql); - if(is_array($relation->params)) - $builder->bindValues($command,$relation->params); - $stats=array(); - foreach($command->queryAll() as $row) - { - $key=array(); - foreach($pkTable->primaryKey as $n=>$pk) - $key[$pk]=$row['c'.$n]; - $stats[serialize($key)]=$row['s']; - } - } - - // populate the results into existing records - foreach($records as $pk=>$record) - $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$relation->defaultValue,false); - } - - private function queryManyMany($joinTableName,$keys) - { - $relation=$this->relation; - $model=TActiveRecord::model($relation->className); - $table=$model->getTableSchema(); - $builder=$model->getCommandBuilder(); - $schema=$builder->getSchema(); - $pkTable=$this->_parent->model->getTableSchema(); - - if(($joinTable=$builder->getSchema()->getTable($joinTableName))===null) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is not specified correctly. The join table "'.$joinTableName.'" given in the foreign key cannot be found in the database.'); - - $fks=preg_split('/[\s,]+/',$keys,-1,PREG_SPLIT_NO_EMPTY); - if(count($fks)!==count($table->primaryKey)+count($pkTable->primaryKey)) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.'); - - $joinCondition=array(); - $map=array(); - foreach($fks as $i=>$fk) - { - if(!isset($joinTable->columns[$fk])) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is specified with an invalid foreign key "'.$fk.'". There is no such column in the table "'.($joinTable->name).'".'); - - if(isset($joinTable->foreignKeys[$fk])) - { - list($tableName,$pk)=$joinTable->foreignKeys[$fk]; - if(!isset($joinCondition[$pk]) && $schema->compareTableNames($table->rawName,$tableName)) - $joinCondition[$pk]=$table->rawName.'.'.$schema->quoteColumnName($pk).'='.$joinTable->rawName.'.'.$schema->quoteColumnName($fk); - else if(!isset($map[$pk]) && $schema->compareTableNames($pkTable->rawName,$tableName)) - $map[$pk]=$fk; - else - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is specified with an invalid foreign key "'.$fk.'". The foreign key does not point to either joining table.'); - } - else // FK constraints not defined - { - if($i<count($pkTable->primaryKey)) - { - $pk=is_array($pkTable->primaryKey) ? $pkTable->primaryKey[$i] : $pkTable->primaryKey; - $map[$pk]=$fk; - } - else - { - $j=$i-count($pkTable->primaryKey); - $pk=is_array($table->primaryKey) ? $table->primaryKey[$j] : $table->primaryKey; - $joinCondition[$pk]=$table->rawName.'.'.$schema->quoteColumnName($pk).'='.$joinTable->rawName.'.'.$schema->quoteColumnName($fk); - } - } - } - - if($joinCondition===array() || $map===array()) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.'); - - $records=$this->_parent->records; - - $cols=array(); - foreach(is_string($pkTable->primaryKey)?array($pkTable->primaryKey):$pkTable->primaryKey as $n=>$pk) - { - $name=$joinTable->rawName.'.'.$schema->quoteColumnName($map[$pk]); - $cols[$name]=$name.' AS '.$schema->quoteColumnName('c'.$n); - } - - $keys=array_keys($records); - if(is_array($pkTable->primaryKey)) - { - foreach($keys as &$key) - { - $key2=unserialize($key); - $key=array(); - foreach($pkTable->primaryKey as $pk) - $key[$map[$pk]]=$key2[$pk]; - } - } - - $where=empty($relation->condition)?'' : ' WHERE ('.$relation->condition.')'; - $group=empty($relation->group)?'' : ', '.$relation->group; - $having=empty($relation->having)?'' : ' AND ('.$relation->having.')'; - $order=empty($relation->order)?'' : ' ORDER BY '.$relation->order; - - $sql='SELECT '.$this->relation->select.' AS '.$schema->quoteColumnName('s').', '.implode(', ',$cols) - .' FROM '.$table->rawName.' INNER JOIN '.$joinTable->rawName - .' ON ('.implode(') AND (',$joinCondition).')' - .$where - .' GROUP BY '.implode(', ',array_keys($cols)).$group - .' HAVING ('.$builder->createInCondition($joinTable,$map,$keys).')' - .$having.$order; - - $command=$builder->getDbConnection()->createCommand($sql); - if(is_array($relation->params)) - $builder->bindValues($command,$relation->params); - - $stats=array(); - foreach($command->queryAll() as $row) - { - if(is_array($pkTable->primaryKey)) - { - $key=array(); - foreach($pkTable->primaryKey as $n=>$k) - $key[$k]=$row['c'.$n]; - $stats[serialize($key)]=$row['s']; - } - else - $stats[$row['c0']]=$row['s']; - } - - foreach($records as $pk=>$record) - $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$this->relation->defaultValue,false); - } -} |