Changeset 3110

Show
Ignore:
Timestamp:
04/13/08 09:14:54 (3 months ago)
Author:
pmjones
Message:

Solar_Sql_Model


Several breaks of internal logic and technique, which should make the system
more flexible and powerful. These are mostly in response to the addition of
logic in the Solar_Sql_Model_Related classes.

In addition, you can now use eager has-many join tables in your select params
without having to write your own fetch methods to construct a special SELECT
object. Cf. upcoming changes in Solar_Model_Nodes and Solar_Model_Tags.

* [CHG] Method call() now gives fully-qualified names to the select columns;

e.g., instead of just "id", the select column is "model_name.id".

* [REF] In all fetch*() methods, standardized internal variable names to

$params, $select, and $result for easier comparison.


* [BRK] Removed support methods _fetchAll() and _fetchAssoc(). Now that the

relation objects carry more logic, and that $params is passed in full to
newSelect(), they're not needed. Wrapped their support logic back into their
"parent" methods fetchAll() and fetchAssoc() respectively.

* [FIX] Method _setCollectionPagerInfo() now correctly sets the 'end' value

(was off by 1 too many).

* [CHG] Method countPages() now honors all eager joins. (Thanks, Jeff Surgeson,

for noting that previously it did not honor the joins; that report was the
driving force behind all these other changes.)

* [CHG] Method fixSelectParams()...

  • Now defaults DISTINCT to null instead of false, so that existing
    DISTINCT values are not modified unless specifically set to true or
    false.
  • Now "uniques" the requested column names.

  • Now "uniques" the requested eager joins.

* [BRK] Method newSelect() now takes the **entire** $params array, not just

the 'eager' key from that array. This affords a lot more consolidation of
logic, and gives more power and flexibility to the building process. The
method is now responsible for building the whole SELECT for fetch*() calls.


* [CHG] Method newSelect() now honors **all types of eager joins** (including

"has many" and "has many through", although columns from the has-many
relations still have to be fetched separately). It does so by using the
"modSelectEager()" method from the relation object. This means you can have
selection criteria based on any eager-join relation.

* [CHG] Method insert() now sets the primary-key of the record object, not just

the first auto-increment key. This will help support non-autoinc-based
primary keys, such as sequence-based keys.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/Solar/Sql/Model.php

    r3105 r3110  
    616616            $col = preg_replace('/([a-z])([A-Z])/', '$1_$2', $col); 
    617617            $col = strtolower($col); 
    618             $where["$col = ?"] = $params[$key]; 
     618            $where["{$this->_model_name}.$col = ?"] = $params[$key]; 
    619619        } 
    620620         
     
    699699    public function fetchAll($params = array()) 
    700700    { 
    701         // setup 
     701        // prepare 
    702702        $params = $this->fixSelectParams($params); 
    703         $select = $this->newSelect($params['eager']); 
    704          
    705         // build 
    706         $select->distinct($params['distinct']) 
    707                ->from("{$this->_table_name} AS {$this->_model_name}", $params['cols']) 
    708                ->multiWhere($params['where']) 
    709                ->group($params['group']) 
    710                ->having($params['having']) 
    711                ->order($params['order']) 
    712                ->setPaging($params['paging']) 
    713                ->limitPage($params['page']) 
    714                ->bind($params['bind']); 
    715          
    716         // fetch all with eager loading 
    717         return $this->_fetchAll($select, $params); 
    718     } 
    719      
    720     /** 
    721      *  
    722      * Support method for fetchAll() to add eager related records. 
    723      *  
    724      * @param Solar_Sql_Select $select The select object for fetching. 
    725      *  
    726      * @param array $params The original params passed to fetchAll(). In 
    727      * general, only needed for the 'eager' key. 
    728      *  
    729      * @return mixed Solar_Sql_Model_Collection of found records, or an empty 
    730      * array if no records were found. 
    731      *  
    732      */ 
    733     protected function _fetchAll($select, $params) 
    734     { 
    735         $data = $select->fetchAll(); 
    736         if ($data) { 
    737             $coll = $this->newCollection($data); 
    738             foreach ((array) $params['eager'] as $name) { 
    739                 $related = $this->getRelated($name); 
    740                 if ($related->type == 'has_many') { 
    741                     $data = $this->fetchRelatedArray($select, $name); 
    742                     $coll->loadRelated($name, $data); 
    743                 } 
    744             } 
    745              
    746             // if we're doing "count_pages", add pager info to the collection 
    747             if ($params['count_pages']) { 
    748                 $this->_setCollectionPagerInfo($coll, $params); 
    749             } 
    750              
    751             // done 
    752             return $coll; 
    753         } else { 
     703        $select = $this->newSelect($params); 
     704         
     705        // fetch 
     706        $result = $select->fetchAll(); 
     707        if (! $result) { 
    754708            return array(); 
    755709        } 
     710         
     711        // create a collection from the result 
     712        $coll = $this->newCollection($result); 
     713         
     714        // add has-many eager data to the collection 
     715        foreach ((array) $params['eager'] as $name) { 
     716            $related = $this->getRelated($name); 
     717            if ($related->type == 'has_many') { 
     718                $result = $this->fetchRelatedArray($select, $name); 
     719                $coll->loadRelated($name, $result); 
     720            } 
     721        } 
     722         
     723        // add pager-info to the collection 
     724        if ($params['count_pages']) { 
     725            $this->_setCollectionPagerInfo($coll, $params); 
     726        } 
     727         
     728        // done 
     729        return $coll; 
    756730    } 
    757731     
     
    797771    public function fetchAssoc($params = array()) 
    798772    { 
    799         // setup 
     773        // prepare 
    800774        $params = $this->fixSelectParams($params); 
    801         $select = $this->newSelect($params['eager']); 
    802          
    803         // build 
    804         $select->distinct($params['distinct']) 
    805                ->from("{$this->_table_name} AS {$this->_model_name}", $params['cols']) 
    806                ->multiWhere($params['where']) 
    807                ->group($params['group']) 
    808                ->having($params['having']) 
    809                ->order($params['order']) 
    810                ->setPaging($params['paging']) 
    811                ->limitPage($params['page']) 
    812                ->bind($params['bind']); 
     775        $select = $this->newSelect($params); 
    813776         
    814777        // fetch 
    815         return $this->_fetchAssoc($select, $params); 
    816     } 
    817      
    818     /** 
    819      *  
    820      * Support method for fetchAssoc() to add eager related records. 
    821      *  
    822      * @param Solar_Sql_Select $select The select object for fetching. 
    823      *  
    824      * @param array $params The original params passed to fetchAll(). In 
    825      * general, only needed for the 'eager' key. 
    826      *  
    827      * @return mixed Solar_Sql_Model_Collection of found records, or an empty 
    828      * array if no records were found. 
    829      *  
    830      */ 
    831     protected function _fetchAssoc($select, $params) 
    832     { 
    833         $data = $select->fetchAssoc(); 
    834         if ($data) { 
    835             $coll = $this->newCollection($data); 
    836             foreach ((array) $params['eager'] as $name) { 
    837                 $related = $this->getRelated($name); 
    838                 if ($related->type == 'has_many') { 
    839                     $data = $this->fetchRelatedArray($select, $name); 
    840                     $coll->loadRelated($name, $data); 
    841                 } 
    842             } 
    843              
    844             // if we're doing "count_pages", add pager info to the collection 
    845             if ($params['count_pages']) { 
    846                 $this->_setCollectionPagerInfo($coll, $params); 
    847             } 
    848              
    849             // done 
    850             return $coll; 
    851         } else { 
     778        $result = $select->fetchAssoc(); 
     779        if (! $result) { 
    852780            return array(); 
    853781        } 
     782         
     783        // create a collection from the result 
     784        $coll = $this->newCollection($result); 
     785         
     786        // add has-many eager data to the collection 
     787        foreach ((array) $params['eager'] as $name) { 
     788            $related = $this->getRelated($name); 
     789            if ($related->type == 'has_many') { 
     790                $result = $this->fetchRelatedArray($select, $name); 
     791                $coll->loadRelated($name, $result); 
     792            } 
     793        } 
     794         
     795        // add pager-info to the collection 
     796        if ($params['count_pages']) { 
     797            $this->_setCollectionPagerInfo($coll, $params); 
     798        } 
     799         
     800        // done 
     801        return $coll; 
    854802    } 
    855803     
     
    870818    { 
    871819        $total = $this->countPages($params); 
    872         $begin = ($params['page'] - 1) * $params['paging'] + 1
     820        $start = ($params['page'] - 1) * $params['paging']
    873821        $coll->setPagerInfo(array( 
    874822            'count'  => $total['count'], 
     
    876824            'paging' => $params['paging'], 
    877825            'page'   => $params['page'], 
    878             'begin'  => $begin
    879             'end'    => $begin + $coll->count(), 
     826            'begin'  => $start + 1
     827            'end'    => $start + $coll->count(), 
    880828        )); 
    881829    } 
     
    917865    public function fetchOne($params = array()) 
    918866    { 
    919         // setup 
     867        // prepare 
    920868        $params = $this->fixSelectParams($params); 
    921         $select = $this->newSelect($params['eager']); 
    922          
    923         // build 
    924         $select->distinct($params['distinct']) 
    925                ->from("{$this->_table_name} AS {$this->_model_name}", $params['cols']) 
    926                ->multiWhere($params['where']) 
    927                ->group($params['group']) 
    928                ->having($params['having']) 
    929                ->order($params['order']) 
    930                ->bind($params['bind']); 
     869        $select = $this->newSelect($params); 
    931870         
    932871        // fetch 
    933         $data = $select->fetchOne(); 
    934         if (! $data) { 
     872        $result = $select->fetchOne(); 
     873        if (! $result) { 
    935874            return null; 
    936875        } 
    937876         
    938877        // get the main record, which sets the belongs_to/has_one data 
    939         $record = $this->newRecord($data); 
     878        $record = $this->newRecord($result); 
    940879         
    941880        // get related data from each eager has_many relationship 
     
    992931    public function fetchCol($params = array()) 
    993932    { 
    994         // setup 
     933        // prepare 
    995934        $params = $this->fixSelectParams($params); 
    996         $select = $this->newSelect($params['eager']); 
    997          
    998         // build 
    999         $select->distinct($params['distinct']) 
    1000                ->from("{$this->_table_name} AS {$this->_model_name}", $params['cols']) 
    1001                ->multiWhere($params['where']) 
    1002                ->group($params['group']) 
    1003                ->having($params['having']) 
    1004                ->order($params['order']) 
    1005                ->setPaging($params['paging']) 
    1006                ->limitPage($params['page']) 
    1007                ->bind($params['bind']); 
     935        $select = $this->newSelect($params); 
    1008936         
    1009937        // fetch 
    1010         $data = $select->fetchCol(); 
    1011         if ($data) { 
    1012             return $data
     938        $result = $select->fetchCol(); 
     939        if ($result) { 
     940            return $result
    1013941        } else { 
    1014942            return array(); 
     
    1057985    public function fetchPairs($params = array()) 
    1058986    { 
    1059         // setup 
     987        // prepare 
    1060988        $params = $this->fixSelectParams($params); 
    1061         $select = $this->newSelect($params['eager']); 
    1062          
    1063         // build 
    1064         $select->distinct($params['distinct']) 
    1065                ->from("{$this->_table_name} AS {$this->_model_name}", $params['cols']) 
    1066                ->multiWhere($params['where']) 
    1067                ->group($params['group']) 
    1068                ->having($params['having']) 
    1069                ->order($params['order']) 
    1070                ->setPaging($params['paging']) 
    1071                ->limitPage($params['page']) 
    1072                ->bind($params['bind']); 
     989        $select = $this->newSelect($params); 
    1073990         
    1074991        // fetch 
    1075         $data = $select->fetchPairs(); 
    1076         if ($data) { 
    1077             return $data
     992        $result = $select->fetchPairs(); 
     993        if ($result) { 
     994            return $result
    1078995        } else { 
    1079996            return array(); 
     
    11221039    public function fetchValue($params = array()) 
    11231040    { 
    1124         // setup 
    11251041        $params = $this->fixSelectParams($params); 
    1126         $select = $this->newSelect($params['eager']); 
    1127         $col = current($params['cols']); 
    1128          
    1129         // build 
    1130         $select->distinct($params['distinct']) 
    1131                ->from("{$this->_table_name} AS {$this->_model_name}", $col) 
    1132                ->multiWhere($params['where']) 
    1133                ->group($params['group']) 
    1134                ->having($params['having']) 
    1135                ->order($params['order']) 
    1136                ->setPaging($params['paging']) 
    1137                ->limitPage($params['page']) 
    1138                ->bind($params['bind']); 
    1139          
    1140         // fetch 
     1042        $select = $this->newSelect($params); 
    11411043        return $select->fetchValue(); 
    11421044    } 
     
    12021104    public function countPages($params = null) 
    12031105    { 
     1106        // fix up the parameters 
    12041107        $params = $this->fixSelectParams($params); 
    12051108         
    1206         $select = $this->newSelect(); 
    1207         $select->distinct($params['distinct']) 
    1208                ->from("{$this->_table_name} AS {$this->_model_name}") 
    1209                ->multiWhere($params['where']) 
    1210                ->group($params['group']) 
    1211                ->having($params['having']) 
    1212                ->setPaging($this->_paging) 
    1213                ->bind($params['bind']); 
    1214          
     1109        // remove the 'eager' param for now, so we don't get the column- 
     1110        // based eager joins. 
     1111        $eager = (array) $params['eager']; 
     1112        $params['eager'] = array(); 
     1113         
     1114        // get the base select 
     1115        $select = $this->newSelect($params); 
     1116         
     1117        // add count-based eager joins 
     1118        foreach ($eager as $name) { 
     1119            $related = $this->getRelated($name); 
     1120            $related->modSelectCountPages($select); 
     1121        } 
     1122         
     1123        // done, count on the primary column 
    12151124        $col = "{$this->_model_name}.{$this->_primary_col}"; 
    1216          
    12171125        return $select->countPages($col); 
    12181126    } 
     
    12721180         
    12731181        if (empty($params['distinct'])) { 
    1274             $params['distinct'] = false; 
    1275         } 
    1276          
     1182            $params['distinct'] = null; 
     1183        } 
     1184         
     1185        // if we have columns, make sure they're unique 
     1186        if (! empty($params['cols'])) { 
     1187            $params['cols'] = array_unique((array) $params['cols']); 
     1188        } 
     1189         
     1190        // even after uniqing, cols might still be empty 
    12771191        if (empty($params['cols'])) { 
    12781192            $params['cols'] = array_keys($this->_table_cols); 
    12791193        } 
    12801194         
     1195        // if we have eager values, make sure they're unique 
     1196        if (! empty($params['eager'])) { 
     1197            $params['eager'] = array_unique((array) $params['eager']); 
     1198        } 
     1199         
     1200        // even after uniqing, the eager values might still be empty 
    12811201        if (empty($params['eager'])) { 
    12821202            $params['eager'] = null; 
     
    13231243     * injected automatically, and with eager "to-one" associations joined. 
    13241244     *  
    1325      * @param array $eager An array of to-one relationship names to eager- 
    1326      * load with LEFT JOIN clauses
     1245     * @param array $params An array of SELECT parameters (esp. the 'eager' 
     1246     * param)
    13271247     *  
    13281248     * @return Solar_Sql_Select 
    13291249     *  
    13301250     */ 
    1331     public function newSelect($eager = null
     1251    public function newSelect($params
    13321252    { 
    13331253        // get the select object 
     
    13371257        ); 
    13381258         
    1339         // add eager has_one/belongs_to joins 
    1340         foreach ((array) $eager as $name) { 
    1341              
    1342             // get the relationship options 
     1259        // modify the select to add eager joins 
     1260        foreach ((array) $params['eager'] as $name) { 
    13431261            $related = $this->getRelated($name); 
    1344              
    1345             // only process eager to-one associations at this point 
    1346             if ($related->type == 'has_many') { 
    1347                 continue; 
    1348             } 
    1349              
    1350             // build column names as "name__col" so that we can extract the 
    1351             // the related data later. 
    1352             $cols = array(); 
    1353             foreach ($related->cols as $col) { 
    1354                 $cols[] = "$col AS {$name}__$col"; 
    1355             } 
    1356              
    1357             // primary-key join condition on foreign table 
    1358             // local.id = foreign_alias.local_id 
    1359             $cond = "{$this->_model_name}.{$related->native_col} = " 
    1360                   . "{$related->foreign_alias}.{$related->foreign_col}"; 
    1361              
    1362             // add the join 
    1363             $select->leftJoin( 
    1364                 "{$related->foreign_table} AS {$related->foreign_alias}", 
    1365                 $cond, 
    1366                 $cols 
    1367             ); 
    1368              
    1369             // inheritance for foreign model 
    1370             if ($related->foreign_inherit_col) { 
    1371                 $select->where( 
    1372                     "{$related->foreign_alias}.{$related->foreign_inherit_col} = ?", 
    1373                     $related->foreign_inherit_val 
    1374                 ); 
    1375              
    1376             } 
    1377              
    1378             // added where conditions for the join 
    1379             $select->multiWhere($related->where); 
     1262            $related->modSelectEager($select); 
    13801263        } 
    13811264         
     
    13871270            ); 
    13881271        } 
     1272         
     1273        // all the other pieces 
     1274        $select->distinct($params['distinct']) 
     1275               ->from("{$this->_table_name} AS {$this->_model_name}", $params['cols']) 
     1276               ->multiWhere($params['where']) 
     1277               ->group($params['group']) 
     1278               ->multiHaving($params['having']) 
     1279               ->order($params['order']) 
     1280               ->setPaging($params['paging']) 
     1281               ->limitPage($params['page']) 
     1282               ->bind($params['bind']); 
    13891283         
    13901284        // done! 
     
    16471541        // no exception thrown, so it must have worked. 
    16481542        // if there was an autoincrement column, set its value in the data. 
    1649         $id = $this->_sql->lastInsertId($this->_table_name, $key); 
    16501543        foreach ($this->_table_cols as $key => $val) { 
    16511544            if ($val['autoinc']) { 
    16521545                // set the value and leave the loop (should be only one) 
    1653                 $data[$key] = $id; 
    1654                 if ($spec instanceof Solar_Sql_Model_Record) { 
    1655                     $spec->$key = $id; 
    1656                 } 
     1546                $data[$key] = $this->_sql->lastInsertId($this->_table_name, $key); 
    16571547                break; 
    16581548            } 
     
    16611551        // refresh the table data in the record 
    16621552        if ($spec instanceof Solar_Sql_Model_Record) { 
     1553            // set the primary column so refresh will work 
     1554            $key = $this->_primary_col; 
     1555            $spec->$key = $data[$key]; 
     1556            // now refresh it 
    16631557            $spec->refresh(); 
    16641558            $spec->setStatus('inserted');