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

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