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