Commit 98352994 authored by catch's avatar catch

Issue #2759757 by gambry, claudiu.cristea, heldercor, dawehner, -enzo-,...

Issue #2759757 by gambry, claudiu.cristea, heldercor, dawehner, -enzo-, TimRutherford, amateescu, joachim: EntityQuery wrong SQL with two reference fields conditions targetting same entity type
parent e394fff4
......@@ -22,10 +22,13 @@ class Tables implements TablesInterface {
protected $sqlQuery;
/**
* Entity table array, key is table name, value is alias.
* Entity table array.
*
* This array contains at most two entries: one for the data, one for the
* properties.
* properties. Its keys are unique references to the tables, values are
* aliases.
*
* @see \Drupal\Core\Entity\Query\Sql\Tables::ensureEntityTable().
*
* @var array
*/
......@@ -297,21 +300,49 @@ public function isFieldCaseSensitive($field_name) {
}
/**
* Join entity table if necessary and return the alias for it.
* Joins the entity table, if necessary, and returns the alias for it.
*
* @param string $index_prefix
* The table array index prefix. For a base table this will be empty,
* for a target entity reference like 'field_tags.entity:taxonomy_term.name'
* this will be 'entity:taxonomy_term.target_id.'.
* @param string $property
* The field property/column.
* @param string $type
* The join type, can either be INNER or LEFT.
* @param string $langcode
* The langcode we use on the join.
* @param string $base_table
* The table to join to. It can be either the table name, its alias or the
* 'base_table' placeholder.
* @param string $id_field
* The name of the ID field/property for the current entity. For instance:
* tid, nid, etc.
* @param array $entity_tables
* Array of entity tables (data and base tables) where decide the entity
* property will be queried from. The first table containing the property
* will be used, so the order is important and the data table is always
* preferred.
*
* @return string
* The alias of the joined table.
*
* @throws \Drupal\Core\Entity\Query\QueryException
* When an invalid property has been passed.
*/
protected function ensureEntityTable($index_prefix, $property, $type, $langcode, $base_table, $id_field, $entity_tables) {
foreach ($entity_tables as $table => $mapping) {
if (isset($mapping[$property])) {
if (!isset($this->entityTables[$index_prefix . $table])) {
$this->entityTables[$index_prefix . $table] = $this->addJoin($type, $table, "%alias.$id_field = $base_table.$id_field", $langcode);
// Ensure a table joined multiple times through different index prefixes
// has unique entityTables entries by concatenating the index prefix
// and the base table alias. In this way i.e. if we join to the same
// entity table several times for different entity reference fields,
// each join gets a separate alias.
$key = $index_prefix . ($base_table === 'base_table' ? $table : $base_table);
if (!isset($this->entityTables[$key])) {
$this->entityTables[$key] = $this->addJoin($type, $table, "%alias.$id_field = $base_table.$id_field", $langcode);
}
return $this->entityTables[$index_prefix . $table];
return $this->entityTables[$key];
}
}
throw new QueryException("'$property' not found");
......@@ -340,6 +371,23 @@ protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $b
return $this->fieldTables[$index_prefix . $field_name];
}
/**
* Adds a join to a given table.
*
* @param string $type
* The join type.
* @param string $table
* The table to join to.
* @param string $join_condition
* The condition on which to join to.
* @param string $langcode
* The langcode we use on the join.
* @param string|null $delta
* (optional) A delta which should be used as additional condition.
*
* @return string
* Returns the alias of the joined table.
*/
protected function addJoin($type, $table, $join_condition, $langcode, $delta = NULL) {
$arguments = [];
if ($langcode) {
......
......@@ -7,6 +7,7 @@
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
......@@ -19,6 +20,8 @@
*/
class EntityQueryTest extends EntityKernelTestBase {
use EntityReferenceTestTrait;
/**
* Modules to enable.
*
......@@ -952,4 +955,54 @@ public function testInjectionInCondition() {
}
}
/**
* Tests that EntityQuery works when querying the same entity from two fields.
*/
public function testWithTwoEntityReferenceFieldsToSameEntityType() {
// Create two entity reference fields referring 'entity_test' entities.
$this->createEntityReferenceField('entity_test', 'entity_test', 'ref1', $this->randomMachineName(), 'entity_test');
$this->createEntityReferenceField('entity_test', 'entity_test', 'ref2', $this->randomMachineName(), 'entity_test');
// Create two entities to be referred.
$ref1 = EntityTest::create(['type' => 'entity_test']);
$ref1->save();
$ref2 = EntityTest::create(['type' => 'entity_test']);
$ref2->save();
// Create a main entity referring the previous created entities.
$entity = EntityTest::create([
'type' => 'entity_test',
'ref1' => $ref1->id(),
'ref2' => $ref2->id(),
]);
$entity->save();
// Check that works when referring with "{$field_name}".
$result = $this->factory->get('entity_test')
->condition('type', 'entity_test')
->condition('ref1', $ref1->id())
->condition('ref2', $ref2->id())
->execute();
$this->assertCount(1, $result);
$this->assertEquals($entity->id(), reset($result));
// Check that works when referring with "{$field_name}.target_id".
$result = $this->factory->get('entity_test')
->condition('type', 'entity_test')
->condition('ref1.target_id', $ref1->id())
->condition('ref2.target_id', $ref2->id())
->execute();
$this->assertCount(1, $result);
$this->assertEquals($entity->id(), reset($result));
// Check that works when referring with "{$field_name}.entity.id".
$result = $this->factory->get('entity_test')
->condition('type', 'entity_test')
->condition('ref1.entity.id', $ref1->id())
->condition('ref2.entity.id', $ref2->id())
->execute();
$this->assertCount(1, $result);
$this->assertEquals($entity->id(), reset($result));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment