source: trunk/server/www/cake/libs/model/datasources/dbo_source.php @ 358

Last change on this file since 358 was 358, checked in by sander, 9 years ago

Kill the SQL query cache. It does not work with the background daemon

File size: 71.2 KB
Line 
1<?php
2/* SVN FILE: $Id: dbo_source.php 7945 2008-12-19 02:16:01Z gwoo $ */
3/**
4 * Short description for file.
5 *
6 * Long description for file
7 *
8 * PHP versions 4 and 5
9 *
10 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
11 * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
12 *
13 * Licensed under The MIT License
14 * Redistributions of files must retain the above copyright notice.
15 *
16 * @filesource
17 * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
18 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
19 * @package       cake
20 * @subpackage    cake.cake.libs.model.datasources
21 * @since         CakePHP(tm) v 0.10.0.1076
22 * @version       $Revision: 7945 $
23 * @modifiedby    $LastChangedBy: gwoo $
24 * @lastmodified  $Date: 2008-12-18 20:16:01 -0600 (Thu, 18 Dec 2008) $
25 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
26 */
27App::import('Core', array('Set', 'String'));
28
29/**
30 * DboSource
31 *
32 * Creates DBO-descendant objects from a given db connection configuration
33 *
34 * @package       cake
35 * @subpackage    cake.cake.libs.model.datasources
36 */
37class DboSource extends DataSource {
38/**
39 * Description string for this Database Data Source.
40 *
41 * @var unknown_type
42 */
43        var $description = "Database Data Source";
44/**
45 * index definition, standard cake, primary, index, unique
46 *
47 * @var array
48 */
49        var $index = array('PRI' => 'primary', 'MUL' => 'index', 'UNI' => 'unique');
50/**
51 * Database keyword used to assign aliases to identifiers.
52 *
53 * @var string
54 */
55        var $alias = 'AS ';
56/**
57 * Caches fields quoted in DboSource::name()
58 *
59 * @var array
60 */
61        var $fieldCache = array();
62/**
63 * Bypass automatic adding of joined fields/associations.
64 *
65 * @var boolean
66 */
67        var $__bypass = false;
68/**
69 * The set of valid SQL operations usable in a WHERE statement
70 *
71 * @var array
72 */
73        var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
74/**
75 * Index of basic SQL commands
76 *
77 * @var array
78 * @access protected
79 */
80        var $_commands = array(
81                'begin'    => 'BEGIN',
82                'commit'   => 'COMMIT',
83                'rollback' => 'ROLLBACK'
84        );
85/**
86 * Constructor
87 */
88        function __construct($config = null, $autoConnect = true) {
89                if (!isset($config['prefix'])) {
90                        $config['prefix'] = '';
91                }
92                parent::__construct($config);
93                $this->fullDebug = Configure::read() > 1;
94
95                if ($autoConnect) {
96                        return $this->connect();
97                } else {
98                        return true;
99                }
100        }
101/**
102 * Reconnects to database server with optional new settings
103 *
104 * @param array $config An array defining the new configuration settings
105 * @return boolean True on success, false on failure
106 */
107        function reconnect($config = null) {
108                $this->disconnect();
109                $this->setConfig($config);
110                $this->_sources = null;
111
112                return $this->connect();
113        }
114/**
115 * Prepares a value, or an array of values for database queries by quoting and escaping them.
116 *
117 * @param mixed $data A value or an array of values to prepare.
118 * @param string $column The column into which this data will be inserted
119 * @param boolean $read Value to be used in READ or WRITE context
120 * @return mixed Prepared value or array of values.
121 */
122        function value($data, $column = null, $read = true) {
123                if (is_array($data) && !empty($data)) {
124                        return array_map(
125                                array(&$this, 'value'),
126                                $data, array_fill(0, count($data), $column), array_fill(0, count($data), $read)
127                        );
128                } elseif (is_object($data) && isset($data->type)) {
129                        if ($data->type == 'identifier') {
130                                return $this->name($data->value);
131                        } elseif ($data->type == 'expression') {
132                                return $data->value;
133                        }
134                } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
135                        return $data;
136                } else {
137                        return null;
138                }
139        }
140/**
141 * Returns an object to represent a database identifier in a query
142 *
143 * @param string $identifier
144 * @return object An object representing a database identifier to be used in a query
145 */
146        function identifier($identifier) {
147                $obj = new stdClass();
148                $obj->type = 'identifier';
149                $obj->value = $identifier;
150                return $obj;
151        }
152/**
153 * Returns an object to represent a database expression in a query
154 *
155 * @param string $expression
156 * @return object An object representing a database expression to be used in a query
157 */
158        function expression($expression) {
159                $obj = new stdClass();
160                $obj->type = 'expression';
161                $obj->value = $expression;
162                return $obj;
163        }
164/**
165 * Executes given SQL statement.
166 *
167 * @param string $sql SQL statement
168 * @return unknown
169 */
170        function rawQuery($sql) {
171                $this->took = $this->error = $this->numRows = false;
172                return $this->execute($sql);
173        }
174/**
175 * Queries the database with given SQL statement, and obtains some metadata about the result
176 * (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
177 * If DEBUG is set, the log is shown all the time, else it is only shown on errors.
178 *
179 * @param string $sql
180 * @param array $options
181 * @return mixed Resource or object representing the result set, or false on failure
182 */
183        function execute($sql, $options = array()) {
184                $defaults = array('stats' => true, 'log' => $this->fullDebug);
185                $options = array_merge($defaults, $options);
186
187                if ($options['stats']) {
188                        $t = getMicrotime();
189                        $this->_result = $this->_execute($sql);
190                        $this->took = round((getMicrotime() - $t) * 1000, 0);
191                        $this->affected = $this->lastAffected();
192                        $this->error = $this->lastError();
193                        $this->numRows = $this->lastNumRows();
194                }
195
196                if ($options['log']) {
197                        $this->logQuery($sql);
198                }
199
200                if ($this->error) {
201                        $this->showQuery($sql);
202                        return false;
203                } else {
204                        return $this->_result;
205                }
206        }
207/**
208 * DataSource Query abstraction
209 *
210 * @return resource Result resource identifier
211 */
212        function query() {
213                $args     = func_get_args();
214                $fields   = null;
215                $order    = null;
216                $limit    = null;
217                $page     = null;
218                $recursive = null;
219
220                if (count($args) == 1) {
221                        return $this->fetchAll($args[0]);
222
223                } elseif (count($args) > 1 && (strpos(strtolower($args[0]), 'findby') === 0 || strpos(strtolower($args[0]), 'findallby') === 0)) {
224                        $params = $args[1];
225
226                        if (strpos(strtolower($args[0]), 'findby') === 0) {
227                                $all  = false;
228                                $field = Inflector::underscore(preg_replace('/findBy/i', '', $args[0]));
229                        } else {
230                                $all  = true;
231                                $field = Inflector::underscore(preg_replace('/findAllBy/i', '', $args[0]));
232                        }
233
234                        $or = (strpos($field, '_or_') !== false);
235                        if ($or) {
236                                $field = explode('_or_', $field);
237                        } else {
238                                $field = explode('_and_', $field);
239                        }
240                        $off = count($field) - 1;
241
242                        if (isset($params[1 + $off])) {
243                                $fields = $params[1 + $off];
244                        }
245
246                        if (isset($params[2 + $off])) {
247                                $order = $params[2 + $off];
248                        }
249
250                        if (!array_key_exists(0, $params)) {
251                                return false;
252                        }
253
254                        $c = 0;
255                        $conditions = array();
256
257                        foreach ($field as $f) {
258                                $conditions[$args[2]->alias . '.' . $f] = $params[$c];
259                                $c++;
260                        }
261
262                        if ($or) {
263                                $conditions = array('OR' => $conditions);
264                        }
265
266                        if ($all) {
267                                if (isset($params[3 + $off])) {
268                                        $limit = $params[3 + $off];
269                                }
270
271                                if (isset($params[4 + $off])) {
272                                        $page = $params[4 + $off];
273                                }
274
275                                if (isset($params[5 + $off])) {
276                                        $recursive = $params[5 + $off];
277                                }
278                                return $args[2]->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
279                        } else {
280                                if (isset($params[3 + $off])) {
281                                        $recursive = $params[3 + $off];
282                                }
283                                return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive'));
284                        }
285                } else {
286                        if (isset($args[1]) && $args[1] === true) {
287                                return $this->fetchAll($args[0], true);
288                        } else if (isset($args[1]) && !is_array($args[1]) ) {
289                                return $this->fetchAll($args[0], false);
290                        } else if (isset($args[1]) && is_array($args[1])) {
291                                $offset = 0;
292                                if (isset($args[2])) {
293                                        $cache = $args[2];
294                                } else {
295                                        $cache = true;
296                                }
297                                $args[1] = array_map(array(&$this, 'value'), $args[1]);
298                                return $this->fetchAll(String::insert($args[0], $args[1]), $cache);
299                        }
300                }
301        }
302/**
303 * Returns a row from current resultset as an array
304 *
305 * @return array The fetched row as an array
306 */
307        function fetchRow($sql = null) {
308                if (!empty($sql) && is_string($sql) && strlen($sql) > 5) {
309                        if (!$this->execute($sql)) {
310                                return null;
311                        }
312                }
313
314                if ($this->hasResult()) {
315                        $this->resultSet($this->_result);
316                        $resultRow = $this->fetchResult();
317                        return $resultRow;
318                } else {
319                        return null;
320                }
321        }
322/**
323 * Returns an array of all result rows for a given SQL query.
324 * Returns false if no rows matched.
325 *
326 * @param string $sql SQL statement
327 * @param boolean $cache Enables returning/storing cached query results
328 * @return array Array of resultset rows, or false if no rows matched
329 */
330        function fetchAll($sql, $cache = true, $modelName = null) {
331                // Forcefully kill the SQL query cache
332                // NOTE: This is a hack
333                // --
334                // Sander Marechal
335                $cache = false;
336
337                if ($cache && isset($this->_queryCache[$sql])) {
338                        if (preg_match('/^\s*select/i', $sql)) {
339                                return $this->_queryCache[$sql];
340                        }
341                }
342
343                if ($this->execute($sql)) {
344                        $out = array();
345
346                        $first = $this->fetchRow();
347                        if ($first != null) {
348                                $out[] = $first;
349                        }
350                        while ($this->hasResult() && $item = $this->fetchResult()) {
351                                $out[] = $item;
352                        }
353
354                        if ($cache) {
355                                if (strpos(trim(strtolower($sql)), 'select') !== false) {
356                                        $this->_queryCache[$sql] = $out;
357                                }
358                        }
359                        return $out;
360
361                } else {
362                        return false;
363                }
364        }
365/**
366 * Returns a single field of the first of query results for a given SQL query, or false if empty.
367 *
368 * @param string $name Name of the field
369 * @param string $sql SQL query
370 * @return unknown
371 */
372        function field($name, $sql) {
373                $data = $this->fetchRow($sql);
374
375                if (!isset($data[$name]) || empty($data[$name])) {
376                        return false;
377                } else {
378                        return $data[$name];
379                }
380        }
381/**
382 * Returns a quoted name of $data for use in an SQL statement.
383 * Strips fields out of SQL functions before quoting.
384 *
385 * @param string $data
386 * @return string SQL field
387 */
388        function name($data) {
389                if ($data == '*') {
390                        return '*';
391                }
392                if (is_object($data) && isset($data->type)) {
393                        return $data->value;
394                }
395                $array = is_array($data);
396                $data = (array)$data;
397                $count = count($data);
398
399                for ($i = 0; $i < $count; $i++) {
400                        if ($data[$i] == '*') {
401                                continue;
402                        }
403                        if (strpos($data[$i], '(') !== false && preg_match_all('/([^(]*)\((.*)\)(.*)/', $data[$i], $fields)) {
404                                $fields = Set::extract($fields, '{n}.0');
405
406                                if (!empty($fields[1])) {
407                                        if (!empty($fields[2])) {
408                                                $data[$i] = $fields[1] . '(' . $this->name($fields[2]) . ')' . $fields[3];
409                                        } else {
410                                                $data[$i] = $fields[1] . '()' . $fields[3];
411                                        }
412                                }
413                        }
414                        $data[$i] = str_replace('.', $this->endQuote . '.' . $this->startQuote, $data[$i]);
415                        $data[$i] = $this->startQuote . $data[$i] . $this->endQuote;
416                        $data[$i] = str_replace($this->startQuote . $this->startQuote, $this->startQuote, $data[$i]);
417                        $data[$i] = str_replace($this->startQuote . '(', '(', $data[$i]);
418                        $data[$i] = str_replace(')' . $this->startQuote, ')', $data[$i]);
419
420                        if (strpos($data[$i], ' AS ')) {
421                                $data[$i] = str_replace(' AS ', $this->endQuote . ' AS ' . $this->startQuote, $data[$i]);
422                        }
423                        if (!empty($this->endQuote) && $this->endQuote == $this->startQuote) {
424                                if (substr_count($data[$i], $this->endQuote) % 2 == 1) {
425                                        $data[$i] = trim($data[$i], $this->endQuote);
426                                }
427                        }
428                        if (strpos($data[$i], '*')) {
429                                $data[$i] = str_replace($this->endQuote . '*' . $this->endQuote, '*', $data[$i]);
430                        }
431                        $data[$i] = str_replace($this->endQuote . $this->endQuote, $this->endQuote, $data[$i]);
432                }
433                return (!$array) ? $data[0] : $data;
434        }
435/**
436 * Checks if it's connected to the database
437 *
438 * @return boolean True if the database is connected, else false
439 */
440        function isConnected() {
441                return $this->connected;
442        }
443/**
444 * Checks if the result is valid
445 *
446 * @return boolean True if the result is valid else false
447 */
448        function hasResult() {
449                return is_resource($this->_result);
450        }
451/**
452 * Outputs the contents of the queries log.
453 *
454 * @param boolean $sorted
455 */
456        function showLog($sorted = false) {
457                if ($sorted) {
458                        $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
459                } else {
460                        $log = $this->_queriesLog;
461                }
462
463                if ($this->_queriesCnt > 1) {
464                        $text = 'queries';
465                } else {
466                        $text = 'query';
467                }
468
469                if (PHP_SAPI != 'cli') {
470                        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->configKeyName}) {$this->_queriesCnt} {$text} took {$this->_queriesTime} ms</caption>\n");
471                        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");
472
473                        foreach ($log as $k => $i) {
474                                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");
475                        }
476                        print ("</tbody></table>\n");
477                } else {
478                        foreach ($log as $k => $i) {
479                                print (($k + 1) . ". {$i['query']} {$i['error']}\n");
480                        }
481                }
482        }
483/**
484 * Log given SQL query.
485 *
486 * @param string $sql SQL statement
487 * @todo: Add hook to log errors instead of returning false
488 */
489        function logQuery($sql) {
490                $this->_queriesCnt++;
491                $this->_queriesTime += $this->took;
492                $this->_queriesLog[] = array(
493                        'query' => $sql,
494                        'error'         => $this->error,
495                        'affected'      => $this->affected,
496                        'numRows'       => $this->numRows,
497                        'took'          => $this->took
498                );
499                if (count($this->_queriesLog) > $this->_queriesLogMax) {
500                        array_pop($this->_queriesLog);
501                }
502                if ($this->error) {
503                        return false;
504                }
505        }
506/**
507 * Output information about an SQL query. The SQL statement, number of rows in resultset,
508 * and execution time in microseconds. If the query fails, an error is output instead.
509 *
510 * @param string $sql Query to show information on.
511 */
512        function showQuery($sql) {
513                $error = $this->error;
514                if (strlen($sql) > 200 && !$this->fullDebug && Configure::read() > 1) {
515                        $sql = substr($sql, 0, 200) . '[...]';
516                }
517                if ($error && Configure::read() > 0) {
518                        $out = null;
519                        if ($error) {
520                                trigger_error("<span style = \"color:Red;text-align:left\"><b>SQL Error:</b> {$this->error}</span>", E_USER_WARNING);
521                        } else {
522                                $out = ("<small>[Aff:{$this->affected} Num:{$this->numRows} Took:{$this->took}ms]</small>");
523                        }
524                        pr(sprintf("<p style = \"text-align:left\"><b>Query:</b> %s %s</p>", $sql, $out));
525                }
526        }
527/**
528 * Gets full table name including prefix
529 *
530 * @param mixed $model
531 * @param boolean $quote
532 * @return string Full quoted table name
533 */
534        function fullTableName($model, $quote = true) {
535                if (is_object($model)) {
536                        $table = $model->tablePrefix . $model->table;
537                } elseif (isset($this->config['prefix'])) {
538                        $table = $this->config['prefix'] . strval($model);
539                } else {
540                        $table = strval($model);
541                }
542                if ($quote) {
543                        return $this->name($table);
544                }
545                return $table;
546        }
547/**
548 * The "C" in CRUD
549 *
550 * @param Model $model
551 * @param array $fields
552 * @param array $values
553 * @return boolean Success
554 */
555        function create(&$model, $fields = null, $values = null) {
556                $id = null;
557
558                if ($fields == null) {
559                        unset($fields, $values);
560                        $fields = array_keys($model->data);
561                        $values = array_values($model->data);
562                }
563                $count = count($fields);
564
565                for ($i = 0; $i < $count; $i++) {
566                        $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]), false);
567                }
568                for ($i = 0; $i < $count; $i++) {
569                        $fieldInsert[] = $this->name($fields[$i]);
570                        if ($fields[$i] == $model->primaryKey) {
571                                $id = $values[$i];
572                        }
573                }
574                $query = array(
575                        'table' => $this->fullTableName($model),
576                        'fields' => join(', ', $fieldInsert),
577                        'values' => join(', ', $valueInsert)
578                );
579
580                if ($this->execute($this->renderStatement('create', $query))) {
581                        if (empty($id)) {
582                                $id = $this->lastInsertId($this->fullTableName($model, false), $model->primaryKey);
583                        }
584                        $model->setInsertID($id);
585                        $model->id = $id;
586                        return true;
587                } else {
588                        $model->onError();
589                        return false;
590                }
591        }
592/**
593 * The "R" in CRUD
594 *
595 * @param Model $model
596 * @param array $queryData
597 * @param integer $recursive Number of levels of association
598 * @return unknown
599 */
600        function read(&$model, $queryData = array(), $recursive = null) {
601                $queryData = $this->__scrubQueryData($queryData);
602
603                $null = null;
604                $array = array();
605                $linkedModels = array();
606                $this->__bypass = false;
607                $this->__booleans = array();
608
609                if ($recursive === null && isset($queryData['recursive'])) {
610                        $recursive = $queryData['recursive'];
611                }
612
613                if (!is_null($recursive)) {
614                        $_recursive = $model->recursive;
615                        $model->recursive = $recursive;
616                }
617
618                if (!empty($queryData['fields'])) {
619                        $this->__bypass = true;
620                        $queryData['fields'] = $this->fields($model, null, $queryData['fields']);
621                } else {
622                        $queryData['fields'] = $this->fields($model);
623                }
624
625                foreach ($model->__associations as $type) {
626                        foreach ($model->{$type} as $assoc => $assocData) {
627                                if ($model->recursive > -1) {
628                                        $linkModel =& $model->{$assoc};
629                                        $external = isset($assocData['external']);
630
631                                        if ($model->useDbConfig == $linkModel->useDbConfig) {
632                                                if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) {
633                                                        $linkedModels[] = $type . '/' . $assoc;
634                                                }
635                                        }
636                                }
637                        }
638                }
639
640                $query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null);
641
642                $resultSet = $this->fetchAll($query, $model->cacheQueries, $model->alias);
643
644                if ($resultSet === false) {
645                        $model->onError();
646                        return false;
647                }
648
649                $filtered = $this->__filterResults($resultSet, $model);
650
651                if ($model->recursive > 0) {
652                        foreach ($model->__associations as $type) {
653                                foreach ($model->{$type} as $assoc => $assocData) {
654                                        $linkModel =& $model->{$assoc};
655
656                                        if (!in_array($type . '/' . $assoc, $linkedModels)) {
657                                                if ($model->useDbConfig == $linkModel->useDbConfig) {
658                                                        $db =& $this;
659                                                } else {
660                                                        $db =& ConnectionManager::getDataSource($linkModel->useDbConfig);
661                                                }
662                                        } elseif ($model->recursive > 1 && ($type == 'belongsTo' || $type == 'hasOne')) {
663                                                $db =& $this;
664                                        }
665
666                                        if (isset($db)) {
667                                                $stack = array($assoc);
668                                                $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
669                                                unset($db);
670                                        }
671                                }
672                        }
673                        $this->__filterResults($resultSet, $model, $filtered);
674                }
675
676                if (!is_null($recursive)) {
677                        $model->recursive = $_recursive;
678                }
679                return $resultSet;
680        }
681/**
682 * Private method.      Passes association results thru afterFind filters of corresponding model
683 *
684 * @param array $results Reference of resultset to be filtered
685 * @param object $model Instance of model to operate against
686 * @param array $filtered List of classes already filtered, to be skipped
687 * @return return
688 */
689        function __filterResults(&$results, &$model, $filtered = array()) {
690                $filtering = array();
691                $count = count($results);
692
693                for ($i = 0; $i < $count; $i++) {
694                        if (is_array($results[$i])) {
695                                $classNames = array_keys($results[$i]);
696                                $count2 = count($classNames);
697
698                                for ($j = 0; $j < $count2; $j++) {
699                                        $className = $classNames[$j];
700                                        if ($model->alias != $className && !in_array($className, $filtered)) {
701                                                if (!in_array($className, $filtering)) {
702                                                        $filtering[] = $className;
703                                                }
704
705                                                if (isset($model->{$className}) && is_object($model->{$className})) {
706                                                        $data = $model->{$className}->afterFind(array(array($className => $results[$i][$className])), false);
707                                                }
708                                                if (isset($data[0][$className])) {
709                                                        $results[$i][$className] = $data[0][$className];
710                                                }
711                                        }
712                                }
713                        }
714                }
715                return $filtering;
716        }
717/**
718 * Enter description here...
719 *
720 * @param Model $model
721 * @param unknown_type $linkModel
722 * @param string $type Association type
723 * @param unknown_type $association
724 * @param unknown_type $assocData
725 * @param unknown_type $queryData
726 * @param unknown_type $external
727 * @param unknown_type $resultSet
728 * @param integer $recursive Number of levels of association
729 * @param array $stack
730 */
731        function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {
732                if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
733                        if (!isset($resultSet) || !is_array($resultSet)) {
734                                if (Configure::read() > 0) {
735                                        e('<div style = "font: Verdana bold 12px; color: #FF0000">' . sprintf(__('SQL Error in model %s:', true), $model->alias) . ' ');
736                                        if (isset($this->error) && $this->error != null) {
737                                                e($this->error);
738                                        }
739                                        e('</div>');
740                                }
741                                return null;
742                        }
743                        $count = count($resultSet);
744
745                        if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) {
746                                $ins = $fetch = array();
747                                for ($i = 0; $i < $count; $i++) {
748                                        if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
749                                                $ins[] = $in;
750                                        }
751                                }
752
753                                if (!empty($ins)) {
754                                        $fetch = $this->fetchAssociated($model, $query, $ins);
755                                }
756
757                                if (!empty($fetch) && is_array($fetch)) {
758                                        if ($recursive > 0) {
759                                                foreach ($linkModel->__associations as $type1) {
760                                                        foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
761                                                                $deepModel =& $linkModel->{$assoc1};
762                                                                $tmpStack = $stack;
763                                                                $tmpStack[] = $assoc1;
764
765                                                                if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
766                                                                        $db =& $this;
767                                                                } else {
768                                                                        $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
769                                                                }
770                                                                $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
771                                                        }
772                                                }
773                                        }
774                                }
775                                $this->__filterResults($fetch, $model);
776                                return $this->__mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive);
777                        } elseif ($type === 'hasAndBelongsToMany') {
778                                $ins = $fetch = array();
779                                for ($i = 0; $i < $count; $i++) {
780                                        if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
781                                                $ins[] = $in;
782                                        }
783                                }
784                                if (!empty($ins)) {
785                                        if (count($ins) > 1) {
786                                                $query = str_replace('{$__cakeID__$}', '(' .join(', ', $ins) .')', $query);
787                                                $query = str_replace('= (', 'IN (', $query);
788                                                $query = str_replace('=  (', 'IN (', $query);
789                                        } else {
790                                                $query = str_replace('{$__cakeID__$}',$ins[0], $query);
791                                        }
792
793                                        $query = str_replace('  WHERE 1 = 1', '', $query);
794                                }
795
796                                $foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
797                                $joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
798                                list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
799                                $habtmFieldsCount = count($habtmFields);
800                                $q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack);
801
802                                if ($q != false) {
803                                        $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
804                                } else {
805                                        $fetch = null;
806                                }
807                        }
808
809                        for ($i = 0; $i < $count; $i++) {
810                                $row =& $resultSet[$i];
811
812                                if ($type !== 'hasAndBelongsToMany') {
813                                        $q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack);
814                                        if ($q != false) {
815                                                $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
816                                        } else {
817                                                $fetch = null;
818                                        }
819                                }
820                                $selfJoin = false;
821
822                                if ($linkModel->name === $model->name) {
823                                        $selfJoin = true;
824                                }
825
826                                if (!empty($fetch) && is_array($fetch)) {
827                                        if ($recursive > 0) {
828                                                foreach ($linkModel->__associations as $type1) {
829                                                        foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
830                                                                $deepModel =& $linkModel->{$assoc1};
831
832                                                                if (($type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) {
833                                                                        $tmpStack = $stack;
834                                                                        $tmpStack[] = $assoc1;
835                                                                        if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
836                                                                                $db =& $this;
837                                                                        } else {
838                                                                                $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
839                                                                        }
840                                                                        $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
841                                                                }
842                                                        }
843                                                }
844                                        }
845                                        if ($type == 'hasAndBelongsToMany') {
846                                                $uniqueIds = $merge = array();
847
848                                                foreach ($fetch as $j => $data) {
849                                                        if (
850                                                                (isset($data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey]) &&
851                                                                (!in_array($data[$with][$joinKeys[1]], $uniqueIds))
852                                                        ) {
853                                                                $uniqueIds[] = $data[$with][$joinKeys[1]];
854
855                                                                if ($habtmFieldsCount <= 2) {
856                                                                        unset($data[$with]);
857                                                                }
858                                                                $merge[] = $data;
859                                                        }
860                                                }
861                                                if (empty($merge) && !isset($row[$association])) {
862                                                        $row[$association] = $merge;
863                                                } else {
864                                                        $this->__mergeAssociation($resultSet[$i], $merge, $association, $type);
865                                                }
866                                        } else {
867                                                $this->__mergeAssociation($resultSet[$i], $fetch, $association, $type, $selfJoin);
868                                        }
869                                        if (isset($resultSet[$i][$association])) {
870                                                $resultSet[$i][$association] = $linkModel->afterFind($resultSet[$i][$association]);
871                                        }
872                                } else {
873                                        $tempArray[0][$association] = false;
874                                        $this->__mergeAssociation($resultSet[$i], $tempArray, $association, $type, $selfJoin);
875                                }
876                        }
877                }
878        }
879/**
880 * A more efficient way to fetch associations.  Woohoo!
881 *
882 * @param model $model          Primary model object
883 * @param string $query         Association query
884 * @param array $ids            Array of IDs of associated records
885 * @return array Association results
886 */
887        function fetchAssociated($model, $query, $ids) {
888                $query = str_replace('{$__cakeID__$}', join(', ', $ids), $query);
889                if (count($ids) > 1) {
890                        $query = str_replace('= (', 'IN (', $query);
891                        $query = str_replace('=  (', 'IN (', $query);
892                }
893                return $this->fetchAll($query, $model->cacheQueries, $model->alias);
894        }
895/**
896 * mergeHasMany - Merge the results of hasMany relations.
897 *
898 *
899 * @param array $resultSet Data to merge into
900 * @param array $merge Data to merge
901 * @param string $association Name of Model being Merged
902 * @param object $model Model being merged onto
903 * @param object $linkModel Model being merged
904 * @return void
905 **/
906        function __mergeHasMany(&$resultSet, $merge, $association, &$model, &$linkModel) {
907                foreach ($resultSet as $i => $value) {
908                        $count = 0;
909                        $merged[$association] = array();
910                        foreach ($merge as $j => $data) {
911                                if (isset($value[$model->alias]) && $value[$model->alias][$model->primaryKey] === $data[$association][$model->hasMany[$association]['foreignKey']]) {
912                                        if (count($data) > 1) {
913                                                $data = array_merge($data[$association], $data);
914                                                unset($data[$association]);
915                                                foreach ($data as $key => $name) {
916                                                        if (is_numeric($key)) {
917                                                                $data[$association][] = $name;
918                                                                unset($data[$key]);
919                                                        }
920                                                }
921                                                $merged[$association][] = $data;
922                                        } else {
923                                                $merged[$association][] = $data[$association];
924                                        }
925                                }
926                                $count++;
927                        }
928                        if (isset($value[$model->alias])) {
929                                $resultSet[$i] = Set::pushDiff($resultSet[$i], $merged);
930                                unset($merged);
931                        }
932                }
933        }
934/**
935 * Enter description here...
936 *
937 * @param unknown_type $data
938 * @param unknown_type $merge
939 * @param unknown_type $association
940 * @param unknown_type $type
941 * @param boolean $selfJoin
942 */
943        function __mergeAssociation(&$data, $merge, $association, $type, $selfJoin = false) {
944                if (isset($merge[0]) && !isset($merge[0][$association])) {
945                        $association = Inflector::pluralize($association);
946                }
947
948                if ($type == 'belongsTo' || $type == 'hasOne') {
949                        if (isset($merge[$association])) {
950                                $data[$association] = $merge[$association][0];
951                        } else {
952                                if (count($merge[0][$association]) > 1) {
953                                        foreach ($merge[0] as $assoc => $data2) {
954                                                if ($assoc != $association) {
955                                                        $merge[0][$association][$assoc] = $data2;
956                                                }
957                                        }
958                                }
959                                if (!isset($data[$association])) {
960                                        if ($merge[0][$association] != null) {
961                                                $data[$association] = $merge[0][$association];
962                                        } else {
963                                                $data[$association] = array();
964                                        }
965                                } else {
966                                        if (is_array($merge[0][$association])) {
967                                                foreach ($data[$association] as $k => $v) {
968                                                        if (!is_array($v)) {
969                                                                $dataAssocTmp[$k] = $v;
970                                                        }
971                                                }
972
973                                                foreach ($merge[0][$association] as $k => $v) {
974                                                        if (!is_array($v)) {
975                                                                $mergeAssocTmp[$k] = $v;
976                                                        }
977                                                }
978                                                $dataKeys = array_keys($data);
979                                                $mergeKeys = array_keys($merge[0]);
980
981                                                if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) {
982                                                        $data[$association][$association] = $merge[0][$association];
983                                                } else {
984                                                        $diff = Set::diff($dataAssocTmp, $mergeAssocTmp);
985                                                        $data[$association] = array_merge($merge[0][$association], $diff);
986                                                }
987                                        } elseif ($selfJoin && array_key_exists($association, $merge[0])) {
988                                                $data[$association] = array_merge($data[$association], array($association => array()));
989                                        }
990                                }
991                        }
992                } else {
993                        if (isset($merge[0][$association]) && $merge[0][$association] === false) {
994                                if (!isset($data[$association])) {
995                                        $data[$association] = array();
996                                }
997                        } else {
998                                foreach ($merge as $i => $row) {
999                                        if (count($row) == 1) {
1000                                                if (empty($data[$association]) || (isset($data[$association]) && !in_array($row[$association], $data[$association]))) {
1001                                                        $data[$association][] = $row[$association];
1002                                                }
1003                                        } else if (!empty($row)) {
1004                                                $tmp = array_merge($row[$association], $row);
1005                                                unset($tmp[$association]);
1006                                                $data[$association][] = $tmp;
1007                                        }
1008                                }
1009                        }
1010                }
1011        }
1012/**
1013 * Generates an array representing a query or part of a query from a single model or two associated models
1014 *
1015 * @param Model $model
1016 * @param Model $linkModel
1017 * @param string $type
1018 * @param string $association
1019 * @param array $assocData
1020 * @param array $queryData
1021 * @param boolean $external
1022 * @param array $resultSet
1023 * @return mixed
1024 */
1025        function generateAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) {
1026                $queryData = $this->__scrubQueryData($queryData);
1027                $assocData = $this->__scrubQueryData($assocData);
1028
1029                if (empty($queryData['fields'])) {
1030                        $queryData['fields'] = $this->fields($model, $model->alias);
1031                } elseif (!empty($model->hasMany) && $model->recursive > -1) {
1032                        $assocFields = $this->fields($model, $model->alias, array("{$model->alias}.{$model->primaryKey}"));
1033                        $passedFields = $this->fields($model, $model->alias, $queryData['fields']);
1034
1035                        if (count($passedFields) === 1) {
1036                                $match = strpos($passedFields[0], $assocFields[0]);
1037                                $match1 = strpos($passedFields[0], 'COUNT(');
1038                                if ($match === false && $match1 === false) {
1039                                        $queryData['fields'] = array_merge($passedFields, $assocFields);
1040                                } else {
1041                                        $queryData['fields'] = $passedFields;
1042                                }
1043                        } else {
1044                                $queryData['fields'] = array_merge($passedFields, $assocFields);
1045                        }
1046                        unset($assocFields, $passedFields);
1047                }
1048
1049                if ($linkModel == null) {
1050                        return $this->buildStatement(
1051                                array(
1052                                        'fields' => array_unique($queryData['fields']),
1053                                        'table' => $this->fullTableName($model),
1054                                        'alias' => $model->alias,
1055                                        'limit' => $queryData['limit'],
1056                                        'offset' => $queryData['offset'],
1057                                        'joins' => $queryData['joins'],
1058                                        'conditions' => $queryData['conditions'],
1059                                        'order' => $queryData['order'],
1060                                        'group' => $queryData['group']
1061                                ),
1062                                $model
1063                        );
1064                }
1065                if ($external && !empty($assocData['finderQuery'])) {
1066                        return $assocData['finderQuery'];
1067                }
1068
1069                $alias = $association;
1070                $self = ($model->name == $linkModel->name);
1071                $fields = array();
1072
1073                if ((!$external && in_array($type, array('hasOne', 'belongsTo')) && $this->__bypass === false) || $external) {
1074                        $fields = $this->fields($linkModel, $alias, $assocData['fields']);
1075                }
1076                if (empty($assocData['offset']) && !empty($assocData['page'])) {
1077                        $assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
1078                }
1079                $assocData['limit'] = $this->limit($assocData['limit'], $assocData['offset']);
1080
1081                switch ($type) {
1082                        case 'hasOne':
1083                        case 'belongsTo':
1084                                $conditions = $this->__mergeConditions(
1085                                        $assocData['conditions'],
1086                                        $this->getConstraint($type, $model, $linkModel, $alias, array_merge($assocData, compact('external', 'self')))
1087                                );
1088
1089                                if (!$self && $external) {
1090                                        foreach ($conditions as $key => $condition) {
1091                                                if (is_numeric($key) && strpos($condition, $model->alias . '.') !== false) {
1092                                                        unset($conditions[$key]);
1093                                                }
1094                                        }
1095                                }
1096
1097                                if ($external) {
1098                                        $query = array_merge($assocData, array(
1099                                                'conditions' => $conditions,
1100                                                'table' => $this->fullTableName($linkModel),
1101                                                'fields' => $fields,
1102                                                'alias' => $alias,
1103                                                'group' => null
1104                                        ));
1105                                        $query = array_merge(array('order' => $assocData['order'], 'limit' => $assocData['limit']), $query);
1106                                } else {
1107                                        $join = array(
1108                                                'table' => $this->fullTableName($linkModel),
1109                                                'alias' => $alias,
1110                                                'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1111                                                'conditions' => trim($this->conditions($conditions, true, false, $model))
1112                                        );
1113                                        $queryData['fields'] = array_merge($queryData['fields'], $fields);
1114
1115                                        if (!empty($assocData['order'])) {
1116                                                $queryData['order'][] = $assocData['order'];
1117                                        }
1118                                        if (!in_array($join, $queryData['joins'])) {
1119                                                $queryData['joins'][] = $join;
1120                                        }
1121                                        return true;
1122                                }
1123                        break;
1124                        case 'hasMany':
1125                                $assocData['fields'] = $this->fields($linkModel, $alias, $assocData['fields']);
1126                                if (!empty($assocData['foreignKey'])) {
1127                                        $assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $alias, array("{$alias}.{$assocData['foreignKey']}")));
1128                                }
1129                                $query = array(
1130                                        'conditions' => $this->__mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $alias, $assocData), $assocData['conditions']),
1131                                        'fields' => array_unique($assocData['fields']),
1132                                        'table' => $this->fullTableName($linkModel),
1133                                        'alias' => $alias,
1134                                        'order' => $assocData['order'],
1135                                        'limit' => $assocData['limit'],
1136                                        'group' => null
1137                                );
1138                        break;
1139                        case 'hasAndBelongsToMany':
1140                                $joinFields = array();
1141                                $joinAssoc = null;
1142
1143                                if (isset($assocData['with']) && !empty($assocData['with'])) {
1144                                        $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
1145                                        list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys);
1146
1147                                        $joinTbl = $this->fullTableName($model->{$with});
1148                                        $joinAlias = $joinTbl;
1149
1150                                        if (is_array($joinFields) && !empty($joinFields)) {
1151                                                $joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields);
1152                                                $joinAssoc = $joinAlias = $model->{$with}->alias;
1153                                        } else {
1154                                                $joinFields = array();
1155                                        }
1156                                } else {
1157                                        $joinTbl = $this->fullTableName($assocData['joinTable']);
1158                                        $joinAlias = $joinTbl;
1159                                }
1160                                $query = array(
1161                                        'conditions' => $assocData['conditions'],
1162                                        'limit' => $assocData['limit'],
1163                                        'table' => $this->fullTableName($linkModel),
1164                                        'alias' => $alias,
1165                                        'fields' => array_merge($this->fields($linkModel, $alias, $assocData['fields']), $joinFields),
1166                                        'order' => $assocData['order'],
1167                                        'group' => null,
1168                                        'joins' => array(array(
1169                                                'table' => $joinTbl,
1170                                                'alias' => $joinAssoc,
1171                                                'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $alias)
1172                                        ))
1173                                );
1174                        break;
1175                }
1176                if (isset($query)) {
1177                        return $this->buildStatement($query, $model);
1178                }
1179                return null;
1180        }
1181/**
1182 * Returns a conditions array for the constraint between two models
1183 *
1184 * @param string $type Association type
1185 * @param object $model Model object
1186 * @param array $association Association array
1187 * @return array Conditions array defining the constraint between $model and $association
1188 */
1189        function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) {
1190                $assoc = array_merge(array('external' => false, 'self' => false), $assoc);
1191
1192                if (array_key_exists('foreignKey', $assoc) && empty($assoc['foreignKey'])) {
1193                        return array();
1194                }
1195
1196                switch (true) {
1197                        case ($assoc['external'] && $type == 'hasOne'):
1198                                return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}');
1199                        break;
1200                        case ($assoc['external'] && $type == 'belongsTo'):
1201                                return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}');
1202                        break;
1203                        case (!$assoc['external'] && $type == 'hasOne'):
1204                                return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}"));
1205                        break;
1206                        case (!$assoc['external'] && $type == 'belongsTo'):
1207                                return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}"));
1208                        break;
1209                        case ($type == 'hasMany'):
1210                                return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}'));
1211                        break;
1212                        case ($type == 'hasAndBelongsToMany'):
1213                                return array(
1214                                        array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'),
1215                                        array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}"))
1216                                );
1217                        break;
1218                }
1219                return array();
1220        }
1221/**
1222 * Builds and generates a JOIN statement from an array.  Handles final clean-up before conversion.
1223 *
1224 * @param array $join An array defining a JOIN statement in a query
1225 * @return string An SQL JOIN statement to be used in a query
1226 * @see DboSource::renderJoinStatement()
1227 * @see DboSource::buildStatement()
1228 */
1229        function buildJoinStatement($join) {
1230                $data = array_merge(array(
1231                        'type' => null,
1232                        'alias' => null,
1233                        'table' => 'join_table',
1234                        'conditions' => array()
1235                ), $join);
1236
1237                if (!empty($data['alias'])) {
1238                        $data['alias'] = $this->alias . $this->name($data['alias']);
1239                }
1240                if (!empty($data['conditions'])) {
1241                        $data['conditions'] = trim($this->conditions($data['conditions'], true, false));
1242                }
1243                return $this->renderJoinStatement($data);
1244        }
1245/**
1246 * Builds and generates an SQL statement from an array.  Handles final clean-up before conversion.
1247 *
1248 * @param array $query An array defining an SQL query
1249 * @param object $model The model object which initiated the query
1250 * @return string An executable SQL statement
1251 * @see DboSource::renderStatement()
1252 */
1253        function buildStatement($query, $model) {
1254                $query = array_merge(array('offset' => null, 'joins' => array()), $query);
1255                if (!empty($query['joins'])) {
1256                        $count = count($query['joins']);
1257                        for ($i = 0; $i < $count; $i++) {
1258                                if (is_array($query['joins'][$i])) {
1259                                        $query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
1260                                }
1261                        }
1262                }
1263                return $this->renderStatement('select', array(
1264                        'conditions' => $this->conditions($query['conditions'], true, true, $model),
1265                        'fields' => join(', ', $query['fields']),
1266                        'table' => $query['table'],
1267                        'alias' => $this->alias . $this->name($query['alias']),
1268                        'order' => $this->order($query['order']),
1269                        'limit' => $this->limit($query['limit'], $query['offset']),
1270                        'joins' => join(' ', $query['joins']),
1271                        'group' => $this->group($query['group'])
1272                ));
1273        }
1274/**
1275 * Renders a final SQL JOIN statement
1276 *
1277 * @param array $data
1278 * @return string
1279 */
1280        function renderJoinStatement($data) {
1281                extract($data);
1282                return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})");
1283        }
1284/**
1285 * Renders a final SQL statement by putting together the component parts in the correct order
1286 *
1287 * @param string $type
1288 * @param array $data
1289 * @return string
1290 */
1291        function renderStatement($type, $data) {
1292                extract($data);
1293                $aliases = null;
1294
1295                switch (strtolower($type)) {
1296                        case 'select':
1297                                return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}";
1298                        break;
1299                        case 'create':
1300                                return "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
1301                        break;
1302                        case 'update':
1303                                if (!empty($alias)) {
1304                                        $aliases = "{$this->alias}{$alias} {$joins} ";
1305                                }
1306                                return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}";
1307                        break;
1308                        case 'delete':
1309                                if (!empty($alias)) {
1310                                        $aliases = "{$this->alias}{$alias} {$joins} ";
1311                                }
1312                                return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}";
1313                        break;
1314                        case 'schema':
1315                                foreach (array('columns', 'indexes') as $var) {
1316                                        if (is_array(${$var})) {
1317                                                ${$var} = "\t" . join(",\n\t", array_filter(${$var}));
1318                                        }
1319                                }
1320                                if (trim($indexes) != '') {
1321                                        $columns .= ',';
1322                                }
1323                                return "CREATE TABLE {$table} (\n{$columns}{$indexes});";
1324                        break;
1325                        case 'alter':
1326                        break;
1327                }
1328        }
1329/**
1330 * Merges a mixed set of string/array conditions
1331 *
1332 * @return array
1333 */
1334        function __mergeConditions($query, $assoc) {
1335                if (empty($assoc)) {
1336                        return $query;
1337                }
1338
1339                if (is_array($query)) {
1340                        return array_merge((array)$assoc, $query);
1341                }
1342
1343                if (!empty($query)) {
1344                        $query = array($query);
1345                        if (is_array($assoc)) {
1346                                $query = array_merge($query, $assoc);
1347                        } else {
1348                                $query[] = $assoc;
1349                        }
1350                        return $query;
1351                }
1352
1353                return $assoc;
1354        }
1355/**
1356 * Generates and executes an SQL UPDATE statement for given model, fields, and values.
1357 * For databases that do not support aliases in UPDATE queries.
1358 *
1359 * @param Model $model
1360 * @param array $fields
1361 * @param array $values
1362 * @param mixed $conditions
1363 * @return boolean Success
1364 */
1365        function update(&$model, $fields = array(), $values = null, $conditions = null) {
1366                if ($values == null) {
1367                        $combined = $fields;
1368                } else {
1369                        $combined = array_combine($fields, $values);
1370                }
1371                $fields = join(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions)));
1372
1373                $alias = $joins = null;
1374                $table = $this->fullTableName($model);
1375                $conditions = $this->_matchRecords($model, $conditions);
1376
1377                if ($conditions === false) {
1378                        return false;
1379                }
1380                $query = compact('table', 'alias', 'joins', 'fields', 'conditions');
1381
1382                if (!$this->execute($this->renderStatement('update', $query))) {
1383                        $model->onError();
1384                        return false;
1385                }
1386                return true;
1387        }
1388/**
1389 * Quotes and prepares fields and values for an SQL UPDATE statement
1390 *
1391 * @param Model $model
1392 * @param array $fields
1393 * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets
1394 * @param boolean $alias Include the model alias in the field name
1395 * @return array Fields and values, quoted and preparted
1396 * @access protected
1397 */
1398        function _prepareUpdateFields(&$model, $fields, $quoteValues = true, $alias = false) {
1399                $quotedAlias = $this->startQuote . $model->alias . $this->startQuote;
1400                foreach ($fields as $field => $value) {
1401                        if ($alias && strpos($field, '.') === false) {
1402                                $quoted = $model->escapeField($field);
1403                        } elseif (!$alias && strpos($field, '.') !== false) {
1404                                $quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace(
1405                                        $model->alias . '.', '', $field
1406                                )));
1407                        } else {
1408                                $quoted = $this->name($field);
1409                        }
1410
1411                        if ($value === null) {
1412                                $updates[] = $quoted . ' = NULL';
1413                        } else {
1414                                $update = $quoted . ' = ';
1415                                if ($quoteValues) {
1416                                        $update .= $this->value($value, $model->getColumnType($field), false);
1417                                } elseif (!$alias) {
1418                                        $update .= str_replace($quotedAlias . '.', '', str_replace(
1419                                                $model->alias . '.', '', $value
1420                                        ));
1421                                } else {
1422                                        $update .= $value;
1423                                }
1424                                $updates[] =  $update;
1425                        }
1426                }
1427                return $updates;
1428        }
1429/**
1430 * Generates and executes an SQL DELETE statement.
1431 * For databases that do not support aliases in UPDATE queries.
1432 *
1433 * @param Model $model
1434 * @param mixed $conditions
1435 * @return boolean Success
1436 */
1437        function delete(&$model, $conditions = null) {
1438                $alias = $joins = null;
1439                $table = $this->fullTableName($model);
1440                $conditions = $this->_matchRecords($model, $conditions);
1441
1442                if ($conditions === false) {
1443                        return false;
1444                }
1445
1446                if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
1447                        $model->onError();
1448                        return false;
1449                }
1450                return true;
1451        }
1452/**
1453 * Gets a list of record IDs for the given conditions.  Used for multi-record updates and deletes
1454 * in databases that do not support aliases in UPDATE/DELETE queries.
1455 *
1456 * @param Model $model
1457 * @param mixed $conditions
1458 * @return array List of record IDs
1459 * @access protected
1460 */
1461        function _matchRecords(&$model, $conditions = null) {
1462                if ($conditions === true) {
1463                        $conditions = $this->conditions(true);
1464                } elseif ($conditions === null) {
1465                        $conditions = $this->conditions($this->defaultConditions($model, $conditions, false), true, true, $model);
1466                } else {
1467                        $idList = $model->find('all', array(
1468                                'fields' => "{$model->alias}.{$model->primaryKey}",
1469                                'conditions' => $conditions
1470                        ));
1471
1472                        if (empty($idList)) {
1473                                return false;
1474                        }
1475                        $conditions = $this->conditions(array(
1476                                $model->primaryKey => Set::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}")
1477                        ));
1478                }
1479                return $conditions;
1480        }
1481/**
1482 * Returns an array of SQL JOIN fragments from a model's associations
1483 *
1484 * @param object $model
1485 * @return array
1486 */
1487        function _getJoins($model) {
1488                $join = array();
1489                $joins = array_merge($model->getAssociated('hasOne'), $model->getAssociated('belongsTo'));
1490
1491                foreach ($joins as $assoc) {
1492                        if (isset($model->{$assoc}) && $model->useDbConfig == $model->{$assoc}->useDbConfig) {
1493                                $assocData = $model->getAssociated($assoc);
1494                                $join[] = $this->buildJoinStatement(array(
1495                                        'table' => $this->fullTableName($model->{$assoc}),
1496                                        'alias' => $assoc,
1497                                        'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1498                                        'conditions' => trim($this->conditions(
1499                                                $this->getConstraint($assocData['association'], $model, $model->{$assoc}, $assoc, $assocData),
1500                                                true, false, $model
1501                                        ))
1502                                ));
1503                        }
1504                }
1505                return $join;
1506        }
1507/**
1508 * Returns the an SQL calculation, i.e. COUNT() or MAX()
1509 *
1510 * @param model $model
1511 * @param string $func Lowercase name of SQL function, i.e. 'count' or 'max'
1512 * @param array $params Function parameters (any values must be quoted manually)
1513 * @return string       An SQL calculation function
1514 * @access public
1515 */
1516        function calculate(&$model, $func, $params = array()) {
1517                $params = (array)$params;
1518
1519                switch (strtolower($func)) {
1520                        case 'count':
1521                                if (!isset($params[0])) {
1522                                        $params[0] = '*';
1523                                }
1524                                if (!isset($params[1])) {
1525                                        $params[1] = 'count';
1526                                }
1527                                return 'COUNT(' . $this->name($params[0]) . ') AS ' . $this->name($params[1]);
1528                        case 'max':
1529                        case 'min':
1530                                if (!isset($params[1])) {
1531                                        $params[1] = $params[0];
1532                                }
1533                                return strtoupper($func) . '(' . $this->name($params[0]) . ') AS ' . $this->name($params[1]);
1534                        break;
1535                }
1536        }
1537/**
1538 * Deletes all the records in a table and resets the count of the auto-incrementing
1539 * primary key, where applicable.
1540 *
1541 * @param mixed $table A string or model class representing the table to be truncated
1542 * @return boolean      SQL TRUNCATE TABLE statement, false if not applicable.
1543 * @access public
1544 */
1545        function truncate($table) {
1546                return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table));
1547        }
1548/**
1549 * Begin a transaction
1550 *
1551 * @param model $model
1552 * @return boolean True on success, false on fail
1553 * (i.e. if the database/model does not support transactions,
1554 * or a transaction has not started).
1555 */
1556        function begin(&$model) {
1557                if (parent::begin($model) && $this->execute($this->_commands['begin'])) {
1558                        $this->_transactionStarted = true;
1559                        return true;
1560                }
1561                return false;
1562        }
1563/**
1564 * Commit a transaction
1565 *
1566 * @param model $model
1567 * @return boolean True on success, false on fail
1568 * (i.e. if the database/model does not support transactions,
1569 * or a transaction has not started).
1570 */
1571        function commit(&$model) {
1572                if (parent::commit($model) && $this->execute($this->_commands['commit'])) {
1573                        $this->_transactionStarted = false;
1574                        return true;
1575                }
1576                return false;
1577        }
1578/**
1579 * Rollback a transaction
1580 *
1581 * @param model $model
1582 * @return boolean True on success, false on fail
1583 * (i.e. if the database/model does not support transactions,
1584 * or a transaction has not started).
1585 */
1586        function rollback(&$model) {
1587                if (parent::rollback($model) && $this->execute($this->_commands['rollback'])) {
1588                        $this->_transactionStarted = false;
1589                        return true;
1590                }
1591                return false;
1592        }
1593/**
1594 * Creates a default set of conditions from the model if $conditions is null/empty.
1595 *
1596 * @param object $model
1597 * @param mixed  $conditions
1598 * @param boolean $useAlias Use model aliases rather than table names when generating conditions
1599 * @return mixed
1600 */
1601        function defaultConditions(&$model, $conditions, $useAlias = true) {
1602                if (!empty($conditions)) {
1603                        return $conditions;
1604                }
1605                if (!$model->exists()) {
1606                        return false;
1607                }
1608                $alias = $model->alias;
1609
1610                if (!$useAlias) {
1611                        $alias = $this->fullTableName($model, false);
1612                }
1613                return array("{$alias}.{$model->primaryKey}" => $model->getID());
1614        }
1615/**
1616 * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name)
1617 *
1618 * @param unknown_type $model
1619 * @param unknown_type $key
1620 * @param unknown_type $assoc
1621 * @return string
1622 */
1623        function resolveKey($model, $key, $assoc = null) {
1624                if (empty($assoc)) {
1625                        $assoc = $model->alias;
1626                }
1627                if (!strpos('.', $key)) {
1628                        return $this->name($model->alias) . '.' . $this->name($key);
1629                }
1630                return $key;
1631        }
1632/**
1633 * Private helper method to remove query metadata in given data array.
1634 *
1635 * @param array $data
1636 * @return array
1637 */
1638        function __scrubQueryData($data) {
1639                foreach (array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group') as $key) {
1640                        if (!isset($data[$key]) || empty($data[$key])) {
1641                                $data[$key] = array();
1642                        }
1643                }
1644                return $data;
1645        }
1646/**
1647 * Generates the fields list of an SQL query.
1648 *
1649 * @param Model $model
1650 * @param string $alias Alias tablename
1651 * @param mixed $fields
1652 * @param boolean $quote If false, returns fields array unquoted
1653 * @return array
1654 */
1655        function fields(&$model, $alias = null, $fields = array(), $quote = true) {
1656                if (empty($alias)) {
1657                        $alias = $model->alias;
1658                }
1659                if (empty($fields)) {
1660                        $fields = array_keys($model->schema());
1661                } elseif (!is_array($fields)) {
1662                        $fields = String::tokenize($fields);
1663                }
1664                $fields = array_values(array_filter($fields));
1665
1666                if (!$quote) {
1667                        return $fields;
1668                }
1669                $count = count($fields);
1670
1671                if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) {
1672                        for ($i = 0; $i < $count; $i++) {
1673                                if (!preg_match('/^.+\\(.*\\)/', $fields[$i])) {
1674                                        $prepend = '';
1675
1676                                        if (strpos($fields[$i], 'DISTINCT') !== false) {
1677                                                $prepend = 'DISTINCT ';
1678                                                $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));
1679                                        }
1680                                        $dot = strpos($fields[$i], '.');
1681
1682                                        if ($dot === false) {
1683                                                $fields[$i] = $this->name($alias . '.' . $fields[$i]);
1684                                        } else {
1685                                                $value = array();
1686                                                $comma = strpos($fields[$i], ',');
1687                                                if ($comma === false) {
1688                                                        $build = explode('.', $fields[$i]);
1689                                                        if (!Set::numeric($build)) {
1690                                                                $fields[$i] = $this->name($build[0] . '.' . $build[1]);
1691                                                        }
1692                                                        $comma = String::tokenize($fields[$i]);
1693                                                        foreach ($comma as $string) {
1694                                                                if (preg_match('/^[0-9]+\.[0-9]+$/', $string)) {
1695                                                                        $value[] = $string;
1696                                                                } else {
1697                                                                        $build = explode('.', $string);
1698                                                                        $value[] = $this->name(trim($build[0]) . '.' . trim($build[1]));
1699                                                                }
1700                                                        }
1701                                                        $fields[$i] = implode(', ', $value);
1702                                                }
1703                                        }
1704                                        $fields[$i] = $prepend . $fields[$i];
1705                                } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) {
1706                                        if (isset($field[1])) {
1707                                                if (strpos($field[1], '.') === false) {
1708                                                        $field[1] = $this->name($alias . '.' . $field[1]);
1709                                                } else {
1710                                                        $field[0] = explode('.', $field[1]);
1711                                                        if (!Set::numeric($field[0])) {
1712                                                                $field[0] = join('.', array_map(array($this, 'name'), $field[0]));
1713                                                                $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1);
1714                                                        }
1715                                                }
1716                                        }
1717                                }
1718                        }
1719                }
1720                return array_unique($fields);
1721        }
1722/**
1723 * Creates a WHERE clause by parsing given conditions data.
1724 *
1725 * @param mixed $conditions Array or string of conditions
1726 * @param boolean $quoteValues If true, values should be quoted
1727 * @param boolean $where If true, "WHERE " will be prepended to the return value
1728 * @param Model $model A reference to the Model instance making the query
1729 * @return string SQL fragment
1730 */
1731        function conditions($conditions, $quoteValues = true, $where = true, $model = null) {
1732                $clause = $out = '';
1733
1734                if ($where) {
1735                        $clause = ' WHERE ';
1736                }
1737
1738                if (is_array($conditions) && !empty($conditions)) {
1739                        $out = $this->conditionKeysToString($conditions, $quoteValues, $model);
1740
1741                        if (empty($out)) {
1742                                return $clause . ' 1 = 1';
1743                        }
1744                        return $clause . join(' AND ', $out);
1745                }
1746
1747                if (empty($conditions) || trim($conditions) == '' || $conditions === true) {
1748                        return $clause . '1 = 1';
1749                }
1750                $clauses = '/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i';
1751
1752                if (preg_match($clauses, $conditions, $match)) {
1753                        $clause = '';
1754                }
1755                if (trim($conditions) == '') {
1756                        $conditions = ' 1 = 1';
1757                } else {
1758                        $conditions = $this->__quoteFields($conditions);
1759                }
1760                return $clause . $conditions;
1761        }
1762/**
1763 * Creates a WHERE clause by parsing given conditions array.  Used by DboSource::conditions().
1764 *
1765 * @param array $conditions Array or string of conditions
1766 * @param boolean $quoteValues If true, values should be quoted
1767 * @param Model $model A reference to the Model instance making the query
1768 * @return string SQL fragment
1769 */
1770        function conditionKeysToString($conditions, $quoteValues = true, $model = null) {
1771                $c = 0;
1772                $out = array();
1773                $data = $columnType = null;
1774                $bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&');
1775
1776                foreach ($conditions as $key => $value) {
1777                        $join = ' AND ';
1778                        $not = null;
1779
1780                        if (is_array($value)) {
1781                                $valueInsert = (
1782                                        !empty($value) &&
1783                                        (substr_count($key, '?') == count($value) || substr_count($key, ':') == count($value))
1784                                );
1785                        }
1786
1787                        if (is_numeric($key) && empty($value)) {
1788                                continue;
1789                        } elseif (is_numeric($key) && is_string($value)) {
1790                                $out[] = $not . $this->__quoteFields($value);
1791                        } elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $bool)) {
1792                                if (in_array(strtolower(trim($key)), $bool)) {
1793                                        $join = ' ' . strtoupper($key) . ' ';
1794                                } else {
1795                                        $key = $join;
1796                                }
1797                                $value = $this->conditionKeysToString($value, $quoteValues, $model);
1798
1799                                if (strpos($join, 'NOT') !== false) {
1800                                        if (strtoupper(trim($key)) == 'NOT') {
1801                                                $key = 'AND ' . trim($key);
1802                                        }
1803                                        $not = 'NOT ';
1804                                }
1805
1806                                if (empty($value[1])) {
1807                                        if ($not) {
1808                                                $out[] = $not . '(' . $value[0] . ')';
1809                                        } else {
1810                                                $out[] = $value[0] ;
1811                                        }
1812                                } else {
1813                                        $out[] = '(' . $not . '(' . join(') ' . strtoupper($key) . ' (', $value) . '))';
1814                                }
1815
1816                        } else {
1817                                if (is_object($value) && isset($value->type)) {
1818                                        if ($value->type == 'identifier') {
1819                                                $data .= $this->name($key) . ' = ' . $this->name($value->value);
1820                                        } elseif ($value->type == 'expression') {
1821                                                if (is_numeric($key)) {
1822                                                        $data .= $value->value;
1823                                                } else {
1824                                                        $data .= $this->name($key) . ' = ' . $value->value;
1825                                                }
1826                                        }
1827                                } elseif (is_array($value) && !empty($value) && !$valueInsert) {
1828                                        $keys = array_keys($value);
1829                                        if (array_keys($value) === array_values(array_keys($value))) {
1830                                                $count = count($value);
1831                                                if ($count === 1) {
1832                                                        $data = $this->name($key) . ' = (';
1833                                                } else
1834                                                        $data = $this->name($key) . ' IN (';
1835                                                if ($quoteValues || strpos($value[0], '-!') !== 0) {
1836                                                        if (is_object($model)) {
1837                                                                $columnType = $model->getColumnType($key);
1838                                                        }
1839                                                        $data .= join(', ', $this->value($value, $columnType));
1840                                                }
1841                                                $data .= ')';
1842                                        } else {
1843                                                $ret = $this->conditionKeysToString($value, $quoteValues, $model);
1844                                                if (count($ret) > 1) {
1845                                                        $data = '(' . join(') AND (', $ret) . ')';
1846                                                } elseif (isset($ret[0])) {
1847                                                        $data = $ret[0];
1848                                                }
1849                                        }
1850                                } elseif (is_numeric($key) && !empty($value)) {
1851                                        $data = $this->__quoteFields($value);
1852                                } else {
1853                                        $data = $this->__parseKey($model, trim($key), $value);
1854                                }
1855
1856                                if ($data != null) {
1857                                        if (preg_match('/^\(\(\((.+)\)\)\)$/', $data)) {
1858                                                $data = substr($data, 1, strlen($data) - 2);
1859                                        }
1860                                        $out[] = $data;
1861                                        $data = null;
1862                                }
1863                        }
1864                        $c++;
1865                }
1866                return $out;
1867        }
1868/**
1869 * Extracts a Model.field identifier and an SQL condition operator from a string, formats
1870 * and inserts values, and composes them into an SQL snippet.
1871 *
1872 * @param Model $model Model object initiating the query
1873 * @param string $key An SQL key snippet containing a field and optional SQL operator
1874 * @param mixed $value The value(s) to be inserted in the string
1875 * @return string
1876 * @access private
1877 */
1878        function __parseKey($model, $key, $value) {
1879                $operatorMatch = '/^((' . join(')|(', $this->__sqlOps);
1880                $operatorMatch .= '\\x20)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is';
1881                $bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false));
1882
1883                if (!strpos($key, ' ')) {
1884                        $operator = '=';
1885                } else {
1886                        list($key, $operator) = explode(' ', trim($key), 2);
1887
1888                        if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) {
1889                                $key = $key . ' ' . $operator;
1890                                $split = strrpos($key, ' ');
1891                                $operator = substr($key, $split);
1892                                $key = substr($key, 0, $split);
1893                        }
1894                }
1895                $type = (is_object($model) ? $model->getColumnType($key) : null);
1896                $null = ($value === null || (is_array($value) && empty($value)));
1897
1898                if (strtolower($operator) === 'not') {
1899                        $data = $this->conditionKeysToString(
1900                                array($operator => array($key => $value)), true, $model
1901                        );
1902                        return $data[0];
1903                }
1904                $value = $this->value($value, $type);
1905
1906                $key = (strpos($key, '(') !== false || strpos($key, ')') !== false) ?
1907                        $this->__quoteFields($key) :
1908                        $key = $this->name($key);
1909
1910                if ($bound) {
1911                        return String::insert($key . ' ' . trim($operator), $value);
1912                }
1913
1914                if (!preg_match($operatorMatch, trim($operator))) {
1915                        $operator .= ' =';
1916                }
1917                $operator = trim($operator);
1918
1919                if (is_array($value)) {
1920                        $value = join(', ', $value);
1921
1922                        switch ($operator) {
1923                                case '=':
1924                                        $operator = 'IN';
1925                                break;
1926                                case '!=':
1927                                case '<>':
1928                                        $operator = 'NOT IN';
1929                                break;
1930                        }
1931                        $value = "({$value})";
1932                } elseif ($null) {
1933                        switch ($operator) {
1934                                case '=':
1935                                        $operator = 'IS';
1936                                break;
1937                                case '!=':
1938                                case '<>':
1939                                        $operator = 'IS NOT';
1940                                break;
1941                        }
1942                }
1943                return "{$key} {$operator} {$value}";
1944        }
1945/**
1946 * Quotes Model.fields
1947 *
1948 * @param string $conditions
1949 * @return string or false if no match
1950 * @access private
1951 */
1952        function __quoteFields($conditions) {
1953                $start = $end  = null;
1954                $original = $conditions;
1955
1956                if (!empty($this->startQuote)) {
1957                        $start = preg_quote($this->startQuote);
1958                }
1959                if (!empty($this->endQuote)) {
1960                        $end = preg_quote($this->endQuote);
1961                }
1962                $conditions = str_replace(array($start, $end), '', $conditions);
1963                preg_match_all('/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', $conditions, $replace, PREG_PATTERN_ORDER);
1964
1965                if (isset($replace['1']['0'])) {
1966                        $pregCount = count($replace['1']);
1967
1968                        for ($i = 0; $i < $pregCount; $i++) {
1969                                if (!empty($replace['1'][$i]) && !is_numeric($replace['1'][$i])) {
1970                                        $conditions = preg_replace('/\b' . preg_quote($replace['1'][$i]) . '\b/', $this->name($replace['1'][$i]), $conditions);
1971                                }
1972                        }
1973                        return $conditions;
1974                }
1975                return $original;
1976        }
1977/**
1978 * Returns a limit statement in the correct format for the particular database.
1979 *
1980 * @param integer $limit Limit of results returned
1981 * @param integer $offset Offset from which to start results
1982 * @return string SQL limit/offset statement
1983 */
1984        function limit($limit, $offset = null) {
1985                if ($limit) {
1986                        $rt = '';
1987                        if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
1988                                $rt = ' LIMIT';
1989                        }
1990
1991                        if ($offset) {
1992                                $rt .= ' ' . $offset . ',';
1993                        }
1994
1995                        $rt .= ' ' . $limit;
1996                        return $rt;
1997                }
1998                return null;
1999        }
2000/**
2001 * Returns an ORDER BY clause as a string.
2002 *
2003 * @param string $key Field reference, as a key (i.e. Post.title)
2004 * @param string $direction Direction (ASC or DESC)
2005 * @return string ORDER BY clause
2006 */
2007        function order($keys, $direction = 'ASC') {
2008                if (is_string($keys) && strpos($keys, ',') && !preg_match('/\(.+\,.+\)/', $keys)) {
2009                        $keys = array_map('trim', explode(',', $keys));
2010                }
2011
2012                if (is_array($keys)) {
2013                        $keys = array_filter($keys);
2014                }
2015
2016                if (empty($keys) || (is_array($keys) && count($keys) && isset($keys[0]) && empty($keys[0]))) {
2017                        return '';
2018                }
2019
2020                if (is_array($keys)) {
2021                        $keys = (Set::countDim($keys) > 1) ? array_map(array(&$this, 'order'), $keys) : $keys;
2022
2023                        foreach ($keys as $key => $value) {
2024                                if (is_numeric($key)) {
2025                                        $key = $value = ltrim(str_replace('ORDER BY ', '', $this->order($value)));
2026                                        $value = (!preg_match('/\\x20ASC|\\x20DESC/i', $key) ? ' ' . $direction : '');
2027                                } else {
2028                                        $value = ' ' . $value;
2029                                }
2030
2031                                if (!preg_match('/^.+\\(.*\\)/', $key) && !strpos($key, ',')) {
2032                                        if (preg_match('/\\x20ASC|\\x20DESC/i', $key, $dir)) {
2033                                                $dir = $dir[0];
2034                                                $key = preg_replace('/\\x20ASC|\\x20DESC/i', '', $key);
2035                                        } else {
2036                                                $dir = '';
2037                                        }
2038                                        $key = trim($key);
2039                                        if (!preg_match('/\s/', $key)) {
2040                                                $key = $this->name($key);
2041                                        }
2042                                        $key .= ' ' . trim($dir);
2043                                }
2044                                $order[] = $this->order($key . $value);
2045                        }
2046                        return ' ORDER BY ' . trim(str_replace('ORDER BY', '', join(',', $order)));
2047                }
2048                $keys = preg_replace('/ORDER\\x20BY/i', '', $keys);
2049
2050                if (strpos($keys, '.')) {
2051                        preg_match_all('/([a-zA-Z0-9_]{1,})\\.([a-zA-Z0-9_]{1,})/', $keys, $result, PREG_PATTERN_ORDER);
2052                        $pregCount = count($result[0]);
2053
2054                        for ($i = 0; $i < $pregCount; $i++) {
2055                                if (!is_numeric($result[0][$i])) {
2056                                        $keys = preg_replace('/' . $result[0][$i] . '/', $this->name($result[0][$i]), $keys);
2057                                }
2058                        }
2059                        $result = ' ORDER BY ' . $keys;
2060                        return $result . (!preg_match('/\\x20ASC|\\x20DESC/i', $keys) ? ' ' . $direction : '');
2061
2062                } elseif (preg_match('/(\\x20ASC|\\x20DESC)/i', $keys, $match)) {
2063                        $direction = $match[1];
2064                        return ' ORDER BY ' . preg_replace('/' . $match[1] . '/', '', $keys) . $direction;
2065                }
2066                return ' ORDER BY ' . $keys . ' ' . $direction;
2067        }
2068/**
2069 * Create a GROUP BY SQL clause
2070 *
2071 * @param string $group Group By Condition
2072 * @return mixed string condition or null
2073 */
2074        function group($group) {
2075                if ($group) {
2076                        if (is_array($group)) {
2077                                $group = join(', ', $group);
2078                        }
2079                        return ' GROUP BY ' . $this->__quoteFields($group);
2080                }
2081                return null;
2082        }
2083/**
2084 * Disconnects database, kills the connection and says the connection is closed,
2085 * and if DEBUG is turned on, the log for this object is shown.
2086 *
2087 */
2088        function close() {
2089                if (Configure::read() > 1) {
2090                        $this->showLog();
2091                }
2092                $this->disconnect();
2093        }
2094/**
2095 * Checks if the specified table contains any record matching specified SQL
2096 *
2097 * @param Model $model Model to search
2098 * @param string $sql SQL WHERE clause (condition only, not the "WHERE" part)
2099 * @return boolean True if the table has a matching record, else false
2100 */
2101        function hasAny(&$Model, $sql) {
2102                $sql = $this->conditions($sql);
2103                $table = $this->fullTableName($Model);
2104                $where = $sql ? "WHERE {$sql}" : 'WHERE 1 = 1';
2105                $id = $Model->primaryKey;
2106
2107                $out = $this->fetchRow("SELECT COUNT({$id}) {$this->alias}count FROM {$table} {$where}");
2108
2109                if (is_array($out)) {
2110                        return $out[0]['count'];
2111                }
2112                return false;
2113        }
2114/**
2115 * Gets the length of a database-native column description, or null if no length
2116 *
2117 * @param string $real Real database-layer column type (i.e. "varchar(255)")
2118 * @return mixed An integer or string representing the length of the column
2119 */
2120        function length($real) {
2121                if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) {
2122                        trigger_error(__('FIXME: Can\'t parse field: ' . $real, true), E_USER_WARNING);
2123                        $col = str_replace(array(')', 'unsigned'), '', $real);
2124                        $limit = null;
2125
2126                        if (strpos($col, '(') !== false) {
2127                                list($col, $limit) = explode('(', $col);
2128                        }
2129                        if ($limit != null) {
2130                                return intval($limit);
2131                        }
2132                        return null;
2133                }
2134
2135                $types = array(
2136                        'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1
2137                );
2138
2139                list($real, $type, $length, $offset, $sign, $zerofill) = $result;
2140                $typeArr = $type;
2141                $type = $type[0];
2142                $length = $length[0];
2143                $offset = $offset[0];
2144
2145                $isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double'));
2146                if ($isFloat && $offset) {
2147                        return $length.','.$offset;
2148                }
2149
2150                if (($real[0] == $type) && (count($real) == 1)) {
2151                        return null;
2152                }
2153
2154                if (isset($types[$type])) {
2155                        $length += $types[$type];
2156                        if (!empty($sign)) {
2157                                $length--;
2158                        }
2159                } elseif (in_array($type, array('enum', 'set'))) {
2160                        $length = 0;
2161                        foreach ($typeArr as $key => $enumValue) {
2162                                if ($key == 0) {
2163                                        continue;
2164                                }
2165                                $tmpLength = strlen($enumValue);
2166                                if ($tmpLength > $length) {
2167                                        $length = $tmpLength;
2168                                }
2169                        }
2170                }
2171                return intval($length);
2172        }
2173/**
2174 * Translates between PHP boolean values and Database (faked) boolean values
2175 *
2176 * @param mixed $data Value to be translated
2177 * @return mixed Converted boolean value
2178 */
2179        function boolean($data) {
2180                if ($data === true || $data === false) {
2181                        if ($data === true) {
2182                                return 1;
2183                        }
2184                        return 0;
2185                } else {
2186                        return !empty($data);
2187                }
2188        }
2189/**
2190 * Inserts multiple values into a table
2191 *
2192 * @param string $table
2193 * @param string $fields
2194 * @param array $values
2195 * @access protected
2196 */
2197        function insertMulti($table, $fields, $values) {
2198                $table = $this->fullTableName($table);
2199                if (is_array($fields)) {
2200                        $fields = join(', ', array_map(array(&$this, 'name'), $fields));
2201                }
2202                $count = count($values);
2203                for ($x = 0; $x < $count; $x++) {
2204                        $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
2205                }
2206        }
2207/**
2208 * Returns an array of the indexes in given datasource name.
2209 *
2210 * @param string $model Name of model to inspect
2211 * @return array Fields in table. Keys are column and unique
2212 */
2213        function index($model) {
2214                return false;
2215        }
2216/**
2217 * Generate a database-native schema for the given Schema object
2218 *
2219 * @param object $schema An instance of a subclass of CakeSchema
2220 * @param string $tableName Optional.  If specified only the table name given will be generated.
2221 *                                              Otherwise, all tables defined in the schema are generated.
2222 * @return string
2223 */
2224        function createSchema($schema, $tableName = null) {
2225                if (!is_a($schema, 'CakeSchema')) {
2226                        trigger_error(__('Invalid schema object', true), E_USER_WARNING);
2227                        return null;
2228                }
2229                $out = '';
2230
2231                foreach ($schema->tables as $curTable => $columns) {
2232                        if (!$tableName || $tableName == $curTable) {
2233                                $cols = $colList = $indexes = array();
2234                                $primary = null;
2235                                $table = $this->fullTableName($curTable);
2236
2237                                foreach ($columns as $name => $col) {
2238                                        if (is_string($col)) {
2239                                                $col = array('type' => $col);
2240                                        }
2241                                        if (isset($col['key']) && $col['key'] == 'primary') {
2242                                                $primary = $name;
2243                                        }
2244                                        if ($name !== 'indexes') {
2245                                                $col['name'] = $name;
2246                                                if (!isset($col['type'])) {
2247                                                        $col['type'] = 'string';
2248                                                }
2249                                                $cols[] = $this->buildColumn($col);
2250                                        } else {
2251                                                $indexes = array_merge($indexes, $this->buildIndex($col, $table));
2252                                        }
2253                                }
2254                                if (empty($indexes) && !empty($primary)) {
2255                                        $col = array('PRIMARY' => array('column' => $primary, 'unique' => 1));
2256                                        $indexes = array_merge($indexes, $this->buildIndex($col, $table));
2257                                }
2258                                $columns = $cols;
2259                                $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes')) . "\n\n";
2260                        }
2261                }
2262                return $out;
2263        }
2264/**
2265 * Generate a alter syntax from  CakeSchema::compare()
2266 *
2267 * @param unknown_type $schema
2268 * @return unknown
2269 */
2270        function alterSchema($compare, $table = null) {
2271                return false;
2272        }
2273/**
2274 * Generate a "drop table" statement for the given Schema object
2275 *
2276 * @param object $schema An instance of a subclass of CakeSchema
2277 * @param string $table Optional.  If specified only the table name given will be generated.
2278 *                                              Otherwise, all tables defined in the schema are generated.
2279 * @return string
2280 */
2281        function dropSchema($schema, $table = null) {
2282                if (!is_a($schema, 'CakeSchema')) {
2283                        trigger_error(__('Invalid schema object', true), E_USER_WARNING);
2284                        return null;
2285                }
2286                $out = '';
2287
2288                foreach ($schema->tables as $curTable => $columns) {
2289                        if (!$table || $table == $curTable) {
2290                                $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . ";\n";
2291                        }
2292                }
2293                return $out;
2294        }
2295/**
2296 * Generate a database-native column schema string
2297 *
2298 * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
2299 *                                              where options can be 'default', 'length', or 'key'.
2300 * @return string
2301 */
2302        function buildColumn($column) {
2303                $name = $type = null;
2304                extract(array_merge(array('null' => true), $column));
2305
2306                if (empty($name) || empty($type)) {
2307                        trigger_error('Column name or type not defined in schema', E_USER_WARNING);
2308                        return null;
2309                }
2310
2311                if (!isset($this->columns[$type])) {
2312                        trigger_error("Column type {$type} does not exist", E_USER_WARNING);
2313                        return null;
2314                }
2315
2316                $real = $this->columns[$type];
2317                $out = $this->name($name) . ' ' . $real['name'];
2318
2319                if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) {
2320                        if (isset($column['length'])) {
2321                                $length = $column['length'];
2322                        } elseif (isset($column['limit'])) {
2323                                $length = $column['limit'];
2324                        } elseif (isset($real['length'])) {
2325                                $length = $real['length'];
2326                        } else {
2327                                $length = $real['limit'];
2328                        }
2329                        $out .= '(' . $length . ')';
2330                }
2331
2332                if (($column['type'] == 'integer' || $column['type'] == 'float' ) && isset($column['default']) && $column['default'] === '') {
2333                        $column['default'] = null;
2334                }
2335
2336                if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') {
2337                        $out .= ' ' . $this->columns['primary_key']['name'];
2338                } elseif (isset($column['key']) && $column['key'] == 'primary') {
2339                        $out .= ' NOT NULL';
2340                } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) {
2341                        $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
2342                } elseif (isset($column['default'])) {
2343                        $out .= ' DEFAULT ' . $this->value($column['default'], $type);
2344                } elseif (isset($column['null']) && $column['null'] == true) {
2345                        $out .= ' DEFAULT NULL';
2346                } elseif (isset($column['null']) && $column['null'] == false) {
2347                        $out .= ' NOT NULL';
2348                }
2349                return $out;
2350        }
2351/**
2352 * Format indexes for create table
2353 *
2354 * @param array $indexes
2355 * @param string $table
2356 * @return array
2357 */
2358        function buildIndex($indexes, $table = null) {
2359                $join = array();
2360                foreach ($indexes as $name => $value) {
2361                        $out = '';
2362                        if ($name == 'PRIMARY') {
2363                                $out .= 'PRIMARY ';
2364                                $name = null;
2365                        } else {
2366                                if (!empty($value['unique'])) {
2367                                        $out .= 'UNIQUE ';
2368                                }
2369                        }
2370                        if (is_array($value['column'])) {
2371                                $out .= 'KEY '. $name .' (' . join(', ', array_map(array(&$this, 'name'), $value['column'])) . ')';
2372                        } else {
2373                                $out .= 'KEY '. $name .' (' . $this->name($value['column']) . ')';
2374                        }
2375                        $join[] = $out;
2376                }
2377                return $join;
2378        }
2379/**
2380 * Guesses the data type of an array
2381 *
2382 * @param string $value
2383 * @return void
2384 * @access public
2385 */
2386        function introspectType($value) {
2387                if (!is_array($value)) {
2388                        if ($value === true || $value === false) {
2389                                return 'boolean';
2390                        }
2391                        if (is_float($value) && floatval($value) === $value) {
2392                                return 'float';
2393                        }
2394                        if (is_int($value) && intval($value) === $value) {
2395                                return 'integer';
2396                        }
2397                        if (is_string($value) && strlen($value) > 255) {
2398                                return 'text';
2399                        }
2400                        return 'string';
2401                }
2402
2403                $isAllFloat = $isAllInt = true;
2404                $containsFloat = $containsInt = $containsString = false;
2405                foreach ($value as $key => $valElement) {
2406                        $valElement = trim($valElement);
2407                        if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) {
2408                                $isAllFloat = false;
2409                        } else {
2410                                $containsFloat = true;
2411                                continue;
2412                        }
2413                        if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) {
2414                                $isAllInt = false;
2415                        } else {
2416                                $containsInt = true;
2417                                continue;
2418                        }
2419                        $containsString = true;
2420                }
2421
2422                if ($isAllFloat) {
2423                        return 'float';
2424                }
2425                if ($isAllInt) {
2426                        return 'integer';
2427                }
2428
2429                if ($containsInt && !$containsString) {
2430                        return 'integer';
2431                }
2432                return 'string';
2433        }
2434}
2435?>
Note: See TracBrowser for help on using the repository browser.