1: <?php
   2:    3:    4:    5:    6:    7:    8:    9: 
  10: 
  11:   12:   13:   14:   15:   16:   17:   18:   19:   20: 
  21: class CActiveFinder extends CComponent
  22: {
  23:       24:   25:   26: 
  27:     public $joinAll=false;
  28:       29:   30:   31: 
  32:     public $baseLimited=false;
  33: 
  34:     private $_joinCount=0;
  35:     private $_joinTree;
  36:     private $_builder;
  37: 
  38:       39:   40:   41:   42:   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:   53:   54:   55:   56:   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:   94:   95:   96:   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:  115:  116:  117:  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:  139:  140:  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:  158:  159:  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:  174:  175:  176:  177:  178:  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:  194:  195:  196:  197:  198:  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:             
 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); 
 245: 
 246:             if(!empty($options['scopes']))
 247:                 $scopes=array_merge($scopes,(array)$options['scopes']); 
 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:             
 259:             if($relation instanceof CStatRelation)
 260:                 $criteria->select='*';
 261: 
 262:             $relation->mergeWith($criteria,true);
 263: 
 264:             
 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:         
 296:         foreach($with as $key=>$value)
 297:         {
 298:             if(is_string($value))  
 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:  309:  310:  311:  312:  313: 
 314: class CJoinElement
 315: {
 316:      317:  318: 
 319:     public $id;
 320:      321:  322: 
 323:     public $relation;
 324:      325:  326: 
 327:     public $master;
 328:      329:  330: 
 331:     public $slave;
 332:      333:  334: 
 335:     public $model;
 336:      337:  338: 
 339:     public $records=array();
 340:      341:  342: 
 343:     public $children=array();
 344:      345:  346: 
 347:     public $stats=array();
 348:      349:  350: 
 351:     public $tableAlias;
 352:      353:  354: 
 355:     public $rawTableAlias;
 356: 
 357:     private $_finder;
 358:     private $_builder;
 359:     private $_parent;
 360:     private $_pkAlias;                  
 361:     private $_columnAliases=array();    
 362:     private $_joined=false;
 363:     private $_table;
 364:     private $_related=array();          
 365: 
 366:      367:  368:  369:  370:  371:  372:  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  
 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:         
 398:         $table=$this->_table;
 399:         if($this->model->getDbConnection()->getDriverName()==='oci')  
 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:  416:  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:  430:  431: 
 432:     public function find($criteria=null)
 433:     {
 434:         if($this->_parent===null) 
 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)) 
 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) 
 452:             $child->find();
 453: 
 454:         foreach($this->stats as $stat)
 455:             $stat->query();
 456:     }
 457: 
 458:      459:  460:  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 
 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:  532:  533:  534:  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]))  
 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]))  
 655:                             $pk=$parent->_table->foreignKeys[$fk][1];
 656:                         elseif(is_array($element->_table->primaryKey)) 
 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]))  
 668:                             $pk=$element->_table->foreignKeys[$fk][1];
 669:                         elseif(is_array($parent->_table->primaryKey)) 
 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:  689:  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:  724:  725:  726: 
 727:     public function count($criteria=null)
 728:     {
 729:         $query=new CJoinQuery($this,$criteria);
 730:         
 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:  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:  783:  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:  803:  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:  814:  815:  816:  817: 
 818:     private function populateRecord($query,$row)
 819:     {
 820:         
 821:         if(is_string($this->_pkAlias))  
 822:         {
 823:             if(isset($row[$this->_pkAlias]))
 824:                 $pk=$row[$this->_pkAlias];
 825:             else    
 826:                 return null;
 827:         }
 828:         else 
 829:         {
 830:             $pk=array();
 831:             foreach($this->_pkAlias as $name=>$alias)
 832:             {
 833:                 if(isset($row[$alias]))
 834:                     $pk[$name]=$row[$alias];
 835:                 else    
 836:                     return null;
 837:             }
 838:             $pk=serialize($pk);
 839:         }
 840: 
 841:         
 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:         
 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 
 871:             {
 872:                 
 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:  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:  905:  906:  907:  908:  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]))  
 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)) 
 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:             
 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:  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: 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: 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: 1029: 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: 1078: 1079: 1080: 1081: 1082: 1083: 1084: 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 
1109:                 {
1110:                     if(is_array($pke->_table->primaryKey)) 
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: 1131: 1132: 1133: 1134: 1135: 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: 1222: 1223: 1224: 1225: 1226: 
1227: class CJoinQuery
1228: {
1229:     1230: 1231: 
1232:     public $selects=array();
1233:     1234: 1235: 
1236:     public $distinct=false;
1237:     1238: 1239: 
1240:     public $joins=array();
1241:     1242: 1243: 
1244:     public $conditions=array();
1245:     1246: 1247: 
1248:     public $orders=array();
1249:     1250: 1251: 
1252:     public $groups=array();
1253:     1254: 1255: 
1256:     public $havings=array();
1257:     1258: 1259: 
1260:     public $limit=-1;
1261:     1262: 1263: 
1264:     public $offset=-1;
1265:     1266: 1267: 
1268:     public $params=array();
1269:     1270: 1271: 
1272:     public $elements=array();
1273: 
1274:     1275: 1276: 1277: 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: 1307: 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: 1334: 1335: 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: 1380: 1381: 1382: 1383: 
1384: class CStatElement
1385: {
1386:     1387: 1388: 
1389:     public $relation;
1390: 
1391:     private $_finder;
1392:     private $_parent;
1393: 
1394:     1395: 1396: 1397: 1398: 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: 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:         
1435:         $map=array();  
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  
1452:             {
1453:                 if(is_array($pkTable->primaryKey)) 
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:         
1474:         if(count($fks)===1)  
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  
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:         
1522:         foreach($records as $pk=>$record)
1523:             $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$relation->defaultValue,false);
1524:     }
1525: 
1526:     1527: 1528: 1529: 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: