--- /dev/null
+<?php
+
+namespace Drupal\KernelTests\Core\Entity;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\entity_test\Entity\EntityTestMulRev;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\taxonomy\Entity\Term;
+use Drupal\taxonomy\Entity\Vocabulary;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Tests Entity Query functionality.
+ *
+ * @group Entity
+ */
+class EntityQueryTest extends EntityKernelTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = ['field_test', 'language'];
+
+ /**
+ * @var array
+ */
+ protected $queryResults;
+
+ /**
+ * @var \Drupal\Core\Entity\Query\QueryFactory
+ */
+ protected $factory;
+
+ /**
+ * A list of bundle machine names created for this test.
+ *
+ * @var string[]
+ */
+ protected $bundles;
+
+ /**
+ * Field name for the greetings field.
+ *
+ * @var string
+ */
+ public $greetings;
+
+ /**
+ * Field name for the figures field.
+ *
+ * @var string
+ */
+ public $figures;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->installEntitySchema('entity_test_mulrev');
+
+ $this->installConfig(['language']);
+
+ $figures = Unicode::strtolower($this->randomMachineName());
+ $greetings = Unicode::strtolower($this->randomMachineName());
+ foreach ([$figures => 'shape', $greetings => 'text'] as $field_name => $field_type) {
+ $field_storage = FieldStorageConfig::create([
+ 'field_name' => $field_name,
+ 'entity_type' => 'entity_test_mulrev',
+ 'type' => $field_type,
+ 'cardinality' => 2,
+ ]);
+ $field_storage->save();
+ $field_storages[] = $field_storage;
+ }
+ $bundles = [];
+ for ($i = 0; $i < 2; $i++) {
+ // For the sake of tablesort, make sure the second bundle is higher than
+ // the first one. Beware: MySQL is not case sensitive.
+ do {
+ $bundle = $this->randomMachineName();
+ } while ($bundles && strtolower($bundles[0]) >= strtolower($bundle));
+ entity_test_create_bundle($bundle);
+ foreach ($field_storages as $field_storage) {
+ FieldConfig::create([
+ 'field_storage' => $field_storage,
+ 'bundle' => $bundle,
+ ])->save();
+ }
+ $bundles[] = $bundle;
+ }
+ // Each unit is a list of field name, langcode and a column-value array.
+ $units[] = [$figures, 'en', [
+ 'color' => 'red',
+ 'shape' => 'triangle',
+ ]];
+ $units[] = [$figures, 'en', [
+ 'color' => 'blue',
+ 'shape' => 'circle',
+ ]];
+ // To make it easier to test sorting, the greetings get formats according
+ // to their langcode.
+ $units[] = [$greetings, 'tr', [
+ 'value' => 'merhaba',
+ 'format' => 'format-tr'
+ ]];
+ $units[] = [$greetings, 'pl', [
+ 'value' => 'siema',
+ 'format' => 'format-pl'
+ ]];
+ // Make these languages available to the greetings field.
+ ConfigurableLanguage::createFromLangcode('tr')->save();
+ ConfigurableLanguage::createFromLangcode('pl')->save();
+ // Calculate the cartesian product of the unit array by looking at the
+ // bits of $i and add the unit at the bits that are 1. For example,
+ // decimal 13 is binary 1101 so unit 3,2 and 0 will be added to the
+ // entity.
+ for ($i = 1; $i <= 15; $i++) {
+ $entity = EntityTestMulRev::create([
+ 'type' => $bundles[$i & 1],
+ 'name' => $this->randomMachineName(),
+ 'langcode' => 'en',
+ ]);
+ // Make sure the name is set for every language that we might create.
+ foreach (['tr', 'pl'] as $langcode) {
+ $entity->addTranslation($langcode)->name = $this->randomMachineName();
+ }
+ foreach (array_reverse(str_split(decbin($i))) as $key => $bit) {
+ if ($bit) {
+ list($field_name, $langcode, $values) = $units[$key];
+ $entity->getTranslation($langcode)->{$field_name}[] = $values;
+ }
+ }
+ $entity->save();
+ }
+ $this->bundles = $bundles;
+ $this->figures = $figures;
+ $this->greetings = $greetings;
+ $this->factory = \Drupal::service('entity.query');
+ }
+
+ /**
+ * Test basic functionality.
+ */
+ public function testEntityQuery() {
+ $greetings = $this->greetings;
+ $figures = $this->figures;
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->exists($greetings, 'tr')
+ ->condition("$figures.color", 'red')
+ ->sort('id')
+ ->execute();
+ // As unit 0 was the red triangle and unit 2 was the turkish greeting,
+ // bit 0 and bit 2 needs to be set.
+ $this->assertResult(5, 7, 13, 15);
+
+ $query = $this->factory->get('entity_test_mulrev', 'OR')
+ ->exists($greetings, 'tr')
+ ->condition("$figures.color", 'red')
+ ->sort('id');
+ $count_query = clone $query;
+ $this->assertEqual(12, $count_query->count()->execute());
+ $this->queryResults = $query->execute();
+ // Now bit 0 (1, 3, 5, 7, 9, 11, 13, 15) or bit 2 (4, 5, 6, 7, 12, 13, 14,
+ // 15) needs to be set.
+ $this->assertResult(1, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15);
+
+ // Test cloning of query conditions.
+ $query = $this->factory->get('entity_test_mulrev')
+ ->condition("$figures.color", 'red')
+ ->sort('id');
+ $cloned_query = clone $query;
+ $cloned_query
+ ->condition("$figures.shape", 'circle');
+ // Bit 0 (1, 3, 5, 7, 9, 11, 13, 15) needs to be set.
+ $this->queryResults = $query->execute();
+ $this->assertResult(1, 3, 5, 7, 9, 11, 13, 15);
+ // No red color has a circle shape.
+ $this->queryResults = $cloned_query->execute();
+ $this->assertResult();
+
+ $query = $this->factory->get('entity_test_mulrev');
+ $group = $query->orConditionGroup()
+ ->exists($greetings, 'tr')
+ ->condition("$figures.color", 'red');
+ $this->queryResults = $query
+ ->condition($group)
+ ->condition("$greetings.value", 'sie', 'STARTS_WITH')
+ ->sort('revision_id')
+ ->execute();
+ // Bit 3 and (bit 0 or 2) -- the above 8 part of the above.
+ $this->assertResult(9, 11, 12, 13, 14, 15);
+
+ // No figure has both the colors blue and red at the same time.
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("$figures.color", 'blue')
+ ->condition("$figures.color", 'red')
+ ->sort('id')
+ ->execute();
+ $this->assertResult();
+
+ // But an entity might have a red and a blue figure both.
+ $query = $this->factory->get('entity_test_mulrev');
+ $group_blue = $query->andConditionGroup()->condition("$figures.color", 'blue');
+ $group_red = $query->andConditionGroup()->condition("$figures.color", 'red');
+ $this->queryResults = $query
+ ->condition($group_blue)
+ ->condition($group_red)
+ ->sort('revision_id')
+ ->execute();
+ // Unit 0 and unit 1, so bits 0 1.
+ $this->assertResult(3, 7, 11, 15);
+
+ // Do the same test but with IN operator.
+ $query = $this->factory->get('entity_test_mulrev');
+ $group_blue = $query->andConditionGroup()->condition("$figures.color", ['blue'], 'IN');
+ $group_red = $query->andConditionGroup()->condition("$figures.color", ['red'], 'IN');
+ $this->queryResults = $query
+ ->condition($group_blue)
+ ->condition($group_red)
+ ->sort('id')
+ ->execute();
+ // Unit 0 and unit 1, so bits 0 1.
+ $this->assertResult(3, 7, 11, 15);
+
+ // An entity might have either red or blue figure.
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("$figures.color", ['blue', 'red'], 'IN')
+ ->sort('id')
+ ->execute();
+ // Bit 0 or 1 is on.
+ $this->assertResult(1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15);
+
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->exists("$figures.color")
+ ->notExists("$greetings.value")
+ ->sort('id')
+ ->execute();
+ // Bit 0 or 1 is on but 2 and 3 are not.
+ $this->assertResult(1, 2, 3);
+ // Now update the 'merhaba' string to xsiemax which is not a meaningful
+ // word but allows us to test revisions and string operations.
+ $ids = $this->factory->get('entity_test_mulrev')
+ ->condition("$greetings.value", 'merhaba')
+ ->sort('id')
+ ->execute();
+ $entities = EntityTestMulRev::loadMultiple($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([$first_entity->id()], [$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')
+ ->execute();
+ $this->assertResult();
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("$greetings.value", 'merhaba')
+ ->allRevisions()
+ ->sort('revision_id')
+ ->execute();
+ // The query only matches the original revisions.
+ $this->assertRevisionResult([4, 5, 6, 7, 12, 13, 14, 15], [4, 5, 6, 7, 12, 13, 14, 15]);
+ $results = $this->factory->get('entity_test_mulrev')
+ ->condition("$greetings.value", 'siema', 'CONTAINS')
+ ->sort('id')
+ ->execute();
+ // This matches both the original and new current revisions, multiple
+ // revisions are returned for some entities.
+ $assert = [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')
+ ->sort('revision_id')
+ ->execute();
+ // Now we only get the ones that originally were siema, entity id 8 and
+ // above.
+ $this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
+ $results = $this->factory->get('entity_test_mulrev')
+ ->condition("$greetings.value", 'a', 'ENDS_WITH')
+ ->sort('revision_id')
+ ->execute();
+ // It is very important that we do not get the ones which only have
+ // xsiemax despite originally they were merhaba, ie. ended with a.
+ $this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
+ $results = $this->factory->get('entity_test_mulrev')
+ ->condition("$greetings.value", 'a', 'ENDS_WITH')
+ ->allRevisions()
+ ->sort('id')
+ ->sort('revision_id')
+ ->execute();
+ // Now we get everything.
+ $assert = [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);
+ }
+
+ /**
+ * Test sort().
+ *
+ * Warning: this is complicated.
+ */
+ public function testSort() {
+ $greetings = $this->greetings;
+ $figures = $this->figures;
+ // Order up and down on a number.
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->sort('id')
+ ->execute();
+ $this->assertResult(range(1, 15));
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->sort('id', 'DESC')
+ ->execute();
+ $this->assertResult(range(15, 1));
+ $query = $this->factory->get('entity_test_mulrev')
+ ->sort("$figures.color")
+ ->sort("$greetings.format")
+ ->sort('id');
+ // As we do not have any conditions, here are the possible colors and
+ // language codes, already in order, with the first occurrence of the
+ // entity id marked with *:
+ // 8 NULL pl *
+ // 12 NULL pl *
+
+ // 4 NULL tr *
+ // 12 NULL tr
+
+ // 2 blue NULL *
+ // 3 blue NULL *
+
+ // 10 blue pl *
+ // 11 blue pl *
+ // 14 blue pl *
+ // 15 blue pl *
+
+ // 6 blue tr *
+ // 7 blue tr *
+ // 14 blue tr
+ // 15 blue tr
+
+ // 1 red NULL
+ // 3 red NULL
+
+ // 9 red pl *
+ // 11 red pl
+ // 13 red pl *
+ // 15 red pl
+
+ // 5 red tr *
+ // 7 red tr
+ // 13 red tr
+ // 15 red tr
+ $count_query = clone $query;
+ $this->assertEqual(15, $count_query->count()->execute());
+ $this->queryResults = $query->execute();
+ $this->assertResult(8, 12, 4, 2, 3, 10, 11, 14, 15, 6, 7, 1, 9, 13, 5);
+
+ // Test the pager by setting element #1 to page 2 with a page size of 4.
+ // Results will be #8-12 from above.
+ $request = Request::createFromGlobals();
+ $request->query->replace([
+ 'page' => '0,2',
+ ]);
+ \Drupal::getContainer()->get('request_stack')->push($request);
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->sort("$figures.color")
+ ->sort("$greetings.format")
+ ->sort('id')
+ ->pager(4, 1)
+ ->execute();
+ $this->assertResult(15, 6, 7, 1);
+
+ // Now test the reversed order.
+ $query = $this->factory->get('entity_test_mulrev')
+ ->sort("$figures.color", 'DESC')
+ ->sort("$greetings.format", 'DESC')
+ ->sort('id', 'DESC');
+ $count_query = clone $query;
+ $this->assertEqual(15, $count_query->count()->execute());
+ $this->queryResults = $query->execute();
+ $this->assertResult(15, 13, 7, 5, 11, 9, 3, 1, 14, 6, 10, 2, 12, 4, 8);
+ }
+
+ /**
+ * Test tablesort().
+ */
+ public function testTableSort() {
+ // While ordering on bundles do not give us a definite order, we can still
+ // assert that all entities from one bundle are after the other as the
+ // order dictates.
+ $request = Request::createFromGlobals();
+ $request->query->replace([
+ 'sort' => 'asc',
+ 'order' => 'Type',
+ ]);
+ \Drupal::getContainer()->get('request_stack')->push($request);
+
+ $header = [
+ 'id' => ['data' => 'Id', 'specifier' => 'id'],
+ 'type' => ['data' => 'Type', 'specifier' => 'type'],
+ ];
+
+ $this->queryResults = array_values($this->factory->get('entity_test_mulrev')
+ ->tableSort($header)
+ ->execute());
+ $this->assertBundleOrder('asc');
+
+ $request->query->add([
+ 'sort' => 'desc',
+ ]);
+ \Drupal::getContainer()->get('request_stack')->push($request);
+
+ $header = [
+ 'id' => ['data' => 'Id', 'specifier' => 'id'],
+ 'type' => ['data' => 'Type', 'specifier' => 'type'],
+ ];
+ $this->queryResults = array_values($this->factory->get('entity_test_mulrev')
+ ->tableSort($header)
+ ->execute());
+ $this->assertBundleOrder('desc');
+
+ // Ordering on ID is definite, however.
+ $request->query->add([
+ 'order' => 'Id',
+ ]);
+ \Drupal::getContainer()->get('request_stack')->push($request);
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->tableSort($header)
+ ->execute();
+ $this->assertResult(range(15, 1));
+ }
+
+ /**
+ * Test that count queries are separated across entity types.
+ */
+ public function testCount() {
+ // Create a field with the same name in a different entity type.
+ $field_name = $this->figures;
+ $field_storage = FieldStorageConfig::create([
+ 'field_name' => $field_name,
+ 'entity_type' => 'entity_test',
+ 'type' => 'shape',
+ 'cardinality' => 2,
+ 'translatable' => TRUE,
+ ]);
+ $field_storage->save();
+ $bundle = $this->randomMachineName();
+ FieldConfig::create([
+ 'field_storage' => $field_storage,
+ 'bundle' => $bundle,
+ ])->save();
+
+ $entity = EntityTest::create([
+ 'id' => 1,
+ 'type' => $bundle,
+ ]);
+ $entity->enforceIsNew();
+ $entity->save();
+
+ // As the single entity of this type we just saved does not have a value
+ // in the color field, the result should be 0.
+ $count = $this->factory->get('entity_test')
+ ->exists("$field_name.color")
+ ->count()
+ ->execute();
+ $this->assertFalse($count);
+ }
+
+ /**
+ * Tests that nested condition groups work as expected.
+ */
+ public function testNestedConditionGroups() {
+ // Query for all entities of the first bundle that have either a red
+ // triangle as a figure or the Turkish greeting as a greeting.
+ $query = $this->factory->get('entity_test_mulrev');
+
+ $first_and = $query->andConditionGroup()
+ ->condition($this->figures . '.color', 'red')
+ ->condition($this->figures . '.shape', 'triangle');
+ $second_and = $query->andConditionGroup()
+ ->condition($this->greetings . '.value', 'merhaba')
+ ->condition($this->greetings . '.format', 'format-tr');
+
+ $or = $query->orConditionGroup()
+ ->condition($first_and)
+ ->condition($second_and);
+
+ $this->queryResults = $query
+ ->condition($or)
+ ->condition('type', reset($this->bundles))
+ ->sort('id')
+ ->execute();
+
+ $this->assertResult(6, 14);
+ }
+
+ /**
+ * Test queries with delta conditions.
+ */
+ public function testDelta() {
+ $figures = $this->figures;
+ // Test numeric delta value in field condition.
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("$figures.0.color", 'red')
+ ->sort('id')
+ ->execute();
+ // As unit 0 at delta 0 was the red triangle bit 0 needs to be set.
+ $this->assertResult(1, 3, 5, 7, 9, 11, 13, 15);
+
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("$figures.1.color", 'red')
+ ->sort('id')
+ ->execute();
+ // Delta 1 is not red.
+ $this->assertResult();
+
+ // Test on two different deltas.
+ $query = $this->factory->get('entity_test_mulrev');
+ $or = $query->andConditionGroup()
+ ->condition("$figures.0.color", 'red')
+ ->condition("$figures.1.color", 'blue');
+ $this->queryResults = $query
+ ->condition($or)
+ ->sort('id')
+ ->execute();
+ $this->assertResult(3, 7, 11, 15);
+
+ // Test the delta range condition.
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("$figures.%delta.color", ['blue', 'red'], 'IN')
+ ->condition("$figures.%delta", [0, 1], 'IN')
+ ->sort('id')
+ ->execute();
+ // Figure delta 0 or 1 can be blue or red, this matches a lot of entities.
+ $this->assertResult(1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15);
+
+ // Test the delta range condition without conditions on the value.
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("$figures.%delta", 1)
+ ->sort('id')
+ ->execute();
+ // Entity needs to have atleast two figures.
+ $this->assertResult(3, 7, 11, 15);
+
+ // Numeric delta on single value base field should return results only if
+ // the first item is being targeted.
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("id.0.value", [1, 3, 5], 'IN')
+ ->sort('id')
+ ->execute();
+ $this->assertResult(1, 3, 5);
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("id.1.value", [1, 3, 5], 'IN')
+ ->sort('id')
+ ->execute();
+ $this->assertResult();
+
+ // Delta range condition on single value base field should return results
+ // only if just the field value is targeted.
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("id.%delta.value", [1, 3, 5], 'IN')
+ ->sort('id')
+ ->execute();
+ $this->assertResult(1, 3, 5);
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("id.%delta.value", [1, 3, 5], 'IN')
+ ->condition("id.%delta", 0, '=')
+ ->sort('id')
+ ->execute();
+ $this->assertResult(1, 3, 5);
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition("id.%delta.value", [1, 3, 5], 'IN')
+ ->condition("id.%delta", 1, '=')
+ ->sort('id')
+ ->execute();
+ $this->assertResult();
+
+ }
+
+ protected function assertResult() {
+ $assert = [];
+ $expected = func_get_args();
+ if ($expected && is_array($expected[0])) {
+ $expected = $expected[0];
+ }
+ foreach ($expected as $binary) {
+ $assert[$binary] = strval($binary);
+ }
+ $this->assertIdentical($this->queryResults, $assert);
+ }
+
+ protected function assertRevisionResult($keys, $expected) {
+ $assert = [];
+ foreach ($expected as $key => $binary) {
+ $assert[$keys[$key]] = strval($binary);
+ }
+ $this->assertIdentical($this->queryResults, $assert);
+ return $assert;
+ }
+
+ protected function assertBundleOrder($order) {
+ // This loop is for bundle1 entities.
+ for ($i = 1; $i <= 15; $i += 2) {
+ $ok = TRUE;
+ $index1 = array_search($i, $this->queryResults);
+ $this->assertNotIdentical($index1, FALSE, "$i found at $index1.");
+ // This loop is for bundle2 entities.
+ for ($j = 2; $j <= 15; $j += 2) {
+ if ($ok) {
+ if ($order == 'asc') {
+ $ok = $index1 > array_search($j, $this->queryResults);
+ }
+ else {
+ $ok = $index1 < array_search($j, $this->queryResults);
+ }
+ }
+ }
+ $this->assertTrue($ok, "$i is after all entities in bundle2");
+ }
+ }
+
+ /**
+ * Test adding a tag and metadata to the Entity query object.
+ *
+ * The tags and metadata should propagate to the SQL query object.
+ */
+ public function testMetaData() {
+ $query = \Drupal::entityQuery('entity_test_mulrev');
+ $query
+ ->addTag('efq_metadata_test')
+ ->addMetaData('foo', 'bar')
+ ->execute();
+
+ global $efq_test_metadata;
+ $this->assertEqual($efq_test_metadata, 'bar', 'Tag and metadata propagated to the SQL query object.');
+ }
+
+ /**
+ * Test case sensitive and in-sensitive query conditions.
+ */
+ public function testCaseSensitivity() {
+ $bundle = $this->randomMachineName();
+
+ $field_storage = FieldStorageConfig::create([
+ 'field_name' => 'field_ci',
+ 'entity_type' => 'entity_test_mulrev',
+ 'type' => 'string',
+ 'cardinality' => 1,
+ 'translatable' => FALSE,
+ 'settings' => [
+ 'case_sensitive' => FALSE,
+ ]
+ ]);
+ $field_storage->save();
+
+ FieldConfig::create([
+ 'field_storage' => $field_storage,
+ 'bundle' => $bundle,
+ ])->save();
+
+ $field_storage = FieldStorageConfig::create([
+ 'field_name' => 'field_cs',
+ 'entity_type' => 'entity_test_mulrev',
+ 'type' => 'string',
+ 'cardinality' => 1,
+ 'translatable' => FALSE,
+ 'settings' => [
+ 'case_sensitive' => TRUE,
+ ],
+ ]);
+ $field_storage->save();
+
+ FieldConfig::create([
+ 'field_storage' => $field_storage,
+ 'bundle' => $bundle,
+ ])->save();
+
+ $fixtures = [];
+
+ for ($i = 0; $i < 2; $i++) {
+ // If the last 4 of the string are all numbers, then there is no
+ // difference between upper and lowercase and the case sensitive CONTAINS
+ // test will fail. Ensure that can not happen by appending a non-numeric
+ // character. See https://www.drupal.org/node/2397297.
+ $string = $this->randomMachineName(7) . 'a';
+ $fixtures[] = [
+ 'original' => $string,
+ 'uppercase' => Unicode::strtoupper($string),
+ 'lowercase' => Unicode::strtolower($string),
+ ];
+ }
+
+ EntityTestMulRev::create([
+ 'type' => $bundle,
+ 'name' => $this->randomMachineName(),
+ 'langcode' => 'en',
+ 'field_ci' => $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'],
+ 'field_cs' => $fixtures[0]['uppercase'] . $fixtures[1]['lowercase']
+ ])->save();
+
+ // Check the case insensitive field, = operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase']
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case insensitive, lowercase');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase']
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case insensitive, uppercase');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase']
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case insensitive, mixed.');
+
+ // Check the case sensitive field, = operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase']
+ )->execute();
+ $this->assertIdentical(count($result), 0, 'Case sensitive, lowercase.');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase']
+ )->execute();
+ $this->assertIdentical(count($result), 0, 'Case sensitive, uppercase.');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase']
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
+
+ // Check the case insensitive field, IN operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', [$fixtures[0]['lowercase'] . $fixtures[1]['lowercase']], 'IN'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case insensitive, lowercase');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', [$fixtures[0]['uppercase'] . $fixtures[1]['uppercase']], 'IN'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case insensitive, uppercase');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', [$fixtures[0]['uppercase'] . $fixtures[1]['lowercase']], 'IN'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case insensitive, mixed');
+
+ // Check the case sensitive field, IN operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', [$fixtures[0]['lowercase'] . $fixtures[1]['lowercase']], 'IN'
+ )->execute();
+ $this->assertIdentical(count($result), 0, 'Case sensitive, lowercase');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', [$fixtures[0]['uppercase'] . $fixtures[1]['uppercase']], 'IN'
+ )->execute();
+ $this->assertIdentical(count($result), 0, 'Case sensitive, uppercase');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', [$fixtures[0]['uppercase'] . $fixtures[1]['lowercase']], 'IN'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, mixed');
+
+ // Check the case insensitive field, STARTS_WITH operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', $fixtures[0]['lowercase'], 'STARTS_WITH'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', $fixtures[0]['uppercase'], 'STARTS_WITH'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
+
+ // Check the case sensitive field, STARTS_WITH operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', $fixtures[0]['lowercase'], 'STARTS_WITH'
+ )->execute();
+ $this->assertIdentical(count($result), 0, 'Case sensitive, lowercase.');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', $fixtures[0]['uppercase'], 'STARTS_WITH'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
+
+
+ // Check the case insensitive field, ENDS_WITH operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', $fixtures[1]['lowercase'], 'ENDS_WITH'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', $fixtures[1]['uppercase'], 'ENDS_WITH'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
+
+ // Check the case sensitive field, ENDS_WITH operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', $fixtures[1]['lowercase'], 'ENDS_WITH'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', $fixtures[1]['uppercase'], 'ENDS_WITH'
+ )->execute();
+ $this->assertIdentical(count($result), 0, 'Case sensitive, exact match.');
+
+
+ // Check the case insensitive field, CONTAINS operator, use the inner 8
+ // characters of the uppercase and lowercase strings.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_ci', Unicode::strtolower(Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
+
+ // Check the case sensitive field, CONTAINS operator.
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS'
+ )->execute();
+ $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
+
+ $result = \Drupal::entityQuery('entity_test_mulrev')->condition(
+ 'field_cs', Unicode::strtolower(Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS'
+ )->execute();
+ $this->assertIdentical(count($result), 0, 'Case sensitive, exact match.');
+
+ }
+
+ /**
+ * Test base fields with multiple columns.
+ */
+ public function testBaseFieldMultipleColumns() {
+ $this->enableModules(['taxonomy']);
+ $this->installEntitySchema('taxonomy_term');
+
+ Vocabulary::create(['vid' => 'tags']);
+
+ $term1 = Term::create([
+ 'name' => $this->randomMachineName(),
+ 'vid' => 'tags',
+ 'description' => [
+ 'value' => $this->randomString(),
+ 'format' => 'format1',
+ ]]);
+ $term1->save();
+
+ $term2 = Term::create([
+ 'name' => $this->randomMachineName(),
+ 'vid' => 'tags',
+ 'description' => [
+ 'value' => $this->randomString(),
+ 'format' => 'format2',
+ ]]);
+ $term2->save();
+
+ $ids = \Drupal::entityQuery('taxonomy_term')
+ ->condition('description.format', 'format1')
+ ->execute();
+
+ $this->assertEqual(count($ids), 1);
+ $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']);
+ }
+
+ /**
+ * Test against SQL inject of condition field. This covers a
+ * database driver's EntityQuery\Condition class.
+ */
+ public function testInjectionInCondition() {
+ try {
+ $this->queryResults = $this->factory->get('entity_test_mulrev')
+ ->condition('1 ; -- ', [0, 1], 'IN')
+ ->sort('id')
+ ->execute();
+ $this->fail('SQL Injection attempt in Entity Query condition in operator should result in an exception.');
+ }
+ catch (\Exception $e) {
+ $this->pass('SQL Injection attempt in Entity Query condition in operator should result in an exception.');
+ }
+ }
+
+}