Mercurial > hg > Members > shoshi > webvirt
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 } |