diff --git a/src/Database/Expression/QueryExpression.php b/src/Database/Expression/QueryExpression.php index 9edcdb54d04..74b13735e16 100644 --- a/src/Database/Expression/QueryExpression.php +++ b/src/Database/Expression/QueryExpression.php @@ -16,6 +16,8 @@ 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 @@ */ 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 @@ public function notIn($field, $values, $type = null) { */ 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 @@ public function and_($conditions, $types = []) { */ 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 @@ public function iterateParts(callable $callable) { 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 @@ protected function _addConditions(array $conditions, array $types) { } 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 @@ protected function _addConditions(array $conditions, array $types) { } if (!$numericKey) { - $this->_conditions[] = $this->_parseCondition($k, $c, $types); + $this->_conditions[] = $this->_parseCondition($k, $c); } } } @@ -455,11 +463,9 @@ protected function _addConditions(array $conditions, array $types) { * @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 @@ protected function _parseCondition($field, $value, $types) { 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 @@ protected function _bindMultiplePlaceholders($field, $values, $type) { return implode(', ', $params); } -} +} \ No newline at end of file diff --git a/src/Database/Expression/ValuesExpression.php b/src/Database/Expression/ValuesExpression.php index f5baad7a2ad..828c412395f 100644 --- a/src/Database/Expression/ValuesExpression.php +++ b/src/Database/Expression/ValuesExpression.php @@ -16,6 +16,7 @@ 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 @@ */ class ValuesExpression implements ExpressionInterface { + use TypeMapTrait; + /** * Array of values to insert. * @@ -42,13 +45,6 @@ class ValuesExpression implements ExpressionInterface { */ protected $_columns = []; -/** - * List of column types. - * - * @var array - */ - protected $_types = []; - /** * The Query object to use as a values expression * @@ -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 @@ public function sql(ValueBinder $generator) { 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); } } diff --git a/src/Database/Query.php b/src/Database/Query.php index ced2464b5f5..1ddf5691730 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -33,6 +33,8 @@ */ class Query implements ExpressionInterface, IteratorAggregate { + use TypeMapTrait; + /** * Connection instance to be used to execute this query. * @@ -128,15 +130,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 @@ public function join($tables = null, $types = [], $overwrite = false) { $tables = [$tables]; } - $types += $this->defaultTypes(); $joins = []; $i = count($this->_parts['join']); foreach ($tables as $alias => $t) { @@ -854,7 +846,7 @@ public function where($conditions = null, $types = [], $overwrite = false) { 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 @@ public function where($conditions = null, $types = [], $overwrite = false) { * @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 @@ public function andWhere($conditions, $types = []) { * @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 @@ public function having($conditions = null, $types = [], $overwrite = false) { 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 @@ public function having($conditions = null, $types = [], $overwrite = false) { * @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 @@ public function andHaving($conditions, $types = []) { * @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 @@ public function insert($columns, $types = []) { $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 @@ public function set($key, $value = null, $types = []) { 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 @@ public function type() { * @return QueryExpression */ public function newExpr() { - return new QueryExpression; + return new QueryExpression([], $this->typeMap()); } /** @@ -1643,32 +1635,6 @@ public function traverseExpressions(callable $callback) { return $this->traverse($visitor); } -/** - * 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. * @@ -1835,7 +1801,7 @@ public function __debugInfo() { return [ 'sql' => $this->sql(), 'params' => $this->valueBinder()->bindings(), - 'defaultTypes' => $this->_defaultTypes, + 'defaultTypes' => $this->defaultTypes(), 'decorators' => count($this->_resultDecorators), 'executed' => $this->_iterator ? true : false ]; diff --git a/src/Database/TypeMap.php b/src/Database/TypeMap.php new file mode 100644 index 00000000000..2328c4060d1 --- /dev/null +++ b/src/Database/TypeMap.php @@ -0,0 +1,115 @@ +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; + } + +} \ No newline at end of file diff --git a/src/Database/TypeMapTrait.php b/src/Database/TypeMapTrait.php new file mode 100644 index 00000000000..b80413281ff --- /dev/null +++ b/src/Database/TypeMapTrait.php @@ -0,0 +1,61 @@ +_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; + } + +} \ No newline at end of file diff --git a/src/ORM/Query.php b/src/ORM/Query.php index 4aefe0935a4..a3be51b6e9a 100644 --- a/src/ORM/Query.php +++ b/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 @@ public function addDefaultTypes(Table $table) { foreach ($schema->columns() as $f) { $fields[$f] = $fields[$alias . '.' . $f] = $schema->columnType($f); } - $this->defaultTypes($this->defaultTypes() + $fields); + $this->defaultTypes($fields); return $this; } diff --git a/tests/TestCase/ORM/Association/BelongsToManyTest.php b/tests/TestCase/ORM/Association/BelongsToManyTest.php index 7c716eec894..c19104c48e0 100644 --- a/tests/TestCase/ORM/Association/BelongsToManyTest.php +++ b/tests/TestCase/ORM/Association/BelongsToManyTest.php @@ -17,6 +17,7 @@ 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 @@ public function setUp() { ] ] ]); + $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 @@ public function testAttachTo() { 'Tags' => [ 'conditions' => new QueryExpression([ 'Tags.name' => 'cake' - ], ['Tags.name' => 'string']), + ], $this->tagsTypeMap), 'type' => 'INNER', 'table' => 'tags' ] @@ -238,7 +251,7 @@ public function testAttachTo() { 'conditions' => new QueryExpression([ ['Articles.id' => $field1], ['Tags.id' => $field2] - ]), + ], $this->articlesTagsTypeMap), 'type' => 'INNER', 'table' => 'articles_tags' ] @@ -271,7 +284,7 @@ public function testAttachToNoFields() { 'Tags' => [ 'conditions' => new QueryExpression([ 'Tags.name' => 'cake' - ], ['Tags.name' => 'string']), + ], $this->tagsTypeMap), 'type' => 'INNER', 'table' => 'tags' ] @@ -285,7 +298,7 @@ public function testAttachToNoFields() { 'conditions' => new QueryExpression([ ['Articles.id' => $field1], ['Tags.id' => $field2] - ]), + ], $this->articlesTagsTypeMap), 'type' => 'INNER', 'table' => 'articles_tags' ] @@ -313,7 +326,7 @@ public function testAttachToWithQueryBuilder() { 'conditions' => new QueryExpression([ 'a' => 1, 'Tags.name' => 'cake', - ], ['Tags.name' => 'string']), + ], $this->tagsTypeMap), 'type' => 'INNER', 'table' => 'tags' ] @@ -327,7 +340,7 @@ public function testAttachToWithQueryBuilder() { 'conditions' => new QueryExpression([ ['Articles.id' => $field1], ['Tags.id' => $field2] - ]), + ], $this->articlesTagsTypeMap), 'type' => 'INNER', 'table' => 'articles_tags' ] @@ -369,7 +382,7 @@ public function testAttachToMultiPrimaryKey() { 'Tags' => [ 'conditions' => new QueryExpression([ 'Tags.name' => 'cake' - ], ['Tags.name' => 'string']), + ], $this->tagsTypeMap), 'type' => 'INNER', 'table' => 'tags' ] @@ -385,7 +398,7 @@ public function testAttachToMultiPrimaryKey() { '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' ] diff --git a/tests/TestCase/ORM/Association/BelongsToTest.php b/tests/TestCase/ORM/Association/BelongsToTest.php index e32643c347b..1dfbad8f07c 100644 --- a/tests/TestCase/ORM/Association/BelongsToTest.php +++ b/tests/TestCase/ORM/Association/BelongsToTest.php @@ -16,6 +16,7 @@ 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 @@ public function setUp() { ] ] ]); + $this->companiesTypeMap = new TypeMap([ + 'Companies.id' => 'integer', + 'id' => 'integer', + 'Companies.company_name' => 'string', + 'company_name' => 'string', + ]); } /** @@ -97,7 +104,7 @@ public function testAttachTo() { 'conditions' => new QueryExpression([ 'Companies.is_active' => true, ['Companies.id' => $field] - ], ['Companies.id' => 'integer']), + ], $this->companiesTypeMap), 'table' => 'companies', 'type' => 'LEFT' ] @@ -126,7 +133,7 @@ public function testAttachToConfigOverride() { 'Companies' => [ 'conditions' => new QueryExpression([ 'Companies.is_active' => false - ]), + ], $this->companiesTypeMap), 'type' => 'LEFT', 'table' => 'companies', ] @@ -162,7 +169,7 @@ public function testAttachToNoFields() { 'conditions' => new QueryExpression([ 'Companies.is_active' => true, ['Companies.id' => $field] - ], ['Companies.id' => 'integer']), + ], $this->companiesTypeMap), 'type' => 'LEFT', 'table' => 'companies', ] @@ -192,7 +199,7 @@ public function testAttachToWithQueryBuilder() { 'a' => 1, 'Companies.is_active' => true, ['Companies.id' => $field] - ], ['Companies.id' => 'integer']), + ], $this->companiesTypeMap), 'type' => 'LEFT', 'table' => 'companies', ] @@ -229,7 +236,7 @@ public function testAttachToMatching() { 'conditions' => new QueryExpression([ 'Companies.is_active' => true, ['Companies.id' => $field] - ], ['Companies.id' => 'integer']), + ], $this->companiesTypeMap), 'table' => 'companies', 'type' => 'INNER' ] @@ -311,7 +318,7 @@ public function testAttachToMultiPrimaryKey() { 'conditions' => new QueryExpression([ 'Companies.is_active' => true, ['Companies.id' => $field1, 'Companies.tenant_id' => $field2] - ], ['Companies.id' => 'integer']), + ], $this->companiesTypeMap), 'table' => 'companies', 'type' => 'LEFT' ] diff --git a/tests/TestCase/ORM/Association/HasManyTest.php b/tests/TestCase/ORM/Association/HasManyTest.php index f253a88a9c4..836b0f5e183 100644 --- a/tests/TestCase/ORM/Association/HasManyTest.php +++ b/tests/TestCase/ORM/Association/HasManyTest.php @@ -17,6 +17,7 @@ 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 @@ public function setUp() { '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 @@ public function testAttachTo() { 'conditions' => new QueryExpression([ 'Articles.is_active' => true, ['Authors.id' => $field] - ]), + ], $this->articlesTypeMap), 'type' => 'INNER', 'table' => 'articles' ] @@ -511,7 +520,7 @@ public function testAttachToConfigOverride() { 'Articles' => [ 'conditions' => new QueryExpression([ 'Articles.is_active' => false - ]), + ], $this->articlesTypeMap), 'type' => 'INNER', 'table' => 'articles' ] @@ -547,7 +556,7 @@ public function testAttachToNoFields() { 'conditions' => new QueryExpression([ 'Articles.is_active' => true, ['Authors.id' => $field] - ]), + ], $this->articlesTypeMap), 'type' => 'INNER', 'table' => 'articles' ] @@ -579,7 +588,7 @@ public function testAttachToMultiPrimaryKey() { 'conditions' => new QueryExpression([ 'Articles.is_active' => true, ['Authors.id' => $field1, 'Authors.site_id' => $field2] - ]), + ], $this->articlesTypeMap), 'type' => 'INNER', 'table' => 'articles' ] @@ -632,7 +641,7 @@ public function testAttachToWithQueryBuilder() { 'a' => 1, 'Articles.is_active' => true, ['Authors.id' => $field], - ]), + ], $this->articlesTypeMap), 'type' => 'INNER', 'table' => 'articles' ] diff --git a/tests/TestCase/ORM/Association/HasOneTest.php b/tests/TestCase/ORM/Association/HasOneTest.php index 65487217e2a..b6527b2899e 100644 --- a/tests/TestCase/ORM/Association/HasOneTest.php +++ b/tests/TestCase/ORM/Association/HasOneTest.php @@ -16,6 +16,7 @@ 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 @@ public function setUp() { ] ] ]); + $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 @@ public function testAttachTo() { 'conditions' => new QueryExpression([ 'Profiles.is_active' => true, ['Users.id' => $field], - ]), + ], $this->profilesTypeMap), 'type' => 'INNER', 'table' => 'profiles' ] @@ -128,7 +137,7 @@ public function testAttachToConfigOverride() { 'Profiles' => [ 'conditions' => new QueryExpression([ 'Profiles.is_active' => false - ]), + ], $this->profilesTypeMap), 'type' => 'INNER', 'table' => 'profiles' ] @@ -164,7 +173,7 @@ public function testAttachToNoFields() { 'conditions' => new QueryExpression([ 'Profiles.is_active' => true, ['Users.id' => $field], - ]), + ], $this->profilesTypeMap), 'type' => 'INNER', 'table' => 'profiles' ] @@ -194,7 +203,7 @@ public function testAttachToWithQueryBuilder() { 'a' => 1, 'Profiles.is_active' => true, ['Users.id' => $field], - ]), + ], $this->profilesTypeMap), 'type' => 'INNER', 'table' => 'profiles' ] @@ -233,7 +242,7 @@ public function testAttachToMultiPrimaryKey() { 'conditions' => new QueryExpression([ 'Profiles.is_active' => true, ['Users.id' => $field1, 'Users.site_id' => $field2], - ]), + ], $this->profilesTypeMap), 'type' => 'INNER', 'table' => 'profiles' ] diff --git a/tests/TestCase/ORM/EagerLoaderTest.php b/tests/TestCase/ORM/EagerLoaderTest.php index 4380ae7da2d..e144c99dfa7 100644 --- a/tests/TestCase/ORM/EagerLoaderTest.php +++ b/tests/TestCase/ORM/EagerLoaderTest.php @@ -16,6 +16,7 @@ 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 @@ public function setUp() { $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 @@ public function testContainToJoinsOneLevel() { $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 @@ public function testContainToJoinsOneLevel() { 'type' => 'INNER', 'conditions' => new QueryExpression([ ['clients.id' => new IdentifierExpression('orders.client_id')] - ]) + ], $this->ordersTypeMap) ]]) ->will($this->returnValue($query)); @@ -135,7 +175,7 @@ public function testContainToJoinsOneLevel() { '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 @@ public function testContainToJoinsOneLevel() { 'type' => 'INNER', 'conditions' => new QueryExpression([ ['orders.id' => new IdentifierExpression('stuff.order_id')] - ]) + ], $this->stuffTypeMap) ]]) ->will($this->returnValue($query)); @@ -155,7 +195,7 @@ public function testContainToJoinsOneLevel() { '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 @@ public function testContainToJoinsOneLevel() { '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 @@ public function testContainToJoinsOneLevel() { 'type' => 'LEFT', 'conditions' => new QueryExpression([ ['categories.id' => new IdentifierExpression('companies.category_id')] - ], ['categories.id' => 'integer']) + ], $this->categoriesTypeMap) ]]) ->will($this->returnValue($query)); diff --git a/tests/TestCase/ORM/QueryTest.php b/tests/TestCase/ORM/QueryTest.php index 560460d173f..8c233f49f06 100644 --- a/tests/TestCase/ORM/QueryTest.php +++ b/tests/TestCase/ORM/QueryTest.php @@ -17,6 +17,7 @@ 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 @@ public function setUp() { $orders->hasOne('stuff'); $stuff->belongsTo('stuffTypes'); $companies->belongsTo('categories'); + + $this->fooTypeMap = new TypeMap(['foo.id' => 'integer', 'id' => 'integer']); } /** @@ -777,13 +780,14 @@ public function testApplyOptions() { $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 @@ public function testApplyOptions() { $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' => []]]; diff --git a/tests/TestCase/ORM/TableTest.php b/tests/TestCase/ORM/TableTest.php index 9b953ea4fde..c240fb94118 100644 --- a/tests/TestCase/ORM/TableTest.php +++ b/tests/TestCase/ORM/TableTest.php @@ -17,6 +17,7 @@ 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 @@ public function setUp() { 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 @@ public function testMagicFindDefaultToAll() { $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 @@ public function testMagicFindFirstAnd() { $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 @@ public function testMagicFindFirstOr() { $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 @@ public function testMagicFindAll() { $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 @@ public function testMagicFindAllAnd() { $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 @@ public function testMagicFindAllOr() { $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']] );