CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.6 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.6
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

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

Generated using CakePHP API Docs