1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18:
19: App::uses('DataSource', 'Model/Datasource');
20: App::uses('CakeText', 'Utility');
21: App::uses('View', 'View');
22:
23: 24: 25: 26: 27: 28: 29:
30: class DboSource extends DataSource {
31:
32: 33: 34: 35: 36:
37: public $description = "Database Data Source";
38:
39: 40: 41: 42: 43:
44: public $index = array('PRI' => 'primary', 'MUL' => 'index', 'UNI' => 'unique');
45:
46: 47: 48: 49: 50:
51: public $alias = 'AS ';
52:
53: 54: 55: 56: 57: 58: 59:
60: public static $methodCache = array();
61:
62: 63: 64: 65: 66: 67:
68: public $cacheMethods = true;
69:
70: 71: 72: 73: 74: 75: 76:
77: public $useNestedTransactions = false;
78:
79: 80: 81: 82: 83:
84: public $fullDebug = false;
85:
86: 87: 88: 89: 90:
91: public $affected = null;
92:
93: 94: 95: 96: 97:
98: public $numRows = null;
99:
100: 101: 102: 103: 104:
105: public $took = null;
106:
107: 108: 109: 110: 111:
112: protected $_result = null;
113:
114: 115: 116: 117: 118:
119: protected $_queriesCnt = 0;
120:
121: 122: 123: 124: 125:
126: protected $_queriesTime = null;
127:
128: 129: 130: 131: 132:
133: protected $_queriesLog = array();
134:
135: 136: 137: 138: 139: 140: 141:
142: protected $_queriesLogMax = 200;
143:
144: 145: 146: 147: 148:
149: protected $_queryCache = array();
150:
151: 152: 153: 154: 155:
156: protected $_connection = null;
157:
158: 159: 160: 161: 162:
163: public $configKeyName = null;
164:
165: 166: 167: 168: 169:
170: public $startQuote = null;
171:
172: 173: 174: 175: 176:
177: public $endQuote = null;
178:
179: 180: 181: 182: 183:
184: protected $_sqlOps = array('like', 'ilike', 'rlike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
185:
186: 187: 188: 189: 190:
191: protected $_sqlBoolOps = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&');
192:
193: 194: 195: 196: 197:
198: protected $_transactionNesting = 0;
199:
200: 201: 202: 203: 204:
205: protected $_queryDefaults = array(
206: 'conditions' => array(),
207: 'fields' => null,
208: 'table' => null,
209: 'alias' => null,
210: 'order' => null,
211: 'limit' => null,
212: 'joins' => array(),
213: 'group' => null,
214: 'offset' => null
215: );
216:
217: 218: 219: 220: 221:
222: public $virtualFieldSeparator = '__';
223:
224: 225: 226: 227: 228:
229: public $tableParameters = array();
230:
231: 232: 233: 234: 235:
236: public $fieldParameters = array();
237:
238: 239: 240: 241: 242: 243:
244: protected $_methodCacheChange = false;
245:
246: 247: 248: 249: 250: 251: 252:
253: public function __construct($config = null, $autoConnect = true) {
254: if (!isset($config['prefix'])) {
255: $config['prefix'] = '';
256: }
257: parent::__construct($config);
258: $this->fullDebug = Configure::read('debug') > 1;
259: if (!$this->enabled()) {
260: throw new MissingConnectionException(array(
261: 'class' => get_class($this),
262: 'message' => __d('cake_dev', 'Selected driver is not enabled'),
263: 'enabled' => false
264: ));
265: }
266: if ($autoConnect) {
267: $this->connect();
268: }
269: }
270:
271: 272: 273: 274: 275: 276:
277: public function reconnect($config = array()) {
278: $this->disconnect();
279: $this->setConfig($config);
280: $this->_sources = null;
281:
282: return $this->connect();
283: }
284:
285: 286: 287: 288: 289:
290: public function disconnect() {
291: if ($this->_result instanceof PDOStatement) {
292: $this->_result->closeCursor();
293: }
294: $this->_connection = null;
295: $this->connected = false;
296: return true;
297: }
298:
299: 300: 301: 302: 303:
304: public function getConnection() {
305: return $this->_connection;
306: }
307:
308: 309: 310: 311: 312:
313: public function getVersion() {
314: return $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
315: }
316:
317: 318: 319: 320: 321: 322: 323: 324:
325: public function value($data, $column = null, $null = true) {
326: if (is_array($data) && !empty($data)) {
327: return array_map(
328: array(&$this, 'value'),
329: $data, array_fill(0, count($data), $column)
330: );
331: } elseif (is_object($data) && isset($data->type, $data->value)) {
332: if ($data->type === 'identifier') {
333: return $this->name($data->value);
334: } elseif ($data->type === 'expression') {
335: return $data->value;
336: }
337: } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
338: return $data;
339: }
340:
341: if ($data === null || (is_array($data) && empty($data))) {
342: return 'NULL';
343: }
344:
345: if (empty($column)) {
346: $column = $this->introspectType($data);
347: }
348:
349: switch ($column) {
350: case 'binary':
351: return $this->_connection->quote($data, PDO::PARAM_LOB);
352: case 'boolean':
353: return $this->_connection->quote($this->boolean($data, true), PDO::PARAM_BOOL);
354: case 'string':
355: case 'text':
356: return $this->_connection->quote($data, PDO::PARAM_STR);
357: default:
358: if ($data === '') {
359: return $null ? 'NULL' : '""';
360: }
361: if (is_float($data)) {
362: return str_replace(',', '.', strval($data));
363: }
364: if ((is_int($data) || $data === '0') || (
365: is_numeric($data) &&
366: strpos($data, ',') === false &&
367: $data[0] != '0' &&
368: strpos($data, 'e') === false)
369: ) {
370: return $data;
371: }
372: return $this->_connection->quote($data);
373: }
374: }
375:
376: 377: 378: 379: 380: 381: 382:
383: public function identifier($identifier) {
384: $obj = new stdClass();
385: $obj->type = 'identifier';
386: $obj->value = $identifier;
387: return $obj;
388: }
389:
390: 391: 392: 393: 394: 395: 396:
397: public function expression($expression) {
398: $obj = new stdClass();
399: $obj->type = 'expression';
400: $obj->value = $expression;
401: return $obj;
402: }
403:
404: 405: 406: 407: 408: 409: 410:
411: public function rawQuery($sql, $params = array()) {
412: $this->took = $this->numRows = false;
413: return $this->execute($sql, array(), $params);
414: }
415:
416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429:
430: public function execute($sql, $options = array(), $params = array()) {
431: $options += array('log' => $this->fullDebug);
432:
433: $t = microtime(true);
434: $this->_result = $this->_execute($sql, $params);
435:
436: if ($options['log']) {
437: $this->took = round((microtime(true) - $t) * 1000, 0);
438: $this->numRows = $this->affected = $this->lastAffected();
439: $this->logQuery($sql, $params);
440: }
441:
442: return $this->_result;
443: }
444:
445: 446: 447: 448: 449: 450: 451: 452: 453: 454:
455: protected function _execute($sql, $params = array(), $prepareOptions = array()) {
456: $sql = trim($sql);
457: if (preg_match('/^(?:CREATE|ALTER|DROP)\s+(?:TABLE|INDEX)/i', $sql)) {
458: $statements = array_filter(explode(';', $sql));
459: if (count($statements) > 1) {
460: $result = array_map(array($this, '_execute'), $statements);
461: return array_search(false, $result) === false;
462: }
463: }
464:
465: try {
466: $query = $this->_connection->prepare($sql, $prepareOptions);
467: $query->setFetchMode(PDO::FETCH_LAZY);
468: if (!$query->execute($params)) {
469: $this->_results = $query;
470: $query->closeCursor();
471: return false;
472: }
473: if (!$query->columnCount()) {
474: $query->closeCursor();
475: if (!$query->rowCount()) {
476: return true;
477: }
478: }
479: return $query;
480: } catch (PDOException $e) {
481: if (isset($query->queryString)) {
482: $e->queryString = $query->queryString;
483: } else {
484: $e->queryString = $sql;
485: }
486: throw $e;
487: }
488: }
489:
490: 491: 492: 493: 494: 495:
496: public function lastError(PDOStatement $query = null) {
497: if ($query) {
498: $error = $query->errorInfo();
499: } else {
500: $error = $this->_connection->errorInfo();
501: }
502: if (empty($error[2])) {
503: return null;
504: }
505: return $error[1] . ': ' . $error[2];
506: }
507:
508: 509: 510: 511: 512: 513: 514:
515: public function lastAffected($source = null) {
516: if ($this->hasResult()) {
517: return $this->_result->rowCount();
518: }
519: return 0;
520: }
521:
522: 523: 524: 525: 526: 527: 528:
529: public function lastNumRows($source = null) {
530: return $this->lastAffected();
531: }
532:
533: 534: 535: 536: 537:
538: public function query() {
539: $args = func_get_args();
540: $fields = null;
541: $order = null;
542: $limit = null;
543: $page = null;
544: $recursive = null;
545:
546: if (count($args) === 1) {
547: return $this->fetchAll($args[0]);
548: } elseif (count($args) > 1 && preg_match('/^find(\w*)By(.+)/', $args[0], $matches)) {
549: $params = $args[1];
550:
551: $findType = lcfirst($matches[1]);
552: $field = Inflector::underscore($matches[2]);
553:
554: $or = (strpos($field, '_or_') !== false);
555: if ($or) {
556: $field = explode('_or_', $field);
557: } else {
558: $field = explode('_and_', $field);
559: }
560: $off = count($field) - 1;
561:
562: if (isset($params[1 + $off])) {
563: $fields = $params[1 + $off];
564: }
565:
566: if (isset($params[2 + $off])) {
567: $order = $params[2 + $off];
568: }
569:
570: if (!array_key_exists(0, $params)) {
571: return false;
572: }
573:
574: $c = 0;
575: $conditions = array();
576:
577: foreach ($field as $f) {
578: $conditions[$args[2]->alias . '.' . $f] = $params[$c++];
579: }
580:
581: if ($or) {
582: $conditions = array('OR' => $conditions);
583: }
584:
585: if ($findType !== 'first' && $findType !== '') {
586: if (isset($params[3 + $off])) {
587: $limit = $params[3 + $off];
588: }
589:
590: if (isset($params[4 + $off])) {
591: $page = $params[4 + $off];
592: }
593:
594: if (isset($params[5 + $off])) {
595: $recursive = $params[5 + $off];
596: }
597: return $args[2]->find($findType, compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
598: }
599: if (isset($params[3 + $off])) {
600: $recursive = $params[3 + $off];
601: }
602: return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive'));
603: }
604: if (isset($args[1]) && $args[1] === true) {
605: return $this->fetchAll($args[0], true);
606: } elseif (isset($args[1]) && !is_array($args[1])) {
607: return $this->fetchAll($args[0], false);
608: } elseif (isset($args[1]) && is_array($args[1])) {
609: if (isset($args[2])) {
610: $cache = $args[2];
611: } else {
612: $cache = true;
613: }
614: return $this->fetchAll($args[0], $args[1], array('cache' => $cache));
615: }
616: }
617:
618: 619: 620: 621: 622: 623:
624: public function fetchRow($sql = null) {
625: if (is_string($sql) && strlen($sql) > 5 && !$this->execute($sql)) {
626: return null;
627: }
628:
629: if ($this->hasResult()) {
630: $this->resultSet($this->_result);
631: $resultRow = $this->fetchResult();
632: if (isset($resultRow[0])) {
633: $this->fetchVirtualField($resultRow);
634: }
635: return $resultRow;
636: }
637: return null;
638: }
639:
640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657:
658: public function fetchAll($sql, $params = array(), $options = array()) {
659: if (is_string($options)) {
660: $options = array('modelName' => $options);
661: }
662: if (is_bool($params)) {
663: $options['cache'] = $params;
664: $params = array();
665: }
666: $options += array('cache' => true);
667: $cache = $options['cache'];
668: if ($cache && ($cached = $this->getQueryCache($sql, $params)) !== false) {
669: return $cached;
670: }
671: $result = $this->execute($sql, array(), $params);
672: if ($result) {
673: $out = array();
674:
675: if ($this->hasResult()) {
676: $first = $this->fetchRow();
677: if ($first) {
678: $out[] = $first;
679: }
680: while ($item = $this->fetchResult()) {
681: if (isset($item[0])) {
682: $this->fetchVirtualField($item);
683: }
684: $out[] = $item;
685: }
686: }
687:
688: if (!is_bool($result) && $cache) {
689: $this->_writeQueryCache($sql, $out, $params);
690: }
691:
692: if (empty($out) && is_bool($this->_result)) {
693: return $this->_result;
694: }
695: return $out;
696: }
697: return false;
698: }
699:
700: 701: 702: 703: 704:
705: public function fetchResult() {
706: return false;
707: }
708:
709: 710: 711: 712: 713: 714:
715: public function fetchVirtualField(&$result) {
716: if (isset($result[0]) && is_array($result[0])) {
717: foreach ($result[0] as $field => $value) {
718: if (strpos($field, $this->virtualFieldSeparator) === false) {
719: continue;
720: }
721:
722: list($alias, $virtual) = explode($this->virtualFieldSeparator, $field);
723:
724: if (!ClassRegistry::isKeySet($alias)) {
725: return;
726: }
727:
728: $Model = ClassRegistry::getObject($alias);
729:
730: if ($Model->isVirtualField($virtual)) {
731: $result[$alias][$virtual] = $value;
732: unset($result[0][$field]);
733: }
734: }
735: if (empty($result[0])) {
736: unset($result[0]);
737: }
738: }
739: }
740:
741: 742: 743: 744: 745: 746: 747:
748: public function field($name, $sql) {
749: $data = $this->fetchRow($sql);
750: if (empty($data[$name])) {
751: return false;
752: }
753: return $data[$name];
754: }
755:
756: 757: 758: 759: 760: 761:
762: public function flushMethodCache() {
763: $this->_methodCacheChange = true;
764: static::$methodCache = array();
765: }
766:
767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777: 778:
779: public function cacheMethod($method, $key, $value = null) {
780: if ($this->cacheMethods === false) {
781: return $value;
782: }
783: if (!$this->_methodCacheChange && empty(static::$methodCache)) {
784: static::$methodCache = (array)Cache::read('method_cache', '_cake_core_');
785: }
786: if ($value === null) {
787: return (isset(static::$methodCache[$method][$key])) ? static::$methodCache[$method][$key] : null;
788: }
789: $this->_methodCacheChange = true;
790: return static::$methodCache[$method][$key] = $value;
791: }
792:
793: 794: 795: 796: 797: 798: 799: 800: 801: 802: 803: 804:
805: public function name($data) {
806: if (is_object($data) && isset($data->type)) {
807: return $data->value;
808: }
809: if ($data === '*') {
810: return '*';
811: }
812: if (is_array($data)) {
813: foreach ($data as $i => $dataItem) {
814: $data[$i] = $this->name($dataItem);
815: }
816: return $data;
817: }
818: $cacheKey = md5($this->startQuote . $data . $this->endQuote);
819: if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) {
820: return $return;
821: }
822: $data = trim($data);
823: if (preg_match('/^[\w-]+(?:\.[^ \*]*)*$/', $data)) {
824: if (strpos($data, '.') === false) {
825: return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote);
826: }
827: $items = explode('.', $data);
828: return $this->cacheMethod(__FUNCTION__, $cacheKey,
829: $this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote
830: );
831: }
832: if (preg_match('/^[\w-]+\.\*$/', $data)) {
833: return $this->cacheMethod(__FUNCTION__, $cacheKey,
834: $this->startQuote . str_replace('.*', $this->endQuote . '.*', $data)
835: );
836: }
837: if (preg_match('/^([\w-]+)\((.*)\)$/', $data, $matches)) {
838: return $this->cacheMethod(__FUNCTION__, $cacheKey,
839: $matches[1] . '(' . $this->name($matches[2]) . ')'
840: );
841: }
842: if (preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+' . preg_quote($this->alias) . '\s*([\w-]+)$/i', $data, $matches)) {
843: return $this->cacheMethod(
844: __FUNCTION__, $cacheKey,
845: preg_replace(
846: '/\s{2,}/', ' ', $this->name($matches[1]) . ' ' . $this->alias . ' ' . $this->name($matches[3])
847: )
848: );
849: }
850: if (preg_match('/^[\w-_\s]*[\w-_]+/', $data)) {
851: return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote);
852: }
853: return $this->cacheMethod(__FUNCTION__, $cacheKey, $data);
854: }
855:
856: 857: 858: 859: 860:
861: public function isConnected() {
862: if ($this->_connection === null) {
863: $connected = false;
864: } else {
865: try {
866: $connected = $this->_connection->query('SELECT 1');
867: } catch (Exception $e) {
868: $connected = false;
869: }
870: }
871: $this->connected = !empty($connected);
872: return $this->connected;
873: }
874:
875: 876: 877: 878: 879:
880: public function hasResult() {
881: return $this->_result instanceof PDOStatement;
882: }
883:
884: 885: 886: 887: 888: 889: 890:
891: public function getLog($sorted = false, $clear = true) {
892: if ($sorted) {
893: $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
894: } else {
895: $log = $this->_queriesLog;
896: }
897: if ($clear) {
898: $this->_queriesLog = array();
899: }
900: return array('log' => $log, 'count' => $this->_queriesCnt, 'time' => $this->_queriesTime);
901: }
902:
903: 904: 905: 906: 907: 908: 909:
910: public function showLog($sorted = false) {
911: $log = $this->getLog($sorted, false);
912: if (empty($log['log'])) {
913: return;
914: }
915: if (PHP_SAPI !== 'cli') {
916: $controller = null;
917: $View = new View($controller, false);
918: $View->set('sqlLogs', array($this->configKeyName => $log));
919: echo $View->element('sql_dump', array('_forced_from_dbo_' => true));
920: } else {
921: foreach ($log['log'] as $k => $i) {
922: print (($k + 1) . ". {$i['query']}\n");
923: }
924: }
925: }
926:
927: 928: 929: 930: 931: 932: 933:
934: public function logQuery($sql, $params = array()) {
935: $this->_queriesCnt++;
936: $this->_queriesTime += $this->took;
937: $this->_queriesLog[] = array(
938: 'query' => $sql,
939: 'params' => $params,
940: 'affected' => $this->affected,
941: 'numRows' => $this->numRows,
942: 'took' => $this->took
943: );
944: if (count($this->_queriesLog) > $this->_queriesLogMax) {
945: array_shift($this->_queriesLog);
946: }
947: }
948:
949: 950: 951: 952: 953: 954: 955: 956:
957: public function fullTableName($model, $quote = true, $schema = true) {
958: if (is_object($model)) {
959: $schemaName = $model->schemaName;
960: $table = $model->tablePrefix . $model->table;
961: } elseif (!empty($this->config['prefix']) && strpos($model, $this->config['prefix']) !== 0) {
962: $table = $this->config['prefix'] . strval($model);
963: } else {
964: $table = strval($model);
965: }
966:
967: if ($schema && !isset($schemaName)) {
968: $schemaName = $this->getSchemaName();
969: }
970:
971: if ($quote) {
972: if ($schema && !empty($schemaName)) {
973: if (strstr($table, '.') === false) {
974: return $this->name($schemaName) . '.' . $this->name($table);
975: }
976: }
977: return $this->name($table);
978: }
979:
980: if ($schema && !empty($schemaName)) {
981: if (strstr($table, '.') === false) {
982: return $schemaName . '.' . $table;
983: }
984: }
985:
986: return $table;
987: }
988:
989: 990: 991: 992: 993: 994: 995: 996: 997: 998: 999: 1000:
1001: public function create(Model $Model, $fields = null, $values = null) {
1002: $id = null;
1003:
1004: if (!$fields) {
1005: unset($fields, $values);
1006: $fields = array_keys($Model->data);
1007: $values = array_values($Model->data);
1008: }
1009: $count = count($fields);
1010:
1011: for ($i = 0; $i < $count; $i++) {
1012: $schema = $Model->schema();
1013: $valueInsert[] = $this->value($values[$i], $Model->getColumnType($fields[$i]), isset($schema[$fields[$i]]) ? $schema[$fields[$i]]['null'] : true);
1014: $fieldInsert[] = $this->name($fields[$i]);
1015: if ($fields[$i] === $Model->primaryKey) {
1016: $id = $values[$i];
1017: }
1018: }
1019:
1020: $query = array(
1021: 'table' => $this->fullTableName($Model),
1022: 'fields' => implode(', ', $fieldInsert),
1023: 'values' => implode(', ', $valueInsert)
1024: );
1025:
1026: if ($this->execute($this->renderStatement('create', $query))) {
1027: if (empty($id)) {
1028: $id = $this->lastInsertId($this->fullTableName($Model, false, false), $Model->primaryKey);
1029: }
1030: $Model->setInsertID($id);
1031: $Model->id = $id;
1032: return true;
1033: }
1034:
1035: $Model->onError();
1036: return false;
1037: }
1038:
1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048:
1049: public function read(Model $Model, $queryData = array(), $recursive = null) {
1050: $queryData = $this->_scrubQueryData($queryData);
1051:
1052: $array = array('callbacks' => $queryData['callbacks']);
1053:
1054: if ($recursive === null && isset($queryData['recursive'])) {
1055: $recursive = $queryData['recursive'];
1056: }
1057:
1058: if ($recursive !== null) {
1059: $modelRecursive = $Model->recursive;
1060: $Model->recursive = $recursive;
1061: }
1062:
1063: if (!empty($queryData['fields'])) {
1064: $noAssocFields = true;
1065: $queryData['fields'] = $this->fields($Model, null, $queryData['fields']);
1066: } else {
1067: $noAssocFields = false;
1068: $queryData['fields'] = $this->fields($Model);
1069: }
1070:
1071: if ($Model->recursive === -1) {
1072:
1073: $associations = array();
1074:
1075: } else {
1076: $associations = $Model->associations();
1077:
1078: if ($Model->recursive === 0) {
1079:
1080: unset($associations[2], $associations[3]);
1081: }
1082: }
1083:
1084: $originalJoins = $queryData['joins'];
1085: $queryData['joins'] = array();
1086:
1087:
1088: $linkedModels = array();
1089: foreach ($associations as $type) {
1090: if ($type !== 'hasOne' && $type !== 'belongsTo') {
1091: continue;
1092: }
1093:
1094: foreach ($Model->{$type} as $assoc => $assocData) {
1095: $LinkModel = $Model->{$assoc};
1096:
1097: if ($Model->useDbConfig !== $LinkModel->useDbConfig) {
1098: continue;
1099: }
1100:
1101: if ($noAssocFields) {
1102: $assocData['fields'] = false;
1103: }
1104:
1105: $external = isset($assocData['external']);
1106:
1107: if ($this->generateAssociationQuery($Model, $LinkModel, $type, $assoc, $assocData, $queryData, $external) === true) {
1108: $linkedModels[$type . '/' . $assoc] = true;
1109: }
1110: }
1111: }
1112:
1113: if (!empty($originalJoins)) {
1114: $queryData['joins'] = array_merge($queryData['joins'], $originalJoins);
1115: }
1116:
1117:
1118: $query = $this->buildAssociationQuery($Model, $queryData);
1119:
1120: $resultSet = $this->fetchAll($query, $Model->cacheQueries);
1121: unset($query);
1122:
1123: if ($resultSet === false) {
1124: $Model->onError();
1125: return false;
1126: }
1127:
1128: $filtered = array();
1129:
1130:
1131: if ($Model->recursive > -1) {
1132: $joined = array();
1133: if (isset($queryData['joins'][0]['alias'])) {
1134: $joined[$Model->alias] = (array)Hash::extract($queryData['joins'], '{n}.alias');
1135: }
1136:
1137: foreach ($associations as $type) {
1138: foreach ($Model->{$type} as $assoc => $assocData) {
1139: $LinkModel = $Model->{$assoc};
1140:
1141: if (!isset($linkedModels[$type . '/' . $assoc])) {
1142: $db = $Model->useDbConfig === $LinkModel->useDbConfig ? $this : $LinkModel->getDataSource();
1143: } elseif ($Model->recursive > 1) {
1144: $db = $this;
1145: }
1146:
1147: if (isset($db) && method_exists($db, 'queryAssociation')) {
1148: $stack = array($assoc);
1149: $stack['_joined'] = $joined;
1150:
1151: $db->queryAssociation($Model, $LinkModel, $type, $assoc, $assocData, $array, true, $resultSet, $Model->recursive - 1, $stack);
1152: unset($db);
1153:
1154: if ($type === 'hasMany' || $type === 'hasAndBelongsToMany') {
1155: $filtered[] = $assoc;
1156: }
1157: }
1158: }
1159: }
1160: }
1161:
1162: if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1163: $this->_filterResults($resultSet, $Model, $filtered);
1164: }
1165:
1166: if ($recursive !== null) {
1167: $Model->recursive = $modelRecursive;
1168: }
1169:
1170: return $resultSet;
1171: }
1172:
1173: 1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182:
1183: protected function _filterResults(&$resultSet, Model $Model, $filtered = array()) {
1184: if (!is_array($resultSet)) {
1185: return array();
1186: }
1187:
1188: $current = reset($resultSet);
1189: if (!is_array($current)) {
1190: return array();
1191: }
1192:
1193: $keys = array_diff(array_keys($current), $filtered, array($Model->alias));
1194: $filtering = array();
1195:
1196: foreach ($keys as $className) {
1197: if (!isset($Model->{$className}) || !is_object($Model->{$className})) {
1198: continue;
1199: }
1200:
1201: $LinkedModel = $Model->{$className};
1202: $filtering[] = $className;
1203:
1204: foreach ($resultSet as $key => &$result) {
1205: $data = $LinkedModel->afterFind(array(array($className => $result[$className])), false);
1206: if (isset($data[0][$className])) {
1207: $result[$className] = $data[0][$className];
1208: } else {
1209: unset($resultSet[$key]);
1210: }
1211: }
1212: }
1213:
1214: return $filtering;
1215: }
1216:
1217: 1218: 1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227:
1228: protected function _filterResultsInclusive(&$resultSet, Model $Model, $toBeFiltered = array()) {
1229: $exclude = array();
1230:
1231: if (is_array($resultSet)) {
1232: $current = reset($resultSet);
1233: if (is_array($current)) {
1234: $exclude = array_diff(array_keys($current), $toBeFiltered);
1235: }
1236: }
1237:
1238: return $this->_filterResults($resultSet, $Model, $exclude);
1239: }
1240:
1241: 1242: 1243: 1244: 1245: 1246: 1247: 1248: 1249: 1250: 1251: 1252: 1253: 1254: 1255: 1256: 1257: 1258: 1259: 1260: 1261: 1262: 1263: 1264:
1265: public function queryAssociation(Model $Model, Model $LinkModel, $type, $association, $assocData, &$queryData, $external, &$resultSet, $recursive, $stack) {
1266: if (isset($stack['_joined'])) {
1267: $joined = $stack['_joined'];
1268: unset($stack['_joined']);
1269: }
1270:
1271: $queryTemplate = $this->generateAssociationQuery($Model, $LinkModel, $type, $association, $assocData, $queryData, $external);
1272: if (empty($queryTemplate)) {
1273: return null;
1274: }
1275:
1276: if (!is_array($resultSet)) {
1277: throw new CakeException(__d('cake_dev', 'Error in Model %s', get_class($Model)));
1278: }
1279:
1280: if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) {
1281:
1282:
1283: $assocIds = array();
1284: foreach ($resultSet as $result) {
1285: $assocIds[] = $this->insertQueryData('{$__cakeID__$}', $result, $association, $Model, $stack);
1286: }
1287: $assocIds = array_filter($assocIds);
1288:
1289:
1290: $assocResultSet = array();
1291: if (!empty($assocIds)) {
1292: $assocResultSet = $this->_fetchHasMany($Model, $queryTemplate, $assocIds);
1293: }
1294:
1295:
1296: if ($recursive > 0 && !empty($assocResultSet) && is_array($assocResultSet)) {
1297: foreach ($LinkModel->associations() as $type1) {
1298: foreach ($LinkModel->{$type1} as $assoc1 => $assocData1) {
1299: $DeepModel = $LinkModel->{$assoc1};
1300: $tmpStack = $stack;
1301: $tmpStack[] = $assoc1;
1302:
1303: $db = $LinkModel->useDbConfig === $DeepModel->useDbConfig ? $this : $DeepModel->getDataSource();
1304:
1305: $db->queryAssociation($LinkModel, $DeepModel, $type1, $assoc1, $assocData1, $queryData, true, $assocResultSet, $recursive - 1, $tmpStack);
1306: }
1307: }
1308: }
1309:
1310:
1311: if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1312: $this->_filterResultsInclusive($assocResultSet, $Model, array($association));
1313: }
1314:
1315:
1316: return $this->_mergeHasMany($resultSet, $assocResultSet, $association, $Model);
1317:
1318: } elseif ($type === 'hasAndBelongsToMany') {
1319:
1320:
1321: $assocIds = array();
1322: foreach ($resultSet as $result) {
1323: $assocIds[] = $this->insertQueryData('{$__cakeID__$}', $result, $association, $Model, $stack);
1324: }
1325: $assocIds = array_filter($assocIds);
1326:
1327:
1328: $assocResultSet = array();
1329: if (!empty($assocIds)) {
1330: $assocResultSet = $this->_fetchHasAndBelongsToMany($Model, $queryTemplate, $assocIds, $association);
1331: }
1332:
1333: $habtmAssocData = $Model->hasAndBelongsToMany[$association];
1334: $foreignKey = $habtmAssocData['foreignKey'];
1335: $joinKeys = array($foreignKey, $habtmAssocData['associationForeignKey']);
1336: list($with, $habtmFields) = $Model->joinModel($habtmAssocData['with'], $joinKeys);
1337: $habtmFieldsCount = count($habtmFields);
1338:
1339:
1340: if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1341: $this->_filterResultsInclusive($assocResultSet, $Model, array($association, $with));
1342: }
1343: }
1344:
1345: $modelAlias = $Model->alias;
1346: $primaryKey = $Model->primaryKey;
1347: $selfJoin = ($Model->name === $LinkModel->name);
1348:
1349: foreach ($resultSet as &$row) {
1350: if ($type === 'hasOne' || $type === 'belongsTo' || $type === 'hasMany') {
1351: $assocResultSet = array();
1352: $prefetched = false;
1353:
1354: if (($type === 'hasOne' || $type === 'belongsTo') &&
1355: isset($row[$LinkModel->alias], $joined[$Model->alias]) &&
1356: in_array($LinkModel->alias, $joined[$Model->alias])
1357: ) {
1358: $joinedData = Hash::filter($row[$LinkModel->alias]);
1359: if (!empty($joinedData)) {
1360: $assocResultSet[0] = array($LinkModel->alias => $row[$LinkModel->alias]);
1361: }
1362: $prefetched = true;
1363: } else {
1364: $query = $this->insertQueryData($queryTemplate, $row, $association, $Model, $stack);
1365: if ($query !== false) {
1366: $assocResultSet = $this->fetchAll($query, $Model->cacheQueries);
1367: }
1368: }
1369: }
1370:
1371: if (!empty($assocResultSet) && is_array($assocResultSet)) {
1372: if ($recursive > 0) {
1373: foreach ($LinkModel->associations() as $type1) {
1374: foreach ($LinkModel->{$type1} as $assoc1 => $assocData1) {
1375: $DeepModel = $LinkModel->{$assoc1};
1376:
1377: if ($type1 === 'belongsTo' ||
1378: ($type === 'belongsTo' && $DeepModel->alias === $modelAlias) ||
1379: ($DeepModel->alias !== $modelAlias)
1380: ) {
1381: $tmpStack = $stack;
1382: $tmpStack[] = $assoc1;
1383:
1384: $db = $LinkModel->useDbConfig === $DeepModel->useDbConfig ? $this : $DeepModel->getDataSource();
1385:
1386: $db->queryAssociation($LinkModel, $DeepModel, $type1, $assoc1, $assocData1, $queryData, true, $assocResultSet, $recursive - 1, $tmpStack);
1387: }
1388: }
1389: }
1390: }
1391:
1392: if ($type === 'hasAndBelongsToMany') {
1393: $merge = array();
1394: foreach ($assocResultSet as $data) {
1395: if (isset($data[$with]) && $data[$with][$foreignKey] === $row[$modelAlias][$primaryKey]) {
1396: if ($habtmFieldsCount <= 2) {
1397: unset($data[$with]);
1398: }
1399: $merge[] = $data;
1400: }
1401: }
1402:
1403: if (empty($merge) && !isset($row[$association])) {
1404: $row[$association] = $merge;
1405: } else {
1406: $this->_mergeAssociation($row, $merge, $association, $type);
1407: }
1408: } else {
1409: if (!$prefetched && $LinkModel->useConsistentAfterFind) {
1410: if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1411: $this->_filterResultsInclusive($assocResultSet, $Model, array($association));
1412: }
1413: }
1414: $this->_mergeAssociation($row, $assocResultSet, $association, $type, $selfJoin);
1415: }
1416:
1417: if ($type !== 'hasAndBelongsToMany' && isset($row[$association]) && !$prefetched && !$LinkModel->useConsistentAfterFind) {
1418: $row[$association] = $LinkModel->afterFind($row[$association], false);
1419: }
1420:
1421: } else {
1422: $tempArray[0][$association] = false;
1423: $this->_mergeAssociation($row, $tempArray, $association, $type, $selfJoin);
1424: }
1425: }
1426: }
1427:
1428: 1429: 1430: 1431: 1432: 1433: 1434: 1435: 1436: 1437: 1438:
1439: public function fetchAssociated(Model $Model, $query, $ids) {
1440: return $this->_fetchHasMany($Model, $query, $ids);
1441: }
1442:
1443: 1444: 1445: 1446: 1447: 1448: 1449: 1450:
1451: protected function _fetchHasMany(Model $Model, $query, $ids) {
1452: $ids = array_unique($ids);
1453:
1454: if (count($ids) > 1) {
1455: $query = str_replace('= ({$__cakeID__$}', 'IN ({$__cakeID__$}', $query);
1456: }
1457: $query = str_replace('{$__cakeID__$}', implode(', ', $ids), $query);
1458: return $this->fetchAll($query, $Model->cacheQueries);
1459: }
1460:
1461: 1462: 1463: 1464: 1465: 1466: 1467: 1468: 1469:
1470: protected function _fetchHasAndBelongsToMany(Model $Model, $query, $ids, $association) {
1471: $ids = array_unique($ids);
1472:
1473: if (count($ids) > 1) {
1474: $query = str_replace('{$__cakeID__$}', '(' . implode(', ', $ids) . ')', $query);
1475: $query = str_replace('= (', 'IN (', $query);
1476: } else {
1477: $query = str_replace('{$__cakeID__$}', $ids[0], $query);
1478: }
1479: $query = str_replace(' WHERE 1 = 1', '', $query);
1480:
1481: return $this->fetchAll($query, $Model->cacheQueries);
1482: }
1483:
1484: 1485: 1486: 1487: 1488: 1489: 1490: 1491: 1492: 1493: 1494:
1495: protected function _mergeHasMany(&$resultSet, $assocResultSet, $association, Model $Model) {
1496: $modelAlias = $Model->alias;
1497: $primaryKey = $Model->primaryKey;
1498: $foreignKey = $Model->hasMany[$association]['foreignKey'];
1499:
1500:
1501:
1502: $mergedByFK = array();
1503: foreach ($assocResultSet as $data) {
1504: $fk = $data[$association][$foreignKey];
1505: if (! array_key_exists($fk, $mergedByFK)) {
1506: $mergedByFK[$fk] = array();
1507: }
1508: if (count($data) > 1) {
1509: $data = array_merge($data[$association], $data);
1510: unset($data[$association]);
1511: foreach ($data as $key => $name) {
1512: if (is_numeric($key)) {
1513: $data[$association][] = $name;
1514: unset($data[$key]);
1515: }
1516: }
1517: $mergedByFK[$fk][] = $data;
1518: } else {
1519: $mergedByFK[$fk][] = $data[$association];
1520: }
1521: }
1522:
1523: foreach ($resultSet as &$result) {
1524: if (!isset($result[$modelAlias])) {
1525: continue;
1526: }
1527: $merged = array();
1528: $pk = $result[$modelAlias][$primaryKey];
1529: if (isset($mergedByFK[$pk])) {
1530: $merged = $mergedByFK[$pk];
1531: }
1532: $result = Hash::mergeDiff($result, array($association => $merged));
1533: }
1534: }
1535:
1536: 1537: 1538: 1539: 1540: 1541: 1542: 1543: 1544: 1545:
1546: protected function _mergeAssociation(&$data, &$merge, $association, $type, $selfJoin = false) {
1547: if (isset($merge[0]) && !isset($merge[0][$association])) {
1548: $association = Inflector::pluralize($association);
1549: }
1550:
1551: $dataAssociation =& $data[$association];
1552:
1553: if ($type === 'belongsTo' || $type === 'hasOne') {
1554: if (isset($merge[$association])) {
1555: $dataAssociation = $merge[$association][0];
1556: } else {
1557: if (!empty($merge[0][$association])) {
1558: foreach ($merge[0] as $assoc => $data2) {
1559: if ($assoc !== $association) {
1560: $merge[0][$association][$assoc] = $data2;
1561: }
1562: }
1563: }
1564: if (!isset($dataAssociation)) {
1565: $dataAssociation = array();
1566: if ($merge[0][$association]) {
1567: $dataAssociation = $merge[0][$association];
1568: }
1569: } else {
1570: if (is_array($merge[0][$association])) {
1571: $mergeAssocTmp = array();
1572: foreach ($dataAssociation as $k => $v) {
1573: if (!is_array($v)) {
1574: $dataAssocTmp[$k] = $v;
1575: }
1576: }
1577:
1578: foreach ($merge[0][$association] as $k => $v) {
1579: if (!is_array($v)) {
1580: $mergeAssocTmp[$k] = $v;
1581: }
1582: }
1583: $dataKeys = array_keys($data);
1584: $mergeKeys = array_keys($merge[0]);
1585:
1586: if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) {
1587: $dataAssociation[$association] = $merge[0][$association];
1588: } else {
1589: $diff = Hash::diff($dataAssocTmp, $mergeAssocTmp);
1590: $dataAssociation = array_merge($merge[0][$association], $diff);
1591: }
1592: } elseif ($selfJoin && array_key_exists($association, $merge[0])) {
1593: $dataAssociation = array_merge($dataAssociation, array($association => array()));
1594: }
1595: }
1596: }
1597: } else {
1598: if (isset($merge[0][$association]) && $merge[0][$association] === false) {
1599: if (!isset($dataAssociation)) {
1600: $dataAssociation = array();
1601: }
1602: } else {
1603: foreach ($merge as $row) {
1604: $insert = array();
1605: if (count($row) === 1) {
1606: $insert = $row[$association];
1607: } elseif (isset($row[$association])) {
1608: $insert = array_merge($row[$association], $row);
1609: unset($insert[$association]);
1610: }
1611:
1612: if (empty($dataAssociation) || (isset($dataAssociation) && !in_array($insert, $dataAssociation, true))) {
1613: $dataAssociation[] = $insert;
1614: }
1615: }
1616: }
1617: }
1618: }
1619:
1620: 1621: 1622: 1623: 1624: 1625: 1626: 1627: 1628:
1629: public function prepareFields(Model $Model, $queryData) {
1630: if (empty($queryData['fields'])) {
1631: $queryData['fields'] = $this->fields($Model);
1632:
1633: } elseif (!empty($Model->hasMany) && $Model->recursive > -1) {
1634:
1635: $assocFields = $this->fields($Model, null, "{$Model->alias}.{$Model->primaryKey}");
1636: $passedFields = $queryData['fields'];
1637:
1638: if (count($passedFields) > 1 ||
1639: (strpos($passedFields[0], $assocFields[0]) === false && !preg_match('/^[a-z]+\(/i', $passedFields[0]))
1640: ) {
1641: $queryData['fields'] = array_merge($passedFields, $assocFields);
1642: }
1643: }
1644:
1645: return array_unique($queryData['fields']);
1646: }
1647:
1648: 1649: 1650: 1651: 1652: 1653: 1654: 1655: 1656: 1657:
1658: public function buildAssociationQuery(Model $Model, $queryData) {
1659: $queryData = $this->_scrubQueryData($queryData);
1660:
1661: return $this->buildStatement(
1662: array(
1663: 'fields' => $this->prepareFields($Model, $queryData),
1664: 'table' => $this->fullTableName($Model),
1665: 'alias' => $Model->alias,
1666: 'limit' => $queryData['limit'],
1667: 'offset' => $queryData['offset'],
1668: 'joins' => $queryData['joins'],
1669: 'conditions' => $queryData['conditions'],
1670: 'order' => $queryData['order'],
1671: 'group' => $queryData['group']
1672: ),
1673: $Model
1674: );
1675: }
1676:
1677: 1678: 1679: 1680: 1681: 1682: 1683: 1684: 1685: 1686: 1687: 1688: 1689: 1690: 1691: 1692:
1693: public function generateAssociationQuery(Model $Model, $LinkModel, $type, $association, $assocData, &$queryData, $external) {
1694: $assocData = $this->_scrubQueryData($assocData);
1695: $queryData = $this->_scrubQueryData($queryData);
1696:
1697: if ($LinkModel === null) {
1698: return $this->buildStatement(
1699: array(
1700: 'fields' => array_unique($queryData['fields']),
1701: 'table' => $this->fullTableName($Model),
1702: 'alias' => $Model->alias,
1703: 'limit' => $queryData['limit'],
1704: 'offset' => $queryData['offset'],
1705: 'joins' => $queryData['joins'],
1706: 'conditions' => $queryData['conditions'],
1707: 'order' => $queryData['order'],
1708: 'group' => $queryData['group']
1709: ),
1710: $Model
1711: );
1712: }
1713:
1714: if ($external && !empty($assocData['finderQuery'])) {
1715: return $assocData['finderQuery'];
1716: }
1717:
1718: if ($type === 'hasMany' || $type === 'hasAndBelongsToMany') {
1719: if (empty($assocData['offset']) && !empty($assocData['page'])) {
1720: $assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
1721: }
1722: }
1723:
1724: switch ($type) {
1725: case 'hasOne':
1726: case 'belongsTo':
1727: $conditions = $this->_mergeConditions(
1728: $assocData['conditions'],
1729: $this->getConstraint($type, $Model, $LinkModel, $association, array_merge($assocData, compact('external')))
1730: );
1731:
1732: if ($external) {
1733:
1734: if ($Model->name !== $LinkModel->name) {
1735: $modelAlias = $Model->alias;
1736: foreach ($conditions as $key => $condition) {
1737: if (is_numeric($key) && strpos($condition, $modelAlias . '.') !== false) {
1738: unset($conditions[$key]);
1739: }
1740: }
1741: }
1742:
1743: $query = array_merge($assocData, array(
1744: 'conditions' => $conditions,
1745: 'table' => $this->fullTableName($LinkModel),
1746: 'fields' => $this->fields($LinkModel, $association, $assocData['fields']),
1747: 'alias' => $association,
1748: 'group' => null
1749: ));
1750: } else {
1751: $join = array(
1752: 'table' => $LinkModel,
1753: 'alias' => $association,
1754: 'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1755: 'conditions' => trim($this->conditions($conditions, true, false, $Model))
1756: );
1757:
1758: $fields = array();
1759: if ($assocData['fields'] !== false) {
1760: $fields = $this->fields($LinkModel, $association, $assocData['fields']);
1761: }
1762:
1763: $queryData['fields'] = array_merge($this->prepareFields($Model, $queryData), $fields);
1764:
1765: if (!empty($assocData['order'])) {
1766: $queryData['order'][] = $assocData['order'];
1767: }
1768: if (!in_array($join, $queryData['joins'], true)) {
1769: $queryData['joins'][] = $join;
1770: }
1771:
1772: return true;
1773: }
1774: break;
1775: case 'hasMany':
1776: $assocData['fields'] = $this->fields($LinkModel, $association, $assocData['fields']);
1777: if (!empty($assocData['foreignKey'])) {
1778: $assocData['fields'] = array_merge($assocData['fields'], $this->fields($LinkModel, $association, array("{$association}.{$assocData['foreignKey']}")));
1779: }
1780:
1781: $query = array(
1782: 'conditions' => $this->_mergeConditions($this->getConstraint('hasMany', $Model, $LinkModel, $association, $assocData), $assocData['conditions']),
1783: 'fields' => array_unique($assocData['fields']),
1784: 'table' => $this->fullTableName($LinkModel),
1785: 'alias' => $association,
1786: 'order' => $assocData['order'],
1787: 'limit' => $assocData['limit'],
1788: 'offset' => $assocData['offset'],
1789: 'group' => null
1790: );
1791: break;
1792: case 'hasAndBelongsToMany':
1793: $joinFields = array();
1794: $joinAssoc = null;
1795:
1796: if (isset($assocData['with']) && !empty($assocData['with'])) {
1797: $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
1798: list($with, $joinFields) = $Model->joinModel($assocData['with'], $joinKeys);
1799:
1800: $joinTbl = $Model->{$with};
1801: $joinAlias = $joinTbl;
1802:
1803: if (is_array($joinFields) && !empty($joinFields)) {
1804: $joinAssoc = $joinAlias = $joinTbl->alias;
1805: $joinFields = $this->fields($joinTbl, $joinAlias, $joinFields);
1806: } else {
1807: $joinFields = array();
1808: }
1809: } else {
1810: $joinTbl = $assocData['joinTable'];
1811: $joinAlias = $this->fullTableName($assocData['joinTable']);
1812: }
1813:
1814: $query = array(
1815: 'conditions' => $assocData['conditions'],
1816: 'limit' => $assocData['limit'],
1817: 'offset' => $assocData['offset'],
1818: 'table' => $this->fullTableName($LinkModel),
1819: 'alias' => $association,
1820: 'fields' => array_merge($this->fields($LinkModel, $association, $assocData['fields']), $joinFields),
1821: 'order' => $assocData['order'],
1822: 'group' => null,
1823: 'joins' => array(array(
1824: 'table' => $joinTbl,
1825: 'alias' => $joinAssoc,
1826: 'conditions' => $this->getConstraint('hasAndBelongsToMany', $Model, $LinkModel, $joinAlias, $assocData, $association)
1827: ))
1828: );
1829: break;
1830: }
1831:
1832: if (isset($query)) {
1833: return $this->buildStatement($query, $Model);
1834: }
1835:
1836: return null;
1837: }
1838:
1839: 1840: 1841: 1842: 1843: 1844: 1845: 1846: 1847: 1848: 1849:
1850: public function getConstraint($type, Model $Model, Model $LinkModel, $association, $assocData, $association2 = null) {
1851: $assocData += array('external' => false);
1852:
1853: if (empty($assocData['foreignKey'])) {
1854: return array();
1855: }
1856:
1857: switch ($type) {
1858: case 'hasOne':
1859: if ($assocData['external']) {
1860: return array(
1861: "{$association}.{$assocData['foreignKey']}" => '{$__cakeID__$}'
1862: );
1863: } else {
1864: return array(
1865: "{$association}.{$assocData['foreignKey']}" => $this->identifier("{$Model->alias}.{$Model->primaryKey}")
1866: );
1867: }
1868: case 'belongsTo':
1869: if ($assocData['external']) {
1870: return array(
1871: "{$association}.{$LinkModel->primaryKey}" => '{$__cakeForeignKey__$}'
1872: );
1873: } else {
1874: return array(
1875: "{$Model->alias}.{$assocData['foreignKey']}" => $this->identifier("{$association}.{$LinkModel->primaryKey}")
1876: );
1877: }
1878: case 'hasMany':
1879: return array("{$association}.{$assocData['foreignKey']}" => array('{$__cakeID__$}'));
1880: case 'hasAndBelongsToMany':
1881: return array(
1882: array(
1883: "{$association}.{$assocData['foreignKey']}" => '{$__cakeID__$}'
1884: ),
1885: array(
1886: "{$association}.{$assocData['associationForeignKey']}" => $this->identifier("{$association2}.{$LinkModel->primaryKey}")
1887: )
1888: );
1889: }
1890:
1891: return array();
1892: }
1893:
1894: 1895: 1896: 1897: 1898: 1899: 1900: 1901:
1902: public function buildJoinStatement($join) {
1903: $data = array_merge(array(
1904: 'type' => null,
1905: 'alias' => null,
1906: 'table' => 'join_table',
1907: 'conditions' => '',
1908: ), $join);
1909:
1910: if (!empty($data['alias'])) {
1911: $data['alias'] = $this->alias . $this->name($data['alias']);
1912: }
1913: if (!empty($data['conditions'])) {
1914: $data['conditions'] = trim($this->conditions($data['conditions'], true, false));
1915: }
1916: if (!empty($data['table']) && (!is_string($data['table']) || strpos($data['table'], '(') !== 0)) {
1917: $data['table'] = $this->fullTableName($data['table']);
1918: }
1919: return $this->renderJoinStatement($data);
1920: }
1921:
1922: 1923: 1924: 1925: 1926: 1927: 1928: 1929:
1930: public function buildStatement($query, Model $Model) {
1931: $query = array_merge($this->_queryDefaults, $query);
1932:
1933: if (!empty($query['joins'])) {
1934: $count = count($query['joins']);
1935: for ($i = 0; $i < $count; $i++) {
1936: if (is_array($query['joins'][$i])) {
1937: $query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
1938: }
1939: }
1940: }
1941:
1942: return $this->renderStatement('select', array(
1943: 'conditions' => $this->conditions($query['conditions'], true, true, $Model),
1944: 'fields' => implode(', ', $query['fields']),
1945: 'table' => $query['table'],
1946: 'alias' => $this->alias . $this->name($query['alias']),
1947: 'order' => $this->order($query['order'], 'ASC', $Model),
1948: 'limit' => $this->limit($query['limit'], $query['offset']),
1949: 'joins' => implode(' ', $query['joins']),
1950: 'group' => $this->group($query['group'], $Model)
1951: ));
1952: }
1953:
1954: 1955: 1956: 1957: 1958: 1959:
1960: public function renderJoinStatement($data) {
1961: if (strtoupper($data['type']) === 'CROSS' || empty($data['conditions'])) {
1962: return "{$data['type']} JOIN {$data['table']} {$data['alias']}";
1963: }
1964: return trim("{$data['type']} JOIN {$data['table']} {$data['alias']} ON ({$data['conditions']})");
1965: }
1966:
1967: 1968: 1969: 1970: 1971: 1972: 1973:
1974: public function renderStatement($type, $data) {
1975: extract($data);
1976: $aliases = null;
1977:
1978: switch (strtolower($type)) {
1979: case 'select':
1980: return trim("SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}");
1981: case 'create':
1982: return "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
1983: case 'update':
1984: if (!empty($alias)) {
1985: $aliases = "{$this->alias}{$alias} {$joins} ";
1986: }
1987: return trim("UPDATE {$table} {$aliases}SET {$fields} {$conditions}");
1988: case 'delete':
1989: if (!empty($alias)) {
1990: $aliases = "{$this->alias}{$alias} {$joins} ";
1991: }
1992: return trim("DELETE {$alias} FROM {$table} {$aliases}{$conditions}");
1993: case 'schema':
1994: foreach (array('columns', 'indexes', 'tableParameters') as $var) {
1995: if (is_array(${$var})) {
1996: ${$var} = "\t" . implode(",\n\t", array_filter(${$var}));
1997: } else {
1998: ${$var} = '';
1999: }
2000: }
2001: if (trim($indexes) !== '') {
2002: $columns .= ',';
2003: }
2004: return "CREATE TABLE {$table} (\n{$columns}{$indexes}) {$tableParameters};";
2005: case 'alter':
2006: return null;
2007: }
2008: }
2009:
2010: 2011: 2012: 2013: 2014: 2015: 2016:
2017: protected function _mergeConditions($query, $assoc) {
2018: if (empty($assoc)) {
2019: return $query;
2020: }
2021:
2022: if (is_array($query)) {
2023: return array_merge((array)$assoc, $query);
2024: }
2025:
2026: if (!empty($query)) {
2027: $query = array($query);
2028: if (is_array($assoc)) {
2029: $query = array_merge($query, $assoc);
2030: } else {
2031: $query[] = $assoc;
2032: }
2033: return $query;
2034: }
2035:
2036: return $assoc;
2037: }
2038:
2039: 2040: 2041: 2042: 2043: 2044: 2045: 2046: 2047: 2048:
2049: public function update(Model $Model, $fields = array(), $values = null, $conditions = null) {
2050: if (!$values) {
2051: $combined = $fields;
2052: } else {
2053: $combined = array_combine($fields, $values);
2054: }
2055:
2056: $fields = implode(', ', $this->_prepareUpdateFields($Model, $combined, empty($conditions)));
2057:
2058: $alias = $joins = null;
2059: $table = $this->fullTableName($Model);
2060: $conditions = $this->_matchRecords($Model, $conditions);
2061:
2062: if ($conditions === false) {
2063: return false;
2064: }
2065: $query = compact('table', 'alias', 'joins', 'fields', 'conditions');
2066:
2067: if (!$this->execute($this->renderStatement('update', $query))) {
2068: $Model->onError();
2069: return false;
2070: }
2071: return true;
2072: }
2073:
2074: 2075: 2076: 2077: 2078: 2079: 2080: 2081: 2082:
2083: protected function _prepareUpdateFields(Model $Model, $fields, $quoteValues = true, $alias = false) {
2084: $quotedAlias = $this->startQuote . $Model->alias . $this->endQuote;
2085: $schema = $Model->schema();
2086:
2087: $updates = array();
2088: foreach ($fields as $field => $value) {
2089: if ($alias && strpos($field, '.') === false) {
2090: $quoted = $Model->escapeField($field);
2091: } elseif (!$alias && strpos($field, '.') !== false) {
2092: $quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace(
2093: $Model->alias . '.', '', $field
2094: )));
2095: } else {
2096: $quoted = $this->name($field);
2097: }
2098:
2099: if ($value === null) {
2100: $updates[] = $quoted . ' = NULL';
2101: continue;
2102: }
2103: $update = $quoted . ' = ';
2104:
2105: if ($quoteValues) {
2106: $update .= $this->value($value, $Model->getColumnType($field), isset($schema[$field]) ? $schema[$field]['null'] : true);
2107: } elseif ($Model->getColumnType($field) === 'boolean' && (is_int($value) || is_bool($value))) {
2108: $update .= $this->boolean($value, true);
2109: } elseif (!$alias) {
2110: $update .= str_replace($quotedAlias . '.', '', str_replace(
2111: $Model->alias . '.', '', $value
2112: ));
2113: } else {
2114: $update .= $value;
2115: }
2116: $updates[] = $update;
2117: }
2118: return $updates;
2119: }
2120:
2121: 2122: 2123: 2124: 2125: 2126: 2127: 2128:
2129: public function delete(Model $Model, $conditions = null) {
2130: $alias = $joins = null;
2131: $table = $this->fullTableName($Model);
2132: $conditions = $this->_matchRecords($Model, $conditions);
2133:
2134: if ($conditions === false) {
2135: return false;
2136: }
2137:
2138: if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
2139: $Model->onError();
2140: return false;
2141: }
2142: return true;
2143: }
2144:
2145: 2146: 2147: 2148: 2149: 2150: 2151: 2152:
2153: protected function _matchRecords(Model $Model, $conditions = null) {
2154: if ($conditions === true) {
2155: $conditions = $this->conditions(true);
2156: } elseif ($conditions === null) {
2157: $conditions = $this->conditions($this->defaultConditions($Model, $conditions, false), true, true, $Model);
2158: } else {
2159: $noJoin = true;
2160: foreach ($conditions as $field => $value) {
2161: $originalField = $field;
2162: if (strpos($field, '.') !== false) {
2163: list(, $field) = explode('.', $field);
2164: $field = ltrim($field, $this->startQuote);
2165: $field = rtrim($field, $this->endQuote);
2166: }
2167: if (!$Model->hasField($field)) {
2168: $noJoin = false;
2169: break;
2170: }
2171: if ($field !== $originalField) {
2172: $conditions[$field] = $value;
2173: unset($conditions[$originalField]);
2174: }
2175: }
2176: if ($noJoin === true) {
2177: return $this->conditions($conditions);
2178: }
2179: $idList = $Model->find('all', array(
2180: 'fields' => "{$Model->alias}.{$Model->primaryKey}",
2181: 'conditions' => $conditions
2182: ));
2183:
2184: if (empty($idList)) {
2185: return false;
2186: }
2187:
2188: $conditions = $this->conditions(array(
2189: $Model->primaryKey => Hash::extract($idList, "{n}.{$Model->alias}.{$Model->primaryKey}")
2190: ));
2191: }
2192:
2193: return $conditions;
2194: }
2195:
2196: 2197: 2198: 2199: 2200: 2201:
2202: protected function _getJoins(Model $Model) {
2203: $join = array();
2204: $joins = array_merge($Model->getAssociated('hasOne'), $Model->getAssociated('belongsTo'));
2205:
2206: foreach ($joins as $assoc) {
2207: if (!isset($Model->{$assoc})) {
2208: continue;
2209: }
2210:
2211: $LinkModel = $Model->{$assoc};
2212:
2213: if ($Model->useDbConfig !== $LinkModel->useDbConfig) {
2214: continue;
2215: }
2216:
2217: $assocData = $Model->getAssociated($assoc);
2218:
2219: $join[] = $this->buildJoinStatement(array(
2220: 'table' => $LinkModel,
2221: 'alias' => $assoc,
2222: 'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
2223: 'conditions' => trim($this->conditions(
2224: $this->_mergeConditions($assocData['conditions'], $this->getConstraint($assocData['association'], $Model, $LinkModel, $assoc, $assocData)),
2225: true,
2226: false,
2227: $Model
2228: ))
2229: ));
2230: }
2231:
2232: return $join;
2233: }
2234:
2235: 2236: 2237: 2238: 2239: 2240: 2241: 2242:
2243: public function calculate(Model $Model, $func, $params = array()) {
2244: $params = (array)$params;
2245:
2246: switch (strtolower($func)) {
2247: case 'count':
2248: if (!isset($params[0])) {
2249: $params[0] = '*';
2250: }
2251: if (!isset($params[1])) {
2252: $params[1] = 'count';
2253: }
2254: if ($Model->isVirtualField($params[0])) {
2255: $arg = $this->_quoteFields($Model->getVirtualField($params[0]));
2256: } else {
2257: $arg = $this->name($params[0]);
2258: }
2259: return 'COUNT(' . $arg . ') AS ' . $this->name($params[1]);
2260: case 'max':
2261: case 'min':
2262: if (!isset($params[1])) {
2263: $params[1] = $params[0];
2264: }
2265: if ($Model->isVirtualField($params[0])) {
2266: $arg = $this->_quoteFields($Model->getVirtualField($params[0]));
2267: } else {
2268: $arg = $this->name($params[0]);
2269: }
2270: return strtoupper($func) . '(' . $arg . ') AS ' . $this->name($params[1]);
2271: }
2272: }
2273:
2274: 2275: 2276: 2277: 2278: 2279: 2280:
2281: public function truncate($table) {
2282: return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table));
2283: }
2284:
2285: 2286: 2287: 2288: 2289:
2290: public function nestedTransactionSupported() {
2291: return false;
2292: }
2293:
2294: 2295: 2296: 2297: 2298: 2299: 2300:
2301: public function begin() {
2302: if ($this->_transactionStarted) {
2303: if ($this->nestedTransactionSupported()) {
2304: return $this->_beginNested();
2305: }
2306: $this->_transactionNesting++;
2307: return $this->_transactionStarted;
2308: }
2309:
2310: $this->_transactionNesting = 0;
2311: if ($this->fullDebug) {
2312: $this->took = $this->numRows = $this->affected = false;
2313: $this->logQuery('BEGIN');
2314: }
2315: return $this->_transactionStarted = $this->_connection->beginTransaction();
2316: }
2317:
2318: 2319: 2320: 2321: 2322:
2323: protected function _beginNested() {
2324: $query = 'SAVEPOINT LEVEL' . ++$this->_transactionNesting;
2325: if ($this->fullDebug) {
2326: $this->took = $this->numRows = $this->affected = false;
2327: $this->logQuery($query);
2328: }
2329: $this->_connection->exec($query);
2330: return true;
2331: }
2332:
2333: 2334: 2335: 2336: 2337: 2338: 2339:
2340: public function commit() {
2341: if (!$this->_transactionStarted) {
2342: return false;
2343: }
2344:
2345: if ($this->_transactionNesting === 0) {
2346: if ($this->fullDebug) {
2347: $this->took = $this->numRows = $this->affected = false;
2348: $this->logQuery('COMMIT');
2349: }
2350: $this->_transactionStarted = false;
2351: return $this->_connection->commit();
2352: }
2353:
2354: if ($this->nestedTransactionSupported()) {
2355: return $this->_commitNested();
2356: }
2357:
2358: $this->_transactionNesting--;
2359: return true;
2360: }
2361:
2362: 2363: 2364: 2365: 2366:
2367: protected function _commitNested() {
2368: $query = 'RELEASE SAVEPOINT LEVEL' . $this->_transactionNesting--;
2369: if ($this->fullDebug) {
2370: $this->took = $this->numRows = $this->affected = false;
2371: $this->logQuery($query);
2372: }
2373: $this->_connection->exec($query);
2374: return true;
2375: }
2376:
2377: 2378: 2379: 2380: 2381: 2382: 2383:
2384: public function rollback() {
2385: if (!$this->_transactionStarted) {
2386: return false;
2387: }
2388:
2389: if ($this->_transactionNesting === 0) {
2390: if ($this->fullDebug) {
2391: $this->took = $this->numRows = $this->affected = false;
2392: $this->logQuery('ROLLBACK');
2393: }
2394: $this->_transactionStarted = false;
2395: return $this->_connection->rollBack();
2396: }
2397:
2398: if ($this->nestedTransactionSupported()) {
2399: return $this->_rollbackNested();
2400: }
2401:
2402: $this->_transactionNesting--;
2403: return true;
2404: }
2405:
2406: 2407: 2408: 2409: 2410:
2411: protected function _rollbackNested() {
2412: $query = 'ROLLBACK TO SAVEPOINT LEVEL' . $this->_transactionNesting--;
2413: if ($this->fullDebug) {
2414: $this->took = $this->numRows = $this->affected = false;
2415: $this->logQuery($query);
2416: }
2417: $this->_connection->exec($query);
2418: return true;
2419: }
2420:
2421: 2422: 2423: 2424: 2425: 2426:
2427: public function lastInsertId($source = null) {
2428: return $this->_connection->lastInsertId();
2429: }
2430:
2431: 2432: 2433: 2434: 2435: 2436: 2437: 2438: 2439: 2440: 2441: 2442: 2443: 2444:
2445: public function defaultConditions(Model $Model, $conditions, $useAlias = true) {
2446: if (!empty($conditions)) {
2447: return $conditions;
2448: }
2449: $exists = $Model->exists();
2450: if (!$exists && ($conditions !== null || !empty($Model->__safeUpdateMode))) {
2451: return false;
2452: } elseif (!$exists) {
2453: return null;
2454: }
2455: $alias = $Model->alias;
2456:
2457: if (!$useAlias) {
2458: $alias = $this->fullTableName($Model, false);
2459: }
2460: return array("{$alias}.{$Model->primaryKey}" => $Model->getID());
2461: }
2462:
2463: 2464: 2465: 2466: 2467: 2468: 2469: 2470:
2471: public function resolveKey(Model $Model, $key, $assoc = null) {
2472: if (strpos('.', $key) !== false) {
2473: return $this->name($Model->alias) . '.' . $this->name($key);
2474: }
2475: return $key;
2476: }
2477:
2478: 2479: 2480: 2481: 2482: 2483:
2484: protected function _scrubQueryData($data) {
2485: static $base = null;
2486: if ($base === null) {
2487: $base = array_fill_keys(array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group'), array());
2488: $base['callbacks'] = null;
2489: }
2490: return (array)$data + $base;
2491: }
2492:
2493: 2494: 2495: 2496: 2497: 2498: 2499: 2500:
2501: protected function _constructVirtualFields(Model $Model, $alias, $fields) {
2502: $virtual = array();
2503: foreach ($fields as $field) {
2504: $virtualField = $this->name($alias . $this->virtualFieldSeparator . $field);
2505: $expression = $this->_quoteFields($Model->getVirtualField($field));
2506: $virtual[] = '(' . $expression . ") {$this->alias} {$virtualField}";
2507: }
2508: return $virtual;
2509: }
2510:
2511: 2512: 2513: 2514: 2515: 2516: 2517: 2518: 2519:
2520: public function fields(Model $Model, $alias = null, $fields = array(), $quote = true) {
2521: if (empty($alias)) {
2522: $alias = $Model->alias;
2523: }
2524: $virtualFields = $Model->getVirtualField();
2525: $cacheKey = array(
2526: $alias,
2527: get_class($Model),
2528: $Model->alias,
2529: $virtualFields,
2530: $fields,
2531: $quote,
2532: ConnectionManager::getSourceName($this),
2533: $Model->schemaName,
2534: $Model->table
2535: );
2536: $cacheKey = md5(serialize($cacheKey));
2537: if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) {
2538: return $return;
2539: }
2540: $allFields = empty($fields);
2541: if ($allFields) {
2542: $fields = array_keys($Model->schema());
2543: } elseif (!is_array($fields)) {
2544: $fields = CakeText::tokenize($fields);
2545: }
2546: $fields = array_values(array_filter($fields));
2547: $allFields = $allFields || in_array('*', $fields) || in_array($Model->alias . '.*', $fields);
2548:
2549: $virtual = array();
2550: if (!empty($virtualFields)) {
2551: $virtualKeys = array_keys($virtualFields);
2552: foreach ($virtualKeys as $field) {
2553: $virtualKeys[] = $Model->alias . '.' . $field;
2554: }
2555: $virtual = ($allFields) ? $virtualKeys : array_intersect($virtualKeys, $fields);
2556: foreach ($virtual as $i => $field) {
2557: if (strpos($field, '.') !== false) {
2558: $virtual[$i] = str_replace($Model->alias . '.', '', $field);
2559: }
2560: $fields = array_diff($fields, array($field));
2561: }
2562: $fields = array_values($fields);
2563: }
2564: if (!$quote) {
2565: if (!empty($virtual)) {
2566: $fields = array_merge($fields, $this->_constructVirtualFields($Model, $alias, $virtual));
2567: }
2568: return $fields;
2569: }
2570: $count = count($fields);
2571:
2572: if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) {
2573: for ($i = 0; $i < $count; $i++) {
2574: if (is_string($fields[$i]) && in_array($fields[$i], $virtual)) {
2575: unset($fields[$i]);
2576: continue;
2577: }
2578: if (is_object($fields[$i]) && isset($fields[$i]->type) && $fields[$i]->type === 'expression') {
2579: $fields[$i] = $fields[$i]->value;
2580: } elseif (preg_match('/^\(.*\)\s' . $this->alias . '.*/i', $fields[$i])) {
2581: continue;
2582: } elseif (!preg_match('/^.+\\(.*\\)/', $fields[$i])) {
2583: $prepend = '';
2584:
2585: if (strpos($fields[$i], 'DISTINCT') !== false) {
2586: $prepend = 'DISTINCT ';
2587: $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));
2588: }
2589: $dot = strpos($fields[$i], '.');
2590:
2591: if ($dot === false) {
2592: $prefix = !(
2593: strpos($fields[$i], ' ') !== false ||
2594: strpos($fields[$i], '(') !== false
2595: );
2596: $fields[$i] = $this->name(($prefix ? $alias . '.' : '') . $fields[$i]);
2597: } else {
2598: if (strpos($fields[$i], ',') === false) {
2599: $build = explode('.', $fields[$i]);
2600: if (!Hash::numeric($build)) {
2601: $fields[$i] = $this->name(implode('.', $build));
2602: }
2603: }
2604: }
2605: $fields[$i] = $prepend . $fields[$i];
2606: } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) {
2607: if (isset($field[1])) {
2608: if (strpos($field[1], '.') === false) {
2609: $field[1] = $this->name($alias . '.' . $field[1]);
2610: } else {
2611: $field[0] = explode('.', $field[1]);
2612: if (!Hash::numeric($field[0])) {
2613: $field[0] = implode('.', array_map(array(&$this, 'name'), $field[0]));
2614: $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1);
2615: }
2616: }
2617: }
2618: }
2619: }
2620: }
2621: if (!empty($virtual)) {
2622: $fields = array_merge($fields, $this->_constructVirtualFields($Model, $alias, $virtual));
2623: }
2624: return $this->cacheMethod(__FUNCTION__, $cacheKey, array_unique($fields));
2625: }
2626:
2627: 2628: 2629: 2630: 2631: 2632: 2633: 2634: 2635: 2636: 2637: 2638: 2639: 2640: 2641:
2642: public function conditions($conditions, $quoteValues = true, $where = true, Model $Model = null) {
2643: $clause = $out = '';
2644:
2645: if ($where) {
2646: $clause = ' WHERE ';
2647: }
2648:
2649: if (is_array($conditions) && !empty($conditions)) {
2650: $out = $this->conditionKeysToString($conditions, $quoteValues, $Model);
2651:
2652: if (empty($out)) {
2653: return $clause . ' 1 = 1';
2654: }
2655: return $clause . implode(' AND ', $out);
2656: }
2657:
2658: if (is_bool($conditions)) {
2659: return $clause . (int)$conditions . ' = 1';
2660: }
2661:
2662: if (empty($conditions) || trim($conditions) === '') {
2663: return $clause . '1 = 1';
2664: }
2665:
2666: $clauses = '/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i';
2667:
2668: if (preg_match($clauses, $conditions)) {
2669: $clause = '';
2670: }
2671:
2672: $conditions = $this->_quoteFields($conditions);
2673:
2674: return $clause . $conditions;
2675: }
2676:
2677: 2678: 2679: 2680: 2681: 2682: 2683: 2684:
2685: public function conditionKeysToString($conditions, $quoteValues = true, Model $Model = null) {
2686: $out = array();
2687: $data = $columnType = null;
2688:
2689: foreach ($conditions as $key => $value) {
2690: $join = ' AND ';
2691: $not = null;
2692:
2693: if (is_array($value)) {
2694: $valueInsert = (
2695: !empty($value) &&
2696: (substr_count($key, '?') === count($value) || substr_count($key, ':') === count($value))
2697: );
2698: }
2699:
2700: if (is_numeric($key) && empty($value)) {
2701: continue;
2702: } elseif (is_numeric($key) && is_string($value)) {
2703: $out[] = $this->_quoteFields($value);
2704: } elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $this->_sqlBoolOps)) {
2705: if (in_array(strtolower(trim($key)), $this->_sqlBoolOps)) {
2706: $join = ' ' . strtoupper($key) . ' ';
2707: } else {
2708: $key = $join;
2709: }
2710: $value = $this->conditionKeysToString($value, $quoteValues, $Model);
2711:
2712: if (strpos($join, 'NOT') !== false) {
2713: if (strtoupper(trim($key)) === 'NOT') {
2714: $key = 'AND ' . trim($key);
2715: }
2716: $not = 'NOT ';
2717: }
2718:
2719: if (empty($value)) {
2720: continue;
2721: }
2722:
2723: if (empty($value[1])) {
2724: if ($not) {
2725: $out[] = $not . '(' . $value[0] . ')';
2726: } else {
2727: $out[] = $value[0];
2728: }
2729: } else {
2730: $out[] = '(' . $not . '(' . implode(') ' . strtoupper($key) . ' (', $value) . '))';
2731: }
2732: } else {
2733: if (is_object($value) && isset($value->type)) {
2734: if ($value->type === 'identifier') {
2735: $data .= $this->name($key) . ' = ' . $this->name($value->value);
2736: } elseif ($value->type === 'expression') {
2737: if (is_numeric($key)) {
2738: $data .= $value->value;
2739: } else {
2740: $data .= $this->name($key) . ' = ' . $value->value;
2741: }
2742: }
2743: } elseif (is_array($value) && !empty($value) && !$valueInsert) {
2744: $keys = array_keys($value);
2745: if ($keys === array_values($keys)) {
2746: if (count($value) === 1 && !preg_match('/\s+(?:NOT|IN|\!=)$/', $key)) {
2747: $data = $this->_quoteFields($key) . ' = (';
2748: if ($quoteValues) {
2749: if ($Model !== null) {
2750: $columnType = $Model->getColumnType($key);
2751: }
2752: $data .= implode(', ', $this->value($value, $columnType));
2753: }
2754: $data .= ')';
2755: } else {
2756: $data = $this->_parseKey($key, $value, $Model);
2757: }
2758: } else {
2759: $ret = $this->conditionKeysToString($value, $quoteValues, $Model);
2760: if (count($ret) > 1) {
2761: $data = '(' . implode(') AND (', $ret) . ')';
2762: } elseif (isset($ret[0])) {
2763: $data = $ret[0];
2764: }
2765: }
2766: } elseif (is_numeric($key) && !empty($value)) {
2767: $data = $this->_quoteFields($value);
2768: } else {
2769: $data = $this->_parseKey(trim($key), $value, $Model);
2770: }
2771:
2772: if ($data) {
2773: $out[] = $data;
2774: $data = null;
2775: }
2776: }
2777: }
2778: return $out;
2779: }
2780:
2781: 2782: 2783: 2784: 2785: 2786: 2787: 2788: 2789:
2790: protected function _parseKey($key, $value, Model $Model = null) {
2791: $operatorMatch = '/^(((' . implode(')|(', $this->_sqlOps);
2792: $operatorMatch .= ')\\x20?)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is';
2793: $bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false));
2794:
2795: $key = trim($key);
2796: if (strpos($key, ' ') === false) {
2797: $operator = '=';
2798: } else {
2799: list($key, $operator) = explode(' ', $key, 2);
2800:
2801: if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) {
2802: $key = $key . ' ' . $operator;
2803: $split = strrpos($key, ' ');
2804: $operator = substr($key, $split);
2805: $key = substr($key, 0, $split);
2806: }
2807: }
2808:
2809: $virtual = false;
2810: $type = null;
2811:
2812: if ($Model !== null) {
2813: if ($Model->isVirtualField($key)) {
2814: $key = $this->_quoteFields($Model->getVirtualField($key));
2815: $virtual = true;
2816: }
2817:
2818: $type = $Model->getColumnType($key);
2819: }
2820:
2821: $null = $value === null || (is_array($value) && empty($value));
2822:
2823: if (strtolower($operator) === 'not') {
2824: $data = $this->conditionKeysToString(
2825: array($operator => array($key => $value)), true, $Model
2826: );
2827: return $data[0];
2828: }
2829:
2830: $value = $this->value($value, $type);
2831:
2832: if (!$virtual && $key !== '?') {
2833: $isKey = (
2834: strpos($key, '(') !== false ||
2835: strpos($key, ')') !== false ||
2836: strpos($key, '|') !== false
2837: );
2838: $key = $isKey ? $this->_quoteFields($key) : $this->name($key);
2839: }
2840:
2841: if ($bound) {
2842: return CakeText::insert($key . ' ' . trim($operator), $value);
2843: }
2844:
2845: if (!preg_match($operatorMatch, trim($operator))) {
2846: $operator .= is_array($value) ? ' IN' : ' =';
2847: }
2848: $operator = trim($operator);
2849:
2850: if (is_array($value)) {
2851: $value = implode(', ', $value);
2852:
2853: switch ($operator) {
2854: case '=':
2855: $operator = 'IN';
2856: break;
2857: case '!=':
2858: case '<>':
2859: $operator = 'NOT IN';
2860: break;
2861: }
2862: $value = "({$value})";
2863: } elseif ($null || $value === 'NULL') {
2864: switch ($operator) {
2865: case '=':
2866: $operator = 'IS';
2867: break;
2868: case '!=':
2869: case '<>':
2870: $operator = 'IS NOT';
2871: break;
2872: }
2873: }
2874: if ($virtual) {
2875: return "({$key}) {$operator} {$value}";
2876: }
2877: return "{$key} {$operator} {$value}";
2878: }
2879:
2880: 2881: 2882: 2883: 2884: 2885:
2886: protected function _quoteFields($conditions) {
2887: $start = $end = null;
2888: $original = $conditions;
2889:
2890: if (!empty($this->startQuote)) {
2891: $start = preg_quote($this->startQuote);
2892: }
2893: if (!empty($this->endQuote)) {
2894: $end = preg_quote($this->endQuote);
2895: }
2896:
2897: $conditions = str_replace(array($start, $end), '', $conditions);
2898: $conditions = preg_replace_callback(
2899: '/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_][a-z0-9\\-_]*\\.[a-z0-9_][a-z0-9_\\-]*)/i',
2900: array(&$this, '_quoteMatchedField'),
2901: $conditions
2902: );
2903:
2904: $conditions = preg_replace(
2905: '/(\s[a-z0-9\\-_.' . $start . $end . ']*' . $end . ')\s+AS\s+([a-z0-9\\-_]+)/i',
2906: '\1 AS ' . $this->startQuote . '\2' . $this->endQuote,
2907: $conditions
2908: );
2909: if ($conditions !== null) {
2910: return $conditions;
2911: }
2912: return $original;
2913: }
2914:
2915: 2916: 2917: 2918: 2919: 2920:
2921: protected function _quoteMatchedField($match) {
2922: if (is_numeric($match[0])) {
2923: return $match[0];
2924: }
2925: return $this->name($match[0]);
2926: }
2927:
2928: 2929: 2930: 2931: 2932: 2933: 2934:
2935: public function limit($limit, $offset = null) {
2936: if ($limit) {
2937: $rt = ' LIMIT';
2938:
2939: if ($offset) {
2940: $rt .= sprintf(' %u,', $offset);
2941: }
2942:
2943: $rt .= sprintf(' %u', $limit);
2944: return $rt;
2945: }
2946: return null;
2947: }
2948:
2949: 2950: 2951: 2952: 2953: 2954: 2955: 2956:
2957: public function order($keys, $direction = 'ASC', Model $Model = null) {
2958: if (!is_array($keys)) {
2959: $keys = array($keys);
2960: }
2961:
2962: $keys = array_filter($keys);
2963:
2964: $result = array();
2965: while (!empty($keys)) {
2966: list($key, $dir) = each($keys);
2967: array_shift($keys);
2968:
2969: if (is_numeric($key)) {
2970: $key = $dir;
2971: $dir = $direction;
2972: }
2973:
2974: if (is_string($key) && strpos($key, ',') !== false && !preg_match('/\(.+\,.+\)/', $key)) {
2975: $key = array_map('trim', explode(',', $key));
2976: }
2977:
2978: if (is_array($key)) {
2979:
2980: $key = array_reverse($key, true);
2981: foreach ($key as $k => $v) {
2982: if (is_numeric($k)) {
2983: array_unshift($keys, $v);
2984: } else {
2985: $keys = array($k => $v) + $keys;
2986: }
2987: }
2988: continue;
2989: } elseif (is_object($key) && isset($key->type) && $key->type === 'expression') {
2990: $result[] = $key->value;
2991: continue;
2992: }
2993:
2994: if (preg_match('/\\x20(ASC|DESC).*/i', $key, $_dir)) {
2995: $dir = $_dir[0];
2996: $key = preg_replace('/\\x20(ASC|DESC).*/i', '', $key);
2997: }
2998:
2999: $key = trim($key);
3000:
3001: if ($Model !== null) {
3002: if ($Model->isVirtualField($key)) {
3003: $key = '(' . $this->_quoteFields($Model->getVirtualField($key)) . ')';
3004: }
3005:
3006: list($alias) = pluginSplit($key);
3007:
3008: if ($alias !== $Model->alias && is_object($Model->{$alias}) && $Model->{$alias}->isVirtualField($key)) {
3009: $key = '(' . $this->_quoteFields($Model->{$alias}->getVirtualField($key)) . ')';
3010: }
3011: }
3012:
3013: if (strpos($key, '.')) {
3014: $key = preg_replace_callback('/([a-zA-Z0-9_-]{1,})\\.([a-zA-Z0-9_-]{1,})/', array(&$this, '_quoteMatchedField'), $key);
3015: }
3016:
3017: if (!preg_match('/\s/', $key) && strpos($key, '.') === false) {
3018: $key = $this->name($key);
3019: }
3020:
3021: $key .= ' ' . trim($dir);
3022:
3023: $result[] = $key;
3024: }
3025:
3026: if (!empty($result)) {
3027: return ' ORDER BY ' . implode(', ', $result);
3028: }
3029:
3030: return '';
3031: }
3032:
3033: 3034: 3035: 3036: 3037: 3038: 3039:
3040: public function group($fields, Model $Model = null) {
3041: if (empty($fields)) {
3042: return null;
3043: }
3044:
3045: if (!is_array($fields)) {
3046: $fields = array($fields);
3047: }
3048:
3049: if ($Model !== null) {
3050: foreach ($fields as $index => $key) {
3051: if ($Model->isVirtualField($key)) {
3052: $fields[$index] = '(' . $Model->getVirtualField($key) . ')';
3053: }
3054: }
3055: }
3056:
3057: $fields = implode(', ', $fields);
3058:
3059: return ' GROUP BY ' . $this->_quoteFields($fields);
3060: }
3061:
3062: 3063: 3064: 3065: 3066:
3067: public function close() {
3068: $this->disconnect();
3069: }
3070:
3071: 3072: 3073: 3074: 3075: 3076: 3077:
3078: public function hasAny(Model $Model, $sql) {
3079: $sql = $this->conditions($sql);
3080: $table = $this->fullTableName($Model);
3081: $alias = $this->alias . $this->name($Model->alias);
3082: $where = $sql ? "{$sql}" : ' WHERE 1 = 1';
3083: $id = $Model->escapeField();
3084:
3085: $out = $this->fetchRow("SELECT COUNT({$id}) {$this->alias}count FROM {$table} {$alias}{$where}");
3086:
3087: if (is_array($out)) {
3088: return $out[0]['count'];
3089: }
3090: return false;
3091: }
3092:
3093: 3094: 3095: 3096: 3097: 3098:
3099: public function length($real) {
3100: if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) {
3101: $col = str_replace(array(')', 'unsigned'), '', $real);
3102: $limit = null;
3103:
3104: if (strpos($col, '(') !== false) {
3105: list($col, $limit) = explode('(', $col);
3106: }
3107: if ($limit !== null) {
3108: return (int)$limit;
3109: }
3110: return null;
3111: }
3112:
3113: $types = array(
3114: 'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1
3115: );
3116:
3117: list($real, $type, $length, $offset, $sign) = $result;
3118: $typeArr = $type;
3119: $type = $type[0];
3120: $length = $length[0];
3121: $offset = $offset[0];
3122:
3123: $isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double'));
3124: if ($isFloat && $offset) {
3125: return $length . ',' . $offset;
3126: }
3127:
3128: if (($real[0] == $type) && (count($real) === 1)) {
3129: return null;
3130: }
3131:
3132: if (isset($types[$type])) {
3133: $length += $types[$type];
3134: if (!empty($sign)) {
3135: $length--;
3136: }
3137: } elseif (in_array($type, array('enum', 'set'))) {
3138: $length = 0;
3139: foreach ($typeArr as $key => $enumValue) {
3140: if ($key === 0) {
3141: continue;
3142: }
3143: $tmpLength = strlen($enumValue);
3144: if ($tmpLength > $length) {
3145: $length = $tmpLength;
3146: }
3147: }
3148: }
3149: return (int)$length;
3150: }
3151:
3152: 3153: 3154: 3155: 3156: 3157: 3158:
3159: public function boolean($data, $quote = false) {
3160: if ($quote) {
3161: return !empty($data) ? '1' : '0';
3162: }
3163: return !empty($data);
3164: }
3165:
3166: 3167: 3168: 3169: 3170: 3171: 3172: 3173: 3174: 3175:
3176: public function insertMulti($table, $fields, $values) {
3177: $table = $this->fullTableName($table);
3178: $holder = implode(',', array_fill(0, count($fields), '?'));
3179: $fields = implode(', ', array_map(array(&$this, 'name'), $fields));
3180:
3181: $pdoMap = array(
3182: 'integer' => PDO::PARAM_INT,
3183: 'float' => PDO::PARAM_STR,
3184: 'boolean' => PDO::PARAM_BOOL,
3185: 'string' => PDO::PARAM_STR,
3186: 'text' => PDO::PARAM_STR
3187: );
3188: $columnMap = array();
3189:
3190: $sql = "INSERT INTO {$table} ({$fields}) VALUES ({$holder})";
3191: $statement = $this->_connection->prepare($sql);
3192: $this->begin();
3193:
3194: foreach ($values[key($values)] as $key => $val) {
3195: $type = $this->introspectType($val);
3196: $columnMap[$key] = $pdoMap[$type];
3197: }
3198:
3199: foreach ($values as $value) {
3200: $i = 1;
3201: foreach ($value as $col => $val) {
3202: $statement->bindValue($i, $val, $columnMap[$col]);
3203: $i += 1;
3204: }
3205: $t = microtime(true);
3206: $statement->execute();
3207: $statement->closeCursor();
3208:
3209: if ($this->fullDebug) {
3210: $this->took = round((microtime(true) - $t) * 1000, 0);
3211: $this->numRows = $this->affected = $statement->rowCount();
3212: $this->logQuery($sql, $value);
3213: }
3214: }
3215: return $this->commit();
3216: }
3217:
3218: 3219: 3220: 3221: 3222: 3223: 3224: 3225: 3226: 3227:
3228: public function resetSequence($table, $column) {
3229: }
3230:
3231: 3232: 3233: 3234: 3235: 3236:
3237: public function index($model) {
3238: return array();
3239: }
3240:
3241: 3242: 3243: 3244: 3245: 3246: 3247: 3248:
3249: public function createSchema($schema, $tableName = null) {
3250: if (!$schema instanceof CakeSchema) {
3251: trigger_error(__d('cake_dev', 'Invalid schema object'), E_USER_WARNING);
3252: return null;
3253: }
3254: $out = '';
3255:
3256: foreach ($schema->tables as $curTable => $columns) {
3257: if (!$tableName || $tableName === $curTable) {
3258: $cols = $indexes = $tableParameters = array();
3259: $primary = null;
3260: $table = $this->fullTableName($curTable);
3261:
3262: $primaryCount = 0;
3263: foreach ($columns as $col) {
3264: if (isset($col['key']) && $col['key'] === 'primary') {
3265: $primaryCount++;
3266: }
3267: }
3268:
3269: foreach ($columns as $name => $col) {
3270: if (is_string($col)) {
3271: $col = array('type' => $col);
3272: }
3273: $isPrimary = isset($col['key']) && $col['key'] === 'primary';
3274:
3275: if ($isPrimary && $primaryCount > 1) {
3276: unset($col['key']);
3277: $isPrimary = false;
3278: }
3279: if ($isPrimary) {
3280: $primary = $name;
3281: }
3282: if ($name !== 'indexes' && $name !== 'tableParameters') {
3283: $col['name'] = $name;
3284: if (!isset($col['type'])) {
3285: $col['type'] = 'string';
3286: }
3287: $cols[] = $this->buildColumn($col);
3288: } elseif ($name === 'indexes') {
3289: $indexes = array_merge($indexes, $this->buildIndex($col, $table));
3290: } elseif ($name === 'tableParameters') {
3291: $tableParameters = array_merge($tableParameters, $this->buildTableParameters($col, $table));
3292: }
3293: }
3294: if (!isset($columns['indexes']['PRIMARY']) && !empty($primary)) {
3295: $col = array('PRIMARY' => array('column' => $primary, 'unique' => 1));
3296: $indexes = array_merge($indexes, $this->buildIndex($col, $table));
3297: }
3298: $columns = $cols;
3299: $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes', 'tableParameters')) . "\n\n";
3300: }
3301: }
3302: return $out;
3303: }
3304:
3305: 3306: 3307: 3308: 3309: 3310: 3311:
3312: public function alterSchema($compare, $table = null) {
3313: return false;
3314: }
3315:
3316: 3317: 3318: 3319: 3320: 3321: 3322: 3323:
3324: public function dropSchema(CakeSchema $schema, $table = null) {
3325: $out = '';
3326:
3327: if ($table && array_key_exists($table, $schema->tables)) {
3328: return $this->_dropTable($table) . "\n";
3329: } elseif ($table) {
3330: return $out;
3331: }
3332:
3333: foreach (array_keys($schema->tables) as $curTable) {
3334: $out .= $this->_dropTable($curTable) . "\n";
3335: }
3336: return $out;
3337: }
3338:
3339: 3340: 3341: 3342: 3343: 3344:
3345: protected function _dropTable($table) {
3346: return 'DROP TABLE ' . $this->fullTableName($table) . ";";
3347: }
3348:
3349: 3350: 3351: 3352: 3353: 3354: 3355:
3356: public function buildColumn($column) {
3357: $name = $type = null;
3358: extract(array_merge(array('null' => true), $column));
3359:
3360: if (empty($name) || empty($type)) {
3361: trigger_error(__d('cake_dev', 'Column name or type not defined in schema'), E_USER_WARNING);
3362: return null;
3363: }
3364:
3365: if (!isset($this->columns[$type])) {
3366: trigger_error(__d('cake_dev', 'Column type %s does not exist', $type), E_USER_WARNING);
3367: return null;
3368: }
3369:
3370: $real = $this->columns[$type];
3371: $out = $this->name($name) . ' ' . $real['name'];
3372:
3373: if (isset($column['length'])) {
3374: $length = $column['length'];
3375: } elseif (isset($column['limit'])) {
3376: $length = $column['limit'];
3377: } elseif (isset($real['length'])) {
3378: $length = $real['length'];
3379: } elseif (isset($real['limit'])) {
3380: $length = $real['limit'];
3381: }
3382: if (isset($length)) {
3383: $out .= '(' . $length . ')';
3384: }
3385:
3386: if (($column['type'] === 'integer' || $column['type'] === 'float') && isset($column['default']) && $column['default'] === '') {
3387: $column['default'] = null;
3388: }
3389: $out = $this->_buildFieldParameters($out, $column, 'beforeDefault');
3390:
3391: if (isset($column['key']) && $column['key'] === 'primary' && ($type === 'integer' || $type === 'biginteger')) {
3392: $out .= ' ' . $this->columns['primary_key']['name'];
3393: } elseif (isset($column['key']) && $column['key'] === 'primary') {
3394: $out .= ' NOT NULL';
3395: } elseif (isset($column['default']) && isset($column['null']) && $column['null'] === false) {
3396: $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
3397: } elseif (isset($column['default'])) {
3398: $out .= ' DEFAULT ' . $this->value($column['default'], $type);
3399: } elseif ($type !== 'timestamp' && !empty($column['null'])) {
3400: $out .= ' DEFAULT NULL';
3401: } elseif ($type === 'timestamp' && !empty($column['null'])) {
3402: $out .= ' NULL';
3403: } elseif (isset($column['null']) && $column['null'] === false) {
3404: $out .= ' NOT NULL';
3405: }
3406: if ($type === 'timestamp' && isset($column['default']) && strtolower($column['default']) === 'current_timestamp') {
3407: $out = str_replace(array("'CURRENT_TIMESTAMP'", "'current_timestamp'"), 'CURRENT_TIMESTAMP', $out);
3408: }
3409: return $this->_buildFieldParameters($out, $column, 'afterDefault');
3410: }
3411:
3412: 3413: 3414: 3415: 3416: 3417: 3418: 3419:
3420: protected function _buildFieldParameters($columnString, $columnData, $position) {
3421: foreach ($this->fieldParameters as $paramName => $value) {
3422: if (isset($columnData[$paramName]) && $value['position'] == $position) {
3423: if (isset($value['options']) && !in_array($columnData[$paramName], $value['options'], true)) {
3424: continue;
3425: }
3426: if (isset($value['types']) && !in_array($columnData['type'], $value['types'], true)) {
3427: continue;
3428: }
3429: $val = $columnData[$paramName];
3430: if ($value['quote']) {
3431: $val = $this->value($val);
3432: }
3433: $columnString .= ' ' . $value['value'] . (empty($value['noVal']) ? $value['join'] . $val : '');
3434: }
3435: }
3436: return $columnString;
3437: }
3438:
3439: 3440: 3441: 3442: 3443: 3444: 3445:
3446: public function buildIndex($indexes, $table = null) {
3447: $join = array();
3448: foreach ($indexes as $name => $value) {
3449: $out = '';
3450: if ($name === 'PRIMARY') {
3451: $out .= 'PRIMARY ';
3452: $name = null;
3453: } else {
3454: if (!empty($value['unique'])) {
3455: $out .= 'UNIQUE ';
3456: }
3457: $name = $this->startQuote . $name . $this->endQuote;
3458: }
3459: if (is_array($value['column'])) {
3460: $out .= 'KEY ' . $name . ' (' . implode(', ', array_map(array(&$this, 'name'), $value['column'])) . ')';
3461: } else {
3462: $out .= 'KEY ' . $name . ' (' . $this->name($value['column']) . ')';
3463: }
3464: $join[] = $out;
3465: }
3466: return $join;
3467: }
3468:
3469: 3470: 3471: 3472: 3473: 3474:
3475: public function readTableParameters($name) {
3476: $parameters = array();
3477: if (method_exists($this, 'listDetailedSources')) {
3478: $currentTableDetails = $this->listDetailedSources($name);
3479: foreach ($this->tableParameters as $paramName => $parameter) {
3480: if (!empty($parameter['column']) && !empty($currentTableDetails[$parameter['column']])) {
3481: $parameters[$paramName] = $currentTableDetails[$parameter['column']];
3482: }
3483: }
3484: }
3485: return $parameters;
3486: }
3487:
3488: 3489: 3490: 3491: 3492: 3493: 3494:
3495: public function buildTableParameters($parameters, $table = null) {
3496: $result = array();
3497: foreach ($parameters as $name => $value) {
3498: if (isset($this->tableParameters[$name])) {
3499: if ($this->tableParameters[$name]['quote']) {
3500: $value = $this->value($value);
3501: }
3502: $result[] = $this->tableParameters[$name]['value'] . $this->tableParameters[$name]['join'] . $value;
3503: }
3504: }
3505: return $result;
3506: }
3507:
3508: 3509: 3510: 3511: 3512: 3513:
3514: public function introspectType($value) {
3515: if (!is_array($value)) {
3516: if (is_bool($value)) {
3517: return 'boolean';
3518: }
3519: if (is_float($value) && (float)$value === $value) {
3520: return 'float';
3521: }
3522: if (is_int($value) && (int)$value === $value) {
3523: return 'integer';
3524: }
3525: if (is_string($value) && strlen($value) > 255) {
3526: return 'text';
3527: }
3528: return 'string';
3529: }
3530:
3531: $isAllFloat = $isAllInt = true;
3532: $containsInt = $containsString = false;
3533: foreach ($value as $valElement) {
3534: $valElement = trim($valElement);
3535: if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) {
3536: $isAllFloat = false;
3537: } else {
3538: continue;
3539: }
3540: if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) {
3541: $isAllInt = false;
3542: } else {
3543: $containsInt = true;
3544: continue;
3545: }
3546: $containsString = true;
3547: }
3548:
3549: if ($isAllFloat) {
3550: return 'float';
3551: }
3552: if ($isAllInt) {
3553: return 'integer';
3554: }
3555:
3556: if ($containsInt && !$containsString) {
3557: return 'integer';
3558: }
3559: return 'string';
3560: }
3561:
3562: 3563: 3564: 3565: 3566: 3567: 3568: 3569:
3570: protected function _writeQueryCache($sql, $data, $params = array()) {
3571: if (preg_match('/^\s*select/i', $sql)) {
3572: $this->_queryCache[$sql][serialize($params)] = $data;
3573: }
3574: }
3575:
3576: 3577: 3578: 3579: 3580: 3581: 3582:
3583: public function getQueryCache($sql, $params = array()) {
3584: if (isset($this->_queryCache[$sql]) && preg_match('/^\s*select/i', $sql)) {
3585: $serialized = serialize($params);
3586: if (isset($this->_queryCache[$sql][$serialized])) {
3587: return $this->_queryCache[$sql][$serialized];
3588: }
3589: }
3590: return false;
3591: }
3592:
3593: 3594: 3595:
3596: public function __destruct() {
3597: if ($this->_methodCacheChange) {
3598: Cache::write('method_cache', static::$methodCache, '_cake_core_');
3599: }
3600: parent::__destruct();
3601: }
3602:
3603: }
3604: