Вход Регистрация
Файл: framework/db/ar/CActiveFinder.php
Строк: 2113
<?php
/**
 * CActiveFinder class file.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.yiiframework.com/
 * @copyright 2008-2013 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

/**
 * 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>
 * @package system.db.ar
 * @since 1.0
 */
class CActiveFinder extends CComponent
{
    
/**
     * @var boolean join all tables all at once. Defaults to false.
     * This property is internally used.
     */
    
public $joinAll=false;
    
/**
     * @var boolean whether the base model has limit or offset.
     * This property is internally used.
     */
    
public $baseLimited=false;

    private 
$_joinCount=0;
    private 
$_joinTree;
    private 
$_builder;

    
/**
     * Constructor.
     * A join tree is built up based on the declared relationships between active record classes.
     * @param CActiveRecord $model the model that initiates the active finding process
     * @param mixed $with the relation names to be actively looked for
     */
    
public function __construct($model,$with)
    {
        
$this->_builder=$model->getCommandBuilder();
        
$this->_joinTree=new CJoinElement($this,$model);
        
$this->buildJoinTree($this->_joinTree,$with);
    }

    
/**
     * Do not call this method. This method is used internally to perform the relational query
     * based on the given DB criteria.
     * @param CDbCriteria $criteria the DB criteria
     * @param boolean $all whether to bring back all records
     * @return mixed the query result
     */
    
public function query($criteria,$all=false)
    {
        
$this->joinAll=$criteria->together===true;

        if(
$criteria->alias!='')
        {
            
$this->_joinTree->tableAlias=$criteria->alias;
            
$this->_joinTree->rawTableAlias=$this->_builder->getSchema()->quoteTableName($criteria->alias);
        }

        
$this->_joinTree->find($criteria);
        
$this->_joinTree->afterFind();

        if(
$all)
        {
            
$result array_values($this->_joinTree->records);
            if (
$criteria->index!==null)
            {
                
$index=$criteria->index;
                
$array=array();
                foreach(
$result as $object)
                    
$array[$object->$index]=$object;
                
$result=$array;
            }
        }
        elseif(
count($this->_joinTree->records))
            
$result reset($this->_joinTree->records);
        else
            
$result null;

        
$this->destroyJoinTree();
        return 
$result;
    }

    
/**
     * This method is internally called.
     * @param string $sql the SQL statement
     * @param array $params parameters to be bound to the SQL statement
     * @return CActiveRecord
     */
    
public function findBySql($sql,$params=array())
    {
        
Yii::trace(get_class($this->_joinTree->model).'.findBySql() eagerly','system.db.ar.CActiveRecord');
        if((
$row=$this->_builder->createSqlCommand($sql,$params)->queryRow())!==false)
        {
            
$baseRecord=$this->_joinTree->model->populateRecord($row,false);
            
$this->_joinTree->findWithBase($baseRecord);
            
$this->_joinTree->afterFind();
            
$this->destroyJoinTree();
            return 
$baseRecord;
        }
        else
            
$this->destroyJoinTree();
    }

    
/**
     * This method is internally called.
     * @param string $sql the SQL statement
     * @param array $params parameters to be bound to the SQL statement
     * @return CActiveRecord[]
     */
    
public function findAllBySql($sql,$params=array())
    {
        
Yii::trace(get_class($this->_joinTree->model).'.findAllBySql() eagerly','system.db.ar.CActiveRecord');
        if((
$rows=$this->_builder->createSqlCommand($sql,$params)->queryAll())!==array())
        {
            
$baseRecords=$this->_joinTree->model->populateRecords($rows,false);
            
$this->_joinTree->findWithBase($baseRecords);
            
$this->_joinTree->afterFind();
            
$this->destroyJoinTree();
            return 
$baseRecords;
        }
        else
        {
            
$this->destroyJoinTree();
            return array();
        }
    }

    
/**
     * This method is internally called.
     * @param CDbCriteria $criteria the query criteria
     * @return string
     */
    
public function count($criteria)
    {
        
Yii::trace(get_class($this->_joinTree->model).'.count() eagerly','system.db.ar.CActiveRecord');
        
$this->joinAll=$criteria->together!==true;

        
$alias=$criteria->alias===null 't' $criteria->alias;
        
$this->_joinTree->tableAlias=$alias;
        
$this->_joinTree->rawTableAlias=$this->_builder->getSchema()->quoteTableName($alias);

        
$n=$this->_joinTree->count($criteria);
        
$this->destroyJoinTree();
        return 
$n;
    }

    
/**
     * Finds the related objects for the specified active record.
     * This method is internally invoked by {@link CActiveRecord} to support lazy loading.
     * @param CActiveRecord $baseRecord the base record whose related objects are to be loaded
     */
    
public function lazyFind($baseRecord)
    {
        
$this->_joinTree->lazyFind($baseRecord);
        if(!empty(
$this->_joinTree->children))
        {
            foreach(
$this->_joinTree->children as $child)
                
$child->afterFind();
        }
        
$this->destroyJoinTree();
    }

    
/**
     * Given active record class name returns new model instance.
     *
     * @param string $className active record class name
     * @return CActiveRecord active record model instance
     *
     * @since 1.1.14
     */
    
public function getModel($className)
    {
        return 
CActiveRecord::model($className);
    }

    private function 
destroyJoinTree()
    {
        if(
$this->_joinTree!==null)
            
$this->_joinTree->destroy();
        
$this->_joinTree=null;
    }

    
/**
     * Builds up the join tree representing the relationships involved in this query.
     * @param CJoinElement $parent the parent tree node
     * @param mixed $with the names of the related objects relative to the parent tree node
     * @param array $options additional query options to be merged with the relation
     * @return CJoinElement|mixed
     * @throws CDbException if given parent tree node is an instance of {@link CStatElement}
     * or relation is not defined in the given parent's tree node model class
     */
    
private function buildJoinTree($parent,$with,$options=null)
    {
        if(
$parent instanceof CStatElement)
            throw new 
CDbException(Yii::t('yii','The STAT relation "{name}" cannot have child relations.',
                array(
'{name}'=>$parent->relation->name)));

        if(
is_string($with))
        {
            if((
$pos=strrpos($with,'.'))!==false)
            {
                
$parent=$this->buildJoinTree($parent,substr($with,0,$pos));
                
$with=substr($with,$pos+1);
            }

            
// named scope
            
$scopes=array();
            if((
$pos=strpos($with,':'))!==false)
            {
                
$scopes=explode(':',substr($with,$pos+1));
                
$with=substr($with,0,$pos);
            }

            if(isset(
$parent->children[$with]) && $parent->children[$with]->master===null)
                return 
$parent->children[$with];

            if((
$relation=$parent->model->getActiveRelation($with))===null)
                throw new 
CDbException(Yii::t('yii','Relation "{name}" is not defined in active record class "{class}".',
                    array(
'{class}'=>get_class($parent->model), '{name}'=>$with)));

            
$relation=clone $relation;
            
$model=$this->getModel($relation->className);

            if(
$relation instanceof CActiveRelation)
            {
                
$oldAlias=$model->getTableAlias(false,false);
                if(isset(
$options['alias']))
                    
$model->setTableAlias($options['alias']);
                elseif(
$relation->alias===null)
                    
$model->setTableAlias($relation->name);
                else
                    
$model->setTableAlias($relation->alias);
            }

            if(!empty(
$relation->scopes))
                
$scopes=array_merge($scopes,(array)$relation->scopes); // no need for complex merging

            
if(!empty($options['scopes']))
                
$scopes=array_merge($scopes,(array)$options['scopes']); // no need for complex merging

            
if(!empty($options['joinOptions']))
                
$relation->joinOptions=$options['joinOptions'];

            
$model->resetScope(false);
            
$criteria=$model->getDbCriteria();
            
$criteria->scopes=$scopes;
            
$model->beforeFindInternal();
            
$model->applyScopes($criteria);

            
// select has a special meaning in stat relation, so we need to ignore select from scope or model criteria
            
if($relation instanceof CStatRelation)
                
$criteria->select='*';

            
$relation->mergeWith($criteria,true);

            
// dynamic options
            
if($options!==null)
                
$relation->mergeWith($options);

            if(
$relation instanceof CActiveRelation)
                
$model->setTableAlias($oldAlias);

            if(
$relation instanceof CStatRelation)
                return new 
CStatElement($this,$relation,$parent);
            else
            {
                if(isset(
$parent->children[$with]))
                {
                    
$element=$parent->children[$with];
                    
$element->relation=$relation;
                }
                else
                    
$element=new CJoinElement($this,$relation,$parent,++$this->_joinCount);
                if(!empty(
$relation->through))
                {
                    
$slave=$this->buildJoinTree($parent,$relation->through,array('select'=>''));
                    
$slave->master=$element;
                    
$element->slave=$slave;
                }
                
$parent->children[$with]=$element;
                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);
            elseif(
is_string($key) && is_array($value))
                
$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>
 * @package system.db.ar
 * @since 1.0
 */
class CJoinElement
{
    
/**
     * @var integer the unique ID of this tree node
     */
    
public $id;
    
/**
     * @var CActiveRelation the relation represented by this tree node
     */
    
public $relation;
    
/**
     * @var CActiveRelation the master relation
     */
    
public $master;
    
/**
     * @var CActiveRelation the slave relation
     */
    
public $slave;
    
/**
     * @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
     */
    
public $stats=array();
    
/**
     * @var string table alias for this join element
     */
    
public $tableAlias;
    
/**
     * @var string the quoted table alias for this element
     */
    
public $rawTableAlias;

    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 $finder the finder
     * @param mixed $relation 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 $parent the parent tree node
     * @param integer $id 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->model=$this->_finder->getModel($relation->className);
            
$this->_builder=$this->model->getCommandBuilder();
            
$this->tableAlias=$relation->alias===null?$relation->name:$relation->alias;
            
$this->rawTableAlias=$this->_builder->getSchema()->quoteTableName($this->tableAlias);
            
$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();
            
$this->tableAlias=$this->model->getTableAlias();
            
$this->rawTableAlias=$this->_builder->getSchema()->quoteTableName($this->tableAlias);
        }

        
// set up column aliases, such as t1_c2
        
$table=$this->_table;
        if(
$this->model->getDbConnection()->getDriverName()==='oci')  // Issue 482
            
$prefix='T'.$id.'_C';
        else
            
$prefix='t'.$id.'_c';
        foreach(
$table->getColumnNames() as $key=>$name)
        {
            
$alias=$prefix.$key;
            
$this->_columnAliases[$name]=$alias;
            if(
$table->primaryKey===$name)
                
$this->_pkAlias=$alias;
            elseif(
is_array($table->primaryKey) && in_array($name,$table->primaryKey))
                
$this->_pkAlias[$name]=$alias;
        }
    }

    
/**
     * Removes references to child elements and finder to avoid circular references.
     * This is internally used.
     */
    
public function destroy()
    {
        if(!empty(
$this->children))
        {
            foreach(
$this->children as $child)
                
$child->destroy();
        }
        unset(
$this->_finder$this->_parent$this->model$this->relation$this->master$this->slave$this->records$this->children$this->stats);
    }

    
/**
     * Performs the recursive finding with the criteria.
     * @param CDbCriteria $criteria the query criteria
     */
    
public function find($criteria=null)
    {
        if(
$this->_parent===null// root element
        
{
            
$query=new CJoinQuery($this,$criteria);
            
$this->_finder->baseLimited=($criteria->offset>=|| $criteria->limit>=0);
            
$this->buildQuery($query);
            
$this->_finder->baseLimited=false;
            
$this->runQuery($query);
        }
        elseif(!
$this->_joined && !empty($this->_parent->records)) // not joined before
        
{
            
$query=new CJoinQuery($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 $baseRecord 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(!
$this->children)
            return;

        
$params=array();
        foreach(
$this->children as $child)
            if(
is_array($child->relation->params))
                
$params=array_merge($params,$child->relation->params);

        
$query=new CJoinQuery($child);
        
$query->selects=array($child->getColumnSelect($child->relation->select));
        
$query->conditions=array(
            
$child->relation->on,
        );
        
$query->groups[]=$child->relation->group;
        
$query->joins[]=$child->relation->join;
        
$query->havings[]=$child->relation->having;
        
$query->orders[]=$child->relation->order;
        
$query->params=$params;
        
$query->elements[$child->id]=true;
        if(
$child->relation instanceof CHasManyRelation)
        {
            
$query->limit=$child->relation->limit;
            
$query->offset=$child->relation->offset;
        }

        
$child->applyLazyCondition($query,$baseRecord);

        
$this->_joined=true;
        
$child->_joined=true;

        
$this->_finder->baseLimited=false;
        
$child->buildQuery($query);
        
$child->runQuery($query);
        foreach(
$child->children as $c)
            
$c->find();

        if(empty(
$child->records))
            return;
        if(
$child->relation instanceof CHasOneRelation || $child->relation instanceof CBelongsToRelation)
            
$baseRecord->addRelatedRecord($child->relation->name,reset($child->records),false);
        else 
// has_many and many_many
        
{
            foreach(
$child->records as $record)
            {
                if(
$child->relation->index!==null)
                    
$index=$record->{$child->relation->index};
                else
                    
$index=true;
                
$baseRecord->addRelatedRecord($child->relation->name,$record,$index);
            }
        }
    }

    
/**
     * Apply Lazy Condition
     * @param CJoinQuery $query represents a JOIN SQL statements
     * @param CActiveRecord $record the active record whose related object is to be fetched.
     * @throws CDbException if relation in active record class is not specified correctly
     */
    
private function applyLazyCondition($query,$record)
    {
        
$schema=$this->_builder->getSchema();
        
$parent=$this->_parent;
        if(
$this->relation instanceof CManyManyRelation)
        {
            
$query->conditions=array(
                
$this->relation->condition,
            );
            
$joinTableName=$this->relation->getJunctionTableName();
            if((
$joinTable=$schema->getTable($joinTableName))===null)
                throw new 
CDbException(Yii::t('yii','The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.',
                    array(
'{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name'{joinTable}'=>$joinTableName)));
            
$fks=$this->relation->getJunctionForeignKeys();

            
$joinAlias=$schema->quoteTableName($this->relation->name.'_'.$this->tableAlias);
            
$parentCondition=array();
            
$childCondition=array();
            
$count=0;
            
$params=array();

            
$fkDefined=true;
            foreach(
$fks as $i=>$fk)
            {
                if(isset(
$joinTable->foreignKeys[$fk]))  // FK defined
                
{
                    list(
$tableName,$pk)=$joinTable->foreignKeys[$fk];
                    if(!isset(
$parentCondition[$pk]) && $schema->compareTableNames($parent->_table->rawName,$tableName))
                    {
                        
$parentCondition[$pk]=$joinAlias.'.'.$schema->quoteColumnName($fk).'=:ypl'.$count;
                        
$params[':ypl'.$count]=$record->$pk;
                        
$count++;
                    }
                    elseif(!isset(
$childCondition[$pk]) && $schema->compareTableNames($this->_table->rawName,$tableName))
                        
$childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk);
                    else
                    {
                        
$fkDefined=false;
                        break;
                    }
                }
                else
                {
                    
$fkDefined=false;
                    break;
                }
            }

            if(!
$fkDefined)
            {
                
$parentCondition=array();
                
$childCondition=array();
                
$count=0;
                
$params=array();
                foreach(
$fks as $i=>$fk)
                {
                    if(
$i<count($parent->_table->primaryKey))
                    {
                        
$pk=is_array($parent->_table->primaryKey) ? $parent->_table->primaryKey[$i] : $parent->_table->primaryKey;
                        
$parentCondition[$pk]=$joinAlias.'.'.$schema->quoteColumnName($fk).'=:ypl'.$count;
                        
$params[':ypl'.$count]=$record->$pk;
                        
$count++;
                    }
                    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='INNER JOIN '.$joinTable->rawName.' '.$joinAlias.' ON ';
                
$join.='('.implode(') AND (',$parentCondition).') AND ('.implode(') AND (',$childCondition).')';
                if(!empty(
$this->relation->on))
                    
$join.=' AND ('.$this->relation->on.')';
                
$query->joins[]=$join;
                foreach(
$params as $name=>$value)
                    
$query->params[$name]=$value;
            }
            else
                throw new 
CDbException(Yii::t('yii','The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.',
                    array(
'{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name)));
        }
        else
        {
            
$element=$this;
            while(
true)
            {
                
$condition=$element->relation->condition;
                if(!empty(
$condition))
                    
$query->conditions[]=$condition;
                
$query->params=array_merge($query->params,$element->relation->params);
                if(
$element->slave!==null)
                {
                    
$query->joins[]=$element->slave->joinOneMany($element->slave,$element->relation->foreignKey,$element,$parent);
                    
$element=$element->slave;
                }
                else
                    break;
            }
            
$fks=is_array($element->relation->foreignKey) ? $element->relation->foreignKey preg_split('/s*,s*/',$element->relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY);
            
$prefix=$element->getColumnPrefix();
            
$params=array();
            foreach(
$fks as $i=>$fk)
            {
                if(!
is_int($i))
                {
                    
$pk=$fk;
                    
$fk=$i;
                }

                if(
$element->relation instanceof CBelongsToRelation)
                {
                    if(
is_int($i))
                    {
                        if(isset(
$parent->_table->foreignKeys[$fk]))  // FK defined
                            
$pk=$parent->_table->foreignKeys[$fk][1];
                        elseif(
is_array($element->_table->primaryKey)) // composite PK
                            
$pk=$element->_table->primaryKey[$i];
                        else
                            
$pk=$element->_table->primaryKey;
                    }
                    
$params[$pk]=$record->$fk;
                }
                else
                {
                    if(
is_int($i))
                    {
                        if(isset(
$element->_table->foreignKeys[$fk]))  // FK defined
                            
$pk=$element->_table->foreignKeys[$fk][1];
                        elseif(
is_array($parent->_table->primaryKey)) // composite PK
                            
$pk=$parent->_table->primaryKey[$i];
                        else
                            
$pk=$parent->_table->primaryKey;
                    }
                    
$params[$fk]=$record->$pk;
                }
            }
            
$count=0;
            foreach(
$params as $name=>$value)
            {
                
$query->conditions[]=$prefix.$schema->quoteColumnName($name).'=:ypl'.$count;
                
$query->params[':ypl'.$count]=$value;
                
$count++;
            }
        }
    }

    
/**
     * Performs the eager loading with the base records ready.
     * @param mixed $baseRecords 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 CJoinQuery($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 $criteria the query criteria
     * @return string number of primary records. Note: type is string to keep max. precision.
     */
    
public function count($criteria=null)
    {
        
$query=new CJoinQuery($this,$criteria);
        
// ensure only one big join statement is used
        
$this->_finder->baseLimited=false;
        
$this->_finder->joinAll=true;
        
$this->buildQuery($query);

        
$query->limit=$query->offset=-1;

        if(!empty(
$criteria->group) || !empty($criteria->having))
        {
            
$query->orders = array();
            
$command=$query->createCommand($this->_builder);
            
$sql=$command->getText();
            
$sql="SELECT COUNT(*) FROM ({$sql}) sq";
            
$command->setText($sql);
            
$command->params=$query->params;
            return 
$command->queryScalar();
        }
        else
        {
            
$select=is_array($criteria->select) ? implode(',',$criteria->select) : $criteria->select;
            if(
$select!=='*' && preg_match('/^counts*(/i',trim($select)))
                
$query->selects=array($select);
            elseif(
is_string($this->_table->primaryKey))
            {
                
$prefix=$this->getColumnPrefix();
                
$schema=$this->_builder->getSchema();
                
$column=$prefix.$schema->quoteColumnName($this->_table->primaryKey);
                
$query->selects=array("COUNT(DISTINCT $column)");
            }
            else
                
$query->selects=array("COUNT(*)");

            
$query->orders=$query->groups=$query->havings=array();
            
$command=$query->createCommand($this->_builder);
            return 
$command->queryScalar();
        }
    }

    
/**
     * Calls {@link CActiveRecord::afterFind} of all the records.
     */
    
public function afterFind()
    {
        foreach(
$this->records as $record)
            
$record->afterFindInternal();
        foreach(
$this->children as $child)
            
$child->afterFind();

        
$this->children null;
    }

    
/**
     * Builds the join query with all descendant HAS_ONE and BELONGS_TO nodes.
     * @param CJoinQuery $query the query being built up
     */
    
public function buildQuery($query)
    {
        foreach(
$this->children as $child)
        {
            if(
$child->master!==null)
                
$child->_joined=true;
            elseif(
$child->relation instanceof CHasOneRelation || $child->relation instanceof CBelongsToRelation
                
|| $this->_finder->joinAll || $child->relation->together || (!$this->_finder->baseLimited && $child->relation->together===null))
            {
                
$child->_joined=true;
                
$query->join($child);
                
$child->buildQuery($query);
            }
        }
    }

    
/**
     * Executes the join query and populates the query results.
     * @param CJoinQuery $query 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 $query the query executed
     * @param array $row 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)
            {
                if(!empty(
$child->relation->select))
                    
$record->addRelatedRecord($child->relation->name,null,$child->relation instanceof CHasManyRelation);
            }
            
$this->records[$pk]=$record;
        }

        
// populate child records recursively
        
foreach($this->children as $child)
        {
            if(!isset(
$query->elements[$child->id]) || empty($child->relation->select))
                continue;
            
$childRecord=$child->populateRecord($query,$row);
            if(
$child->relation instanceof CHasOneRelation || $child->relation instanceof CBelongsToRelation)
                
$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 CActiveRecord)
                    
$fpk=serialize($childRecord->getPrimaryKey());
                else
                    
$fpk=0;
                if(!isset(
$this->_related[$pk][$child->relation->name][$fpk]))
                {
                    if(
$childRecord instanceof CActiveRecord && $child->relation->index!==null)
                        
$index=$childRecord->{$child->relation->index};
                    else
                        
$index=true;
                    
$record->addRelatedRecord($child->relation->name,$childRecord,$index);
                    
$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->rawTableAlias;
        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 $select columns to be selected. Defaults to '*', indicating all columns.
     * @throws CDbException if active record class is trying to select an invalid column
     * @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;
                
$key=trim($key,''"`');

                if(
$key==='*')
                {
                    foreach(
$this->_table->columns as $name=>$column)
                    {
                        
$alias=$this->_columnAliases[$name];
                        if(!isset(
$selected[$alias]))
                        {
                            
$columns[]=$prefix.$column->rawName.' AS '.$schema->quoteColumnName($alias);
                            
$selected[$alias]=1;
                        }
                    }
                    continue;
                }

                if(isset(
$this->_columnAliases[$key]))  // simple column names
                {
                    
$columns[]=$prefix.$schema->quoteColumnName($key).' AS '.$schema->quoteColumnName($this->_columnAliases[$key]);
                    
$selected[$this->_columnAliases[$key]]=1;
                }
                elseif(preg_match('/^(.*?)s+ASs+(w+)$/im',
$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 CDbException(Yii::t('yii','Active record "
{class}" is trying to select an invalid column "{column}". Note, the column must exist in the table or be an expression with alias.',
                        array('{class}'=>get_class(
$this->model), '{column}'=>$name)));
            }
            // 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);
            elseif(is_array(
$this->_pkAlias))
            {
                foreach(
$this->_pkAlias as $name=>$alias)
                    if(!isset(
$selected[$alias]))
                        
$columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($alias);
            }
        }

        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);
        elseif(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->rawTableAlias.'.';
        else
            return 
$this->_table->rawName.'.';
    }

    /**
     * @throws CDbException if relation in active record class is not specified correctly
     * @return string the join statement (this node joins with its parent)
     */
    public function getJoinCondition()
    {
        
$parent=$this->_parent;
        if(
$this->relation instanceof CManyManyRelation)
        {
            
$schema=$this->_builder->getSchema();
            
$joinTableName=$this->relation->getJunctionTableName();
            if((
$joinTable=$schema->getTable($joinTableName))===null)
                throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.',
                    array('{class}'=>get_class(
$parent->model), '{relation}'=>$this->relation->name, '{joinTable}'=>$joinTableName)));
            
$fks=$this->relation->getJunctionForeignKeys();

            return 
$this->joinManyMany($joinTable,$fks,$parent);
        }
        else
        {
            
$fks=is_array($this->relation->foreignKey) ? $this->relation->foreignKey : preg_split('/s*,s*/',$this->relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY);
            if(
$this->slave!==null)
            {
                if(
$this->relation instanceof CBelongsToRelation)
                {
                    
$fks=array_flip($fks);
                    
$pke=$this->slave;
                    
$fke=$this;
                }
                else
                {
                    
$pke=$this;
                    
$fke=$this->slave;
                }
            }
            elseif(
$this->relation instanceof CBelongsToRelation)
            {
                
$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 
$fke the join element containing foreign keys
     * @param array 
$fks the foreign keys
     * @param CJoinElement 
$pke the join element contains primary keys
     * @param CJoinElement 
$parent 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();
        if(is_string(
$fks))
            
$fks=preg_split('/s*,s*/',$fks,-1,PREG_SPLIT_NO_EMPTY);
        foreach(
$fks as $i=>$fk)
        {
            if(!is_int(
$i))
            {
                
$pk=$fk;
                
$fk=$i;
            }

            if(!isset(
$fke->_table->columns[$fk]))
                throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".',
                    array('{class}'=>get_class(
$parent->model), '{relation}'=>$this->relation->name, '{key}'=>$fk, '{table}'=>$fke->_table->name)));

            if(is_int(
$i))
            {
                if(isset(
$fke->_table->foreignKeys[$fk]) && $schema->compareTableNames($pke->_table->rawName, $fke->_table->foreignKeys[$fk][0]))
                    
$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;

        if(!empty(
$this->relation->joinOptions) && is_string($this->relation->joinOptions))
            return 
$this->relation->joinType.' '.$this->getTableNameWithAlias().' '.$this->relation->joinOptions.
                ' ON ('.implode(') AND (',
$joins).')';
        else
            return 
$this->relation->joinType.' '.$this->getTableNameWithAlias().' ON ('.implode(') AND (',$joins).')';
    }

    /**
     * Generates the join statement for many-many relationship.
     * @param CDbTableSchema 
$joinTable the join table
     * @param array 
$fks the foreign keys
     * @param CJoinElement 
$parent 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=$schema->quoteTableName($this->relation->name.'_'.$this->tableAlias);
        
$parentCondition=array();
        
$childCondition=array();

        
$fkDefined=true;
        foreach(
$fks as $i=>$fk)
        {
            if(!isset(
$joinTable->columns[$fk]))
                throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".',
                    array('{class}'=>get_class(
$parent->model), '{relation}'=>$this->relation->name, '{key}'=>$fk, '{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);
                elseif(!isset(
$childCondition[$pk]) && $schema->compareTableNames($this->_table->rawName,$tableName))
                    
$childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk);
                else
                {
                    
$fkDefined=false;
                    break;
                }
            }
            else
            {
                
$fkDefined=false;
                break;
            }
        }

        if(!
$fkDefined)
        {
            
$parentCondition=array();
            
$childCondition=array();
            foreach(
$fks as $i=>$fk)
            {
                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;

            if(is_array(
$this->relation->joinOptions) && isset($this->relation->joinOptions[0]) &&
                is_string(
$this->relation->joinOptions[0]))
                
$join.=' '.$this->relation->joinOptions[0];
            elseif(!empty(
$this->relation->joinOptions) && is_string($this->relation->joinOptions))
                
$join.=' '.$this->relation->joinOptions;

            
$join.=' ON ('.implode(') AND (',$parentCondition).')';
            
$join.=' '.$this->relation->joinType.' '.$this->getTableNameWithAlias();

            if(is_array(
$this->relation->joinOptions) && isset($this->relation->joinOptions[1]) &&
                is_string(
$this->relation->joinOptions[1]))
                
$join.=' '.$this->relation->joinOptions[1];

            
$join.=' ON ('.implode(') AND (',$childCondition).')';
            if(!empty(
$this->relation->on))
                
$join.=' AND ('.$this->relation->on.')';
            return 
$join;
        }
        else
            throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.',
                array('{class}'=>get_class(
$parent->model), '{relation}'=>$this->relation->name)));
    }
}


/**
 * CJoinQuery represents a JOIN SQL statement.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @package system.db.ar
 * @since 1.0
 */
class CJoinQuery
{
    /**
     * @var array list of column selections
     */
    public 
$selects=array();
    /**
     * @var boolean whether to select distinct result set
     */
    public 
$distinct=false;
    /**
     * @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 
$joinElement The root join tree.
     * @param CDbCriteria 
$criteria 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;
            if(!
$this->distinct && $criteria->distinct)
                
$this->distinct=true;
        }
        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 
$element the element to be joined
     */
    public function join(
$element)
    {
        if(
$element->slave!==null)
            
$this->join($element->slave);
        if(!empty(
$element->relation->select))
            
$this->selects[]=$element->getColumnSelect($element->relation->select);
        
$this->conditions[]=$element->relation->condition;
        
$this->orders[]=$element->relation->order;
        
$this->joins[]=$element->getJoinCondition();
        
$this->joins[]=$element->relation->join;
        
$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 
$builder the command builder
     * @return CDbCommand DB command instance representing the SQL statement
     */
    public function createCommand(
$builder)
    {
        
$sql=($this->distinct ? 'SELECT DISTINCT ':'SELECT ') . implode(', ',$this->selects);
        
$sql.=' FROM ' . implode(' ',array_unique($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>
 * @package system.db.ar
 */
class CStatElement
{
    /**
     * @var CActiveRelation the relation represented by this tree node
     */
    public 
$relation;

    private 
$_finder;
    private 
$_parent;

    /**
     * Constructor.
     * @param CActiveFinder 
$finder the finder
     * @param CStatRelation 
$relation the STAT relation
     * @param CJoinElement 
$parent 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=$this->_finder->getModel($relation->className);
        
$builder=$model->getCommandBuilder();
        
$schema=$builder->getSchema();
        
$table=$model->getTableSchema();
        
$parent=$this->_parent;
        
$pkTable=$parent->model->getTableSchema();

        
$fks=preg_split('/s*,s*/',$relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY);
        if(count(
$fks)!==count($pkTable->primaryKey))
            throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with an invalid foreign key. The columns in the key must match the primary keys of the table "{table}".',
                        array('{class}'=>get_class(
$parent->model), '{relation}'=>$relation->name, '{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 CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".',
                    array('{class}'=>get_class(
$parent->model), '{relation}'=>$relation->name, '{key}'=>$fk, '{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 CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with a foreign key "{key}" that does not point to the parent table "{table}".',
                        array('{class}'=>get_class(
$parent->model), '{relation}'=>$relation->name, '{key}'=>$fk, '{table}'=>$pkTable->name)));
            }
            else  // FK constraints undefined
            {
                if(is_array(
$pkTable->primaryKey)) // composite PK
                    
$map[$pkTable->primaryKey[$i]]=$fk;
                else
                    
$map[$pkTable->primaryKey]=$fk;
            }
        }

        
$records=$this->_parent->records;

        
$join=empty($relation->join)?'' : ' '.$relation->join;
        
$where=empty($relation->condition)?' WHERE ' : ' WHERE ('.$relation->condition.') AND ';
        
$group=empty($relation->group)?'' : ', '.$relation->group;
        
$having=empty($relation->having)?'' : ' HAVING ('.$relation->having.')';
        
$order=empty($relation->order)?'' : ' ORDER BY '.$relation->order;

        
$c=$schema->quoteColumnName('c');
        
$s=$schema->quoteColumnName('s');

        
$tableAlias=$model->getTableAlias(true);

        // generate and perform query
        if(count(
$fks)===1)  // single column FK
        {
            
$col=$tableAlias.'.'.$table->columns[$fks[0]]->rawName;
            
$sql="SELECT $col AS $c, {$relation->select} AS $s FROM {$table->rawName".$tableAlias.$join
                .
$where.'('.$builder->createInCondition($table,$fks[0],array_keys($records),$tableAlias.'.').')'
                ." 
GROUP BY $col".$group
                .
$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=$tableAlias.'.'.$table->columns[$map[$pk]]->rawName;
                
$cols[$name]=$name.' AS '.$schema->quoteColumnName('c'.$n);
            }
            
$sql='SELECT '.implode(', ',$cols).", {$relation->select} AS $s FROM {$table->rawName".$tableAlias.$join
                .
$where.'('.$builder->createInCondition($table,$fks,$keys,$tableAlias.'.').')'
                .' GROUP BY '.implode(', ',array_keys(
$cols)).$group
                .
$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);
    }

    /**
     * @param string 
$joinTableName jointablename
     * @param string 
$keys keys
     * @throws CDbException
     */
    private function queryManyMany(
$joinTableName,$keys)
    {
        
$relation=$this->relation;
        
$model=$this->_finder->getModel($relation->className);
        
$table=$model->getTableSchema();
        
$builder=$model->getCommandBuilder();
        
$schema=$builder->getSchema();
        
$pkTable=$this->_parent->model->getTableSchema();

        
$tableAlias=$model->getTableAlias(true);

        if((
$joinTable=$builder->getSchema()->getTable($joinTableName))===null)
            throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.',
                array('{class}'=>get_class(
$this->_parent->model), '{relation}'=>$relation->name, '{joinTable}'=>$joinTableName)));

        
$fks=preg_split('/s*,s*/',$keys,-1,PREG_SPLIT_NO_EMPTY);
        if(count(
$fks)!==count($table->primaryKey)+count($pkTable->primaryKey))
            throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.',
                array('{class}'=>get_class(
$this->_parent->model), '{relation}'=>$relation->name)));

        
$joinCondition=array();
        
$map=array();

        
$fkDefined=true;
        foreach(
$fks as $i=>$fk)
        {
            if(!isset(
$joinTable->columns[$fk]))
                throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".',
                    array('{class}'=>get_class(
$this->_parent->model), '{relation}'=>$relation->name, '{key}'=>$fk, '{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]=$tableAlias.'.'.$schema->quoteColumnName($pk).'='.$joinTable->rawName.'.'.$schema->quoteColumnName($fk);
                elseif(!isset(
$map[$pk]) && $schema->compareTableNames($pkTable->rawName,$tableName))
                    
$map[$pk]=$fk;
                else
                {
                    
$fkDefined=false;
                    break;
                }
            }
            else
            {
                
$fkDefined=false;
                break;
            }
        }

        if(!
$fkDefined)
        {
            
$joinCondition=array();
            
$map=array();
            foreach(
$fks as $i=>$fk)
            {
                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]=$tableAlias.'.'.$schema->quoteColumnName($pk).'='.$joinTable->rawName.'.'.$schema->quoteColumnName($fk);
                }
            }
        }

        if(
$joinCondition===array() || $map===array())
            throw new CDbException(Yii::t('yii','The relation "
{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.',
                array('{class}'=>get_class(
$this->_parent->model), '{relation}'=>$relation->name)));

        
$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];
            }
        }

        
$join=empty($relation->join)?'' : ' '.$relation->join;
        
$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.' '.$tableAlias.' INNER JOIN '.$joinTable->rawName
            .' ON ('.implode(') AND (',
$joinCondition).')'.$join
            .
$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);
    }
}
Онлайн: 1
Реклама