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: