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