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

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