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.9 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.9
      • 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
  • None

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