PageRenderTime 588ms CodeModel.GetById 141ms app.highlight 234ms RepoModel.GetById 107ms app.codeStats 2ms

/lib/Cake/Model/Datasource/DboSource.php

https://bitbucket.org/udeshika/fake_twitter
PHP | 3129 lines | 2045 code | 264 blank | 820 comment | 546 complexity | 86f1999909b5c8a4f0c01da8c2f24612 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * Dbo Source
   4 *
   5 * PHP 5
   6 *
   7 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
   8 * Copyright 2005-2011, 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-2011, 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
  20App::uses('DataSource', 'Model/Datasource');
  21App::uses('String', 'Utility');
  22App::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 */
  31class 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 `crc32()` which is
  57 * fast but can collisions more easily than other hashing algorithms.  If you have problems
  58 * with collisions, set DboSource::$cacheMethods to false.
  59 *
  60 * @var array
  61 */
  62	public static $methodCache = array();
  63
  64/**
  65 * Whether or not to cache the results of DboSource::name() and DboSource::conditions()
  66 * into the memory cache.  Set to false to disable the use of the memory cache.
  67 *
  68 * @var boolean.
  69 */
  70	public $cacheMethods = true;
  71
  72/**
  73 * Print full query debug info?
  74 *
  75 * @var boolean
  76 */
  77	public $fullDebug = false;
  78
  79/**
  80 * String to hold how many rows were affected by the last SQL operation.
  81 *
  82 * @var string
  83 */
  84	public $affected = null;
  85
  86/**
  87 * Number of rows in current resultset
  88 *
  89 * @var integer
  90 */
  91	public $numRows = null;
  92
  93/**
  94 * Time the last query took
  95 *
  96 * @var integer
  97 */
  98	public $took = null;
  99
 100/**
 101 * Result
 102 *
 103 * @var array
 104 */
 105	protected $_result = null;
 106
 107/**
 108 * Queries count.
 109 *
 110 * @var integer
 111 */
 112	protected $_queriesCnt = 0;
 113
 114/**
 115 * Total duration of all queries.
 116 *
 117 * @var integer
 118 */
 119	protected $_queriesTime = null;
 120
 121/**
 122 * Log of queries executed by this DataSource
 123 *
 124 * @var array
 125 */
 126	protected $_queriesLog = array();
 127
 128/**
 129 * Maximum number of items in query log
 130 *
 131 * This is to prevent query log taking over too much memory.
 132 *
 133 * @var integer Maximum number of queries in the queries log.
 134 */
 135	protected $_queriesLogMax = 200;
 136
 137/**
 138 * Caches serialized results of executed queries
 139 *
 140 * @var array Maximum number of queries in the queries log.
 141 */
 142	protected $_queryCache = array();
 143
 144/**
 145 * A reference to the physical connection of this DataSource
 146 *
 147 * @var array
 148 */
 149	protected $_connection = null;
 150
 151/**
 152 * The DataSource configuration key name
 153 *
 154 * @var string
 155 */
 156	public $configKeyName = null;
 157
 158/**
 159 * The starting character that this DataSource uses for quoted identifiers.
 160 *
 161 * @var string
 162 */
 163	public $startQuote = null;
 164
 165/**
 166 * The ending character that this DataSource uses for quoted identifiers.
 167 *
 168 * @var string
 169 */
 170	public $endQuote = null;
 171
 172/**
 173 * The set of valid SQL operations usable in a WHERE statement
 174 *
 175 * @var array
 176 */
 177	protected $_sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
 178
 179/**
 180 * Indicates the level of nested transactions
 181 *
 182 * @var integer
 183 */
 184	protected $_transactionNesting = 0;
 185
 186/**
 187 * Index of basic SQL commands
 188 *
 189 * @var array
 190 */
 191	protected $_commands = array(
 192		'begin' => 'BEGIN',
 193		'commit' => 'COMMIT',
 194		'rollback' => 'ROLLBACK'
 195	);
 196
 197/**
 198 * Separator string for virtualField composition
 199 *
 200 * @var string
 201 */
 202	public $virtualFieldSeparator = '__';
 203
 204/**
 205 * List of table engine specific parameters used on table creating
 206 *
 207 * @var array
 208 */
 209	public $tableParameters = array();
 210
 211/**
 212 * List of engine specific additional field parameters used on table creating
 213 *
 214 * @var array
 215 */
 216	public $fieldParameters = array();
 217
 218/**
 219 * Indicates whether there was a change on the cached results on the methods of this class
 220 * This will be used for storing in a more persistent cache
 221 *
 222 * @var boolean
 223 */
 224	protected $_methodCacheChange = false;
 225
 226/**
 227 * Constructor
 228 *
 229 * @param array $config Array of configuration information for the Datasource.
 230 * @param boolean $autoConnect Whether or not the datasource should automatically connect.
 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			));
 242		}
 243		if ($autoConnect) {
 244			$this->connect();
 245		}
 246	}
 247
 248/**
 249 * Reconnects to database server with optional new settings
 250 *
 251 * @param array $config An array defining the new configuration settings
 252 * @return boolean True on success, false on failure
 253 */
 254	public function reconnect($config = array()) {
 255		$this->disconnect();
 256		$this->setConfig($config);
 257		$this->_sources = null;
 258
 259		return $this->connect();
 260	}
 261
 262/**
 263 * Disconnects from database.
 264 *
 265 * @return boolean True if the database could be disconnected, else false
 266 */
 267	public function disconnect() {
 268		if ($this->_result instanceof PDOStatement) {
 269			$this->_result->closeCursor();
 270		}
 271		unset($this->_connection);
 272		$this->connected = false;
 273		return true;
 274	}
 275
 276/**
 277 * Get the underlying connection object.
 278 *
 279 * @return PDOConnection
 280 */
 281	public function getConnection() {
 282		return $this->_connection;
 283	}
 284
 285/**
 286 * Returns a quoted and escaped string of $data for use in an SQL statement.
 287 *
 288 * @param string $data String to be prepared for use in an SQL statement
 289 * @param string $column The column into which this data will be inserted
 290 * @return string Quoted and escaped data
 291 */
 292	public function value($data, $column = null) {
 293		if (is_array($data) && !empty($data)) {
 294			return array_map(
 295				array(&$this, 'value'),
 296				$data, array_fill(0, count($data), $column)
 297			);
 298		} elseif (is_object($data) && isset($data->type, $data->value)) {
 299			if ($data->type == 'identifier') {
 300				return $this->name($data->value);
 301			} elseif ($data->type == 'expression') {
 302				return $data->value;
 303			}
 304		} elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
 305			return $data;
 306		}
 307
 308		if ($data === null || (is_array($data) && empty($data))) {
 309			return 'NULL';
 310		}
 311
 312		if (empty($column)) {
 313			$column = $this->introspectType($data);
 314		}
 315
 316		switch ($column) {
 317			case 'binary':
 318				return $this->_connection->quote($data, PDO::PARAM_LOB);
 319			break;
 320			case 'boolean':
 321				return $this->_connection->quote($this->boolean($data, true), PDO::PARAM_BOOL);
 322			break;
 323			case 'string':
 324			case 'text':
 325				return $this->_connection->quote($data, PDO::PARAM_STR);
 326			default:
 327				if ($data === '') {
 328					return 'NULL';
 329				}
 330				if (is_float($data)) {
 331					return str_replace(',', '.', strval($data));
 332				}
 333				if ((is_int($data) || $data === '0') || (
 334					is_numeric($data) && strpos($data, ',') === false &&
 335					$data[0] != '0' && strpos($data, 'e') === false)
 336				) {
 337					return $data;
 338				}
 339				return $this->_connection->quote($data);
 340			break;
 341		}
 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
 395 * @param array $options
 396 * @param array $params values to be bided 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);
 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 */
 423	protected function _execute($sql, $params = array(), $prepareOptions = array()) {
 424		$sql = trim($sql);
 425		if (preg_match('/^(?:CREATE|ALTER|DROP)/i', $sql)) {
 426			$statements = array_filter(explode(';', $sql));
 427			if (count($statements) > 1) {
 428				$result = array_map(array($this, '_execute'), $statements);
 429				return array_search(false, $result) === false;
 430			}
 431		}
 432
 433		try {
 434			$query = $this->_connection->prepare($sql, $prepareOptions);
 435			$query->setFetchMode(PDO::FETCH_LAZY);
 436			if (!$query->execute($params)) {
 437				$this->_results = $query;
 438				$query->closeCursor();
 439				return false;
 440			}
 441			if (!$query->columnCount()) {
 442				$query->closeCursor();
 443				if (!$query->rowCount()) {
 444					return true;
 445				}
 446			}
 447			return $query;
 448		} catch (PDOException $e) {
 449			if (isset($query->queryString)) {
 450				$e->queryString = $query->queryString;
 451			} else {
 452				$e->queryString = $sql;
 453			}
 454			throw $e;
 455		}
 456	}
 457
 458/**
 459 * Returns a formatted error message from previous database operation.
 460 *
 461 * @param PDOStatement $query the query to extract the error from if any
 462 * @return string Error message with error number
 463 */
 464	public function lastError(PDOStatement $query = null) {
 465		if ($query) {
 466			$error = $query->errorInfo();
 467		} else {
 468			$error = $this->_connection->errorInfo();
 469		}
 470		if (empty($error[2])) {
 471			return null;
 472		}
 473		return $error[1] . ': ' . $error[2];
 474	}
 475
 476/**
 477 * Returns number of affected rows in previous database operation. If no previous operation exists,
 478 * this returns false.
 479 *
 480 * @param mixed $source
 481 * @return integer Number of affected rows
 482 */
 483	public function lastAffected($source = null) {
 484		if ($this->hasResult()) {
 485			return $this->_result->rowCount();
 486		}
 487		return null;
 488	}
 489
 490/**
 491 * Returns number of rows in previous resultset. If no previous resultset exists,
 492 * this returns false.
 493 *
 494 * @param mixed $source Not used
 495 * @return integer Number of rows in resultset
 496 */
 497	public function lastNumRows($source = null) {
 498		return $this->lastAffected();
 499	}
 500
 501/**
 502 * DataSource Query abstraction
 503 *
 504 * @return resource Result resource identifier.
 505 */
 506	public function query() {
 507		$args	  = func_get_args();
 508		$fields	  = null;
 509		$order	  = null;
 510		$limit	  = null;
 511		$page	  = null;
 512		$recursive = null;
 513
 514		if (count($args) === 1) {
 515			return $this->fetchAll($args[0]);
 516		} elseif (count($args) > 1 && (strpos($args[0], 'findBy') === 0 || strpos($args[0], 'findAllBy') === 0)) {
 517			$params = $args[1];
 518
 519			if (substr($args[0], 0, 6) === 'findBy') {
 520				$all = false;
 521				$field = Inflector::underscore(substr($args[0], 6));
 522			} else {
 523				$all = true;
 524				$field = Inflector::underscore(substr($args[0], 9));
 525			}
 526
 527			$or = (strpos($field, '_or_') !== false);
 528			if ($or) {
 529				$field = explode('_or_', $field);
 530			} else {
 531				$field = explode('_and_', $field);
 532			}
 533			$off = count($field) - 1;
 534
 535			if (isset($params[1 + $off])) {
 536				$fields = $params[1 + $off];
 537			}
 538
 539			if (isset($params[2 + $off])) {
 540				$order = $params[2 + $off];
 541			}
 542
 543			if (!array_key_exists(0, $params)) {
 544				return false;
 545			}
 546
 547			$c = 0;
 548			$conditions = array();
 549
 550			foreach ($field as $f) {
 551				$conditions[$args[2]->alias . '.' . $f] = $params[$c++];
 552			}
 553
 554			if ($or) {
 555				$conditions = array('OR' => $conditions);
 556			}
 557
 558			if ($all) {
 559				if (isset($params[3 + $off])) {
 560					$limit = $params[3 + $off];
 561				}
 562
 563				if (isset($params[4 + $off])) {
 564					$page = $params[4 + $off];
 565				}
 566
 567				if (isset($params[5 + $off])) {
 568					$recursive = $params[5 + $off];
 569				}
 570				return $args[2]->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
 571			} else {
 572				if (isset($params[3 + $off])) {
 573					$recursive = $params[3 + $off];
 574				}
 575				return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive'));
 576			}
 577		} else {
 578			if (isset($args[1]) && $args[1] === true) {
 579				return $this->fetchAll($args[0], true);
 580			} else if (isset($args[1]) && !is_array($args[1]) ) {
 581				return $this->fetchAll($args[0], false);
 582			} else if (isset($args[1]) && is_array($args[1])) {
 583				$offset = 0;
 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 simple hashing algorithm it can infrequently 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 = crc32($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 * @return void
 896 */
 897	public function logQuery($sql) {
 898		$this->_queriesCnt++;
 899		$this->_queriesTime += $this->took;
 900		$this->_queriesLog[] = array(
 901			'query'		=> $sql,
 902			'affected'	=> $this->affected,
 903			'numRows'	=> $this->numRows,
 904			'took'		=> $this->took
 905		);
 906		if (count($this->_queriesLog) > $this->_queriesLogMax) {
 907			array_pop($this->_queriesLog);
 908		}
 909	}
 910
 911/**
 912 * Gets full table name including prefix
 913 *
 914 * @param mixed $model Either a Model object or a string table name.
 915 * @param boolean $quote Whether you want the table name quoted.
 916 * @return string Full quoted table name
 917 */
 918	public function fullTableName($model, $quote = true) {
 919		if (is_object($model)) {
 920			$table = $model->tablePrefix . $model->table;
 921		} elseif (isset($this->config['prefix'])) {
 922			$table = $this->config['prefix'] . strval($model);
 923		} else {
 924			$table = strval($model);
 925		}
 926		if ($quote) {
 927			return $this->name($table);
 928		}
 929		return $table;
 930	}
 931
 932/**
 933 * The "C" in CRUD
 934 *
 935 * Creates new records in the database.
 936 *
 937 * @param Model $model Model object that the record is for.
 938 * @param array $fields An array of field names to insert. If null, $model->data will be
 939 *   used to generate field names.
 940 * @param array $values An array of values with keys matching the fields. If null, $model->data will
 941 *   be used to generate values.
 942 * @return boolean Success
 943 */
 944	public function create(Model $model, $fields = null, $values = null) {
 945		$id = null;
 946
 947		if ($fields == null) {
 948			unset($fields, $values);
 949			$fields = array_keys($model->data);
 950			$values = array_values($model->data);
 951		}
 952		$count = count($fields);
 953
 954		for ($i = 0; $i < $count; $i++) {
 955			$valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]));
 956			$fieldInsert[] = $this->name($fields[$i]);
 957			if ($fields[$i] == $model->primaryKey) {
 958				$id = $values[$i];
 959			}
 960		}
 961		$query = array(
 962			'table' => $this->fullTableName($model),
 963			'fields' => implode(', ', $fieldInsert),
 964			'values' => implode(', ', $valueInsert)
 965		);
 966
 967		if ($this->execute($this->renderStatement('create', $query))) {
 968			if (empty($id)) {
 969				$id = $this->lastInsertId($this->fullTableName($model, false), $model->primaryKey);
 970			}
 971			$model->setInsertID($id);
 972			$model->id = $id;
 973			return true;
 974		}
 975		$model->onError();
 976		return false;
 977	}
 978
 979/**
 980 * The "R" in CRUD
 981 *
 982 * Reads record(s) from the database.
 983 *
 984 * @param Model $model A Model object that the query is for.
 985 * @param array $queryData An array of queryData information containing keys similar to Model::find()
 986 * @param integer $recursive Number of levels of association
 987 * @return mixed boolean false on error/failure.  An array of results on success.
 988 */
 989	public function read(Model $model, $queryData = array(), $recursive = null) {
 990		$queryData = $this->_scrubQueryData($queryData);
 991
 992		$null = null;
 993		$array = array('callbacks' => $queryData['callbacks']);
 994		$linkedModels = array();
 995		$bypass = false;
 996
 997		if ($recursive === null && isset($queryData['recursive'])) {
 998			$recursive = $queryData['recursive'];
 999		}
1000
1001		if (!is_null($recursive)) {
1002			$_recursive = $model->recursive;
1003			$model->recursive = $recursive;
1004		}
1005
1006		if (!empty($queryData['fields'])) {
1007			$bypass = true;
1008			$queryData['fields'] = $this->fields($model, null, $queryData['fields']);
1009		} else {
1010			$queryData['fields'] = $this->fields($model);
1011		}
1012
1013		$_associations = $model->associations();
1014
1015		if ($model->recursive == -1) {
1016			$_associations = array();
1017		} elseif ($model->recursive == 0) {
1018			unset($_associations[2], $_associations[3]);
1019		}
1020
1021		foreach ($_associations as $type) {
1022			foreach ($model->{$type} as $assoc => $assocData) {
1023				$linkModel = $model->{$assoc};
1024				$external = isset($assocData['external']);
1025
1026				$linkModel->getDataSource();
1027				if ($model->useDbConfig === $linkModel->useDbConfig) {
1028					if ($bypass) {
1029						$assocData['fields'] = false;
1030					}
1031					if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) {
1032						$linkedModels[$type . '/' . $assoc] = true;
1033					}
1034				}
1035			}
1036		}
1037
1038		$query = trim($this->generateAssociationQuery($model, null, null, null, null, $queryData, false, $null));
1039
1040		$resultSet = $this->fetchAll($query, $model->cacheQueries);
1041		if ($resultSet === false) {
1042			$model->onError();
1043			return false;
1044		}
1045
1046		$filtered = array();
1047
1048		if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1049			$filtered = $this->_filterResults($resultSet, $model);
1050		}
1051
1052		if ($model->recursive > -1) {
1053			foreach ($_associations as $type) {
1054				foreach ($model->{$type} as $assoc => $assocData) {
1055					$linkModel = $model->{$assoc};
1056
1057					if (!isset($linkedModels[$type . '/' . $assoc])) {
1058						if ($model->useDbConfig === $linkModel->useDbConfig) {
1059							$db = $this;
1060						} else {
1061							$db = ConnectionManager::getDataSource($linkModel->useDbConfig);
1062						}
1063					} elseif ($model->recursive > 1 && ($type === 'belongsTo' || $type === 'hasOne')) {
1064						$db = $this;
1065					}
1066
1067					if (isset($db) && method_exists($db, 'queryAssociation')) {
1068						$stack = array($assoc);
1069						$db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
1070						unset($db);
1071
1072						if ($type === 'hasMany') {
1073							$filtered[] = $assoc;
1074						}
1075					}
1076				}
1077			}
1078			if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1079				$this->_filterResults($resultSet, $model, $filtered);
1080			}
1081		}
1082
1083		if (!is_null($recursive)) {
1084			$model->recursive = $_recursive;
1085		}
1086		return $resultSet;
1087	}
1088
1089/**
1090 * Passes association results thru afterFind filters of corresponding model
1091 *
1092 * @param array $results Reference of resultset to be filtered
1093 * @param Model $model Instance of model to operate against
1094 * @param array $filtered List of classes already filtered, to be skipped
1095 * @return array Array of results that have been filtered through $model->afterFind
1096 */
1097	protected function _filterResults(&$results, Model $model, $filtered = array()) {
1098		$current = current($results);
1099		if (!is_array($current)) {
1100			return array();
1101		}
1102		$keys = array_diff(array_keys($current), $filtered, array($model->alias));
1103		$filtering = array();
1104		foreach ($keys as $className) {
1105			if (!isset($model->{$className}) || !is_object($model->{$className})) {
1106				continue;
1107			}
1108			$linkedModel = $model->{$className};
1109			$filtering[] = $className;
1110			foreach ($results as &$result) {
1111				$data = $linkedModel->afterFind(array(array($className => $result[$className])), false);
1112				if (isset($data[0][$className])) {
1113					$result[$className] = $data[0][$className];
1114				}
1115			}
1116		}
1117		return $filtering;
1118	}
1119
1120/**
1121 * Queries associations.  Used to fetch results on recursive models.
1122 *
1123 * @param Model $model Primary Model object
1124 * @param Model $linkModel Linked model that
1125 * @param string $type Association type, one of the model association types ie. hasMany
1126 * @param string $association
1127 * @param array $assocData
1128 * @param array $queryData
1129 * @param boolean $external Whether or not the association query is on an external datasource.
1130 * @param array $resultSet Existing results
1131 * @param integer $recursive Number of levels of association
1132 * @param array $stack
1133 * @return mixed
1134 */
1135	public function queryAssociation($model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {
1136		if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
1137			if (!is_array($resultSet)) {
1138				throw new CakeException(__d('cake_dev', 'Error in Model %s', get_class($model)));
1139			}
1140			if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) {
1141				$ins = $fetch = array();
1142				foreach ($resultSet as &$result) {
1143					if ($in = $this->insertQueryData('{$__cakeID__$}', $result, $association, $assocData, $model, $linkModel, $stack)) {
1144						$ins[] = $in;
1145					}
1146				}
1147
1148				if (!empty($ins)) {
1149					$ins = array_unique($ins);
1150					$fetch = $this->fetchAssociated($model, $query, $ins);
1151				}
1152
1153				if (!empty($fetch) && is_array($fetch)) {
1154					if ($recursive > 0) {
1155						foreach ($linkModel->associations() as $type1) {
1156							foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
1157								$deepModel = $linkModel->{$assoc1};
1158								$tmpStack = $stack;
1159								$tmpStack[] = $assoc1;
1160
1161								if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
1162									$db = $this;
1163								} else {
1164									$db = ConnectionManager::getDataSource($deepModel->useDbConfig);
1165								}
1166								$db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
1167							}
1168						}
1169					}
1170				}
1171				if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1172					$this->_filterResults($fetch, $model);
1173				}
1174				return $this->_mergeHasMany($resultSet, $fetch, $association, $model, $linkModel);
1175			} elseif ($type === 'hasAndBelongsToMany') {
1176				$ins = $fetch = array();
1177				foreach ($resultSet as &$result) {
1178					if ($in = $this->insertQueryData('{$__cakeID__$}', $result, $association, $assocData, $model, $linkModel, $stack)) {
1179						$ins[] = $in;
1180					}
1181				}
1182				if (!empty($ins)) {
1183					$ins = array_unique($ins);
1184					if (count($ins) > 1) {
1185						$query = str_replace('{$__cakeID__$}', '(' .implode(', ', $ins) .')', $query);
1186						$query = str_replace('= (', 'IN (', $query);
1187					} else {
1188						$query = str_replace('{$__cakeID__$}', $ins[0], $query);
1189					}
1190
1191					$query = str_replace(' WHERE 1 = 1', '', $query);
1192				}
1193
1194				$foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
1195				$joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
1196				list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
1197				$habtmFieldsCount = count($habtmFields);
1198				$q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack);
1199
1200				if ($q !== false) {
1201					$fetch = $this->fetchAll($q, $model->cacheQueries);
1202				} else {
1203					$fetch = null;
1204				}
1205			}
1206
1207			$modelAlias = $model->alias;
1208			$modelPK = $model->primaryKey;
1209			foreach ($resultSet as &$row) {
1210				if ($type !== 'hasAndBelongsToMany') {
1211					$q = $this->insertQueryData($query, $row, $association, $assocData, $model, $linkModel, $stack);
1212					if ($q !== false) {
1213						$fetch = $this->fetchAll($q, $model->cacheQueries);
1214					} else {
1215						$fetch = null;
1216					}
1217				}
1218				$selfJoin = $linkModel->name === $model->name;
1219
1220				if (!empty($fetch) && is_array($fetch)) {
1221					if ($recursive > 0) {
1222						foreach ($linkModel->associations() as $type1) {
1223							foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
1224								$deepModel = $linkModel->{$assoc1};
1225
1226								if ($type1 === 'belongsTo' || ($deepModel->alias === $modelAlias && $type === 'belongsTo') || ($deepModel->alias !== $modelAlias)) {
1227									$tmpStack = $stack;
1228									$tmpStack[] = $assoc1;
1229									if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
1230										$db = $this;
1231									} else {
1232										$db = ConnectionManager::getDataSource($deepModel->useDbConfig);
1233									}
1234									$db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
1235								}
1236							}
1237						}
1238					}
1239					if ($type === 'hasAndBelongsToMany') {
1240						$uniqueIds = $merge = array();
1241
1242						foreach ($fetch as $j => $data) {
1243							if (isset($data[$with]) && $data[$with][$foreignKey] === $row[$modelAlias][$modelPK]) {
1244								if ($habtmFieldsCount <= 2) {
1245									unset($data[$with]);
1246								}
1247								$merge[] = $data;
1248							}
1249						}
1250						if (empty($merge) && !isset($row[$association])) {
1251							$row[$association] = $merge;
1252						} else {
1253							$this->_mergeAssociation($row, $merge, $association, $type);
1254						}
1255					} else {
1256						$this->_mergeAssociation($row, $fetch, $association, $type, $selfJoin);
1257					}
1258					if (isset($row[$association])) {
1259						$row[$association] = $linkModel->afterFind($row[$association], false);
1260					}
1261				} else {
1262					$tempArray[0][$association] = false;
1263					$this->_mergeAssociation($row, $tempArray, $association, $type, $selfJoin);
1264				}
1265			}
1266		}
1267	}
1268
1269/**
1270 * A more efficient way to fetch associations.	Woohoo!
1271 *
1272 * @param Model $model Primary model object
1273 * @param string $query Association query
1274 * @param array $ids Array of IDs of associated records
1275 * @return array Association results
1276 */
1277	public function fetchAssociated($model, $query, $ids) {
1278		$query = str_replace('{$__cakeID__$}', implode(', ', $ids), $query);
1279		if (count($ids) > 1) {
1280			$query = str_replace('= (', 'IN (', $query);
1281		}
1282		return $this->fetchAll($query, $model->cacheQueries);
1283	}
1284
1285/**
1286 * mergeHasMany - Merge the results of hasMany relations.
1287 *
1288 *
1289 * @param array $resultSet Data to merge into
1290 * @param array $merge Data to merge
1291 * @param string $association Name of Model being Merged
1292 * @param Model $model Model being merged onto
1293 * @param Model $linkModel Model being merged
1294 * @return void
1295 */
1296	protected function _mergeHasMany(&$resultSet, $merge, $association, $model, $linkModel) {
1297		$modelAlias = $model->alias;
1298		$modelPK = $model->primaryKey;
1299		$modelFK = $model->hasMany[$association]['foreignKey'];
1300		foreach ($resultSet as &$result) {
1301			if (!isset($result[$modelAlias])) {
1302				continue;
1303			}
1304			$merged = array();
1305			foreach ($merge as $data) {
1306				if ($result[$modelAlias][$modelPK] === $data[$association][$modelFK]) {
1307					if (count($data) > 1) {
1308						$data = array_merge($data[$association], $data);
1309						unset($data[$association]);
1310						foreach ($data as $key => $name) {
1311							if (is_numeric($key)) {
1312								$data[$association][] = $name;
1313								unset($data[$key]);
1314							}
1315						}
1316						$merged[] = $data;
1317					} else {
1318						$merged[] = $data[$association];
1319					}
1320				}
1321			}
1322			$result = Set::pushDiff($result, array($association => $merged));
1323		}
1324	}
1325
1326/**
1327 * Merge association of merge into data
1328 *
1329 * @param array $data
1330 * @param array $merge
1331 * @param string $association
1332 * @param string $type
1333 * @param boolean $selfJoin
1334 * @return void
1335 */
1336	protected function _mergeAssociation(&$data, &$merge, $association, $type, $selfJoin = false) {
1337		if (isset($merge[0]) && !isset($merge[0][$association])) {
1338			$association = Inflector::pluralize($association);
1339		}
1340
1341		if ($type === 'belongsTo' || $type === 'hasOne') {
1342			if (isset($merge[$association])) {
1343				$data[$association] = $merge[$association][0];
1344			} else {
1345				if (count($merge[0][$association]) > 1) {
1346					foreach ($merge[0] as $assoc => $data2) {
1347						if ($assoc !== $association) {
1348							$merge[0][$association][$assoc] = $data2;
1349						}
1350					}
1351				}
1352				if (!isset($data[$association])) {
1353					if ($merge[0][$association] != null) {
1354						$data[$association] = $merge[0][$association];
1355					} else {
1356						$data[$association] = array();
1357					}
1358				} else {
1359					if (is_array($merge[0][$association])) {
1360						foreach ($data[$association] as $k => $v) {
1361							if (!is_array($v)) {
1362								$dataAssocTmp[$k] = $v;
1363							}
1364						}
1365
1366						foreach ($merge[0][$association] as $k => $v) {
1367							if (!is_array($v)) {
1368								$mergeAssocTmp[$k] = $v;
1369							}
1370						}
1371						$dataKeys = array_keys($data);
1372						$mergeKeys = array_keys($merge[0]);
1373
1374						if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) {
1375							$data[$association][$association] = $merge[0][$association];
1376						} else {
1377							$diff = Set::diff($dataAssocTmp, $mergeAssocTmp);
1378							$data[$association] = array_merge($merge[0][$association], $diff);
1379						}
1380					} elseif ($selfJoin && array_key_exists($association, $merge[0])) {
1381						$data[$association] = array_merge($data[$association], array($association => array()));
1382					}
1383				}
1384			}
1385		} else {
1386			if (isset($merge[0][$association]) && $merge[0][$association] === false) {
1387				if (!isset($data[$association])) {
1388					$data[$association] = array();
1389				}
1390			} else {
1391				foreach ($merge as $i => $row) {
1392					if (count($row) === 1) {
1393						if (empty($data[$association]) || (isset($data[$association]) && !in_array($row[$association], $data[$association]))) {
1394							$data[$association][] = $row[$association];
1395						}
1396					} elseif (!empty($row)) {
1397						$tmp = array_merge($row[$association], $row);
1398						unset($tmp[$association]);
1399						$data[$association][] = $tmp;
1400					}
1401				}
1402			}
1403		}
1404	}
1405
1406/**
1407 * Generates an array representing a query or part of a query from a single model or two associated models
1408 *
1409 * @param Model $model
1410 * @param Model $linkModel
1411 * @param string $type
1412 * @param string $association
1413 * @param array $assocData
1414 * @param array $queryData
1415 * @param boolean $external
1416 * @param array $resultSet
1417 * @return mixed
1418 */
1419	public function generateAssociationQuery($model, $linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) {
1420		$queryData = $this->_scrubQueryData($queryData);
1421		$assocData = $this->_scrubQueryData($assocData);
1422		$modelAlias = $model->alias;
1423
1424		if (empty($queryData['fields'])) {
1425			$queryData['fields'] = $this->fields($model, $modelAlias);
1426		} elseif (!empty($model->hasMany) && $model->recursive > -1) {
1427			$assocFields = $this->fields($model, $modelAlias, array("{$modelAlias}.{$model->primaryKey}"));
1428			$passedFields = $queryData['fields'];
1429			if (count($passedFields) === 1) {
1430				if (strpos($passedFields[0], $assocFields[0]) === false && !preg_match('/^[a-z]+\(/i', $passedFields[0])) {
1431					$queryData['fields'] = array_merge($passedFields, $assocFields);
1432				} else {
1433					$queryData['fields'] = $passedFields;
1434				}
1435			} else {
1436				$queryData['fields'] = array_merge($passedFields, $assocFields);
1437			}
1438			unset($assocFields, $passedFields);
1439		}
1440
1441		if ($linkModel === null) {
1442			return $this->buildStatement(
1443				array(
1444					'fields' => array_unique($queryData['fields']),
1445					'table' => $this->fullTableName($model),
1446					'alias' => $modelAlias,
1447					'limit' => $queryData['limit'],
1448					'offset' => $queryData['offset'],
1449					'joins' => $queryData['joins'],
1450					'conditions' => $queryData['conditions'],
1451					'order' => $queryData['order'],
1452					'group' => $queryData['group']
1453				),
1454				$model
1455			);
1456		}
1457		if ($external && !empty($assocData['finderQuery'])) {
1458			return $assocData['finderQuery'];
1459		}
1460
1461		$self = $model->name === $linkModel->name;
1462		$fields = array();
1463
1464		if ($external || (in_array($type, array('hasOne', 'belongsTo')) && $assocData['fields'] !== false)) {
1465			$fields = $this->fields($linkModel, $association, $assocData['fields']);
1466		}
1467		if (empty($assocData['offset']) && !empty($assocData['page'])) {
1468			$assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
1469		}
1470		$assocData['limit'] = $this->limit($assocData['limit'], $assocData['offset']);
1471
1472		switch ($type) {
1473			case 'hasOne':
1474			case 'belongsTo':
1475				$conditions = $this->_mergeConditions(
1476					$assocData['conditions'],
1477					$this->getConstraint($type, $model, $linkModel, $association, array_merge($assocData, compact('external', 'self')))
1478				);
1479
1480				if (!$self && $external) {
1481					foreach ($conditions as $key => $condition) {
1482						if (is_numeric($key) && strpos($condition, $modelAlias . '.') !== false) {
1483							unset($conditions[$key]);
1484						}
1485					}
1486				}
1487
1488				if ($external) {
1489					$query = array_merge($assocData, array(
1490						'conditions' => $conditions,
1491						'table' => $this->fullTableName($linkModel),
1492						'fields' => $fields,
1493						'alias' => $association,
1494						'group' => null
1495					));
1496					$query += array('order' => $assocData['order'], 'limit' => $assocData['limit']);
1497				} else {
1498					$join = array(
1499						'table' => $linkModel,
1500						'alias' => $association,
1501						'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1502						'conditions' => trim($this->conditions($conditions, true, false, $model))
1503					);
1504					$queryData['fields'] = array_merge($queryData['fields'], $fields);
1505
1506					if (!empty($assocData['order'])) {
1507						$queryData['order'][] = $assocData['order'];
1508					}
1509					if (!in_array($join, $queryData['joins'])) {
1510						$queryData['joins'][] = $join;
1511					}
1512					return true;
1513				}
1514			break;
1515			case 'hasMany':
1516				$assocData['fields'] = $this->fields($linkModel, $association, $assocData['fields']);
1517				if (!empty($assocData['foreignKey'])) {
1518					$assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $association, array("{$association}.{$assocData['foreignKey']}")));
1519				}
1520				$query = array(
1521					'conditions' => $this->_mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $association, $assocData), $assocData['conditions']),
1522					'fields' => array_unique($assocData['fields']),
1523					'table' => $this->fullTableName($linkModel),
1524					'alias' => $association,
1525					'order' => $assocData['order'],
1526					'limit' => $assocData['limit'],
1527					'group' => null
1528				);
1529			break;
1530			case 'hasAndBelongsToMany':
1531				$joinFields = array();
1532				$joinAssoc = null;
1533
1534				if (isset($assocData['with']) && !empty($assocData['with'])) {
1535					$joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
1536					list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys);
1537
1538					$joinTbl = $model->{$with};
1539					$joinAlias = $joinTbl;
1540
1541					if (is_array($joinFields) && !empty($joinFields)) {
1542						$joinAssoc = $joinAlias = $model->{$with}->alias;
1543						$joinFields = $this->fields($model->{$with}, $joinAlias, $joinFields);
1544					} else {
1545						$joinFields = array();
1546					}
1547				} else {
1548					$joinTbl = $assocData['joinTable'];
1549					$joinAlias = $this->fullTableName($assocData['joinTable']);
1550				}
1551				$query = array(
1552					'conditions' => $assocData['conditions'],
1553					'limit' => $assocData['limit'],
1554					'table' => $this->fullTableName($linkModel),
1555					'alias' => $association,
1556					'fields' => array_merge($this->fields($linkModel, $association, $assocData['fields']), $joinFields),
1557					'order' => $assocData['order'],
1558					'group' => null,
1559					'joins' => array(array(
1560						'table' => $joinTbl,
1561						'alias' => $joinAssoc,
1562						'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $association)
1563					))
1564				);
1565			break;
1566		}
1567		if (isset($query)) {
1568			return $this->buildStatement($query, $model);
1569		}
1570		return null;
1571	}
1572
1573/**
1574 * Returns a conditions array for the constraint between two models
1575 *
1576 * @param string $type Association type
1577 * @param Model $model Model object
1578 * @param string $linkModel
1579 * @param string $alias
1580 * @param array $assoc
1581 * @param string $alias2
1582 * @return array Conditions array defining the constraint between $model and $association
1583 */
1584	public function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) {
1585		$assoc += array('external' => false, 'self' => false);
1586
1587		if (empty($assoc['foreignKey'])) {
1588			return array();
1589		}
1590
1591		switch (true) {
1592			case ($assoc['external'] && $type === 'hasOne'):
1593				return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}');
1594			case ($assoc['external'] && $type === 'belongsTo'):
1595				return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}');
1596			case (!$assoc['external'] && $type === 'hasOne'):
1597				return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}"));
1598			case (!$assoc['external'] && $type === 'belongsTo'):
1599				return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}"));
1600			case ($type === 'hasMany'):
1601				return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}'));
1602			case ($type === 'hasAndBelongsToMany'):
1603				return array(
1604					array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'),
1605					array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}"))
1606				);
1607		}
1608		return array();
1609	}
1610
1611/**
1612 * Builds and generates a JOIN statement from an array.	 Handles final clean-up before conversion.
1613 *
1614 * @param array $join An array defining a JOIN statement in a query
1615 * @return string An SQL JOIN statement to be used in a query
1616 * @see DboSource::renderJoinStatement()
1617 * @see DboSource::buildStatement()
1618 */
1619	public function buildJoinStatement($join) {
1620		$data = array_merge(array(
1621			'type' => null,
1622			'alias' => null,
1623			'table' => 'join_table',
1624			'conditions' => array()
1625		), $join);
1626
1627		if (!empty($data['alias'])) {
1628			$data['alias'] = $this->alias . $this->name($data['alias']);
1629		}
1630		if (!empty($data['conditions'])) {
1631			$data['conditions'] = trim($this->conditions($data['conditions'], true, false));
1632		}
1633		if (!empty($data['table'])) {
1634			$data['table'] = $this->fullTableName($data['table']);
1635		}
1636		return $this->renderJoinStatement($data);
1637	}
1638
1639/**
1640 * Builds and generates an SQL statement from an array.	 Handles final clean-up before conversion.
1641 *
1642 * @param array $query An array defining an SQL query
1643 * @param Model $model The model object which initiated the query
1644 * @return string An executable SQL statement
1645 * @see DboSource::renderStatement()
1646 */
1647	public function buildStatement($query, $model) {
1648		$query = array_merge(array('offset' => null, 'joins' => array()), $query);
1649		if (!empty($query['joins'])) {
1650			$count = count($query['joins']);
1651			for ($i = 0; $i < $count; $i++) {
1652				if (is_array($query['joins'][$i])) {
1653					$query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
1654				}
1655			}
1656		}
1657		return $this->renderStatement('select', array(
1658			'conditions' => $this->conditions($query['conditions'], true, true, $model),
1659			'fields' => implode(', ', $query['fields']),
1660			'table' => $query['table'],
1661			'alias' => $this->alias . $this->name($query['alias']),
1662			'order' => $this->order($query['order'], 'ASC', $model),
1663			'limit' => $this->limit($query['limit'], $query['offset']),
1664			'joins' => implode(' ', $query['joins']),
1665			'group' => $this->group($query['group'], $model)
1666		));
1667	}
1668
1669/**
1670 * Renders a final SQL JOIN statement
1671 *
1672 * @param array $data
1673 * @return string
1674 */
1675	public function renderJoinStatement($data) {
1676		extract($data);
1677		return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})");
1678	}
1679
1680/**
1681 * Renders a final SQL statement by putting together the component parts in the correct order
1682 *
1683 * @param string $type type of query being run.  e.g select, create, update, delete, schema, alter.
1684 * @param array $data Array of data to insert into the query.
1685 * @return string Rendered SQL expression to be run.
1686 */
1687	public function renderStatement($type, $data) {
1688		extract($data);
1689		$aliases = null;
1690
1691		switch (strtolower($type)) {
1692			case 'select':
1693				return "SELECT {$fields} FROM {$ta…

Large files files are truncated, but you can click here to view the full file