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