dbo_source.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: dbo__source_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 /**
00004  * Short description for file.
00005  *
00006  * Long description for file
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00011  * Copyright 2005-2008, Cake Software Foundation, Inc.
00012  *                              1785 E. Sahara Avenue, Suite 490-204
00013  *                              Las Vegas, Nevada 89104
00014  *
00015  * Licensed under The MIT License
00016  * Redistributions of files must retain the above copyright notice.
00017  *
00018  * @filesource
00019  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00020  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00021  * @package         cake
00022  * @subpackage      cake.cake.libs.model.datasources
00023  * @since           CakePHP(tm) v 0.10.0.1076
00024  * @version         $Revision: 580 $
00025  * @modifiedby      $LastChangedBy: gwoo $
00026  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00027  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00028  */
00029 App::import('Core', 'Set');
00030 
00031 /**
00032  * DboSource
00033  *
00034  * Creates DBO-descendant objects from a given db connection configuration
00035  *
00036  * @package     cake
00037  * @subpackage  cake.cake.libs.model.datasources
00038  */
00039 class DboSource extends DataSource {
00040 /**
00041  * Description string for this Database Data Source.
00042  *
00043  * @var unknown_type
00044  */
00045     var $description = "Database Data Source";
00046 /**
00047  * index definition, standard cake, primary, index, unique
00048  *
00049  * @var array
00050  */
00051     var $index = array('PRI' => 'primary', 'MUL' => 'index', 'UNI' => 'unique');
00052 /**
00053  * Enter description here...
00054  *
00055  * @var unknown_type
00056  */
00057     var $startQuote = null;
00058 /**
00059  * Enter description here...
00060  *
00061  * @var unknown_type
00062  */
00063     var $endQuote = null;
00064 /**
00065  * Enter description here...
00066  *
00067  * @var unknown_type
00068  */
00069     var $alias = 'AS ';
00070 /**
00071  * Caches fields quoted in DboSource::name()
00072  *
00073  * @var array
00074  */
00075     var $fieldCache = array();
00076 /**
00077  * Enter description here...
00078  *
00079  * @var unknown_type
00080  */
00081     var $__bypass = false;
00082 /**
00083  * The set of valid SQL operations usable in a WHERE statement
00084  *
00085  * @var array
00086  */
00087     var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
00088 /**
00089  * Index of basic SQL commands
00090  *
00091  * @var array
00092  * @access protected
00093  */
00094     var $_commands = array(
00095         'begin'    => 'BEGIN',
00096         'commit'   => 'COMMIT',
00097         'rollback' => 'ROLLBACK'
00098     );
00099 /**
00100  * Constructor
00101  */
00102     function __construct($config = null, $autoConnect = true) {
00103         if (!isset($config['prefix'])) {
00104             $config['prefix'] = '';
00105         }
00106         parent::__construct($config);
00107         $this->fullDebug = Configure::read() > 1;
00108 
00109         if ($autoConnect) {
00110             return $this->connect();
00111         } else {
00112             return true;
00113         }
00114     }
00115 /**
00116  * Reconnects to database server with optional new settings
00117  *
00118  * @param array $config An array defining the new configuration settings
00119  * @return boolean True on success, false on failure
00120  */
00121     function reconnect($config = null) {
00122         $this->disconnect();
00123         if ($config != null) {
00124             $this->config = array_merge($this->_baseConfig, $config);
00125         }
00126         return $this->connect();
00127     }
00128 /**
00129  * Prepares a value, or an array of values for database queries by quoting and escaping them.
00130  *
00131  * @param mixed $data A value or an array of values to prepare.
00132  * @return mixed Prepared value or array of values.
00133  */
00134     function value($data, $column = null) {
00135         if (is_array($data) && !empty($data)) {
00136             return array_map(array(&$this, 'value'), $data, array_fill(0, count($data), $column));
00137         } elseif (is_object($data)) {
00138             if (isset($data->type) && $data->type == 'identifier') {
00139                 return $this->name($data->value);
00140             }
00141         } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
00142             return $data;
00143         } else {
00144             return null;
00145         }
00146     }
00147 /**
00148  * Returns an object to represent a database identifier in a query
00149  *
00150  * @param string $identifier
00151  * @return object An object representing a database identifier to be used in a query
00152  */
00153     function identifier($identifier) {
00154         $obj = new stdClass();
00155         $obj->type = 'identifier';
00156         $obj->value = $identifier;
00157         return $obj;
00158     }
00159 /**
00160  * Returns an object to represent a database expression in a query
00161  *
00162  * @param string $expression
00163  * @return object An object representing a database expression to be used in a query
00164  */
00165     function expression($expression) {
00166         $obj = new stdClass();
00167         $obj->type = 'expression';
00168         $obj->value = $expression;
00169         return $obj;
00170     }
00171 /**
00172  * Executes given SQL statement.
00173  *
00174  * @param string $sql SQL statement
00175  * @return unknown
00176  */
00177     function rawQuery($sql) {
00178         $this->took = $this->error = $this->numRows = false;
00179         return $this->execute($sql);
00180     }
00181 /**
00182  * Queries the database with given SQL statement, and obtains some metadata about the result
00183  * (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
00184  * If DEBUG is set, the log is shown all the time, else it is only shown on errors.
00185  *
00186  * @param string $sql
00187  * @return mixed Resource or object representing the result set, or false on failure
00188  */
00189     function execute($sql) {
00190         $t = getMicrotime();
00191         $this->_result = $this->_execute($sql);
00192         $this->took = round((getMicrotime() - $t) * 1000, 0);
00193         $this->affected = $this->lastAffected();
00194         $this->error = $this->lastError();
00195         $this->numRows = $this->lastNumRows();
00196 
00197         if (Configure::read() > 1) {
00198             $this->logQuery($sql);
00199         }
00200 
00201         if ($this->error) {
00202             $this->showQuery($sql);
00203             return false;
00204         } else {
00205             return $this->_result;
00206         }
00207     }
00208 /**
00209  * DataSource Query abstraction
00210  *
00211  * @return resource Result resource identifier
00212  */
00213     function query() {
00214         $args     = func_get_args();
00215         $fields   = null;
00216         $order    = null;
00217         $limit    = null;
00218         $page     = null;
00219         $recursive = null;
00220 
00221         if (count($args) == 1) {
00222             return $this->fetchAll($args[0]);
00223 
00224         } elseif (count($args) > 1 && (strpos(strtolower($args[0]), 'findby') === 0 || strpos(strtolower($args[0]), 'findallby') === 0)) {
00225             $params = $args[1];
00226 
00227             if (strpos(strtolower($args[0]), 'findby') === 0) {
00228                 $all  = false;
00229                 $field = Inflector::underscore(preg_replace('/findBy/i', '', $args[0]));
00230             } else {
00231                 $all  = true;
00232                 $field = Inflector::underscore(preg_replace('/findAllBy/i', '', $args[0]));
00233             }
00234 
00235             $or = (strpos($field, '_or_') !== false);
00236             if ($or) {
00237                 $field = explode('_or_', $field);
00238             } else {
00239                 $field = explode('_and_', $field);
00240             }
00241             $off = count($field) - 1;
00242 
00243             if (isset($params[1 + $off])) {
00244                 $fields = $params[1 + $off];
00245             }
00246 
00247             if (isset($params[2 + $off])) {
00248                 $order = $params[2 + $off];
00249             }
00250 
00251             if (!array_key_exists(0, $params)) {
00252                 return false;
00253             }
00254 
00255             $c = 0;
00256             $query = array();
00257             foreach ($field as $f) {
00258                 $query[$args[2]->alias . '.' . $f] = $params[$c];
00259                 $c++;
00260             }
00261 
00262             if ($or) {
00263                 $query = array('OR' => $query);
00264             }
00265 
00266             if ($all) {
00267 
00268                 if (isset($params[3 + $off])) {
00269                     $limit = $params[3 + $off];
00270                 }
00271 
00272                 if (isset($params[4 + $off])) {
00273                     $page = $params[4 + $off];
00274                 }
00275 
00276                 if (isset($params[5 + $off])) {
00277                     $recursive = $params[5 + $off];
00278                 }
00279                 return $args[2]->findAll($query, $fields, $order, $limit, $page, $recursive);
00280             } else {
00281                 if (isset($params[3 + $off])) {
00282                     $recursive = $params[3 + $off];
00283                 }
00284                 return $args[2]->find($query, $fields, $order, $recursive);
00285             }
00286         } else {
00287             if (isset($args[1]) && $args[1] === true) {
00288                 return $this->fetchAll($args[0], true);
00289             } else if (isset($args[1]) && !is_array($args[1]) ) {
00290                 return $this->fetchAll($args[0], false);
00291             } else if (isset($args[1]) && is_array($args[1])) {
00292                 $offset = 0;
00293                 if (isset($args[2])) {
00294                     $cache = $args[2];
00295                 } else {
00296                     $cache = true;
00297                 }
00298                 $args[1] = array_map(array(&$this, 'value'), $args[1]);
00299                 return $this->fetchAll(String::insert($args[0], $args[1]), $cache);
00300             }
00301         }
00302     }
00303 /**
00304  * Returns a row from current resultset as an array
00305  *
00306  * @return array The fetched row as an array
00307  */
00308     function fetchRow($sql = null) {
00309         if (!empty($sql) && is_string($sql) && strlen($sql) > 5) {
00310             if (!$this->execute($sql)) {
00311                 return null;
00312             }
00313         }
00314 
00315         if ($this->hasResult()) {
00316             $this->resultSet($this->_result);
00317             $resultRow = $this->fetchResult();
00318             return $resultRow;
00319         } else {
00320             return null;
00321         }
00322     }
00323 /**
00324  * Returns an array of all result rows for a given SQL query.
00325  * Returns false if no rows matched.
00326  *
00327  * @param string $sql SQL statement
00328  * @param boolean $cache Enables returning/storing cached query results
00329  * @return array Array of resultset rows, or false if no rows matched
00330  */
00331     function fetchAll($sql, $cache = true, $modelName = null) {
00332         if ($cache && isset($this->_queryCache[$sql])) {
00333             if (preg_match('/^\s*select/i', $sql)) {
00334                 return $this->_queryCache[$sql];
00335             }
00336         }
00337 
00338         if ($this->execute($sql)) {
00339             $out = array();
00340 
00341             $first = $this->fetchRow();
00342             if ($first != null){
00343                 $out[] = $first;
00344             }
00345             while ($this->hasResult() && $item = $this->fetchResult()) {
00346                 $out[] = $item;
00347             }
00348 
00349             if ($cache) {
00350                 if (strpos(trim(strtolower($sql)), 'select') !== false) {
00351                     $this->_queryCache[$sql] = $out;
00352                 }
00353             }
00354             return $out;
00355 
00356         } else {
00357             return false;
00358         }
00359     }
00360 /**
00361  * Returns a single field of the first of query results for a given SQL query, or false if empty.
00362  *
00363  * @param string $name Name of the field
00364  * @param string $sql SQL query
00365  * @return unknown
00366  */
00367     function field($name, $sql) {
00368         $data = $this->fetchRow($sql);
00369 
00370         if (!isset($data[$name]) || empty($data[$name])) {
00371             return false;
00372         } else {
00373             return $data[$name];
00374         }
00375     }
00376 /**
00377  * Returns a quoted name of $data for use in an SQL statement.
00378  * Strips fields out of SQL functions before quoting.
00379  *
00380  * @param string $data
00381  * @return string SQL field
00382  */
00383     function name($data) {
00384         if ($data == '*') {
00385             return '*';
00386         }
00387         $array = is_array($data);
00388 
00389         $data = (array)$data;
00390         $count = count($data);
00391 
00392         for($i = 0; $i < $count; $i++) {
00393             if ($data[$i] == '*') {
00394                 continue;
00395             }
00396             if (strpos($data[$i], '(') !== false && preg_match_all('/([^(]*)\((.*)\)(.*)/', $data[$i], $fields)) {
00397                 $fields = Set::extract($fields, '{n}.0');
00398                 if (!empty($fields[1])) {
00399                     if (!empty($fields[2])) {
00400                         $data[$i] = $fields[1] . '(' . $this->name($fields[2]) . ')' . $fields[3];
00401                     } else {
00402                         $data[$i] = $fields[1] . '()' . $fields[3];
00403                     }
00404                 }
00405             }
00406             $data[$i] = $this->startQuote . str_replace('.', $this->endQuote . '.' . $this->startQuote, $data[$i]) . $this->endQuote;
00407             $data[$i] = str_replace($this->startQuote . $this->startQuote, $this->startQuote, $data[$i]);
00408             if (strpos($data[$i], ' AS ')) {
00409                 $data[$i] = str_replace(' AS ', $this->endQuote . ' AS ' . $this->startQuote, $data[$i]);
00410             }
00411 
00412             if (!empty($this->endQuote) && $this->endQuote == $this->startQuote) {
00413                 if (substr_count($data[$i], $this->endQuote) % 2 == 1) {
00414                     $data[$i] = trim($data[$i], $this->endQuote);
00415                 }
00416             }
00417             if (strpos($data[$i], '*')) {
00418                 $data[$i] = str_replace($this->endQuote . '*' . $this->endQuote, '*', $data[$i]);
00419             }
00420             $data[$i] = str_replace($this->endQuote . $this->endQuote, $this->endQuote, $data[$i]);
00421         }
00422         if (!$array) {
00423             return $data[0];
00424         }
00425         return $data;
00426     }
00427 /**
00428  * Checks if it's connected to the database
00429  *
00430  * @return boolean True if the database is connected, else false
00431  */
00432     function isConnected() {
00433         return $this->connected;
00434     }
00435 /**
00436  * Checks if the result is valid
00437  *
00438  * @return boolean True if the result is valid else false
00439  */
00440     function hasResult() {
00441         return is_resource($this->_result);
00442     }
00443 /**
00444  * Outputs the contents of the queries log.
00445  *
00446  * @param boolean $sorted
00447  */
00448     function showLog($sorted = false) {
00449         if ($sorted) {
00450             $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
00451         } else {
00452             $log = $this->_queriesLog;
00453         }
00454 
00455         if ($this->_queriesCnt > 1) {
00456             $text = 'queries';
00457         } else {
00458             $text = 'query';
00459         }
00460 
00461         if (php_sapi_name() != 'cli') {
00462             print ("<table class=\"cake-sql-log\" id=\"cakeSqlLog_" . preg_replace('/[^A-Za-z0-9_]/', '_', uniqid(time(), true)) . "\" summary=\"Cake SQL Log\" cellspacing=\"0\" border = \"0\">\n<caption>{$this->_queriesCnt} {$text} took {$this->_queriesTime} ms</caption>\n");
00463             print ("<thead>\n<tr><th>Nr</th><th>Query</th><th>Error</th><th>Affected</th><th>Num. rows</th><th>Took (ms)</th></tr>\n</thead>\n<tbody>\n");
00464 
00465             foreach ($log as $k => $i) {
00466                 print ("<tr><td>" . ($k + 1) . "</td><td>" . h($i['query']) . "</td><td>{$i['error']}</td><td style = \"text-align: right\">{$i['affected']}</td><td style = \"text-align: right\">{$i['numRows']}</td><td style = \"text-align: right\">{$i['took']}</td></tr>\n");
00467             }
00468             print ("</tbody></table>\n");
00469         } else {
00470             foreach ($log as $k => $i) {
00471                 print (($k + 1) . ". {$i['query']} {$i['error']}\n");
00472             }
00473         }
00474     }
00475 /**
00476  * Log given SQL query.
00477  *
00478  * @param string $sql SQL statement
00479  * @todo: Add hook to log errors instead of returning false
00480  */
00481     function logQuery($sql) {
00482         $this->_queriesCnt++;
00483         $this->_queriesTime += $this->took;
00484         $this->_queriesLog[] = array(
00485             'query' => $sql,
00486             'error'     => $this->error,
00487             'affected'  => $this->affected,
00488             'numRows'   => $this->numRows,
00489             'took'      => $this->took
00490         );
00491         if (count($this->_queriesLog) > $this->_queriesLogMax) {
00492             array_pop($this->_queriesLog);
00493         }
00494         if ($this->error) {
00495             return false;
00496         }
00497     }
00498 /**
00499  * Output information about an SQL query. The SQL statement, number of rows in resultset,
00500  * and execution time in microseconds. If the query fails, an error is output instead.
00501  *
00502  * @param string $sql Query to show information on.
00503  */
00504     function showQuery($sql) {
00505         $error = $this->error;
00506         if (strlen($sql) > 200 && !$this->fullDebug && Configure::read() > 1) {
00507             $sql = substr($sql, 0, 200) . '[...]';
00508         }
00509         if ($error && Configure::read() > 0) {
00510             $out = null;
00511             if ($error) {
00512                 trigger_error("<span style = \"color:Red;text-align:left\"><b>SQL Error:</b> {$this->error}</span>", E_USER_WARNING);
00513             } else {
00514                 $out = ("<small>[Aff:{$this->affected} Num:{$this->numRows} Took:{$this->took}ms]</small>");
00515             }
00516             e(sprintf("<p style = \"text-align:left\"><b>Query:</b> %s %s</p>", $sql, $out));
00517         }
00518     }
00519 /**
00520  * Gets full table name including prefix
00521  *
00522  * @param mixed $model
00523  * @param boolean $quote
00524  * @return string Full quoted table name
00525  */
00526     function fullTableName($model, $quote = true) {
00527         if (is_object($model)) {
00528             $table = $model->tablePrefix . $model->table;
00529         } elseif (isset($this->config['prefix'])) {
00530             $table = $this->config['prefix'] . strval($model);
00531         } else {
00532             $table = strval($model);
00533         }
00534         if ($quote) {
00535             return $this->name($table);
00536         }
00537         return $table;
00538     }
00539 /**
00540  * The "C" in CRUD
00541  *
00542  * @param Model $model
00543  * @param array $fields
00544  * @param array $values
00545  * @return boolean Success
00546  */
00547     function create(&$model, $fields = null, $values = null) {
00548         $id = null;
00549 
00550         if ($fields == null) {
00551             unset($fields, $values);
00552             $fields = array_keys($model->data);
00553             $values = array_values($model->data);
00554         }
00555         $count = count($fields);
00556 
00557         for ($i = 0; $i < $count; $i++) {
00558             $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]));
00559         }
00560         for ($i = 0; $i < $count; $i++) {
00561             $fieldInsert[] = $this->name($fields[$i]);
00562             if ($fields[$i] == $model->primaryKey) {
00563                 $id = $values[$i];
00564             }
00565         }
00566 
00567         if ($this->execute('INSERT INTO ' . $this->fullTableName($model) . ' (' . join(',', $fieldInsert). ') VALUES (' . join(',', $valueInsert) . ')')) {
00568             if (empty($id)) {
00569                 $id = $this->lastInsertId($this->fullTableName($model, false), $model->primaryKey);
00570             }
00571             $model->setInsertID($id);
00572             $model->id = $id;
00573             return true;
00574         } else {
00575             $model->onError();
00576             return false;
00577         }
00578     }
00579 /**
00580  * The "R" in CRUD
00581  *
00582  * @param Model $model
00583  * @param array $queryData
00584  * @param integer $recursive Number of levels of association
00585  * @return unknown
00586  */
00587     function read(&$model, $queryData = array(), $recursive = null) {
00588         $queryData = $this->__scrubQueryData($queryData);
00589         $null = null;
00590         $array = array();
00591         $linkedModels = array();
00592         $this->__bypass = false;
00593         $this->__booleans = array();
00594 
00595         if ($recursive === null && isset($queryData['recursive'])) {
00596             $recursive = $queryData['recursive'];
00597         }
00598 
00599         if (!is_null($recursive)) {
00600             $_recursive = $model->recursive;
00601             $model->recursive = $recursive;
00602         }
00603 
00604         if (!empty($queryData['fields'])) {
00605             $this->__bypass = true;
00606             $queryData['fields'] = $this->fields($model, null, $queryData['fields']);
00607         } else {
00608             $queryData['fields'] = $this->fields($model);
00609         }
00610 
00611         foreach ($model->__associations as $type) {
00612             foreach ($model->{$type} as $assoc => $assocData) {
00613                 if ($model->recursive > -1) {
00614                     $linkModel =& $model->{$assoc};
00615                     $external = isset($assocData['external']);
00616 
00617                     if ($model->useDbConfig == $linkModel->useDbConfig) {
00618                         if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) {
00619                             $linkedModels[] = $type . '/' . $assoc;
00620                         }
00621                     }
00622                 }
00623             }
00624         }
00625 
00626         $query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null);
00627         $resultSet = $this->fetchAll($query, $model->cacheQueries, $model->alias);
00628 
00629         if ($resultSet === false) {
00630             $model->onError();
00631             return false;
00632         }
00633 
00634         $filtered = $this->__filterResults($resultSet, $model);
00635 
00636         if ($model->recursive > 0) {
00637             foreach ($model->__associations as $type) {
00638                 foreach ($model->{$type} as $assoc => $assocData) {
00639                     $linkModel =& $model->{$assoc};
00640 
00641                     if (!in_array($type . '/' . $assoc, $linkedModels)) {
00642                         if ($model->useDbConfig == $linkModel->useDbConfig) {
00643                             $db =& $this;
00644                         } else {
00645                             $db =& ConnectionManager::getDataSource($linkModel->useDbConfig);
00646                         }
00647                     } elseif ($model->recursive > 1 && ($type == 'belongsTo' || $type == 'hasOne')) {
00648                         $db =& $this;
00649                     }
00650 
00651                     if (isset($db)) {
00652                         $stack = array($assoc);
00653                         $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
00654                         unset($db);
00655                     }
00656                 }
00657             }
00658             $this->__filterResults($resultSet, $model, $filtered);
00659         }
00660 
00661         if (!is_null($recursive)) {
00662             $model->recursive = $_recursive;
00663         }
00664         return $resultSet;
00665     }
00666 /**
00667  * Private method.  Passes association results thru afterFind filters of corresponding model
00668  *
00669  * @param array $results Reference of resultset to be filtered
00670  * @param object $model Instance of model to operate against
00671  * @param array $filtered List of classes already filtered, to be skipped
00672  * @return return
00673  */
00674     function __filterResults(&$results, &$model, $filtered = array()) {
00675         $filtering = array();
00676         $count = count($results);
00677 
00678         for ($i = 0; $i < $count; $i++) {
00679             if (is_array($results[$i])) {
00680                 $classNames = array_keys($results[$i]);
00681                 $count2 = count($classNames);
00682 
00683                 for ($j = 0; $j < $count2; $j++) {
00684                     $className = $classNames[$j];
00685 
00686                     if ($model->alias != $className && !in_array($className, $filtered)) {
00687                         if (!in_array($className, $filtering)) {
00688                             $filtering[] = $className;
00689                         }
00690 
00691                         if (isset($model->{$className}) && is_object($model->{$className})) {
00692                             $data = $model->{$className}->afterFind(array(array($className => $results[$i][$className])), false);
00693                         }
00694                         if (isset($data[0][$className])) {
00695                             $results[$i][$className] = $data[0][$className];
00696                         }
00697                     }
00698                 }
00699             }
00700         }
00701         return $filtering;
00702     }
00703 /**
00704  * Enter description here...
00705  *
00706  * @param Model $model
00707  * @param unknown_type $linkModel
00708  * @param string $type Association type
00709  * @param unknown_type $association
00710  * @param unknown_type $assocData
00711  * @param unknown_type $queryData
00712  * @param unknown_type $external
00713  * @param unknown_type $resultSet
00714  * @param integer $recursive Number of levels of association
00715  * @param array $stack
00716  */
00717     function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {
00718         if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
00719             if (!isset($resultSet) || !is_array($resultSet)) {
00720                 if (Configure::read() > 0) {
00721                     e('<div style = "font: Verdana bold 12px; color: #FF0000">' . sprintf(__('SQL Error in model %s:', true), $model->alias) . ' ');
00722                     if (isset($this->error) && $this->error != null) {
00723                         e($this->error);
00724                     }
00725                     e('</div>');
00726                 }
00727                 return null;
00728             }
00729             $count = count($resultSet);
00730 
00731             if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) {
00732                 $ins = $fetch = array();
00733                 for ($i = 0; $i < $count; $i++) {
00734                     if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
00735                         $ins[] = $in;
00736                     }
00737                 }
00738 
00739                 if (!empty($ins)) {
00740                     $fetch = $this->fetchAssociated($model, $query, $ins);
00741                 }
00742 
00743                 if (!empty($fetch) && is_array($fetch)) {
00744                     if ($recursive > 0) {
00745                         foreach ($linkModel->__associations as $type1) {
00746                             foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
00747                                 $deepModel =& $linkModel->{$assoc1};
00748                                 $tmpStack = $stack;
00749                                 $tmpStack[] = $assoc1;
00750 
00751                                 if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
00752                                     $db =& $this;
00753                                 } else {
00754                                     $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
00755                                 }
00756                                 $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
00757                             }
00758                         }
00759                     }
00760                 }
00761                 return $this->__mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive);
00762             } elseif ($type === 'hasAndBelongsToMany') {
00763                 $ins = $fetch = array();
00764                 for ($i = 0; $i < $count; $i++) {
00765                     if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
00766                         $ins[] = $in;
00767                     }
00768                 }
00769                 if (!empty($ins)) {
00770                     if (count($ins) > 1) {
00771                         $query = str_replace('{$__cakeID__$}', '(' .join(', ', $ins) .')', $query);
00772                         $query = str_replace('= (', 'IN (', $query);
00773                         $query = str_replace('=  (', 'IN (', $query);
00774                     } else {
00775                         $query = str_replace('{$__cakeID__$}',$ins[0], $query);
00776                     }
00777 
00778                     $query = str_replace('  WHERE 1 = 1', '', $query);
00779                 }
00780 
00781                 $foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
00782                 $joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
00783                 list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
00784                 $habtmFieldsCount = count($habtmFields);
00785                 $q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack);
00786 
00787                 if ($q != false) {
00788                     $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
00789                 } else {
00790                     $fetch = null;
00791                 }
00792             }
00793 
00794             for ($i = 0; $i < $count; $i++) {
00795                 $row =& $resultSet[$i];
00796 
00797                 if ($type !== 'hasAndBelongsToMany') {
00798                     $q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack);
00799                     if ($q != false) {
00800                         $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
00801                     } else {
00802                         $fetch = null;
00803                     }
00804                 }
00805                 $selfJoin = false;
00806 
00807                 if ($linkModel->name === $model->name) {
00808                     $selfJoin = true;
00809                 }
00810 
00811                 if (!empty($fetch) && is_array($fetch)) {
00812                     if ($recursive > 0) {
00813                         foreach ($linkModel->__associations as $type1) {
00814                             foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
00815                                 $deepModel =& $linkModel->{$assoc1};
00816 
00817                                 if (($type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) {
00818                                     $tmpStack = $stack;
00819                                     $tmpStack[] = $assoc1;
00820                                     if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
00821                                         $db =& $this;
00822                                     } else {
00823                                         $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
00824                                     }
00825                                     $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
00826                                 }
00827                             }
00828                         }
00829                     }
00830                     if ($type == 'hasAndBelongsToMany') {
00831                         $uniqueIds = $merge = array();
00832 
00833                         foreach($fetch as $j => $data) {
00834                             if (
00835                                 (isset($data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey]) &&
00836                                 (!in_array($data[$with][$joinKeys[1]], $uniqueIds))
00837                             ) {
00838                                 $uniqueIds[] = $data[$with][$joinKeys[1]];
00839 
00840                                 if ($habtmFieldsCount <= 2) {
00841                                     unset($data[$with]);
00842                                 }
00843                                 $merge[] = $data;
00844                             }
00845                         }
00846                         if (empty($merge) && !isset($row[$association])) {
00847                             $row[$association] = $merge;
00848                         } else {
00849                             $this->__mergeAssociation($resultSet[$i], $merge, $association, $type);
00850                         }
00851                     } else {
00852                         $this->__mergeAssociation($resultSet[$i], $fetch, $association, $type, $selfJoin);
00853                     }
00854                     $resultSet[$i][$association] = $linkModel->afterfind($resultSet[$i][$association]);
00855 
00856                 } else {
00857                     $tempArray[0][$association] = false;
00858                     $this->__mergeAssociation($resultSet[$i], $tempArray, $association, $type, $selfJoin);
00859                 }
00860             }
00861         }
00862     }
00863 /**
00864  * A more efficient way to fetch associations.  Woohoo!
00865  *
00866  * @param model $model      Primary model object
00867  * @param string $query     Association query
00868  * @param array $ids        Array of IDs of associated records
00869  * @return array Association results
00870  */
00871     function fetchAssociated($model, $query, $ids) {
00872         $query = str_replace('{$__cakeID__$}', join(', ', $ids), $query);
00873         if (count($ids) > 1) {
00874             $query = str_replace('= (', 'IN (', $query);
00875             $query = str_replace('=  (', 'IN (', $query);
00876         }
00877         return $this->fetchAll($query, $model->cacheQueries, $model->alias);
00878     }
00879 
00880     function __mergeHasMany(&$resultSet, $merge, $association, &$model, &$linkModel) {
00881         foreach ($resultSet as $i => $value) {
00882             $count = 0;
00883             $merged[$association] = array();
00884             foreach ($merge as $j => $data) {
00885                 if (isset($value[$model->alias]) && $value[$model->alias][$model->primaryKey] === $data[$association][$model->hasMany[$association]['foreignKey']]) {
00886                     if (count($data) > 1) {
00887                         $data = array_merge($data[$association], $data);
00888                         unset($data[$association]);
00889                         foreach ($data as $key => $name) {
00890                             if (is_numeric($key)) {
00891                                 $data[$association][] = $name;
00892                                 unset($data[$key]);
00893                             }
00894                         }
00895                         $merged[$association][] = $data;
00896                     } else {
00897                         $merged[$association][] = $data[$association];
00898                     }
00899                 }
00900                 $count++;
00901             }
00902             if (isset($value[$model->alias])) {
00903                 $resultSet[$i] = Set::pushDiff($resultSet[$i], $merged);
00904                 unset($merged);
00905             }
00906         }
00907     }
00908 /**
00909  * Enter description here...
00910  *
00911  * @param unknown_type $data
00912  * @param unknown_type $merge
00913  * @param unknown_type $association
00914  * @param unknown_type $type
00915  * @param boolean $selfJoin
00916  */
00917     function __mergeAssociation(&$data, $merge, $association, $type, $selfJoin = false) {
00918         if (isset($merge[0]) && !isset($merge[0][$association])) {
00919             $association = Inflector::pluralize($association);
00920         }
00921 
00922         if ($type == 'belongsTo' || $type == 'hasOne') {
00923             if (isset($merge[$association])) {
00924                 $data[$association] = $merge[$association][0];
00925             } else {
00926                 if (count($merge[0][$association]) > 1) {
00927                     foreach ($merge[0] as $assoc => $data2) {
00928                         if ($assoc != $association) {
00929                             $merge[0][$association][$assoc] = $data2;
00930                         }
00931                     }
00932                 }
00933                 if (!isset($data[$association])) {
00934                     if ($merge[0][$association] != null) {
00935                         $data[$association] = $merge[0][$association];
00936                     } else {
00937                         $data[$association] = array();
00938                     }
00939                 } else {
00940                     if (is_array($merge[0][$association])) {
00941                         foreach ($data[$association] as $k => $v) {
00942                             if (!is_array($v)) {
00943                                 $dataAssocTmp[$k] = $v;
00944                             }
00945                         }
00946 
00947                         foreach ($merge[0][$association] as $k => $v) {
00948                             if (!is_array($v)) {
00949                                 $mergeAssocTmp[$k] = $v;
00950                             }
00951                         }
00952                         $dataKeys = array_keys($data);
00953                         $mergeKeys = array_keys($merge[0]);
00954 
00955                         if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) {
00956                             $data[$association][$association] = $merge[0][$association];
00957                         } else {
00958                             $diff = Set::diff($dataAssocTmp, $mergeAssocTmp);
00959                             $data[$association] = array_merge($merge[0][$association], $diff);
00960                         }
00961                     } elseif ($selfJoin && array_key_exists($association, $merge[0])) {
00962                         $data[$association] = array_merge($data[$association], array($association => array()));
00963                     }
00964                 }
00965             }
00966         } else {
00967             if ($merge[0][$association] === false) {
00968                 if (!isset($data[$association])) {
00969                     $data[$association] = array();
00970                 }
00971             } else {
00972                 foreach ($merge as $i => $row) {
00973                     if (count($row) == 1) {
00974                         if (empty($data[$association]) || (isset($data[$association]) && !in_array($row[$association], $data[$association]))) {
00975                             $data[$association][] = $row[$association];
00976                         }
00977                     } else {
00978                         $tmp = array_merge($row[$association], $row);
00979                         unset($tmp[$association]);
00980                         $data[$association][] = $tmp;
00981                     }
00982                 }
00983             }
00984         }
00985     }
00986 /**
00987  * Generates an array representing a query or part of a query from a single model or two associated models
00988  *
00989  * @param Model $model
00990  * @param Model $linkModel
00991  * @param string $type
00992  * @param string $association
00993  * @param array $assocData
00994  * @param array $queryData
00995  * @param boolean $external
00996  * @param array $resultSet
00997  * @return mixed
00998  */
00999     function generateAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) {
01000         $queryData = $this->__scrubQueryData($queryData);
01001         $assocData = $this->__scrubQueryData($assocData);
01002 
01003         if (empty($queryData['fields'])) {
01004             $queryData['fields'] = $this->fields($model, $model->alias);
01005         } elseif (!empty($model->hasMany) && $model->recursive > -1) {
01006             $assocFields = $this->fields($model, $model->alias, array("{$model->alias}.{$model->primaryKey}"));
01007             $passedFields = $this->fields($model, $model->alias, $queryData['fields']);
01008 
01009             if (count($passedFields) === 1) {
01010                 $match = strpos($passedFields[0], $assocFields[0]);
01011                 $match1 = strpos($passedFields[0], 'COUNT(');
01012                 if ($match === false && $match1 === false) {
01013                     $queryData['fields'] = array_merge($passedFields, $assocFields);
01014                 } else {
01015                     $queryData['fields'] = $passedFields;
01016                 }
01017             } else {
01018                 $queryData['fields'] = array_merge($passedFields, $assocFields);
01019             }
01020             unset($assocFields, $passedFields);
01021         }
01022 
01023         /* Saving for later refactor
01024             $tmpModel =& $model;
01025 
01026             if ($linkModel != null) {
01027                 $tmpModel =& $linkModel;
01028             }
01029             foreach ($tmpModel->schema() as $field => $info) {
01030                 if ($info['type'] == 'boolean') {
01031                     $this->_booleans[$tmpModel->alias][] = $field;
01032                 }
01033             }
01034             unset($tmpModel);
01035         */
01036 
01037         if ($linkModel == null) {
01038             return $this->buildStatement(
01039                 array(
01040                     'fields' => array_unique($queryData['fields']),
01041                     'table' => $this->fullTableName($model),
01042                     'alias' => $model->alias,
01043                     'limit' => $queryData['limit'],
01044                     'offset' => $queryData['offset'],
01045                     'joins' => $queryData['joins'],
01046                     'conditions' => $queryData['conditions'],
01047                     'order' => $queryData['order'],
01048                     'group' => $queryData['group']
01049                 ),
01050                 $model
01051             );
01052         }
01053         if ($external && !empty($assocData['finderQuery'])) {
01054             return $assocData['finderQuery'];
01055         }
01056 
01057         $alias = $association;
01058         $self = ($model->name == $linkModel->name);
01059         $fields = array();
01060 
01061         if ((!$external && in_array($type, array('hasOne', 'belongsTo')) && $this->__bypass === false) || $external) {
01062             $fields = $this->fields($linkModel, $alias, $assocData['fields']);
01063         }
01064         if (empty($assocData['offset']) && !empty($assocData['page'])) {
01065             $assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
01066         }
01067         $assocData['limit'] = $this->limit($assocData['limit'], $assocData['offset']);
01068 
01069         switch($type) {
01070             case 'hasOne':
01071             case 'belongsTo':
01072                 $conditions = $this->__mergeConditions(
01073                     $assocData['conditions'],
01074                     $this->getConstraint($type, $model, $linkModel, $alias, array_merge($assocData, compact('external', 'self')))
01075                 );
01076                 if ($external) {
01077                     $query = array_merge($assocData, array(
01078                         'conditions' => $conditions,
01079                         'table' => $this->fullTableName($linkModel),
01080                         'fields' => $fields,
01081                         'alias' => $alias,
01082                         'group' => null
01083                     ));
01084                     $query = array_merge(array('order' => $assocData['order'], 'limit' => $assocData['limit']), $query);
01085                 } else {
01086                     $join = array(
01087                         'table' => $this->fullTableName($linkModel),
01088                         'alias' => $alias,
01089                         'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
01090                         'conditions' => trim($this->conditions($conditions, true, false, $model))
01091                     );
01092                     $queryData['fields'] = array_merge($queryData['fields'], $fields);
01093 
01094                     if (!empty($assocData['order'])) {
01095                         $queryData['order'][] = $assocData['order'];
01096                     }
01097                     if (!in_array($join, $queryData['joins'])) {
01098                         $queryData['joins'][] = $join;
01099                     }
01100                     return true;
01101                 }
01102             break;
01103             case 'hasMany':
01104                 $assocData['fields'] = $this->fields($linkModel, $alias, $assocData['fields']);
01105                 if (!empty($assocData['foreignKey'])) {
01106                     $assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $alias, array("{$alias}.{$assocData['foreignKey']}")));
01107                 }
01108                 $query = array(
01109                     'conditions' => $this->__mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $alias, $assocData), $assocData['conditions']),
01110                     'fields' => array_unique($assocData['fields']),
01111                     'table' => $this->fullTableName($linkModel),
01112                     'alias' => $alias,
01113                     'order' => $assocData['order'],
01114                     'limit' => $assocData['limit'],
01115                     'group' => null
01116                 );
01117             break;
01118             case 'hasAndBelongsToMany':
01119                 $joinFields = array();
01120                 $joinAssoc = null;
01121 
01122                 if (isset($assocData['with']) && !empty($assocData['with'])) {
01123                     $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
01124                     list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys);
01125 
01126                     $joinTbl = $this->fullTableName($model->{$with});
01127                     $joinAlias = $joinTbl;
01128 
01129                     if (is_array($joinFields) && !empty($joinFields)) {
01130                         $joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields);
01131                         $joinAssoc = $joinAlias = $model->{$with}->alias;
01132                     } else {
01133                         $joinFields = array();
01134                     }
01135                 } else {
01136                     $joinTbl = $this->fullTableName($assocData['joinTable']);
01137                     $joinAlias = $joinTbl;
01138                 }
01139                 $query = array(
01140                     'conditions' => $assocData['conditions'],
01141                     'limit' => $assocData['limit'],
01142                     'table' => $this->fullTableName($linkModel),
01143                     'alias' => $alias,
01144                     'fields' => array_merge($this->fields($linkModel, $alias, $assocData['fields']), $joinFields),
01145                     'order' => $assocData['order'],
01146                     'group' => null,
01147                     'joins' => array(array(
01148                         'table' => $joinTbl,
01149                         'alias' => $joinAssoc,
01150                         'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $alias)
01151                     ))
01152                 );
01153             break;
01154         }
01155         if (isset($query)) {
01156             return $this->buildStatement($query, $model);
01157         }
01158         return null;
01159     }
01160 /**
01161  * Returns a conditions array for the constraint between two models
01162  *
01163  * @param string $type Association type
01164  * @param object $model Model object
01165  * @param array $association Association array
01166  * @return array Conditions array defining the constraint between $model and $association
01167  */
01168     function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) {
01169         $assoc = array_merge(array('external' => false, 'self' => false), $assoc);
01170 
01171         if (array_key_exists('foreignKey', $assoc) && empty($assoc['foreignKey'])) {
01172             return array();
01173         }
01174 
01175         switch (true) {
01176             case ($assoc['external'] && $type == 'hasOne'):
01177                 return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}');
01178             break;
01179             case ($assoc['external'] && $type == 'belongsTo'):
01180                 return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}');
01181             break;
01182             case (!$assoc['external'] && $type == 'hasOne'):
01183                 return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}"));
01184             break;
01185             case (!$assoc['external'] && $type == 'belongsTo'):
01186                 return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}"));
01187             break;
01188             case ($type == 'hasMany'):
01189                 return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}'));
01190             break;
01191             case ($type == 'hasAndBelongsToMany'):
01192                 return array(
01193                     array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'),
01194                     array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}"))
01195                 );
01196             break;
01197         }
01198         return array();
01199     }
01200 /**
01201  * Builds and generates a JOIN statement from an array.  Handles final clean-up before conversion.
01202  *
01203  * @param array $join An array defining a JOIN statement in a query
01204  * @return string An SQL JOIN statement to be used in a query
01205  * @see DboSource::renderJoinStatement()
01206  * @see DboSource::buildStatement()
01207  */
01208     function buildJoinStatement($join) {
01209         $data = array_merge(array(
01210             'type' => null,
01211             'alias' => null,
01212             'table' => 'join_table',
01213             'conditions' => array()
01214         ), $join);
01215 
01216         if (!empty($data['alias'])) {
01217             $data['alias'] = $this->alias . $this->name($data['alias']);
01218         }
01219         if (!empty($data['conditions'])) {
01220             $data['conditions'] = trim($this->conditions($data['conditions'], true, false));
01221         }
01222         return $this->renderJoinStatement($data);
01223     }
01224 /**
01225  * Builds and generates an SQL statement from an array.  Handles final clean-up before conversion.
01226  *
01227  * @param array $query An array defining an SQL query
01228  * @param object $model The model object which initiated the query
01229  * @return string An executable SQL statement
01230  * @see DboSource::renderStatement()
01231  */
01232     function buildStatement($query, $model) {
01233         $query = array_merge(array('offset' => null, 'joins' => array()), $query);
01234         if (!empty($query['joins'])) {
01235             for ($i = 0; $i < count($query['joins']); $i++) {
01236                 if (is_array($query['joins'][$i])) {
01237                     $query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
01238                 }
01239             }
01240         }
01241         return $this->renderStatement('select', array(
01242             'conditions' => $this->conditions($query['conditions'], true, true, $model),
01243             'fields' => join(', ', $query['fields']),
01244             'table' => $query['table'],
01245             'alias' => $this->alias . $this->name($query['alias']),
01246             'order' => $this->order($query['order']),
01247             'limit' => $this->limit($query['limit'], $query['offset']),
01248             'joins' => join(' ', $query['joins']),
01249             'group' => $this->group($query['group'])
01250         ));
01251     }
01252 /**
01253  * Renders a final SQL JOIN statement
01254  *
01255  * @param array $data
01256  * @return string
01257  */
01258     function renderJoinStatement($data) {
01259         extract($data);
01260         return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})");
01261     }
01262 /**
01263  * Renders a final SQL statement by putting together the component parts in the correct order
01264  *
01265  * @param string $type
01266  * @param array $data
01267  * @return string
01268  */
01269     function renderStatement($type, $data) {
01270         extract($data);
01271         $aliases = null;
01272 
01273         switch (strtolower($type)) {
01274             case 'select':
01275                 return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}";
01276             break;
01277             case 'update':
01278                 if (!empty($alias)) {
01279                     $aliases = "{$this->alias}{$alias} {$joins} ";
01280                 }
01281                 return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}";
01282             break;
01283             case 'delete':
01284                 if (!empty($alias)) {
01285                     $aliases = "{$this->alias}{$alias} {$joins} ";
01286                 }
01287                 return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}";
01288             break;
01289             case 'schema':
01290                 foreach (array('columns', 'indexes') as $var) {
01291                     if (is_array(${$var})) {
01292                         ${$var} = "\t" . join(",\n\t", array_filter(${$var}));
01293                     }
01294                 }
01295                 if (trim($indexes) != '') {
01296                     $columns .= ',';
01297                 }
01298                 return "CREATE TABLE {$table} (\n{$columns}{$indexes});";
01299             break;
01300             case 'alter':
01301             break;
01302         }
01303     }
01304 /**
01305  * Merges a mixed set of string/array conditions
01306  *
01307  * @return array
01308  */
01309     function __mergeConditions($query, $assoc) {
01310         if (!empty($assoc)) {
01311             if (is_array($query)) {
01312                 return array_merge((array)$assoc, $query);
01313             } else {
01314                 if (!empty($query)) {
01315                     $query = array($query);
01316                     if (is_array($assoc)) {
01317                         $query = array_merge($query, $assoc);
01318                     } else {
01319                         $query[] = $assoc;
01320                     }
01321                     return $query;
01322                 } else {
01323                     return $assoc;
01324                 }
01325             }
01326         }
01327         return $query;
01328     }
01329 /**
01330  * Generates and executes an SQL UPDATE statement for given model, fields, and values.
01331  * For databases that do not support aliases in UPDATE queries.
01332  *
01333  * @param Model $model
01334  * @param array $fields
01335  * @param array $values
01336  * @param mixed $conditions