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