Skip to content

Commit

Permalink
Make additional mappings for contained associations more efficient.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
markstory committed Oct 6, 2015
1 parent 4d5ec31 commit af274b8
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 21 deletions.
60 changes: 39 additions & 21 deletions src/ORM/Query.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -150,8 +149,8 @@ public function select($fields = [], $overwrite = false)
/**
* 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.
*
Expand Down Expand Up @@ -281,36 +280,55 @@ public function eagerLoader(EagerLoader $instance = null)
* 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.
* @return array|$this
*/
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.
Expand Down Expand Up @@ -859,7 +877,8 @@ public function sql(ValueBinder $binder = null)
$this->triggerBeforeFind();

$this->_transformQuery();
return parent::sql($binder);
$sql = parent::sql($binder);
return $sql;
}

/**
Expand Down Expand Up @@ -887,7 +906,6 @@ protected function _execute()
* 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
*/
Expand Down Expand Up @@ -1023,7 +1041,7 @@ public function __call($method, $arguments)
return $this->_call($method, $arguments);
}

throw new BadMethodCallException(
throw new \BadMethodCallException(
sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type())
);
}
Expand Down
28 changes: 28 additions & 0 deletions tests/TestCase/ORM/QueryRegressionTest.php
Expand Up @@ -1016,6 +1016,34 @@ public function testComplexTypesInJoinedWhere()
$this->assertInstanceOf('Cake\I18n\Time', $result->comment->updated);
}

/**
* 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
Expand Down

0 comments on commit af274b8

Please sign in to comment.