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

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

Packages

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

Classes

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