Commit 593b2b64 authored by alexpott's avatar alexpott

Issue #2458543 by mbovan, Anushka-mp, jhedstrom: Entity query...

Issue #2458543 by mbovan, Anushka-mp, jhedstrom: Entity query age(EntityStorageInterface::FIELD_LOAD_REVISION) only gets current revision ID
parent 79be8cfc
......@@ -8,7 +8,6 @@
namespace Drupal\Core\Config\Entity\Query;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\QueryBase;
use Drupal\Core\Entity\Query\QueryInterface;
......
......@@ -8,7 +8,6 @@
namespace Drupal\Core\Entity\Query;
use Drupal\Core\Database\Query\PagerSelectExtender;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
/**
......@@ -110,12 +109,9 @@
/**
* Flag indicating whether to query the current revision or all revisions.
*
* Can be either EntityStorageInterface::FIELD_LOAD_CURRENT or
* EntityStorageInterface::FIELD_LOAD_REVISION.
*
* @var string
* @var bool
*/
protected $age = EntityStorageInterface::FIELD_LOAD_CURRENT;
protected $allRevisions = FALSE;
/**
* The query pager data.
......@@ -257,10 +253,18 @@ public function accessCheck($access_check = TRUE) {
}
/**
* Implements \Drupal\Core\Entity\Query\QueryInterface::age().
* {@inheritdoc}
*/
public function currentRevision() {
$this->allRevisions = FALSE;
return $this;
}
/**
* {@inheritdoc}
*/
public function age($age = EntityStorageInterface::FIELD_LOAD_CURRENT) {
$this->age = $age;
public function allRevisions() {
$this->allRevisions = TRUE;
return $this;
}
......
......@@ -7,7 +7,6 @@
namespace Drupal\Core\Entity\Query;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Database\Query\AlterableInterface;
/**
......@@ -165,24 +164,6 @@ public function tableSort(&$headers);
*/
public function accessCheck($access_check = TRUE);
/**
* Queries the current or every revision.
*
* Note that this only affects field conditions. Property conditions always
* apply to the current revision.
* @TODO: Once revision tables have been cleaned up, revisit this.
*
* @param $age
* - EntityStorageInterface::FIELD_LOAD_CURRENT (default): Query
* the most recent revisions only,
* - EntityStorageInterface::FIELD_LOAD_REVISION: Query all
* revisions.
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* The called object.
*/
public function age($age = EntityStorageInterface::FIELD_LOAD_CURRENT);
/**
* Execute the query.
*
......@@ -244,4 +225,18 @@ public function andConditionGroup();
*/
public function orConditionGroup();
/**
* Queries the current revision.
*
* @return $this
*/
public function currentRevision();
/**
* Queries all the revisions.
*
* @return $this
*/
public function allRevisions();
}
......@@ -9,7 +9,6 @@
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\QueryBase;
use Drupal\Core\Entity\Query\QueryException;
......@@ -100,8 +99,15 @@ public function execute() {
* Returns the called object.
*/
protected function prepare() {
if (!$base_table = $this->entityType->getBaseTable()) {
throw new QueryException("No base table, invalid query.");
if ($this->allRevisions) {
if (!$base_table = $this->entityType->getRevisionTable()) {
throw new QueryException("No revision table for " . $this->entityTypeId . ", invalid query.");
}
}
else {
if (!$base_table = $this->entityType->getBaseTable()) {
throw new QueryException("No base table for " . $this->entityTypeId . ", invalid query.");
}
}
$simple_query = TRUE;
if ($this->entityType->getDataTable()) {
......@@ -146,7 +152,7 @@ protected function prepare() {
}
// This now contains first the table containing entity properties and
// last the entity base table. They might be the same.
$this->sqlQuery->addMetaData('age', $this->age);
$this->sqlQuery->addMetaData('all_revisions', $this->allRevisions);
$this->sqlQuery->addMetaData('simple_query', $simple_query);
return $this;
}
......
......@@ -8,7 +8,6 @@
namespace Drupal\Core\Entity\Query\Sql;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\Query\QueryException;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
......@@ -68,7 +67,7 @@ public function __construct(SelectInterface $sql_query) {
*/
public function addField($field, $type, $langcode) {
$entity_type_id = $this->sqlQuery->getMetaData('entity_type');
$age = $this->sqlQuery->getMetaData('age');
$all_revisions = $this->sqlQuery->getMetaData('all_revisions');
// This variable ensures grouping works correctly. For example:
// ->condition('tags', 2, '>')
// ->condition('tags', 20, '<')
......@@ -89,7 +88,7 @@ public function addField($field, $type, $langcode) {
for ($key = 0; $key <= $count; $key ++) {
// If there is revision support and only the current revision is being
// queried then use the revision id. Otherwise, the entity id will do.
if (($revision_key = $entity_type->getKey('revision')) && $age == EntityStorageInterface::FIELD_LOAD_CURRENT) {
if (($revision_key = $entity_type->getKey('revision')) && $all_revisions) {
// This contains the relevant SQL field to be used when joining entity
// tables.
$entity_id_field = $revision_key;
......@@ -158,11 +157,11 @@ public function addField($field, $type, $langcode) {
// finds the property first. The data table is preferred, which is why
// it gets added before the base table.
$entity_tables = array();
if ($data_table = $entity_type->getDataTable()) {
if ($data_table = $all_revisions ? $entity_type->getRevisionDataTable() : $entity_type->getDataTable()) {
$this->sqlQuery->addMetaData('simple_query', FALSE);
$entity_tables[$data_table] = $this->getTableMapping($data_table, $entity_type_id);
}
$entity_base_table = $entity_type->getBaseTable();
$entity_base_table = $all_revisions ? $entity_type->getRevisionTable() : $entity_type->getBaseTable();
$entity_tables[$entity_base_table] = $this->getTableMapping($entity_base_table, $entity_type_id);
$sql_column = $specifier;
......@@ -265,7 +264,7 @@ protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $b
$entity_type_id = $this->sqlQuery->getMetaData('entity_type');
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $this->entityManager->getStorage($entity_type_id)->getTableMapping();
$table = $this->sqlQuery->getMetaData('age') == EntityStorageInterface::FIELD_LOAD_CURRENT ? $table_mapping->getDedicatedDataTableName($field) : $table_mapping->getDedicatedRevisionTableName($field);
$table = !$this->sqlQuery->getMetaData('all_revisions') ? $table_mapping->getDedicatedDataTableName($field) : $table_mapping->getDedicatedRevisionTableName($field);
if ($field->getCardinality() != 1) {
$this->sqlQuery->addMetaData('simple_query', FALSE);
}
......
......@@ -8,7 +8,6 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
......@@ -225,11 +224,26 @@ function testEntityQuery() {
->sort('id')
->execute();
$entities = entity_load_multiple('entity_test_mulrev', $ids);
$first_entity = reset($entities);
$old_name = $first_entity->name->value;
foreach ($entities as $entity) {
$entity->setNewRevision();
$entity->getTranslation('tr')->$greetings->value = 'xsiemax';
$entity->name->value .= 'x';
$entity->save();
}
// We changed the entity names, so the current revision should not match.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition('name.value', $old_name)
->execute();
$this->assertResult();
// Only if all revisions are queried, we find the old revision.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition('name.value', $old_name)
->allRevisions()
->sort('revision_id')
->execute();
$this->assertRevisionResult(array($first_entity->id()), array($first_entity->id()));
// When querying current revisions, this string is no longer found.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'merhaba')
......@@ -237,21 +251,18 @@ function testEntityQuery() {
$this->assertResult();
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'merhaba')
->age(EntityStorageInterface::FIELD_LOAD_REVISION)
->allRevisions()
->sort('revision_id')
->execute();
// Bit 2 needs to be set.
// The keys must be 16-23 because the first batch stopped at 15 so the
// second started at 16 and eight entities were saved.
$assert = $this->assertRevisionResult(range(16, 23), array(4, 5, 6, 7, 12, 13, 14, 15));
// The query only matches the original revisions.
$this->assertRevisionResult(array(4, 5, 6, 7, 12, 13, 14, 15), array(4, 5, 6, 7, 12, 13, 14, 15));
$results = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'siema', 'CONTAINS')
->sort('id')
->execute();
// This is the same as the previous one because xsiemax replaced merhaba
// but also it contains the entities that siema originally but not
// merhaba.
$assert = array_slice($assert, 0, 4, TRUE) + array(8 => '8', 9 => '9', 10 => '10', 11 => '11') + array_slice($assert, 4, 4, TRUE);
// This matches both the original and new current revisions, multiple
// revisions are returned for some entities.
$assert = array(16 => '4', 17 => '5', 18 => '6', 19 => '7', 8 => '8', 9 => '9', 10 => '10', 11 => '11', 20 => '12', 21 => '13', 22 => '14', 23 => '15');
$this->assertIdentical($results, $assert);
$results = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'siema', 'STARTS_WITH')
......@@ -269,10 +280,12 @@ function testEntityQuery() {
$this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
$results = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'a', 'ENDS_WITH')
->age(EntityStorageInterface::FIELD_LOAD_REVISION)
->allRevisions()
->sort('id')
->sort('revision_id')
->execute();
// Now we get everything.
$assert = array(4 => '4', 5 => '5', 6 => '6', 7 => '7', 8 => '8', 9 => '9', 10 => '10', 11 => '11', 12 => '12', 20 => '12', 13 => '13', 21 => '13', 14 => '14', 22 => '14', 15 => '15', 23 => '15');
$this->assertIdentical($results, $assert);
}
......@@ -706,4 +719,47 @@ public function testBaseFieldMultipleColumns() {
$this->assertEqual($term1->id(), reset($ids));
}
/**
* Test forward-revisions.
*/
public function testForwardRevisions() {
// Ensure entity 14 is returned.
$result = \Drupal::entityQuery('entity_test_mulrev')
->condition('id', [14], 'IN')
->execute();
$this->assertEqual(count($result), 1);
// Set a revision on entity 14 that isn't the current default.
$entity = EntityTestMulRev::load(14);
$current_values = $entity->{$this->figures}->getValue();
$entity->setNewRevision(TRUE);
$entity->isDefaultRevision(FALSE);
$entity->{$this->figures}->setValue([
'color' => 'red',
'shape' => 'square'
]);
$entity->save();
// Entity query should still return entity 14.
$result = \Drupal::entityQuery('entity_test_mulrev')
->condition('id', [14], 'IN')
->execute();
$this->assertEqual(count($result), 1);
// Verify that field conditions on the default and forward revision are
// work as expected.
$result = \Drupal::entityQuery('entity_test_mulrev')
->condition('id', [14], 'IN')
->condition("$this->figures.color", $current_values[0]['color'])
->execute();
$this->assertEqual($result, [14 => '14']);
$result = $this->factory->get('entity_test_mulrev')
->condition('id', [14], 'IN')
->condition("$this->figures.color", 'red')
->allRevisions()
->execute();
$this->assertEqual($result, [16 => '14']);
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Entity\Query\Sql\QueryTest.
*/
namespace Drupal\Tests\Core\Entity\Query\Sql;
use Drupal\Core\Entity\EntityType;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Entity\Query\Sql\Query;
/**
* @coversDefaultClass \Drupal\Core\Entity\Query\Sql\Query
* @group Entity
*/
class QueryTest extends UnitTestCase {
/**
* The query object.
*
* @var \Drupal\Core\Entity\Query\Sql\Query
*/
protected $query;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$entity_type = new EntityType(['id' => 'example_entity_query']);
$conjunction = 'AND';
$connection = $this->getMockBuilder('Drupal\Core\Database\Connection')->disableOriginalConstructor()->getMock();
$namespaces = ['Drupal\Core\Entity\Query\Sql'];
$this->query = new Query($entity_type, $conjunction, $connection, $namespaces);
}
/**
* Tests entity query for entity type without base table.
*
* @covers ::prepare
*
* @expectedException \Drupal\Core\Entity\Query\QueryException
* @expectedExceptionMessage No base table for example_entity_query, invalid query.
*/
public function testNoBaseTable() {
$this->query->execute();
}
/**
* Tests revision entity query for entity type without revision table.
*
* @covers ::prepare
*
* @expectedException \Drupal\Core\Entity\Query\QueryException
* @expectedExceptionMessage No revision table for example_entity_query, invalid query.
*/
public function testNoRevisionTable() {
$this->query->allRevisions()->execute();
}
}
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