comparison cake/libs/model/datasources/dbo_source.php @ 0:261e66bd5a0c

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