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