Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • Net
  • None
  • PHP
  • system
    • base
    • caching
      • dependencies
    • collections
    • console
    • db
      • ar
      • schema
        • cubrid
        • mssql
        • mysql
        • oci
        • pgsql
        • sqlite
    • i18n
      • gettext
    • logging
    • test
    • utils
    • validators
    • web
      • actions
      • auth
      • filters
      • form
      • helpers
      • renderers
      • services
      • widgets
        • captcha
        • pagers
  • Text
    • Highlighter
  • zii
    • behaviors
    • widgets
      • grid
      • jui

Classes

  • ActionToRecord
  • CActiveFinder
  • CActiveRecord
  • CActiveRecordBehavior
  • CActiveRecordMetaData
  • CActiveRelation
  • CBaseActiveRelation
  • CBelongsToRelation
  • CHasManyRelation
  • CHasOneRelation
  • CJoinElement
  • CJoinQuery
  • CManyManyRelation
  • ContactsNameBehavior
  • CStatElement
  • CStatRelation
  • ERememberFiltersBehavior
  • FileSystemObjectBehavior
  • MobileLayouts
  • RecordAliases
  • RelationshipsBehavior
  • TopicReplies
  • X2ActiveRecord
  • X2Flow
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * CActiveFinder class file.
   4:  *
   5:  * @author Qiang Xue <qiang.xue@gmail.com>
   6:  * @link http://www.yiiframework.com/
   7:  * @copyright 2008-2013 Yii Software LLC
   8:  * @license http://www.yiiframework.com/license/
   9:  */
  10: 
  11: /**
  12:  * CActiveFinder implements eager loading and lazy loading of related active records.
  13:  *
  14:  * When used in eager loading, this class provides the same set of find methods as
  15:  * {@link CActiveRecord}.
  16:  *
  17:  * @author Qiang Xue <qiang.xue@gmail.com>
  18:  * @package system.db.ar
  19:  * @since 1.0
  20:  */
  21: class CActiveFinder extends CComponent
  22: {
  23:     /**
  24:      * @var boolean join all tables all at once. Defaults to false.
  25:      * This property is internally used.
  26:      */
  27:     public $joinAll=false;
  28:     /**
  29:      * @var boolean whether the base model has limit or offset.
  30:      * This property is internally used.
  31:      */
  32:     public $baseLimited=false;
  33: 
  34:     private $_joinCount=0;
  35:     private $_joinTree;
  36:     private $_builder;
  37: 
  38:     /**
  39:      * Constructor.
  40:      * A join tree is built up based on the declared relationships between active record classes.
  41:      * @param CActiveRecord $model the model that initiates the active finding process
  42:      * @param mixed $with the relation names to be actively looked for
  43:      */
  44:     public function __construct($model,$with)
  45:     {
  46:         $this->_builder=$model->getCommandBuilder();
  47:         $this->_joinTree=new CJoinElement($this,$model);
  48:         $this->buildJoinTree($this->_joinTree,$with);
  49:     }
  50: 
  51:     /**
  52:      * Do not call this method. This method is used internally to perform the relational query
  53:      * based on the given DB criteria.
  54:      * @param CDbCriteria $criteria the DB criteria
  55:      * @param boolean $all whether to bring back all records
  56:      * @return mixed the query result
  57:      */
  58:     public function query($criteria,$all=false)
  59:     {
  60:         $this->joinAll=$criteria->together===true;
  61: 
  62:         if($criteria->alias!='')
  63:         {
  64:             $this->_joinTree->tableAlias=$criteria->alias;
  65:             $this->_joinTree->rawTableAlias=$this->_builder->getSchema()->quoteTableName($criteria->alias);
  66:         }
  67: 
  68:         $this->_joinTree->find($criteria);
  69:         $this->_joinTree->afterFind();
  70: 
  71:         if($all)
  72:         {
  73:             $result = array_values($this->_joinTree->records);
  74:             if ($criteria->index!==null)
  75:             {
  76:                 $index=$criteria->index;
  77:                 $array=array();
  78:                 foreach($result as $object)
  79:                     $array[$object->$index]=$object;
  80:                 $result=$array;
  81:             }
  82:         }
  83:         elseif(count($this->_joinTree->records))
  84:             $result = reset($this->_joinTree->records);
  85:         else
  86:             $result = null;
  87: 
  88:         $this->destroyJoinTree();
  89:         return $result;
  90:     }
  91: 
  92:     /**
  93:      * This method is internally called.
  94:      * @param string $sql the SQL statement
  95:      * @param array $params parameters to be bound to the SQL statement
  96:      * @return CActiveRecord
  97:      */
  98:     public function findBySql($sql,$params=array())
  99:     {
 100:         Yii::trace(get_class($this->_joinTree->model).'.findBySql() eagerly','system.db.ar.CActiveRecord');
 101:         if(($row=$this->_builder->createSqlCommand($sql,$params)->queryRow())!==false)
 102:         {
 103:             $baseRecord=$this->_joinTree->model->populateRecord($row,false);
 104:             $this->_joinTree->findWithBase($baseRecord);
 105:             $this->_joinTree->afterFind();
 106:             $this->destroyJoinTree();
 107:             return $baseRecord;
 108:         }
 109:         else
 110:             $this->destroyJoinTree();
 111:     }
 112: 
 113:     /**
 114:      * This method is internally called.
 115:      * @param string $sql the SQL statement
 116:      * @param array $params parameters to be bound to the SQL statement
 117:      * @return CActiveRecord[]
 118:      */
 119:     public function findAllBySql($sql,$params=array())
 120:     {
 121:         Yii::trace(get_class($this->_joinTree->model).'.findAllBySql() eagerly','system.db.ar.CActiveRecord');
 122:         if(($rows=$this->_builder->createSqlCommand($sql,$params)->queryAll())!==array())
 123:         {
 124:             $baseRecords=$this->_joinTree->model->populateRecords($rows,false);
 125:             $this->_joinTree->findWithBase($baseRecords);
 126:             $this->_joinTree->afterFind();
 127:             $this->destroyJoinTree();
 128:             return $baseRecords;
 129:         }
 130:         else
 131:         {
 132:             $this->destroyJoinTree();
 133:             return array();
 134:         }
 135:     }
 136: 
 137:     /**
 138:      * This method is internally called.
 139:      * @param CDbCriteria $criteria the query criteria
 140:      * @return string
 141:      */
 142:     public function count($criteria)
 143:     {
 144:         Yii::trace(get_class($this->_joinTree->model).'.count() eagerly','system.db.ar.CActiveRecord');
 145:         $this->joinAll=$criteria->together!==true;
 146: 
 147:         $alias=$criteria->alias===null ? 't' : $criteria->alias;
 148:         $this->_joinTree->tableAlias=$alias;
 149:         $this->_joinTree->rawTableAlias=$this->_builder->getSchema()->quoteTableName($alias);
 150: 
 151:         $n=$this->_joinTree->count($criteria);
 152:         $this->destroyJoinTree();
 153:         return $n;
 154:     }
 155: 
 156:     /**
 157:      * Finds the related objects for the specified active record.
 158:      * This method is internally invoked by {@link CActiveRecord} to support lazy loading.
 159:      * @param CActiveRecord $baseRecord the base record whose related objects are to be loaded
 160:      */
 161:     public function lazyFind($baseRecord)
 162:     {
 163:         $this->_joinTree->lazyFind($baseRecord);
 164:         if(!empty($this->_joinTree->children))
 165:         {
 166:             foreach($this->_joinTree->children as $child)
 167:                 $child->afterFind();
 168:         }
 169:         $this->destroyJoinTree();
 170:     }
 171: 
 172:     /**
 173:      * Given active record class name returns new model instance.
 174:      *
 175:      * @param string $className active record class name
 176:      * @return CActiveRecord active record model instance
 177:      *
 178:      * @since 1.1.14
 179:      */
 180:     public function getModel($className)
 181:     {
 182:         return CActiveRecord::model($className);
 183:     }
 184: 
 185:     private function destroyJoinTree()
 186:     {
 187:         if($this->_joinTree!==null)
 188:             $this->_joinTree->destroy();
 189:         $this->_joinTree=null;
 190:     }
 191: 
 192:     /**
 193:      * Builds up the join tree representing the relationships involved in this query.
 194:      * @param CJoinElement $parent the parent tree node
 195:      * @param mixed $with the names of the related objects relative to the parent tree node
 196:      * @param array $options additional query options to be merged with the relation
 197:      * @throws CDbException if given parent tree node is an instance of {@link CStatElement}
 198:      * or relation is not defined in the given parent's tree node model class
 199:      */
 200:     private function buildJoinTree($parent,$with,$options=null)
 201:     {
 202:         if($parent instanceof CStatElement)
 203:             throw new CDbException(Yii::t('yii','The STAT relation "{name}" cannot have child relations.',
 204:                 array('{name}'=>$parent->relation->name)));
 205: 
 206:         if(is_string($with))
 207:         {
 208:             if(($pos=strrpos($with,'.'))!==false)
 209:             {
 210:                 $parent=$this->buildJoinTree($parent,substr($with,0,$pos));
 211:                 $with=substr($with,$pos+1);
 212:             }
 213: 
 214:             // named scope
 215:             $scopes=array();
 216:             if(($pos=strpos($with,':'))!==false)
 217:             {
 218:                 $scopes=explode(':',substr($with,$pos+1));
 219:                 $with=substr($with,0,$pos);
 220:             }
 221: 
 222:             if(isset($parent->children[$with]) && $parent->children[$with]->master===null)
 223:                 return $parent->children[$with];
 224: 
 225:             if(($relation=$parent->model->getActiveRelation($with))===null)
 226:                 throw new CDbException(Yii::t('yii','Relation "{name}" is not defined in active record class "{class}".',
 227:                     array('{class}'=>get_class($parent->model), '{name}'=>$with)));
 228: 
 229:             $relation=clone $relation;
 230:             $model=$this->getModel($relation->className);
 231: 
 232:             if($relation instanceof CActiveRelation)
 233:             {
 234:                 $oldAlias=$model->getTableAlias(false,false);
 235:                 if(isset($options['alias']))
 236:                     $model->setTableAlias($options['alias']);
 237:                 elseif($relation->alias===null)
 238:                     $model->setTableAlias($relation->name);
 239:                 else
 240:                     $model->setTableAlias($relation->alias);
 241:             }
 242: 
 243:             if(!empty($relation->scopes))
 244:                 $scopes=array_merge($scopes,(array)$relation->scopes); // no need for complex merging
 245: 
 246:             if(!empty($options['scopes']))
 247:                 $scopes=array_merge($scopes,(array)$options['scopes']); // no need for complex merging
 248: 
 249:             if(!empty($options['joinOptions']))
 250:                 $relation->joinOptions=$options['joinOptions'];
 251: 
 252:             $model->resetScope(false);
 253:             $criteria=$model->getDbCriteria();
 254:             $criteria->scopes=$scopes;
 255:             $model->beforeFindInternal();
 256:             $model->applyScopes($criteria);
 257: 
 258:             // select has a special meaning in stat relation, so we need to ignore select from scope or model criteria
 259:             if($relation instanceof CStatRelation)
 260:                 $criteria->select='*';
 261: 
 262:             $relation->mergeWith($criteria,true);
 263: 
 264:             // dynamic options
 265:             if($options!==null)
 266:                 $relation->mergeWith($options);
 267: 
 268:             if($relation instanceof CActiveRelation)
 269:                 $model->setTableAlias($oldAlias);
 270: 
 271:             if($relation instanceof CStatRelation)
 272:                 return new CStatElement($this,$relation,$parent);
 273:             else
 274:             {
 275:                 if(isset($parent->children[$with]))
 276:                 {
 277:                     $element=$parent->children[$with];
 278:                     $element->relation=$relation;
 279:                 }
 280:                 else
 281:                     $element=new CJoinElement($this,$relation,$parent,++$this->_joinCount);
 282:                 if(!empty($relation->through))
 283:                 {
 284:                     $slave=$this->buildJoinTree($parent,$relation->through,array('select'=>''));
 285:                     $slave->master=$element;
 286:                     $element->slave=$slave;
 287:                 }
 288:                 $parent->children[$with]=$element;
 289:                 if(!empty($relation->with))
 290:                     $this->buildJoinTree($element,$relation->with);
 291:                 return $element;
 292:             }
 293:         }
 294: 
 295:         // $with is an array, keys are relation name, values are relation spec
 296:         foreach($with as $key=>$value)
 297:         {
 298:             if(is_string($value))  // the value is a relation name
 299:                 $this->buildJoinTree($parent,$value);
 300:             elseif(is_string($key) && is_array($value))
 301:                 $this->buildJoinTree($parent,$key,$value);
 302:         }
 303:     }
 304: }
 305: 
 306: 
 307: /**
 308:  * CJoinElement represents a tree node in the join tree created by {@link CActiveFinder}.
 309:  *
 310:  * @author Qiang Xue <qiang.xue@gmail.com>
 311:  * @package system.db.ar
 312:  * @since 1.0
 313:  */
 314: class CJoinElement
 315: {
 316:     /**
 317:      * @var integer the unique ID of this tree node
 318:      */
 319:     public $id;
 320:     /**
 321:      * @var CActiveRelation the relation represented by this tree node
 322:      */
 323:     public $relation;
 324:     /**
 325:      * @var CActiveRelation the master relation
 326:      */
 327:     public $master;
 328:     /**
 329:      * @var CActiveRelation the slave relation
 330:      */
 331:     public $slave;
 332:     /**
 333:      * @var CActiveRecord the model associated with this tree node
 334:      */
 335:     public $model;
 336:     /**
 337:      * @var array list of active records found by the queries. They are indexed by primary key values.
 338:      */
 339:     public $records=array();
 340:     /**
 341:      * @var array list of child join elements
 342:      */
 343:     public $children=array();
 344:     /**
 345:      * @var array list of stat elements
 346:      */
 347:     public $stats=array();
 348:     /**
 349:      * @var string table alias for this join element
 350:      */
 351:     public $tableAlias;
 352:     /**
 353:      * @var string the quoted table alias for this element
 354:      */
 355:     public $rawTableAlias;
 356: 
 357:     private $_finder;
 358:     private $_builder;
 359:     private $_parent;
 360:     private $_pkAlias;                  // string or name=>alias
 361:     private $_columnAliases=array();    // name=>alias
 362:     private $_joined=false;
 363:     private $_table;
 364:     private $_related=array();          // PK, relation name, related PK => true
 365: 
 366:     /**
 367:      * Constructor.
 368:      * @param CActiveFinder $finder the finder
 369:      * @param mixed $relation the relation (if the third parameter is not null)
 370:      * or the model (if the third parameter is null) associated with this tree node.
 371:      * @param CJoinElement $parent the parent tree node
 372:      * @param integer $id the ID of this tree node that is unique among all the tree nodes
 373:      */
 374:     public function __construct($finder,$relation,$parent=null,$id=0)
 375:     {
 376:         $this->_finder=$finder;
 377:         $this->id=$id;
 378:         if($parent!==null)
 379:         {
 380:             $this->relation=$relation;
 381:             $this->_parent=$parent;
 382:             $this->model=$this->_finder->getModel($relation->className);
 383:             $this->_builder=$this->model->getCommandBuilder();
 384:             $this->tableAlias=$relation->alias===null?$relation->name:$relation->alias;
 385:             $this->rawTableAlias=$this->_builder->getSchema()->quoteTableName($this->tableAlias);
 386:             $this->_table=$this->model->getTableSchema();
 387:         }
 388:         else  // root element, the first parameter is the model.
 389:         {
 390:             $this->model=$relation;
 391:             $this->_builder=$relation->getCommandBuilder();
 392:             $this->_table=$relation->getTableSchema();
 393:             $this->tableAlias=$this->model->getTableAlias();
 394:             $this->rawTableAlias=$this->_builder->getSchema()->quoteTableName($this->tableAlias);
 395:         }
 396: 
 397:         // set up column aliases, such as t1_c2
 398:         $table=$this->_table;
 399:         if($this->model->getDbConnection()->getDriverName()==='oci')  // Issue 482
 400:             $prefix='T'.$id.'_C';
 401:         else
 402:             $prefix='t'.$id.'_c';
 403:         foreach($table->getColumnNames() as $key=>$name)
 404:         {
 405:             $alias=$prefix.$key;
 406:             $this->_columnAliases[$name]=$alias;
 407:             if($table->primaryKey===$name)
 408:                 $this->_pkAlias=$alias;
 409:             elseif(is_array($table->primaryKey) && in_array($name,$table->primaryKey))
 410:                 $this->_pkAlias[$name]=$alias;
 411:         }
 412:     }
 413: 
 414:     /**
 415:      * Removes references to child elements and finder to avoid circular references.
 416:      * This is internally used.
 417:      */
 418:     public function destroy()
 419:     {
 420:         if(!empty($this->children))
 421:         {
 422:             foreach($this->children as $child)
 423:                 $child->destroy();
 424:         }
 425:         unset($this->_finder, $this->_parent, $this->model, $this->relation, $this->master, $this->slave, $this->records, $this->children, $this->stats);
 426:     }
 427: 
 428:     /**
 429:      * Performs the recursive finding with the criteria.
 430:      * @param CDbCriteria $criteria the query criteria
 431:      */
 432:     public function find($criteria=null)
 433:     {
 434:         if($this->_parent===null) // root element
 435:         {
 436:             $query=new CJoinQuery($this,$criteria);
 437:             $this->_finder->baseLimited=($criteria->offset>=0 || $criteria->limit>=0);
 438:             $this->buildQuery($query);
 439:             $this->_finder->baseLimited=false;
 440:             $this->runQuery($query);
 441:         }
 442:         elseif(!$this->_joined && !empty($this->_parent->records)) // not joined before
 443:         {
 444:             $query=new CJoinQuery($this->_parent);
 445:             $this->_joined=true;
 446:             $query->join($this);
 447:             $this->buildQuery($query);
 448:             $this->_parent->runQuery($query);
 449:         }
 450: 
 451:         foreach($this->children as $child) // find recursively
 452:             $child->find();
 453: 
 454:         foreach($this->stats as $stat)
 455:             $stat->query();
 456:     }
 457: 
 458:     /**
 459:      * Performs lazy find with the specified base record.
 460:      * @param CActiveRecord $baseRecord the active record whose related object is to be fetched.
 461:      */
 462:     public function lazyFind($baseRecord)
 463:     {
 464:         if(is_string($this->_table->primaryKey))
 465:             $this->records[$baseRecord->{$this->_table->primaryKey}]=$baseRecord;
 466:         else
 467:         {
 468:             $pk=array();
 469:             foreach($this->_table->primaryKey as $name)
 470:                 $pk[$name]=$baseRecord->$name;
 471:             $this->records[serialize($pk)]=$baseRecord;
 472:         }
 473: 
 474:         foreach($this->stats as $stat)
 475:             $stat->query();
 476: 
 477:         if(!$this->children)
 478:             return;
 479: 
 480:         $params=array();
 481:         foreach($this->children as $child)
 482:             if(is_array($child->relation->params))
 483:                 $params=array_merge($params,$child->relation->params);
 484: 
 485:         $query=new CJoinQuery($child);
 486:         $query->selects=array($child->getColumnSelect($child->relation->select));
 487:         $query->conditions=array(
 488:             $child->relation->on,
 489:         );
 490:         $query->groups[]=$child->relation->group;
 491:         $query->joins[]=$child->relation->join;
 492:         $query->havings[]=$child->relation->having;
 493:         $query->orders[]=$child->relation->order;
 494:         $query->params=$params;
 495:         $query->elements[$child->id]=true;
 496:         if($child->relation instanceof CHasManyRelation)
 497:         {
 498:             $query->limit=$child->relation->limit;
 499:             $query->offset=$child->relation->offset;
 500:         }
 501: 
 502:         $child->applyLazyCondition($query,$baseRecord);
 503: 
 504:         $this->_joined=true;
 505:         $child->_joined=true;
 506: 
 507:         $this->_finder->baseLimited=false;
 508:         $child->buildQuery($query);
 509:         $child->runQuery($query);
 510:         foreach($child->children as $c)
 511:             $c->find();
 512: 
 513:         if(empty($child->records))
 514:             return;
 515:         if($child->relation instanceof CHasOneRelation || $child->relation instanceof CBelongsToRelation)
 516:             $baseRecord->addRelatedRecord($child->relation->name,reset($child->records),false);
 517:         else // has_many and many_many
 518:         {
 519:             foreach($child->records as $record)
 520:             {
 521:                 if($child->relation->index!==null)
 522:                     $index=$record->{$child->relation->index};
 523:                 else
 524:                     $index=true;
 525:                 $baseRecord->addRelatedRecord($child->relation->name,$record,$index);
 526:             }
 527:         }
 528:     }
 529: 
 530:     /**
 531:      * Apply Lazy Condition
 532:      * @param CJoinQuery $query represents a JOIN SQL statements
 533:      * @param CActiveRecord $record the active record whose related object is to be fetched.
 534:      * @throws CDbException if relation in active record class is not specified correctly
 535:      */
 536:     private function applyLazyCondition($query,$record)
 537:     {
 538:         $schema=$this->_builder->getSchema();
 539:         $parent=$this->_parent;
 540:         if($this->relation instanceof CManyManyRelation)
 541:         {
 542:             $query->conditions=array(
 543:                 $this->relation->condition,
 544:             );
 545:             $joinTableName=$this->relation->getJunctionTableName();
 546:             if(($joinTable=$schema->getTable($joinTableName))===null)
 547:                 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.',
 548:                     array('{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name, '{joinTable}'=>$joinTableName)));
 549:             $fks=$this->relation->getJunctionForeignKeys();
 550: 
 551:             $joinAlias=$schema->quoteTableName($this->relation->name.'_'.$this->tableAlias);
 552:             $parentCondition=array();
 553:             $childCondition=array();
 554:             $count=0;
 555:             $params=array();
 556: 
 557:             $fkDefined=true;
 558:             foreach($fks as $i=>$fk)
 559:             {
 560:                 if(isset($joinTable->foreignKeys[$fk]))  // FK defined
 561:                 {
 562:                     list($tableName,$pk)=$joinTable->foreignKeys[$fk];
 563:                     if(!isset($parentCondition[$pk]) && $schema->compareTableNames($parent->_table->rawName,$tableName))
 564:                     {
 565:                         $parentCondition[$pk]=$joinAlias.'.'.$schema->quoteColumnName($fk).'=:ypl'.$count;
 566:                         $params[':ypl'.$count]=$record->$pk;
 567:                         $count++;
 568:                     }
 569:                     elseif(!isset($childCondition[$pk]) && $schema->compareTableNames($this->_table->rawName,$tableName))
 570:                         $childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk);
 571:                     else
 572:                     {
 573:                         $fkDefined=false;
 574:                         break;
 575:                     }
 576:                 }
 577:                 else
 578:                 {
 579:                     $fkDefined=false;
 580:                     break;
 581:                 }
 582:             }
 583: 
 584:             if(!$fkDefined)
 585:             {
 586:                 $parentCondition=array();
 587:                 $childCondition=array();
 588:                 $count=0;
 589:                 $params=array();
 590:                 foreach($fks as $i=>$fk)
 591:                 {
 592:                     if($i<count($parent->_table->primaryKey))
 593:                     {
 594:                         $pk=is_array($parent->_table->primaryKey) ? $parent->_table->primaryKey[$i] : $parent->_table->primaryKey;
 595:                         $parentCondition[$pk]=$joinAlias.'.'.$schema->quoteColumnName($fk).'=:ypl'.$count;
 596:                         $params[':ypl'.$count]=$record->$pk;
 597:                         $count++;
 598:                     }
 599:                     else
 600:                     {
 601:                         $j=$i-count($parent->_table->primaryKey);
 602:                         $pk=is_array($this->_table->primaryKey) ? $this->_table->primaryKey[$j] : $this->_table->primaryKey;
 603:                         $childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk);
 604:                     }
 605:                 }
 606:             }
 607: 
 608:             if($parentCondition!==array() && $childCondition!==array())
 609:             {
 610:                 $join='INNER JOIN '.$joinTable->rawName.' '.$joinAlias.' ON ';
 611:                 $join.='('.implode(') AND (',$parentCondition).') AND ('.implode(') AND (',$childCondition).')';
 612:                 if(!empty($this->relation->on))
 613:                     $join.=' AND ('.$this->relation->on.')';
 614:                 $query->joins[]=$join;
 615:                 foreach($params as $name=>$value)
 616:                     $query->params[$name]=$value;
 617:             }
 618:             else
 619:                 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.',
 620:                     array('{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name)));
 621:         }
 622:         else
 623:         {
 624:             $element=$this;
 625:             while(true)
 626:             {
 627:                 $condition=$element->relation->condition;
 628:                 if(!empty($condition))
 629:                     $query->conditions[]=$condition;
 630:                 $query->params=array_merge($query->params,$element->relation->params);
 631:                 if($element->slave!==null)
 632:                 {
 633:                     $query->joins[]=$element->slave->joinOneMany($element->slave,$element->relation->foreignKey,$element,$parent);
 634:                     $element=$element->slave;
 635:                 }
 636:                 else
 637:                     break;
 638:             }
 639:             $fks=is_array($element->relation->foreignKey) ? $element->relation->foreignKey : preg_split('/\s*,\s*/',$element->relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY);
 640:             $prefix=$element->getColumnPrefix();
 641:             $params=array();
 642:             foreach($fks as $i=>$fk)
 643:             {
 644:                 if(!is_int($i))
 645:                 {
 646:                     $pk=$fk;
 647:                     $fk=$i;
 648:                 }
 649: 
 650:                 if($element->relation instanceof CBelongsToRelation)
 651:                 {
 652:                     if(is_int($i))
 653:                     {
 654:                         if(isset($parent->_table->foreignKeys[$fk]))  // FK defined
 655:                             $pk=$parent->_table->foreignKeys[$fk][1];
 656:                         elseif(is_array($element->_table->primaryKey)) // composite PK
 657:                             $pk=$element->_table->primaryKey[$i];
 658:                         else
 659:                             $pk=$element->_table->primaryKey;
 660:                     }
 661:                     $params[$pk]=$record->$fk;
 662:                 }
 663:                 else
 664:                 {
 665:                     if(is_int($i))
 666:                     {
 667:                         if(isset($element->_table->foreignKeys[$fk]))  // FK defined
 668:                             $pk=$element->_table->foreignKeys[$fk][1];
 669:                         elseif(is_array($parent->_table->primaryKey)) // composite PK
 670:                             $pk=$parent->_table->primaryKey[$i];
 671:                         else
 672:                             $pk=$parent->_table->primaryKey;
 673:                     }
 674:                     $params[$fk]=$record->$pk;
 675:                 }
 676:             }
 677:             $count=0;
 678:             foreach($params as $name=>$value)
 679:             {
 680:                 $query->conditions[]=$prefix.$schema->quoteColumnName($name).'=:ypl'.$count;
 681:                 $query->params[':ypl'.$count]=$value;
 682:                 $count++;
 683:             }
 684:         }
 685:     }
 686: 
 687:     /**
 688:      * Performs the eager loading with the base records ready.
 689:      * @param mixed $baseRecords the available base record(s).
 690:      */
 691:     public function findWithBase($baseRecords)
 692:     {
 693:         if(!is_array($baseRecords))
 694:             $baseRecords=array($baseRecords);
 695:         if(is_string($this->_table->primaryKey))
 696:         {
 697:             foreach($baseRecords as $baseRecord)
 698:                 $this->records[$baseRecord->{$this->_table->primaryKey}]=$baseRecord;
 699:         }
 700:         else
 701:         {
 702:             foreach($baseRecords as $baseRecord)
 703:             {
 704:                 $pk=array();
 705:                 foreach($this->_table->primaryKey as $name)
 706:                     $pk[$name]=$baseRecord->$name;
 707:                 $this->records[serialize($pk)]=$baseRecord;
 708:             }
 709:         }
 710: 
 711:         $query=new CJoinQuery($this);
 712:         $this->buildQuery($query);
 713:         if(count($query->joins)>1)
 714:             $this->runQuery($query);
 715:         foreach($this->children as $child)
 716:             $child->find();
 717: 
 718:         foreach($this->stats as $stat)
 719:             $stat->query();
 720:     }
 721: 
 722:     /**
 723:      * Count the number of primary records returned by the join statement.
 724:      * @param CDbCriteria $criteria the query criteria
 725:      * @return string number of primary records. Note: type is string to keep max. precision.
 726:      */
 727:     public function count($criteria=null)
 728:     {
 729:         $query=new CJoinQuery($this,$criteria);
 730:         // ensure only one big join statement is used
 731:         $this->_finder->baseLimited=false;
 732:         $this->_finder->joinAll=true;
 733:         $this->buildQuery($query);
 734: 
 735:         $query->limit=$query->offset=-1;
 736: 
 737:         if(!empty($criteria->group) || !empty($criteria->having))
 738:         {
 739:             $query->orders = array();
 740:             $command=$query->createCommand($this->_builder);
 741:             $sql=$command->getText();
 742:             $sql="SELECT COUNT(*) FROM ({$sql}) sq";
 743:             $command->setText($sql);
 744:             $command->params=$query->params;
 745:             return $command->queryScalar();
 746:         }
 747:         else
 748:         {
 749:             $select=is_array($criteria->select) ? implode(',',$criteria->select) : $criteria->select;
 750:             if($select!=='*' && preg_match('/^count\s*\(/',trim($select)))
 751:                 $query->selects=array($select);
 752:             elseif(is_string($this->_table->primaryKey))
 753:             {
 754:                 $prefix=$this->getColumnPrefix();
 755:                 $schema=$this->_builder->getSchema();
 756:                 $column=$prefix.$schema->quoteColumnName($this->_table->primaryKey);
 757:                 $query->selects=array("COUNT(DISTINCT $column)");
 758:             }
 759:             else
 760:                 $query->selects=array("COUNT(*)");
 761: 
 762:             $query->orders=$query->groups=$query->havings=array();
 763:             $command=$query->createCommand($this->_builder);
 764:             return $command->queryScalar();
 765:         }
 766:     }
 767: 
 768:     /**
 769:      * Calls {@link CActiveRecord::afterFind} of all the records.
 770:      */
 771:     public function afterFind()
 772:     {
 773:         foreach($this->records as $record)
 774:             $record->afterFindInternal();
 775:         foreach($this->children as $child)
 776:             $child->afterFind();
 777: 
 778:         $this->children = null;
 779:     }
 780: 
 781:     /**
 782:      * Builds the join query with all descendant HAS_ONE and BELONGS_TO nodes.
 783:      * @param CJoinQuery $query the query being built up
 784:      */
 785:     public function buildQuery($query)
 786:     {
 787:         foreach($this->children as $child)
 788:         {
 789:             if($child->master!==null)
 790:                 $child->_joined=true;
 791:             elseif($child->relation instanceof CHasOneRelation || $child->relation instanceof CBelongsToRelation
 792:                 || $this->_finder->joinAll || $child->relation->together || (!$this->_finder->baseLimited && $child->relation->together===null))
 793:             {
 794:                 $child->_joined=true;
 795:                 $query->join($child);
 796:                 $child->buildQuery($query);
 797:             }
 798:         }
 799:     }
 800: 
 801:     /**
 802:      * Executes the join query and populates the query results.
 803:      * @param CJoinQuery $query the query to be executed.
 804:      */
 805:     public function runQuery($query)
 806:     {
 807:         $command=$query->createCommand($this->_builder);
 808:         foreach($command->queryAll() as $row)
 809:             $this->populateRecord($query,$row);
 810:     }
 811: 
 812:     /**
 813:      * Populates the active records with the query data.
 814:      * @param CJoinQuery $query the query executed
 815:      * @param array $row a row of data
 816:      * @return CActiveRecord the populated record
 817:      */
 818:     private function populateRecord($query,$row)
 819:     {
 820:         // determine the primary key value
 821:         if(is_string($this->_pkAlias))  // single key
 822:         {
 823:             if(isset($row[$this->_pkAlias]))
 824:                 $pk=$row[$this->_pkAlias];
 825:             else    // no matching related objects
 826:                 return null;
 827:         }
 828:         else // is_array, composite key
 829:         {
 830:             $pk=array();
 831:             foreach($this->_pkAlias as $name=>$alias)
 832:             {
 833:                 if(isset($row[$alias]))
 834:                     $pk[$name]=$row[$alias];
 835:                 else    // no matching related objects
 836:                     return null;
 837:             }
 838:             $pk=serialize($pk);
 839:         }
 840: 
 841:         // retrieve or populate the record according to the primary key value
 842:         if(isset($this->records[$pk]))
 843:             $record=$this->records[$pk];
 844:         else
 845:         {
 846:             $attributes=array();
 847:             $aliases=array_flip($this->_columnAliases);
 848:             foreach($row as $alias=>$value)
 849:             {
 850:                 if(isset($aliases[$alias]))
 851:                     $attributes[$aliases[$alias]]=$value;
 852:             }
 853:             $record=$this->model->populateRecord($attributes,false);
 854:             foreach($this->children as $child)
 855:             {
 856:                 if(!empty($child->relation->select))
 857:                     $record->addRelatedRecord($child->relation->name,null,$child->relation instanceof CHasManyRelation);
 858:             }
 859:             $this->records[$pk]=$record;
 860:         }
 861: 
 862:         // populate child records recursively
 863:         foreach($this->children as $child)
 864:         {
 865:             if(!isset($query->elements[$child->id]) || empty($child->relation->select))
 866:                 continue;
 867:             $childRecord=$child->populateRecord($query,$row);
 868:             if($child->relation instanceof CHasOneRelation || $child->relation instanceof CBelongsToRelation)
 869:                 $record->addRelatedRecord($child->relation->name,$childRecord,false);
 870:             else // has_many and many_many
 871:             {
 872:                 // need to double check to avoid adding duplicated related objects
 873:                 if($childRecord instanceof CActiveRecord)
 874:                     $fpk=serialize($childRecord->getPrimaryKey());
 875:                 else
 876:                     $fpk=0;
 877:                 if(!isset($this->_related[$pk][$child->relation->name][$fpk]))
 878:                 {
 879:                     if($childRecord instanceof CActiveRecord && $child->relation->index!==null)
 880:                         $index=$childRecord->{$child->relation->index};
 881:                     else
 882:                         $index=true;
 883:                     $record->addRelatedRecord($child->relation->name,$childRecord,$index);
 884:                     $this->_related[$pk][$child->relation->name][$fpk]=true;
 885:                 }
 886:             }
 887:         }
 888: 
 889:         return $record;
 890:     }
 891: 
 892:     /**
 893:      * @return string the table name and the table alias (if any). This can be used directly in SQL query without escaping.
 894:      */
 895:     public function getTableNameWithAlias()
 896:     {
 897:         if($this->tableAlias!==null)
 898:             return $this->_table->rawName . ' ' . $this->rawTableAlias;
 899:         else
 900:             return $this->_table->rawName;
 901:     }
 902: 
 903:     /**
 904:      * Generates the list of columns to be selected.
 905:      * Columns will be properly aliased and primary keys will be added to selection if they are not specified.
 906:      * @param mixed $select columns to be selected. Defaults to '*', indicating all columns.
 907:      * @throws CDbException if active record class is trying to select an invalid column
 908:      * @return string the column selection
 909:      */
 910:     public function getColumnSelect($select='*')
 911:     {
 912:         $schema=$this->_builder->getSchema();
 913:         $prefix=$this->getColumnPrefix();
 914:         $columns=array();
 915:         if($select==='*')
 916:         {
 917:             foreach($this->_table->getColumnNames() as $name)
 918:                 $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($this->_columnAliases[$name]);
 919:         }
 920:         else
 921:         {
 922:             if(is_string($select))
 923:                 $select=explode(',',$select);
 924:             $selected=array();
 925:             foreach($select as $name)
 926:             {
 927:                 $name=trim($name);
 928:                 $matches=array();
 929:                 if(($pos=strrpos($name,'.'))!==false)
 930:                     $key=substr($name,$pos+1);
 931:                 else
 932:                     $key=$name;
 933:                 $key=trim($key,'\'"`');
 934: 
 935:                 if($key==='*')
 936:                 {
 937:                     foreach($this->_table->columns as $name=>$column)
 938:                     {
 939:                         $alias=$this->_columnAliases[$name];
 940:                         if(!isset($selected[$alias]))
 941:                         {
 942:                             $columns[]=$prefix.$column->rawName.' AS '.$schema->quoteColumnName($alias);
 943:                             $selected[$alias]=1;
 944:                         }
 945:                     }
 946:                     continue;
 947:                 }
 948: 
 949:                 if(isset($this->_columnAliases[$key]))  // simple column names
 950:                 {
 951:                     $columns[]=$prefix.$schema->quoteColumnName($key).' AS '.$schema->quoteColumnName($this->_columnAliases[$key]);
 952:                     $selected[$this->_columnAliases[$key]]=1;
 953:                 }
 954:                 elseif(preg_match('/^(.*?)\s+AS\s+(\w+)$/im',$name,$matches)) // if the column is already aliased
 955:                 {
 956:                     $alias=$matches[2];
 957:                     if(!isset($this->_columnAliases[$alias]) || $this->_columnAliases[$alias]!==$alias)
 958:                     {
 959:                         $this->_columnAliases[$alias]=$alias;
 960:                         $columns[]=$name;
 961:                         $selected[$alias]=1;
 962:                     }
 963:                 }
 964:                 else
 965:                     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.',
 966:                         array('{class}'=>get_class($this->model), '{column}'=>$name)));
 967:             }
 968:             // add primary key selection if they are not selected
 969:             if(is_string($this->_pkAlias) && !isset($selected[$this->_pkAlias]))
 970:                 $columns[]=$prefix.$schema->quoteColumnName($this->_table->primaryKey).' AS '.$schema->quoteColumnName($this->_pkAlias);
 971:             elseif(is_array($this->_pkAlias))
 972:             {
 973:                 foreach($this->_pkAlias as $name=>$alias)
 974:                     if(!isset($selected[$alias]))
 975:                         $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($alias);
 976:             }
 977:         }
 978: 
 979:         return implode(', ',$columns);
 980:     }
 981: 
 982:     /**
 983:      * @return string the primary key selection
 984:      */
 985:     public function getPrimaryKeySelect()
 986:     {
 987:         $schema=$this->_builder->getSchema();
 988:         $prefix=$this->getColumnPrefix();
 989:         $columns=array();
 990:         if(is_string($this->_pkAlias))
 991:             $columns[]=$prefix.$schema->quoteColumnName($this->_table->primaryKey).' AS '.$schema->quoteColumnName($this->_pkAlias);
 992:         elseif(is_array($this->_pkAlias))
 993:         {
 994:             foreach($this->_pkAlias as $name=>$alias)
 995:                 $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($alias);
 996:         }
 997:         return implode(', ',$columns);
 998:     }
 999: 
1000:     /**
1001:      * @return string the condition that specifies only the rows with the selected primary key values.
1002:      */
1003:     public function getPrimaryKeyRange()
1004:     {
1005:         if(empty($this->records))
1006:             return '';
1007:         $values=array_keys($this->records);
1008:         if(is_array($this->_table->primaryKey))
1009:         {
1010:             foreach($values as &$value)
1011:                 $value=unserialize($value);
1012:         }
1013:         return $this->_builder->createInCondition($this->_table,$this->_table->primaryKey,$values,$this->getColumnPrefix());
1014:     }
1015: 
1016:     /**
1017:      * @return string the column prefix for column reference disambiguation
1018:      */
1019:     public function getColumnPrefix()
1020:     {
1021:         if($this->tableAlias!==null)
1022:             return $this->rawTableAlias.'.';
1023:         else
1024:             return $this->_table->rawName.'.';
1025:     }
1026: 
1027:     /**
1028:      * @throws CDbException if relation in active record class is not specified correctly
1029:      * @return string the join statement (this node joins with its parent)
1030:      */
1031:     public function getJoinCondition()
1032:     {
1033:         $parent=$this->_parent;
1034:         if($this->relation instanceof CManyManyRelation)
1035:         {
1036:             $schema=$this->_builder->getSchema();
1037:             $joinTableName=$this->relation->getJunctionTableName();
1038:             if(($joinTable=$schema->getTable($joinTableName))===null)
1039:                 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.',
1040:                     array('{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name, '{joinTable}'=>$joinTableName)));
1041:             $fks=$this->relation->getJunctionForeignKeys();
1042: 
1043:             return $this->joinManyMany($joinTable,$fks,$parent);
1044:         }
1045:         else
1046:         {
1047:             $fks=is_array($this->relation->foreignKey) ? $this->relation->foreignKey : preg_split('/\s*,\s*/',$this->relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY);
1048:             if($this->slave!==null)
1049:             {
1050:                 if($this->relation instanceof CBelongsToRelation)
1051:                 {
1052:                     $fks=array_flip($fks);
1053:                     $pke=$this->slave;
1054:                     $fke=$this;
1055:                 }
1056:                 else
1057:                 {
1058:                     $pke=$this;
1059:                     $fke=$this->slave;
1060:                 }
1061:             }
1062:             elseif($this->relation instanceof CBelongsToRelation)
1063:             {
1064:                 $pke=$this;
1065:                 $fke=$parent;
1066:             }
1067:             else
1068:             {
1069:                 $pke=$parent;
1070:                 $fke=$this;
1071:             }
1072:             return $this->joinOneMany($fke,$fks,$pke,$parent);
1073:         }
1074:     }
1075: 
1076:     /**
1077:      * Generates the join statement for one-many relationship.
1078:      * This works for HAS_ONE, HAS_MANY and BELONGS_TO.
1079:      * @param CJoinElement $fke the join element containing foreign keys
1080:      * @param array $fks the foreign keys
1081:      * @param CJoinElement $pke the join element contains primary keys
1082:      * @param CJoinElement $parent the parent join element
1083:      * @return string the join statement
1084:      * @throws CDbException if a foreign key is invalid
1085:      */
1086:     private function joinOneMany($fke,$fks,$pke,$parent)
1087:     {
1088:         $schema=$this->_builder->getSchema();
1089:         $joins=array();
1090:         if(is_string($fks))
1091:             $fks=preg_split('/\s*,\s*/',$fks,-1,PREG_SPLIT_NO_EMPTY);
1092:         foreach($fks as $i=>$fk)
1093:         {
1094:             if(!is_int($i))
1095:             {
1096:                 $pk=$fk;
1097:                 $fk=$i;
1098:             }
1099: 
1100:             if(!isset($fke->_table->columns[$fk]))
1101:                 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}".',
1102:                     array('{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name, '{key}'=>$fk, '{table}'=>$fke->_table->name)));
1103: 
1104:             if(is_int($i))
1105:             {
1106:                 if(isset($fke->_table->foreignKeys[$fk]) && $schema->compareTableNames($pke->_table->rawName, $fke->_table->foreignKeys[$fk][0]))
1107:                     $pk=$fke->_table->foreignKeys[$fk][1];
1108:                 else // FK constraints undefined
1109:                 {
1110:                     if(is_array($pke->_table->primaryKey)) // composite PK
1111:                         $pk=$pke->_table->primaryKey[$i];
1112:                     else
1113:                         $pk=$pke->_table->primaryKey;
1114:                 }
1115:             }
1116: 
1117:             $joins[]=$fke->getColumnPrefix().$schema->quoteColumnName($fk) . '=' . $pke->getColumnPrefix().$schema->quoteColumnName($pk);
1118:         }
1119:         if(!empty($this->relation->on))
1120:             $joins[]=$this->relation->on;
1121: 
1122:         if(!empty($this->relation->joinOptions) && is_string($this->relation->joinOptions))
1123:             return $this->relation->joinType.' '.$this->getTableNameWithAlias().' '.$this->relation->joinOptions.
1124:                 ' ON ('.implode(') AND (',$joins).')';
1125:         else
1126:             return $this->relation->joinType.' '.$this->getTableNameWithAlias().' ON ('.implode(') AND (',$joins).')';
1127:     }
1128: 
1129:     /**
1130:      * Generates the join statement for many-many relationship.
1131:      * @param CDbTableSchema $joinTable the join table
1132:      * @param array $fks the foreign keys
1133:      * @param CJoinElement $parent the parent join element
1134:      * @return string the join statement
1135:      * @throws CDbException if a foreign key is invalid
1136:      */
1137:     private function joinManyMany($joinTable,$fks,$parent)
1138:     {
1139:         $schema=$this->_builder->getSchema();
1140:         $joinAlias=$schema->quoteTableName($this->relation->name.'_'.$this->tableAlias);
1141:         $parentCondition=array();
1142:         $childCondition=array();
1143: 
1144:         $fkDefined=true;
1145:         foreach($fks as $i=>$fk)
1146:         {
1147:             if(!isset($joinTable->columns[$fk]))
1148:                 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}".',
1149:                     array('{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name, '{key}'=>$fk, '{table}'=>$joinTable->name)));
1150: 
1151:             if(isset($joinTable->foreignKeys[$fk]))
1152:             {
1153:                 list($tableName,$pk)=$joinTable->foreignKeys[$fk];
1154:                 if(!isset($parentCondition[$pk]) && $schema->compareTableNames($parent->_table->rawName,$tableName))
1155:                     $parentCondition[$pk]=$parent->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk);
1156:                 elseif(!isset($childCondition[$pk]) && $schema->compareTableNames($this->_table->rawName,$tableName))
1157:                     $childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk);
1158:                 else
1159:                 {
1160:                     $fkDefined=false;
1161:                     break;
1162:                 }
1163:             }
1164:             else
1165:             {
1166:                 $fkDefined=false;
1167:                 break;
1168:             }
1169:         }
1170: 
1171:         if(!$fkDefined)
1172:         {
1173:             $parentCondition=array();
1174:             $childCondition=array();
1175:             foreach($fks as $i=>$fk)
1176:             {
1177:                 if($i<count($parent->_table->primaryKey))
1178:                 {
1179:                     $pk=is_array($parent->_table->primaryKey) ? $parent->_table->primaryKey[$i] : $parent->_table->primaryKey;
1180:                     $parentCondition[$pk]=$parent->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk);
1181:                 }
1182:                 else
1183:                 {
1184:                     $j=$i-count($parent->_table->primaryKey);
1185:                     $pk=is_array($this->_table->primaryKey) ? $this->_table->primaryKey[$j] : $this->_table->primaryKey;
1186:                     $childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk);
1187:                 }
1188:             }
1189:         }
1190: 
1191:         if($parentCondition!==array() && $childCondition!==array())
1192:         {
1193:             $join=$this->relation->joinType.' '.$joinTable->rawName.' '.$joinAlias;
1194: 
1195:             if(is_array($this->relation->joinOptions) && isset($this->relation->joinOptions[0]) &&
1196:                 is_string($this->relation->joinOptions[0]))
1197:                 $join.=' '.$this->relation->joinOptions[0];
1198:             elseif(!empty($this->relation->joinOptions) && is_string($this->relation->joinOptions))
1199:                 $join.=' '.$this->relation->joinOptions;
1200: 
1201:             $join.=' ON ('.implode(') AND (',$parentCondition).')';
1202:             $join.=' '.$this->relation->joinType.' '.$this->getTableNameWithAlias();
1203: 
1204:             if(is_array($this->relation->joinOptions) && isset($this->relation->joinOptions[1]) &&
1205:                 is_string($this->relation->joinOptions[1]))
1206:                 $join.=' '.$this->relation->joinOptions[1];
1207: 
1208:             $join.=' ON ('.implode(') AND (',$childCondition).')';
1209:             if(!empty($this->relation->on))
1210:                 $join.=' AND ('.$this->relation->on.')';
1211:             return $join;
1212:         }
1213:         else
1214:             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.',
1215:                 array('{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name)));
1216:     }
1217: }
1218: 
1219: 
1220: /**
1221:  * CJoinQuery represents a JOIN SQL statement.
1222:  *
1223:  * @author Qiang Xue <qiang.xue@gmail.com>
1224:  * @package system.db.ar
1225:  * @since 1.0
1226:  */
1227: class CJoinQuery
1228: {
1229:     /**
1230:      * @var array list of column selections
1231:      */
1232:     public $selects=array();
1233:     /**
1234:      * @var boolean whether to select distinct result set
1235:      */
1236:     public $distinct=false;
1237:     /**
1238:      * @var array list of join statement
1239:      */
1240:     public $joins=array();
1241:     /**
1242:      * @var array list of WHERE clauses
1243:      */
1244:     public $conditions=array();
1245:     /**
1246:      * @var array list of ORDER BY clauses
1247:      */
1248:     public $orders=array();
1249:     /**
1250:      * @var array list of GROUP BY clauses
1251:      */
1252:     public $groups=array();
1253:     /**
1254:      * @var array list of HAVING clauses
1255:      */
1256:     public $havings=array();
1257:     /**
1258:      * @var integer row limit
1259:      */
1260:     public $limit=-1;
1261:     /**
1262:      * @var integer row offset
1263:      */
1264:     public $offset=-1;
1265:     /**
1266:      * @var array list of query parameters
1267:      */
1268:     public $params=array();
1269:     /**
1270:      * @var array list of join element IDs (id=>true)
1271:      */
1272:     public $elements=array();
1273: 
1274:     /**
1275:      * Constructor.
1276:      * @param CJoinElement $joinElement The root join tree.
1277:      * @param CDbCriteria $criteria the query criteria
1278:      */
1279:     public function __construct($joinElement,$criteria=null)
1280:     {
1281:         if($criteria!==null)
1282:         {
1283:             $this->selects[]=$joinElement->getColumnSelect($criteria->select);
1284:             $this->joins[]=$joinElement->getTableNameWithAlias();
1285:             $this->joins[]=$criteria->join;
1286:             $this->conditions[]=$criteria->condition;
1287:             $this->orders[]=$criteria->order;
1288:             $this->groups[]=$criteria->group;
1289:             $this->havings[]=$criteria->having;
1290:             $this->limit=$criteria->limit;
1291:             $this->offset=$criteria->offset;
1292:             $this->params=$criteria->params;
1293:             if(!$this->distinct && $criteria->distinct)
1294:                 $this->distinct=true;
1295:         }
1296:         else
1297:         {
1298:             $this->selects[]=$joinElement->getPrimaryKeySelect();
1299:             $this->joins[]=$joinElement->getTableNameWithAlias();
1300:             $this->conditions[]=$joinElement->getPrimaryKeyRange();
1301:         }
1302:         $this->elements[$joinElement->id]=true;
1303:     }
1304: 
1305:     /**
1306:      * Joins with another join element
1307:      * @param CJoinElement $element the element to be joined
1308:      */
1309:     public function join($element)
1310:     {
1311:         if($element->slave!==null)
1312:             $this->join($element->slave);
1313:         if(!empty($element->relation->select))
1314:             $this->selects[]=$element->getColumnSelect($element->relation->select);
1315:         $this->conditions[]=$element->relation->condition;
1316:         $this->orders[]=$element->relation->order;
1317:         $this->joins[]=$element->getJoinCondition();
1318:         $this->joins[]=$element->relation->join;
1319:         $this->groups[]=$element->relation->group;
1320:         $this->havings[]=$element->relation->having;
1321: 
1322:         if(is_array($element->relation->params))
1323:         {
1324:             if(is_array($this->params))
1325:                 $this->params=array_merge($this->params,$element->relation->params);
1326:             else
1327:                 $this->params=$element->relation->params;
1328:         }
1329:         $this->elements[$element->id]=true;
1330:     }
1331: 
1332:     /**
1333:      * Creates the SQL statement.
1334:      * @param CDbCommandBuilder $builder the command builder
1335:      * @return CDbCommand DB command instance representing the SQL statement
1336:      */
1337:     public function createCommand($builder)
1338:     {
1339:         $sql=($this->distinct ? 'SELECT DISTINCT ':'SELECT ') . implode(', ',$this->selects);
1340:         $sql.=' FROM ' . implode(' ',array_unique($this->joins));
1341: 
1342:         $conditions=array();
1343:         foreach($this->conditions as $condition)
1344:             if($condition!=='')
1345:                 $conditions[]=$condition;
1346:         if($conditions!==array())
1347:             $sql.=' WHERE (' . implode(') AND (',$conditions).')';
1348: 
1349:         $groups=array();
1350:         foreach($this->groups as $group)
1351:             if($group!=='')
1352:                 $groups[]=$group;
1353:         if($groups!==array())
1354:             $sql.=' GROUP BY ' . implode(', ',$groups);
1355: 
1356:         $havings=array();
1357:         foreach($this->havings as $having)
1358:             if($having!=='')
1359:                 $havings[]=$having;
1360:         if($havings!==array())
1361:             $sql.=' HAVING (' . implode(') AND (',$havings).')';
1362: 
1363:         $orders=array();
1364:         foreach($this->orders as $order)
1365:             if($order!=='')
1366:                 $orders[]=$order;
1367:         if($orders!==array())
1368:             $sql.=' ORDER BY ' . implode(', ',$orders);
1369: 
1370:         $sql=$builder->applyLimit($sql,$this->limit,$this->offset);
1371:         $command=$builder->getDbConnection()->createCommand($sql);
1372:         $builder->bindValues($command,$this->params);
1373:         return $command;
1374:     }
1375: }
1376: 
1377: 
1378: /**
1379:  * CStatElement represents STAT join element for {@link CActiveFinder}.
1380:  *
1381:  * @author Qiang Xue <qiang.xue@gmail.com>
1382:  * @package system.db.ar
1383:  */
1384: class CStatElement
1385: {
1386:     /**
1387:      * @var CActiveRelation the relation represented by this tree node
1388:      */
1389:     public $relation;
1390: 
1391:     private $_finder;
1392:     private $_parent;
1393: 
1394:     /**
1395:      * Constructor.
1396:      * @param CActiveFinder $finder the finder
1397:      * @param CStatRelation $relation the STAT relation
1398:      * @param CJoinElement $parent the join element owning this STAT element
1399:      */
1400:     public function __construct($finder,$relation,$parent)
1401:     {
1402:         $this->_finder=$finder;
1403:         $this->_parent=$parent;
1404:         $this->relation=$relation;
1405:         $parent->stats[]=$this;
1406:     }
1407: 
1408:     /**
1409:      * Performs the STAT query.
1410:      */
1411:     public function query()
1412:     {
1413:         if(preg_match('/^\s*(.*?)\((.*)\)\s*$/',$this->relation->foreignKey,$matches))
1414:             $this->queryManyMany($matches[1],$matches[2]);
1415:         else
1416:             $this->queryOneMany();
1417:     }
1418: 
1419:     private function queryOneMany()
1420:     {
1421:         $relation=$this->relation;
1422:         $model=$this->_finder->getModel($relation->className);
1423:         $builder=$model->getCommandBuilder();
1424:         $schema=$builder->getSchema();
1425:         $table=$model->getTableSchema();
1426:         $parent=$this->_parent;
1427:         $pkTable=$parent->model->getTableSchema();
1428: 
1429:         $fks=preg_split('/\s*,\s*/',$relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY);
1430:         if(count($fks)!==count($pkTable->primaryKey))
1431:             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}".',
1432:                         array('{class}'=>get_class($parent->model), '{relation}'=>$relation->name, '{table}'=>$pkTable->name)));
1433: 
1434:         // set up mapping between fk and pk columns
1435:         $map=array();  // pk=>fk
1436:         foreach($fks as $i=>$fk)
1437:         {
1438:             if(!isset($table->columns[$fk]))
1439:                 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}".',
1440:                     array('{class}'=>get_class($parent->model), '{relation}'=>$relation->name, '{key}'=>$fk, '{table}'=>$table->name)));
1441: 
1442:             if(isset($table->foreignKeys[$fk]))
1443:             {
1444:                 list($tableName,$pk)=$table->foreignKeys[$fk];
1445:                 if($schema->compareTableNames($pkTable->rawName,$tableName))
1446:                     $map[$pk]=$fk;
1447:                 else
1448:                     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}".',
1449:                         array('{class}'=>get_class($parent->model), '{relation}'=>$relation->name, '{key}'=>$fk, '{table}'=>$pkTable->name)));
1450:             }
1451:             else  // FK constraints undefined
1452:             {
1453:                 if(is_array($pkTable->primaryKey)) // composite PK
1454:                     $map[$pkTable->primaryKey[$i]]=$fk;
1455:                 else
1456:                     $map[$pkTable->primaryKey]=$fk;
1457:             }
1458:         }
1459: 
1460:         $records=$this->_parent->records;
1461: 
1462:         $join=empty($relation->join)?'' : ' '.$relation->join;
1463:         $where=empty($relation->condition)?' WHERE ' : ' WHERE ('.$relation->condition.') AND ';
1464:         $group=empty($relation->group)?'' : ', '.$relation->group;
1465:         $having=empty($relation->having)?'' : ' HAVING ('.$relation->having.')';
1466:         $order=empty($relation->order)?'' : ' ORDER BY '.$relation->order;
1467: 
1468:         $c=$schema->quoteColumnName('c');
1469:         $s=$schema->quoteColumnName('s');
1470: 
1471:         $tableAlias=$model->getTableAlias(true);
1472: 
1473:         // generate and perform query
1474:         if(count($fks)===1)  // single column FK
1475:         {
1476:             $col=$tableAlias.'.'.$table->columns[$fks[0]]->rawName;
1477:             $sql="SELECT $col AS $c, {$relation->select} AS $s FROM {$table->rawName} ".$tableAlias.$join
1478:                 .$where.'('.$builder->createInCondition($table,$fks[0],array_keys($records),$tableAlias.'.').')'
1479:                 ." GROUP BY $col".$group
1480:                 .$having.$order;
1481:             $command=$builder->getDbConnection()->createCommand($sql);
1482:             if(is_array($relation->params))
1483:                 $builder->bindValues($command,$relation->params);
1484:             $stats=array();
1485:             foreach($command->queryAll() as $row)
1486:                 $stats[$row['c']]=$row['s'];
1487:         }
1488:         else  // composite FK
1489:         {
1490:             $keys=array_keys($records);
1491:             foreach($keys as &$key)
1492:             {
1493:                 $key2=unserialize($key);
1494:                 $key=array();
1495:                 foreach($pkTable->primaryKey as $pk)
1496:                     $key[$map[$pk]]=$key2[$pk];
1497:             }
1498:             $cols=array();
1499:             foreach($pkTable->primaryKey as $n=>$pk)
1500:             {
1501:                 $name=$tableAlias.'.'.$table->columns[$map[$pk]]->rawName;
1502:                 $cols[$name]=$name.' AS '.$schema->quoteColumnName('c'.$n);
1503:             }
1504:             $sql='SELECT '.implode(', ',$cols).", {$relation->select} AS $s FROM {$table->rawName} ".$tableAlias.$join
1505:                 .$where.'('.$builder->createInCondition($table,$fks,$keys,$tableAlias.'.').')'
1506:                 .' GROUP BY '.implode(', ',array_keys($cols)).$group
1507:                 .$having.$order;
1508:             $command=$builder->getDbConnection()->createCommand($sql);
1509:             if(is_array($relation->params))
1510:                 $builder->bindValues($command,$relation->params);
1511:             $stats=array();
1512:             foreach($command->queryAll() as $row)
1513:             {
1514:                 $key=array();
1515:                 foreach($pkTable->primaryKey as $n=>$pk)
1516:                     $key[$pk]=$row['c'.$n];
1517:                 $stats[serialize($key)]=$row['s'];
1518:             }
1519:         }
1520: 
1521:         // populate the results into existing records
1522:         foreach($records as $pk=>$record)
1523:             $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$relation->defaultValue,false);
1524:     }
1525: 
1526:     /**
1527:      * @param string $joinTableName jointablename
1528:      * @param string $keys keys
1529:      * @throws CDbException
1530:      */
1531:     private function queryManyMany($joinTableName,$keys)
1532:     {
1533:         $relation=$this->relation;
1534:         $model=$this->_finder->getModel($relation->className);
1535:         $table=$model->getTableSchema();
1536:         $builder=$model->getCommandBuilder();
1537:         $schema=$builder->getSchema();
1538:         $pkTable=$this->_parent->model->getTableSchema();
1539: 
1540:         $tableAlias=$model->getTableAlias(true);
1541: 
1542:         if(($joinTable=$builder->getSchema()->getTable($joinTableName))===null)
1543:             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.',
1544:                 array('{class}'=>get_class($this->_parent->model), '{relation}'=>$relation->name, '{joinTable}'=>$joinTableName)));
1545: 
1546:         $fks=preg_split('/\s*,\s*/',$keys,-1,PREG_SPLIT_NO_EMPTY);
1547:         if(count($fks)!==count($table->primaryKey)+count($pkTable->primaryKey))
1548:             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.',
1549:                 array('{class}'=>get_class($this->_parent->model), '{relation}'=>$relation->name)));
1550: 
1551:         $joinCondition=array();
1552:         $map=array();
1553: 
1554:         $fkDefined=true;
1555:         foreach($fks as $i=>$fk)
1556:         {
1557:             if(!isset($joinTable->columns[$fk]))
1558:                 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}".',
1559:                     array('{class}'=>get_class($this->_parent->model), '{relation}'=>$relation->name, '{key}'=>$fk, '{table}'=>$joinTable->name)));
1560: 
1561:             if(isset($joinTable->foreignKeys[$fk]))
1562:             {
1563:                 list($tableName,$pk)=$joinTable->foreignKeys[$fk];
1564:                 if(!isset($joinCondition[$pk]) && $schema->compareTableNames($table->rawName,$tableName))
1565:                     $joinCondition[$pk]=$tableAlias.'.'.$schema->quoteColumnName($pk).'='.$joinTable->rawName.'.'.$schema->quoteColumnName($fk);
1566:                 elseif(!isset($map[$pk]) && $schema->compareTableNames($pkTable->rawName,$tableName))
1567:                     $map[$pk]=$fk;
1568:                 else
1569:                 {
1570:                     $fkDefined=false;
1571:                     break;
1572:                 }
1573:             }
1574:             else
1575:             {
1576:                 $fkDefined=false;
1577:                 break;
1578:             }
1579:         }
1580: 
1581:         if(!$fkDefined)
1582:         {
1583:             $joinCondition=array();
1584:             $map=array();
1585:             foreach($fks as $i=>$fk)
1586:             {
1587:                 if($i<count($pkTable->primaryKey))
1588:                 {
1589:                     $pk=is_array($pkTable->primaryKey) ? $pkTable->primaryKey[$i] : $pkTable->primaryKey;
1590:                     $map[$pk]=$fk;
1591:                 }
1592:                 else
1593:                 {
1594:                     $j=$i-count($pkTable->primaryKey);
1595:                     $pk=is_array($table->primaryKey) ? $table->primaryKey[$j] : $table->primaryKey;
1596:                     $joinCondition[$pk]=$tableAlias.'.'.$schema->quoteColumnName($pk).'='.$joinTable->rawName.'.'.$schema->quoteColumnName($fk);
1597:                 }
1598:             }
1599:         }
1600: 
1601:         if($joinCondition===array() || $map===array())
1602:             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.',
1603:                 array('{class}'=>get_class($this->_parent->model), '{relation}'=>$relation->name)));
1604: 
1605:         $records=$this->_parent->records;
1606: 
1607:         $cols=array();
1608:         foreach(is_string($pkTable->primaryKey)?array($pkTable->primaryKey):$pkTable->primaryKey as $n=>$pk)
1609:         {
1610:             $name=$joinTable->rawName.'.'.$schema->quoteColumnName($map[$pk]);
1611:             $cols[$name]=$name.' AS '.$schema->quoteColumnName('c'.$n);
1612:         }
1613: 
1614:         $keys=array_keys($records);
1615:         if(is_array($pkTable->primaryKey))
1616:         {
1617:             foreach($keys as &$key)
1618:             {
1619:                 $key2=unserialize($key);
1620:                 $key=array();
1621:                 foreach($pkTable->primaryKey as $pk)
1622:                     $key[$map[$pk]]=$key2[$pk];
1623:             }
1624:         }
1625: 
1626:         $join=empty($relation->join)?'' : ' '.$relation->join;
1627:         $where=empty($relation->condition)?'' : ' WHERE ('.$relation->condition.')';
1628:         $group=empty($relation->group)?'' : ', '.$relation->group;
1629:         $having=empty($relation->having)?'' : ' AND ('.$relation->having.')';
1630:         $order=empty($relation->order)?'' : ' ORDER BY '.$relation->order;
1631: 
1632:         $sql='SELECT '.$this->relation->select.' AS '.$schema->quoteColumnName('s').', '.implode(', ',$cols)
1633:             .' FROM '.$table->rawName.' '.$tableAlias.' INNER JOIN '.$joinTable->rawName
1634:             .' ON ('.implode(') AND (',$joinCondition).')'.$join
1635:             .$where
1636:             .' GROUP BY '.implode(', ',array_keys($cols)).$group
1637:             .' HAVING ('.$builder->createInCondition($joinTable,$map,$keys).')'
1638:             .$having.$order;
1639: 
1640:         $command=$builder->getDbConnection()->createCommand($sql);
1641:         if(is_array($relation->params))
1642:             $builder->bindValues($command,$relation->params);
1643: 
1644:         $stats=array();
1645:         foreach($command->queryAll() as $row)
1646:         {
1647:             if(is_array($pkTable->primaryKey))
1648:             {
1649:                 $key=array();
1650:                 foreach($pkTable->primaryKey as $n=>$k)
1651:                     $key[$k]=$row['c'.$n];
1652:                 $stats[serialize($key)]=$row['s'];
1653:             }
1654:             else
1655:                 $stats[$row['c0']]=$row['s'];
1656:         }
1657: 
1658:         foreach($records as $pk=>$record)
1659:             $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$this->relation->defaultValue,false);
1660:     }
1661: }
1662: 
API documentation generated by ApiGen 2.8.0