Browse Source

Merge pull request #3133 from tigrang/type_mapping_objects

Use objects for type maps and pass them to QueryExpressions (to be able to use default mapping from schema)
Mark Story 12 years ago
parent
commit
10377c6acb

+ 20 - 14
src/Database/Expression/QueryExpression.php

@@ -16,6 +16,8 @@ namespace Cake\Database\Expression;
 
 use Cake\Database\ExpressionInterface;
 use Cake\Database\Query;
+use Cake\Database\TypeMap;
+use Cake\Database\TypeMapTrait;
 use Cake\Database\ValueBinder;
 use \Countable;
 
@@ -27,6 +29,8 @@ use \Countable;
  */
 class QueryExpression implements ExpressionInterface, Countable {
 
+	use TypeMapTrait;
+
 /**
  * String to be used for joining each of the internal expressions
  * this object internally stores for example "AND", "OR", etc.
@@ -52,16 +56,18 @@ class QueryExpression implements ExpressionInterface, Countable {
  *
  * @param array $conditions tree-like array structure containing all the conditions
  * to be added or nested inside this expression object.
- * @param array $types associative array of types to be associated with the values
+ * @param array|TypeMap $types associative array of types to be associated with the values
  * passed in $conditions.
  * @param string $conjunction the glue that will join all the string conditions at this
  * level of the expression tree. For example "AND", "OR", "XOR"...
+ * @param TypeMap $typeMap contains default and call specific type mapping
  * @see QueryExpression::add() for more details on $conditions and $types
  */
 	public function __construct($conditions = [], $types = [], $conjunction = 'AND') {
+		$this->typeMap($types);
 		$this->type(strtoupper($conjunction));
 		if (!empty($conditions)) {
-			$this->add($conditions, $types);
+			$this->add($conditions, $this->typeMap()->types());
 		}
 	}
 
@@ -281,9 +287,9 @@ class QueryExpression implements ExpressionInterface, Countable {
  */
 	public function and_($conditions, $types = []) {
 		if (is_callable($conditions)) {
-			return $conditions(new self);
+			return $conditions(new self([], $this->typeMap()->types($types)));
 		}
-		return new self($conditions, $types);
+		return new self($conditions, $this->typeMap()->types($types));
 	}
 
 /**
@@ -297,9 +303,9 @@ class QueryExpression implements ExpressionInterface, Countable {
  */
 	public function or_($conditions, $types = []) {
 		if (is_callable($conditions)) {
-			return $conditions(new self([], [], 'OR'));
+			return $conditions(new self([], $this->typeMap()->types($types), 'OR'));
 		}
-		return new self($conditions, $types, 'OR');
+		return new self($conditions, $this->typeMap()->types($types), 'OR');
 	}
 // @codingStandardsIgnoreEnd
 
@@ -412,6 +418,8 @@ class QueryExpression implements ExpressionInterface, Countable {
 	protected function _addConditions(array $conditions, array $types) {
 		$operators = ['and', 'or', 'xor'];
 
+		$typeMap = $this->typeMap()->types($types);
+
 		foreach ($conditions as $k => $c) {
 			$numericKey = is_numeric($k);
 
@@ -425,12 +433,12 @@ class QueryExpression implements ExpressionInterface, Countable {
 			}
 
 			if ($numericKey && is_array($c) || in_array(strtolower($k), $operators)) {
-				$this->_conditions[] = new self($c, $types, $numericKey ? 'AND' : $k);
+				$this->_conditions[] = new self($c, $typeMap, $numericKey ? 'AND' : $k);
 				continue;
 			}
 
 			if (strtolower($k) === 'not') {
-				$this->_conditions[] = new UnaryExpression(new self($c, $types), [], 'NOT');
+				$this->_conditions[] = new UnaryExpression(new self($c, $typeMap), [], 'NOT');
 				continue;
 			}
 
@@ -440,7 +448,7 @@ class QueryExpression implements ExpressionInterface, Countable {
 			}
 
 			if (!$numericKey) {
-				$this->_conditions[] = $this->_parseCondition($k, $c, $types);
+				$this->_conditions[] = $this->_parseCondition($k, $c);
 			}
 		}
 	}
@@ -455,11 +463,9 @@ class QueryExpression implements ExpressionInterface, Countable {
  * @param string $field The value from with the actual field and operator will
  * be extracted.
  * @param mixed $value The value to be bound to a placeholder for the field
- * @param array $types List of types where the field can be found so the value
- * can be converted accordingly.
  * @return string|QueryExpression
  */
-	protected function _parseCondition($field, $value, $types) {
+	protected function _parseCondition($field, $value) {
 		$operator = '=';
 		$expression = $field;
 		$parts = explode(' ', trim($field), 2);
@@ -468,7 +474,7 @@ class QueryExpression implements ExpressionInterface, Countable {
 			list($expression, $operator) = $parts;
 		}
 
-		$type = isset($types[$expression]) ? $types[$expression] : null;
+		$type = $this->typeMap()->type($expression);
 		$multi = false;
 
 		$typeMultiple = strpos($type, '[]') !== false;
@@ -505,4 +511,4 @@ class QueryExpression implements ExpressionInterface, Countable {
 		return implode(', ', $params);
 	}
 
-}
+}

+ 7 - 11
src/Database/Expression/ValuesExpression.php

@@ -16,6 +16,7 @@ namespace Cake\Database\Expression;
 
 use Cake\Database\ExpressionInterface;
 use Cake\Database\Query;
+use Cake\Database\TypeMapTrait;
 use Cake\Database\ValueBinder;
 use Cake\Error;
 use \Countable;
@@ -28,6 +29,8 @@ use \Countable;
  */
 class ValuesExpression implements ExpressionInterface {
 
+	use TypeMapTrait;
+
 /**
  * Array of values to insert.
  *
@@ -43,13 +46,6 @@ class ValuesExpression implements ExpressionInterface {
 	protected $_columns = [];
 
 /**
- * List of column types.
- *
- * @var array
- */
-	protected $_types = [];
-
-/**
  * The Query object to use as a values expression
  *
  * @var \Cake\Database\Query
@@ -60,11 +56,11 @@ class ValuesExpression implements ExpressionInterface {
  * Constructor
  *
  * @param array $columns The list of columns that are going to be part of the values.
- * @param array $types A dictionary of column -> type names
+ * @param TypeMap $types A dictionary of column -> type names
  */
-	public function __construct(array $columns, array $types = []) {
+	public function __construct(array $columns, $typeMap) {
 		$this->_columns = $columns;
-		$this->_types = $types;
+		$this->typeMap($typeMap);
 	}
 
 /**
@@ -152,7 +148,7 @@ class ValuesExpression implements ExpressionInterface {
 		foreach ($this->_values as $row) {
 			$row = array_merge($defaults, $row);
 			foreach ($row as $column => $value) {
-				$type = isset($this->_types[$column]) ? $this->_types[$column] : null;
+				$type = $this->typeMap()->type($column);
 				$generator->bind($i++, $value, $type);
 			}
 		}

+ 13 - 47
src/Database/Query.php

@@ -33,6 +33,8 @@ use IteratorAggregate;
  */
 class Query implements ExpressionInterface, IteratorAggregate {
 
+	use TypeMapTrait;
+
 /**
  * Connection instance to be used to execute this query.
  *
@@ -129,15 +131,6 @@ class Query implements ExpressionInterface, IteratorAggregate {
 	protected $_iterator;
 
 /**
- * Associative array with the default fields and their types this query might contain
- * used to avoid repetition when calling multiple times functions inside this class that
- * may require a custom type for a specific field.
- *
- * @var array
- */
-	protected $_defaultTypes = [];
-
-/**
  * The object responsible for generating query placeholders and temporarily store values
  * associated to each of those.
  *
@@ -665,7 +658,6 @@ class Query implements ExpressionInterface, IteratorAggregate {
 			$tables = [$tables];
 		}
 
-		$types += $this->defaultTypes();
 		$joins = [];
 		$i = count($this->_parts['join']);
 		foreach ($tables as $alias => $t) {
@@ -854,7 +846,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
 		if ($overwrite) {
 			$this->_parts['where'] = $this->newExpr();
 		}
-		$this->_conjugate('where', $conditions, 'AND', $types + $this->defaultTypes());
+		$this->_conjugate('where', $conditions, 'AND', $types);
 		return $this;
 	}
 
@@ -915,7 +907,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
  * @return Query
  */
 	public function andWhere($conditions, $types = []) {
-		$this->_conjugate('where', $conditions, 'AND', $types + $this->defaultTypes());
+		$this->_conjugate('where', $conditions, 'AND', $types);
 		return $this;
 	}
 
@@ -976,7 +968,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
  * @return Query
  */
 	public function orWhere($conditions, $types = []) {
-		$this->_conjugate('where', $conditions, 'OR', $types + $this->defaultTypes());
+		$this->_conjugate('where', $conditions, 'OR', $types);
 		return $this;
 	}
 
@@ -1081,7 +1073,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
 		if ($overwrite) {
 			$this->_parts['having'] = $this->newExpr();
 		}
-		$this->_conjugate('having', $conditions, 'AND', $types + $this->defaultTypes());
+		$this->_conjugate('having', $conditions, 'AND', $types);
 		return $this;
 	}
 
@@ -1097,7 +1089,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
  * @return Query
  */
 	public function andHaving($conditions, $types = []) {
-		$this->_conjugate('having', $conditions, 'AND', $types + $this->defaultTypes());
+		$this->_conjugate('having', $conditions, 'AND', $types);
 		return $this;
 	}
 
@@ -1113,7 +1105,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
  * @return Query
  */
 	public function orHaving($conditions, $types = []) {
-		$this->_conjugate('having', $conditions, 'OR', $types + $this->defaultTypes());
+		$this->_conjugate('having', $conditions, 'OR', $types);
 		return $this;
 	}
 
@@ -1343,7 +1335,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
 		$this->_parts['insert'][1] = $columns;
 
 		if (!$this->_parts['values']) {
-			$this->_parts['values'] = new ValuesExpression($columns, $types + $this->defaultTypes());
+			$this->_parts['values'] = new ValuesExpression($columns, $this->typeMap()->types($types));
 		}
 
 		return $this;
@@ -1429,14 +1421,14 @@ class Query implements ExpressionInterface, IteratorAggregate {
 
 		if (is_array($key) || $key instanceof ExpressionInterface) {
 			$types = (array)$value;
-			$this->_parts['set']->add($key, $types + $this->defaultTypes());
+			$this->_parts['set']->add($key, $types);
 			return $this;
 		}
 
 		if (is_string($types) && is_string($key)) {
 			$types = [$key => $types];
 		}
-		$this->_parts['set']->eq($key, $value, $types + $this->defaultTypes());
+		$this->_parts['set']->eq($key, $value, $types);
 
 		return $this;
 	}
@@ -1498,7 +1490,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
  * @return QueryExpression
  */
 	public function newExpr() {
-		return new QueryExpression;
+		return new QueryExpression([], $this->typeMap());
 	}
 
 /**
@@ -1644,32 +1636,6 @@ class Query implements ExpressionInterface, IteratorAggregate {
 	}
 
 /**
- * Configures a map of default fields and their associated types to be
- * used as the default list of types for every function in this class
- * with a $types param. Useful to avoid repetition when calling the same
- * functions using the same fields and types.
- *
- * If called with no arguments it will return the currently configured types.
- *
- * ## Example
- *
- * {{{
- *	$query->defaultTypes(['created' => 'datetime', 'is_visible' => 'boolean']);
- * }}}
- *
- * @param array $types associative array where keys are field names and values
- * are the correspondent type.
- * @return Query|array
- */
-	public function defaultTypes(array $types = null) {
-		if ($types === null) {
-			return $this->_defaultTypes;
-		}
-		$this->_defaultTypes = $types;
-		return $this;
-	}
-
-/**
  * Associates a query placeholder to a value and a type.
  *
  * If type is expressed as "atype[]" (note braces) then it will cause the
@@ -1835,7 +1801,7 @@ class Query implements ExpressionInterface, IteratorAggregate {
 		return [
 			'sql' => $this->sql(),
 			'params' => $this->valueBinder()->bindings(),
-			'defaultTypes' => $this->_defaultTypes,
+			'defaultTypes' => $this->defaultTypes(),
 			'decorators' => count($this->_resultDecorators),
 			'executed' => $this->_iterator ? true : false
 		];

+ 115 - 0
src/Database/TypeMap.php

@@ -0,0 +1,115 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace Cake\Database;
+
+/**
+ * Implements default and single-use mappings for columns to their associated types
+ */
+class TypeMap {
+
+/**
+ * Associative array with the default fields and their types this query might contain
+ * used to avoid repetition when calling multiple times functions inside this class that
+ * may require a custom type for a specific field.
+ *
+ * @var array
+ */
+	protected $_defaults;
+
+/**
+ * Associative array with the fields and their types that override defaults this query might contain
+ * used to avoid repetition when calling multiple times functions inside this class that
+ * may require a custom type for a specific field.
+ *
+ * @var array
+ */
+	protected $_types = [];
+
+/**
+ * Creates an instance with the given defaults
+ *
+ * @param array $defaults
+ */
+	public function __construct(array $defaults = []) {
+		$this->defaults($defaults);
+	}
+
+/**
+ * Configures a map of default fields and their associated types to be
+ * used as the default list of types for every function in this class
+ * with a $types param. Useful to avoid repetition when calling the same
+ * functions using the same fields and types.
+ *
+ * If called with no arguments it will return the currently configured types.
+ *
+ * ## Example
+ *
+ * {{{
+ *	$query->defaults(['created' => 'datetime', 'is_visible' => 'boolean']);
+ * }}}
+ *
+ * @param array $defaults associative array where keys are field names and values
+ * are the correspondent type.
+ * @return this|array
+ */
+	public function defaults(array $defaults = null) {
+		if ($defaults === null) {
+			return $this->_defaults;
+		}
+		$this->_defaults = $defaults;
+		return $this;
+	}
+
+/**
+ * Configures a map of fields and their associated types for single-use.
+ *
+ * If called with no arguments it will return the currently configured types.
+ *
+ * ## Example
+ *
+ * {{{
+ *	$query->types(['created' => 'time']);
+ * }}}
+ *
+ * @param array $defaults associative array where keys are field names and values
+ * are the correspondent type.
+ * @return this|array
+ */
+	public function types(array $types = null) {
+		if ($types === null) {
+			return $this->_types;
+		}
+		$this->_types = $types;
+		return $this;
+	}
+
+/**
+ * Returns the type of the given column. If there is no single use type is configured,
+ * the column type will be looked for inside the default mapping. If neither exist,
+ * null will be returned.
+ *
+ * @var null|string
+ */
+	public function type($column) {
+		if (isset($this->_types[$column])) {
+			return $this->_types[$column];
+		}
+		if (isset($this->_defaults[$column])) {
+			return $this->_defaults[$column];
+		}
+		return null;
+	}
+
+}

+ 61 - 0
src/Database/TypeMapTrait.php

@@ -0,0 +1,61 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace Cake\Database;
+
+use Cake\Database\TypeMap;
+
+/*
+ * Represents a class that holds a TypeMap object
+ */
+trait TypeMapTrait {
+
+/**
+ * @var \Cake\Database\TypeMap
+ */
+	protected $_typeMap;
+
+/**
+ * Creates a new TypeMap if $typeMap is an array, otherwise returns the existing type map
+ * or exchanges it for the given one.
+ *
+ * @param array|TypeMap $typeMap Creates a TypeMap if array, otherwise sets the given TypeMap
+ * @return this|TypeMap
+ */
+	public function typeMap($typeMap = null) {
+		if (!$this->_typeMap) {
+			$this->_typeMap = new TypeMap();
+		}
+		if ($typeMap === null) {
+			return $this->_typeMap;
+		}
+		$this->_typeMap = is_array($typeMap) ? new TypeMap($typeMap) : $typeMap;
+		return $this;
+	}
+
+/**
+ * Allows setting default types when chaining query
+ *
+ * @param array $types
+ * @return this|array
+ */
+	public function defaultTypes(array $types = null) {
+		if ($types === null) {
+			return $this->typeMap()->defaults();
+		}
+		$this->typeMap()->defaults($types);
+		return $this;
+	}
+
+}

+ 2 - 2
src/ORM/Query.php

@@ -102,7 +102,7 @@ class Query extends DatabaseQuery {
  * @param \Cake\ORM\Table $table
  */
 	public function __construct($connection, $table) {
-		$this->connection($connection);
+		parent::__construct($connection);
 		$this->repository($table);
 
 		if ($this->_repository) {
@@ -128,7 +128,7 @@ class Query extends DatabaseQuery {
 		foreach ($schema->columns() as $f) {
 			$fields[$f] = $fields[$alias . '.' . $f] = $schema->columnType($f);
 		}
-		$this->defaultTypes($this->defaultTypes() + $fields);
+		$this->defaultTypes($fields);
 
 		return $this;
 	}

+ 21 - 8
tests/TestCase/ORM/Association/BelongsToManyTest.php

@@ -17,6 +17,7 @@ namespace Cake\Test\TestCase\ORM\Association;
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\QueryExpression;
 use Cake\Database\Expression\TupleComparison;
+use Cake\Database\TypeMap;
 use Cake\Datasource\ConnectionManager;
 use Cake\ORM\Association\BelongsToMany;
 use Cake\ORM\Entity;
@@ -69,6 +70,18 @@ class BelongsToManyTest extends TestCase {
 				]
 			]
 		]);
+		$this->tagsTypeMap = new TypeMap([
+			'Tags.id' => 'integer',
+			'id' => 'integer',
+			'Tags.name' => 'string',
+			'name' => 'string',
+		]);
+		$this->articlesTagsTypeMap = new TypeMap([
+			'ArticlesTags.article_id' => 'integer',
+			'article_id' => 'integer',
+			'ArticlesTags.tag_id' => 'integer',
+			'tag_id' => 'integer',
+		]);
 	}
 
 /**
@@ -224,7 +237,7 @@ class BelongsToManyTest extends TestCase {
 			'Tags' => [
 				'conditions' => new QueryExpression([
 					'Tags.name' => 'cake'
-				], ['Tags.name' => 'string']),
+				], $this->tagsTypeMap),
 				'type' => 'INNER',
 				'table' => 'tags'
 			]
@@ -238,7 +251,7 @@ class BelongsToManyTest extends TestCase {
 				'conditions' => new QueryExpression([
 					['Articles.id' => $field1],
 					['Tags.id' => $field2]
-				]),
+				], $this->articlesTagsTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles_tags'
 			]
@@ -271,7 +284,7 @@ class BelongsToManyTest extends TestCase {
 			'Tags' => [
 				'conditions' => new QueryExpression([
 					'Tags.name' => 'cake'
-				], ['Tags.name' => 'string']),
+				], $this->tagsTypeMap),
 				'type' => 'INNER',
 				'table' => 'tags'
 			]
@@ -285,7 +298,7 @@ class BelongsToManyTest extends TestCase {
 				'conditions' => new QueryExpression([
 					['Articles.id' => $field1],
 					['Tags.id' => $field2]
-				]),
+				], $this->articlesTagsTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles_tags'
 			]
@@ -313,7 +326,7 @@ class BelongsToManyTest extends TestCase {
 				'conditions' => new QueryExpression([
 					'a' => 1,
 					'Tags.name' => 'cake',
-				], ['Tags.name' => 'string']),
+				], $this->tagsTypeMap),
 				'type' => 'INNER',
 				'table' => 'tags'
 			]
@@ -327,7 +340,7 @@ class BelongsToManyTest extends TestCase {
 				'conditions' => new QueryExpression([
 					['Articles.id' => $field1],
 					['Tags.id' => $field2]
-				]),
+				], $this->articlesTagsTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles_tags'
 			]
@@ -369,7 +382,7 @@ class BelongsToManyTest extends TestCase {
 			'Tags' => [
 				'conditions' => new QueryExpression([
 					'Tags.name' => 'cake'
-				], ['Tags.name' => 'string']),
+				], $this->tagsTypeMap),
 				'type' => 'INNER',
 				'table' => 'tags'
 			]
@@ -385,7 +398,7 @@ class BelongsToManyTest extends TestCase {
 				'conditions' => new QueryExpression([
 					['Articles.id' => $fieldA, 'Articles.site_id' => $fieldB],
 					['Tags.id' => $fieldC, 'Tags.my_site_id' => $fieldD]
-				]),
+				], $this->articlesTagsTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles_tags'
 			]

+ 13 - 6
tests/TestCase/ORM/Association/BelongsToTest.php

@@ -16,6 +16,7 @@ namespace Cake\Test\TestCase\ORM\Association;
 
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\QueryExpression;
+use Cake\Database\TypeMap;
 use Cake\ORM\Association\BelongsTo;
 use Cake\ORM\Entity;
 use Cake\ORM\Query;
@@ -54,6 +55,12 @@ class BelongsToTest extends \Cake\TestSuite\TestCase {
 				]
 			]
 		]);
+		$this->companiesTypeMap = new TypeMap([
+			'Companies.id' => 'integer',
+			'id' => 'integer',
+			'Companies.company_name' => 'string',
+			'company_name' => 'string',
+		]);
 	}
 
 /**
@@ -97,7 +104,7 @@ class BelongsToTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Companies.is_active' => true,
 					['Companies.id' => $field]
-				], ['Companies.id' => 'integer']),
+				], $this->companiesTypeMap),
 				'table' => 'companies',
 				'type' => 'LEFT'
 			]
@@ -126,7 +133,7 @@ class BelongsToTest extends \Cake\TestSuite\TestCase {
 			'Companies' => [
 				'conditions' => new QueryExpression([
 					'Companies.is_active' => false
-				]),
+				], $this->companiesTypeMap),
 				'type' => 'LEFT',
 				'table' => 'companies',
 			]
@@ -162,7 +169,7 @@ class BelongsToTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Companies.is_active' => true,
 					['Companies.id' => $field]
-				], ['Companies.id' => 'integer']),
+				], $this->companiesTypeMap),
 				'type' => 'LEFT',
 				'table' => 'companies',
 			]
@@ -192,7 +199,7 @@ class BelongsToTest extends \Cake\TestSuite\TestCase {
 					'a' => 1,
 					'Companies.is_active' => true,
 					['Companies.id' => $field]
-				], ['Companies.id' => 'integer']),
+				], $this->companiesTypeMap),
 				'type' => 'LEFT',
 				'table' => 'companies',
 			]
@@ -229,7 +236,7 @@ class BelongsToTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Companies.is_active' => true,
 					['Companies.id' => $field]
-				], ['Companies.id' => 'integer']),
+				], $this->companiesTypeMap),
 				'table' => 'companies',
 				'type' => 'INNER'
 			]
@@ -311,7 +318,7 @@ class BelongsToTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Companies.is_active' => true,
 					['Companies.id' => $field1, 'Companies.tenant_id' => $field2]
-				], ['Companies.id' => 'integer']),
+				], $this->companiesTypeMap),
 				'table' => 'companies',
 				'type' => 'LEFT'
 			]

+ 14 - 5
tests/TestCase/ORM/Association/HasManyTest.php

@@ -17,6 +17,7 @@ namespace Cake\Test\TestCase\ORM\Association;
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\QueryExpression;
 use Cake\Database\Expression\TupleComparison;
+use Cake\Database\TypeMap;
 use Cake\ORM\Association\HasMany;
 use Cake\ORM\Entity;
 use Cake\ORM\Query;
@@ -58,6 +59,14 @@ class HasManyTest extends \Cake\TestSuite\TestCase {
 				'primary' => ['type' => 'primary', 'columns' => ['id']]
 			]
 		]);
+		$this->articlesTypeMap = new TypeMap([
+			'Articles.id' => 'integer',
+			'id' => 'integer',
+			'Articles.title' => 'string',
+			'title' => 'string',
+			'Articles.author_id' => 'integer',
+			'author_id' => 'integer',
+		]);
 	}
 
 /**
@@ -481,7 +490,7 @@ class HasManyTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Articles.is_active' => true,
 					['Authors.id' => $field]
-				]),
+				], $this->articlesTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles'
 			]
@@ -511,7 +520,7 @@ class HasManyTest extends \Cake\TestSuite\TestCase {
 			'Articles' => [
 				'conditions' => new QueryExpression([
 					'Articles.is_active' => false
-				]),
+				], $this->articlesTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles'
 			]
@@ -547,7 +556,7 @@ class HasManyTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Articles.is_active' => true,
 					['Authors.id' => $field]
-				]),
+				], $this->articlesTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles'
 			]
@@ -579,7 +588,7 @@ class HasManyTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Articles.is_active' => true,
 					['Authors.id' => $field1, 'Authors.site_id' => $field2]
-				]),
+				], $this->articlesTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles'
 			]
@@ -632,7 +641,7 @@ class HasManyTest extends \Cake\TestSuite\TestCase {
 					'a' => 1,
 					'Articles.is_active' => true,
 					['Authors.id' => $field],
-				]),
+				], $this->articlesTypeMap),
 				'type' => 'INNER',
 				'table' => 'articles'
 			]

+ 14 - 5
tests/TestCase/ORM/Association/HasOneTest.php

@@ -16,6 +16,7 @@ namespace Cake\Test\TestCase\ORM\Association;
 
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\QueryExpression;
+use Cake\Database\TypeMap;
 use Cake\ORM\Association\HasOne;
 use Cake\ORM\Entity;
 use Cake\ORM\Query;
@@ -54,6 +55,14 @@ class HasOneTest extends \Cake\TestSuite\TestCase {
 				]
 			]
 		]);
+		$this->profilesTypeMap = new TypeMap([
+			'Profiles.id' => 'integer',
+			'id' => 'integer',
+			'Profiles.first_name' => 'string',
+			'first_name' => 'string',
+			'Profiles.user_id' => 'integer',
+			'user_id' => 'integer',
+		]);
 	}
 
 /**
@@ -97,7 +106,7 @@ class HasOneTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Profiles.is_active' => true,
 					['Users.id' => $field],
-				]),
+				], $this->profilesTypeMap),
 				'type' => 'INNER',
 				'table' => 'profiles'
 			]
@@ -128,7 +137,7 @@ class HasOneTest extends \Cake\TestSuite\TestCase {
 			'Profiles' => [
 				'conditions' => new QueryExpression([
 					'Profiles.is_active' => false
-				]),
+				], $this->profilesTypeMap),
 				'type' => 'INNER',
 				'table' => 'profiles'
 			]
@@ -164,7 +173,7 @@ class HasOneTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Profiles.is_active' => true,
 					['Users.id' => $field],
-				]),
+				], $this->profilesTypeMap),
 				'type' => 'INNER',
 				'table' => 'profiles'
 			]
@@ -194,7 +203,7 @@ class HasOneTest extends \Cake\TestSuite\TestCase {
 					'a' => 1,
 					'Profiles.is_active' => true,
 					['Users.id' => $field],
-				]),
+				], $this->profilesTypeMap),
 				'type' => 'INNER',
 				'table' => 'profiles'
 			]
@@ -233,7 +242,7 @@ class HasOneTest extends \Cake\TestSuite\TestCase {
 				'conditions' => new QueryExpression([
 					'Profiles.is_active' => true,
 					['Users.id' => $field1, 'Users.site_id' => $field2],
-				]),
+				], $this->profilesTypeMap),
 				'type' => 'INNER',
 				'table' => 'profiles'
 			]

+ 48 - 8
tests/TestCase/ORM/EagerLoaderTest.php

@@ -16,6 +16,7 @@ namespace Cake\Test\TestCase\ORM;
 
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\QueryExpression;
+use Cake\Database\TypeMap;
 use Cake\Datasource\ConnectionManager;
 use Cake\ORM\EagerLoader;
 use Cake\ORM\Query;
@@ -76,6 +77,43 @@ class EagerLoaderTest extends TestCase {
 		$orders->hasOne('stuff');
 		$stuff->belongsTo('stuffTypes');
 		$companies->belongsTo('categories');
+
+		$this->clientsTypeMap = new TypeMap([
+			'clients.id' => 'integer',
+			'id' => 'integer',
+			'clients.name' => 'string',
+			'name' => 'string',
+			'clients.phone' => 'string',
+			'phone' => 'string',
+		]);
+		$this->ordersTypeMap = new TypeMap([
+			'orders.id' => 'integer',
+			'id' => 'integer',
+			'orders.total' => 'string',
+			'total' => 'string',
+			'orders.placed' => 'datetime',
+			'placed' => 'datetime',
+		]);
+		$this->orderTypesTypeMap = new TypeMap([
+			'orderTypes.id' => 'integer',
+			'id' => 'integer',
+		]);
+		$this->stuffTypeMap = new TypeMap([
+			'stuff.id' => 'integer',
+			'id' => 'integer',
+		]);
+		$this->stuffTypesTypeMap = new TypeMap([
+			'stuffTypes.id' => 'integer',
+			'id' => 'integer',
+		]);
+		$this->companiesTypeMap = new TypeMap([
+			'companies.id' => 'integer',
+			'id' => 'integer',
+		]);
+		$this->categoriesTypeMap = new TypeMap([
+			'categories.id' => 'integer',
+			'id' => 'integer',
+		]);
 	}
 
 /**
@@ -109,13 +147,15 @@ class EagerLoaderTest extends TestCase {
 
 		$query = $this->getMock('\Cake\ORM\Query', ['join'], [$this->connection, $this->table]);
 
+		$query->typeMap($this->clientsTypeMap);
+
 		$query->expects($this->at(0))->method('join')
 			->with(['clients' => [
 				'table' => 'clients',
 				'type' => 'LEFT',
 				'conditions' => new QueryExpression([
-					['clients.id' => new IdentifierExpression('foo.client_id')]
-				], ['clients.id' => 'integer'])
+					['clients.id' => new IdentifierExpression('foo.client_id')],
+				], $this->clientsTypeMap)
 			]])
 			->will($this->returnValue($query));
 
@@ -125,7 +165,7 @@ class EagerLoaderTest extends TestCase {
 				'type' => 'INNER',
 				'conditions' => new QueryExpression([
 					['clients.id' => new IdentifierExpression('orders.client_id')]
-				])
+				], $this->ordersTypeMap)
 			]])
 			->will($this->returnValue($query));
 
@@ -135,7 +175,7 @@ class EagerLoaderTest extends TestCase {
 				'type' => 'LEFT',
 				'conditions' => new QueryExpression([
 					['orderTypes.id' => new IdentifierExpression('orders.order_type_id')]
-				], ['orderTypes.id' => 'integer'])
+				], $this->orderTypesTypeMap)
 			]])
 			->will($this->returnValue($query));
 
@@ -145,7 +185,7 @@ class EagerLoaderTest extends TestCase {
 				'type' => 'INNER',
 				'conditions' => new QueryExpression([
 					['orders.id' => new IdentifierExpression('stuff.order_id')]
-				])
+				], $this->stuffTypeMap)
 			]])
 			->will($this->returnValue($query));
 
@@ -155,7 +195,7 @@ class EagerLoaderTest extends TestCase {
 				'type' => 'LEFT',
 				'conditions' => new QueryExpression([
 					['stuffTypes.id' => new IdentifierExpression('stuff.stuff_type_id')]
-				], ['stuffTypes.id' => 'integer'])
+				], $this->stuffTypesTypeMap)
 			]])
 			->will($this->returnValue($query));
 
@@ -165,7 +205,7 @@ class EagerLoaderTest extends TestCase {
 				'type' => 'LEFT',
 				'conditions' => new QueryExpression([
 					['companies.id' => new IdentifierExpression('clients.organization_id')]
-				], ['companies.id' => 'integer'])
+				], $this->companiesTypeMap)
 			]])
 			->will($this->returnValue($query));
 
@@ -175,7 +215,7 @@ class EagerLoaderTest extends TestCase {
 				'type' => 'LEFT',
 				'conditions' => new QueryExpression([
 					['categories.id' => new IdentifierExpression('companies.category_id')]
-				], ['categories.id' => 'integer'])
+				], $this->categoriesTypeMap)
 			]])
 			->will($this->returnValue($query));
 

+ 6 - 1
tests/TestCase/ORM/QueryTest.php

@@ -17,6 +17,7 @@ namespace Cake\Test\TestCase\ORM;
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\OrderByExpression;
 use Cake\Database\Expression\QueryExpression;
+use Cake\Database\TypeMap;
 use Cake\Datasource\ConnectionManager;
 use Cake\ORM\Query;
 use Cake\ORM\ResultSet;
@@ -85,6 +86,8 @@ class QueryTest extends TestCase {
 		$orders->hasOne('stuff');
 		$stuff->belongsTo('stuffTypes');
 		$companies->belongsTo('categories');
+
+		$this->fooTypeMap = new TypeMap(['foo.id' => 'integer', 'id' => 'integer']);
 	}
 
 /**
@@ -777,13 +780,14 @@ class QueryTest extends TestCase {
 
 		$this->assertEquals(['field_a', 'field_b'], $query->clause('select'));
 
-		$expected = new QueryExpression($options['conditions']);
+		$expected = new QueryExpression($options['conditions'], $this->fooTypeMap);
 		$result = $query->clause('where');
 		$this->assertEquals($expected, $result);
 
 		$this->assertEquals(1, $query->clause('limit'));
 
 		$expected = new QueryExpression(['a > b']);
+		$expected->typeMap($this->fooTypeMap);
 		$result = $query->clause('join');
 		$this->assertEquals([
 			'table_a' => ['alias' => 'table_a', 'type' => 'INNER', 'conditions' => $expected]
@@ -796,6 +800,7 @@ class QueryTest extends TestCase {
 		$this->assertEquals(['field_a'], $query->clause('group'));
 
 		$expected = new QueryExpression($options['having']);
+		$expected->typeMap($this->fooTypeMap);
 		$this->assertEquals($expected, $query->clause('having'));
 
 		$expected = ['table_a' => ['table_b' => []]];

+ 49 - 15
tests/TestCase/ORM/TableTest.php

@@ -17,6 +17,7 @@ namespace Cake\Test\TestCase\ORM;
 use Cake\Core\Configure;
 use Cake\Database\Expression\OrderByExpression;
 use Cake\Database\Expression\QueryExpression;
+use Cake\Database\TypeMap;
 use Cake\Datasource\ConnectionManager;
 use Cake\ORM\Table;
 use Cake\ORM\TableRegistry;
@@ -52,6 +53,31 @@ class TableTest extends \Cake\TestSuite\TestCase {
 		parent::setUp();
 		$this->connection = ConnectionManager::get('test');
 		Configure::write('App.namespace', 'TestApp');
+
+		$this->usersTypeMap = new TypeMap([
+			'Users.id' => 'integer',
+			'id' => 'integer',
+			'Users.username' => 'string',
+			'username' => 'string',
+			'Users.password' => 'string',
+			'password' => 'string',
+			'Users.created' => 'timestamp',
+			'created' => 'timestamp',
+			'Users.updated' => 'timestamp',
+			'updated' => 'timestamp',
+		]);
+		$this->articlesTypeMap = new TypeMap([
+			'Articles.id' => 'integer',
+			'id' => 'integer',
+			'Articles.title' => 'string',
+			'title' => 'string',
+			'Articles.author_id' => 'integer',
+			'author_id' => 'integer',
+			'Articles.body' => 'text',
+			'body' => 'text',
+			'Articles.published' => 'string',
+			'published' => 'string',
+		]);
 	}
 
 	public function tearDown() {
@@ -2037,7 +2063,8 @@ class TableTest extends \Cake\TestSuite\TestCase {
 
 		$result = $table->findByUsername('garrett');
 		$this->assertInstanceOf('Cake\ORM\Query', $result);
-		$expected = new QueryExpression(['username' => 'garrett'], ['username' => 'string']);
+
+		$expected = new QueryExpression(['username' => 'garrett'], $this->usersTypeMap);
 		$this->assertEquals($expected, $result->clause('where'));
 	}
 
@@ -2090,11 +2117,8 @@ class TableTest extends \Cake\TestSuite\TestCase {
 
 		$result = $table->findByUsernameAndId('garrett', 4);
 		$this->assertInstanceOf('Cake\ORM\Query', $result);
-		$expected = new QueryExpression(
-			['username' => 'garrett', 'id' => 4],
-			['username' => 'string', 'id' => 'integer'],
-			'AND'
-		);
+
+		$expected = new QueryExpression(['username' => 'garrett', 'id' => 4], $this->usersTypeMap);
 		$this->assertEquals($expected, $result->clause('where'));
 	}
 
@@ -2108,13 +2132,13 @@ class TableTest extends \Cake\TestSuite\TestCase {
 
 		$result = $table->findByUsernameOrId('garrett', 4);
 		$this->assertInstanceOf('Cake\ORM\Query', $result);
-		$expected = new QueryExpression();
+
+		$expected = new QueryExpression([], $this->usersTypeMap);
 		$expected->add([
 			'OR' => [
 				'username' => 'garrett',
 				'id' => 4
-			]],
-			['username' => 'string', 'id' => 'integer']
+			]]
 		);
 		$this->assertEquals($expected, $result->clause('where'));
 	}
@@ -2130,11 +2154,8 @@ class TableTest extends \Cake\TestSuite\TestCase {
 		$result = $table->findAllByAuthorId(1);
 		$this->assertInstanceOf('Cake\ORM\Query', $result);
 		$this->assertNull($result->clause('limit'));
-		$expected = new QueryExpression(
-			['author_id' => 1],
-			['author_id' => 'integer'],
-			'AND'
-		);
+
+		$expected = new QueryExpression(['author_id' => 1], $this->articlesTypeMap);
 		$this->assertEquals($expected, $result->clause('where'));
 	}
 
@@ -2150,7 +2171,8 @@ class TableTest extends \Cake\TestSuite\TestCase {
 		$this->assertInstanceOf('Cake\ORM\Query', $result);
 		$this->assertNull($result->clause('limit'));
 		$expected = new QueryExpression(
-			['author_id' => 1, 'published' => 'Y']
+			['author_id' => 1, 'published' => 'Y'],
+			$this->usersTypeMap
 		);
 		$this->assertEquals($expected, $result->clause('where'));
 	}
@@ -2167,6 +2189,18 @@ class TableTest extends \Cake\TestSuite\TestCase {
 		$this->assertInstanceOf('Cake\ORM\Query', $result);
 		$this->assertNull($result->clause('limit'));
 		$expected = new QueryExpression();
+		$expected->typeMap()->defaults([
+			'Users.id' => 'integer',
+			'id' => 'integer',
+			'Users.username' => 'string',
+			'username' => 'string',
+			'Users.password' => 'string',
+			'password' => 'string',
+			'Users.created' => 'timestamp',
+			'created' => 'timestamp',
+			'Users.updated' => 'timestamp',
+			'updated' => 'timestamp',
+		]);
 		$expected->add(
 			['or' => ['author_id' => 1, 'published' => 'Y']]
 		);