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 { ...@@ -22,10 +22,13 @@ class Tables implements TablesInterface {
protected $sqlQuery; 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 * 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 * @var array
*/ */
...@@ -297,21 +300,49 @@ public function isFieldCaseSensitive($field_name) { ...@@ -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 * @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 * @return string
* The alias of the joined table.
* *
* @throws \Drupal\Core\Entity\Query\QueryException * @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) { protected function ensureEntityTable($index_prefix, $property, $type, $langcode, $base_table, $id_field, $entity_tables) {
foreach ($entity_tables as $table => $mapping) { foreach ($entity_tables as $table => $mapping) {
if (isset($mapping[$property])) { if (isset($mapping[$property])) {
if (!isset($this->entityTables[$index_prefix . $table])) { // Ensure a table joined multiple times through different index prefixes
$this->entityTables[$index_prefix . $table] = $this->addJoin($type, $table, "%alias.$id_field = $base_table.$id_field", $langcode); // 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"); throw new QueryException("'$property' not found");
...@@ -340,6 +371,23 @@ protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $b ...@@ -340,6 +371,23 @@ protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $b
return $this->fieldTables[$index_prefix . $field_name]; 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) { protected function addJoin($type, $table, $join_condition, $langcode, $delta = NULL) {
$arguments = []; $arguments = [];
if ($langcode) { if ($langcode) {
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
use Drupal\entity_test\Entity\EntityTestMulRev; use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig; use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\language\Entity\ConfigurableLanguage; use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary; use Drupal\taxonomy\Entity\Vocabulary;
...@@ -19,6 +20,8 @@ ...@@ -19,6 +20,8 @@
*/ */
class EntityQueryTest extends EntityKernelTestBase { class EntityQueryTest extends EntityKernelTestBase {
use EntityReferenceTestTrait;
/** /**
* Modules to enable. * Modules to enable.
* *
...@@ -952,4 +955,54 @@ public function testInjectionInCondition() { ...@@ -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