Browse Source

Make additional mappings for contained associations more efficient.

normalized() is a bit of a hog. Not calling that halved the performance
impact of mapping association column types. Include a test showing
nested association type mapping as well.

Refs #6975
Mark Story 10 years ago
parent
commit
af274b8a66
2 changed files with 67 additions and 21 deletions
  1. 39 21
      src/ORM/Query.php
  2. 28 0
      tests/TestCase/ORM/QueryRegressionTest.php

+ 39 - 21
src/ORM/Query.php

@@ -15,7 +15,6 @@
 namespace Cake\ORM;
 
 use ArrayObject;
-use BadMethodCallException;
 use Cake\Database\ExpressionInterface;
 use Cake\Database\Query as DatabaseQuery;
 use Cake\Database\ValueBinder;
@@ -110,7 +109,7 @@ class Query extends DatabaseQuery implements JsonSerializable
     /**
      * Constructor
      *
-     * @param \Cake\Datasource\ConnectionInterface $connection The connection object
+     * @param \Cake\Database\Connection $connection The connection object
      * @param \Cake\ORM\Table $table The table this query is starting on
      */
     public function __construct($connection, $table)
@@ -150,8 +149,8 @@ class Query extends DatabaseQuery implements JsonSerializable
     /**
      * Hints this object to associate the correct types when casting conditions
      * for the database. This is done by extracting the field types from the schema
-     * associated to the passed table object. This prevents developers from repeating
-     * themselves when specifying conditions.
+     * associated to the passed table object. This prevents the user from repeating
+     * himself when specifying conditions.
      *
      * This method returns the same query object for chaining.
      *
@@ -281,10 +280,6 @@ class Query extends DatabaseQuery implements JsonSerializable
      * If called with an empty first argument and $override is set to true, the
      * previous list will be emptied.
      *
-     * Contained associations will have their column types mapped allowing you
-     * to use complex types in where() conditions. Nested associations will not have
-     * their types mapped.
-     *
      * @param array|string $associations list of table aliases to be queried
      * @param bool $override whether override previous list with the one passed
      * defaults to merging previous list with the new one.
@@ -292,26 +287,49 @@ class Query extends DatabaseQuery implements JsonSerializable
      */
     public function contain($associations = null, $override = false)
     {
-        if ($override) {
-            $this->_eagerLoader->clearContain();
-        }
-
-        $result = $this->eagerLoader()->contain($associations);
-        if ($associations !== null || $override) {
+        $loader = $this->eagerLoader();
+        if ($override === true) {
+            $loader->clearContain();
             $this->_dirty();
         }
+
+        $result = $loader->contain($associations);
         if ($associations === null) {
             return $result;
         }
 
-        foreach ($this->eagerLoader()->normalized($this->repository()) as $loader) {
-            $this->addDefaultTypes($loader->instance()->target());
-        }
-
+        $this->_addAssociationsToTypeMap($this->repository(), $this->typeMap(), $result);
         return $this;
     }
 
     /**
+     * Used to recursively add contained association column types to
+     * the query.
+     *
+     * @param \Cake\ORM\Table $table The table instance to pluck associations from.
+     * @param array $associations The nested tree of associations to walk.
+     * @return void
+     */
+    protected function _addAssociationsToTypeMap($table, $typeMap, $associations)
+    {
+        $typeMap = $this->typeMap();
+        foreach ($associations as $name => $nested) {
+            $association = $table->association($name);
+            if (!$association) {
+                continue;
+            }
+            $target = $association->target();
+            $primary = (array)$target->primaryKey();
+            if ($typeMap->type($target->aliasField($primary[0])) === null) {
+                $this->addDefaultTypes($target);
+            }
+            if (!empty($nested)) {
+                $this->_addAssociationsToTypeMap($target, $typeMap, $nested);
+            }
+        }
+    }
+
+    /**
      * Adds filtering conditions to this query to only bring rows that have a relation
      * to another from an associated table, based on conditions in the associated table.
      *
@@ -859,7 +877,8 @@ class Query extends DatabaseQuery implements JsonSerializable
         $this->triggerBeforeFind();
 
         $this->_transformQuery();
-        return parent::sql($binder);
+        $sql = parent::sql($binder);
+        return $sql;
     }
 
     /**
@@ -887,7 +906,6 @@ class Query extends DatabaseQuery implements JsonSerializable
      * specified and applies the joins required to eager load associations defined
      * using `contain`
      *
-     * @see \Cake\ORM\Query::sql()
      * @see \Cake\Database\Query::execute()
      * @return void
      */
@@ -1023,7 +1041,7 @@ class Query extends DatabaseQuery implements JsonSerializable
             return $this->_call($method, $arguments);
         }
 
-        throw new BadMethodCallException(
+        throw new \BadMethodCallException(
             sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type())
         );
     }

+ 28 - 0
tests/TestCase/ORM/QueryRegressionTest.php

@@ -1017,6 +1017,34 @@ class QueryRegressionTest extends TestCase
     }
 
     /**
+     * Test that nested contain queries map types correctly.
+     *
+     * @return void
+     */
+    public function testComplexNestedTypesInJoinedWhere()
+    {
+        $table = TableRegistry::get('Users');
+        $table->hasOne('Comments', [
+            'foreignKey' => 'user_id',
+        ]);
+        $table->Comments->belongsTo('Articles');
+        $table->Comments->Articles->belongsTo('Authors', [
+            'className' => 'Users',
+            'foreignKey' => 'author_id'
+        ]);
+
+        $query = $table->find()
+            ->contain('Comments.Articles.Authors')
+            ->where([
+                'Authors.created >' => new \DateTime('2007-03-17 01:16:00')
+            ]);
+
+        $result = $query->first();
+        $this->assertNotEmpty($result);
+        $this->assertInstanceOf('Cake\I18n\Time', $result->comment->article->author->updated);
+    }
+
+    /**
      * Tests that it is possible to use matching with dot notation
      * even when part of the part of the path in the dot notation is
      * shared for two different calls