class EntityQueryTest

Same name in other branches
  1. 9 core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php \Drupal\KernelTests\Core\Entity\EntityQueryTest
  2. 8.9.x core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php \Drupal\KernelTests\Core\Entity\EntityQueryTest
  3. 11.x core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php \Drupal\KernelTests\Core\Entity\EntityQueryTest

Tests Entity Query functionality.

@group Entity

Hierarchy

Expanded class hierarchy of EntityQueryTest

File

core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php, line 28

Namespace

Drupal\KernelTests\Core\Entity
View source
class EntityQueryTest extends EntityKernelTestBase {
    use EntityReferenceFieldCreationTrait;
    
    /**
     * {@inheritdoc}
     */
    protected static $modules = [
        'field_test',
        'language',
    ];
    
    /**
     * @var array
     */
    protected $queryResults;
    
    /**
     * 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;
    
    /**
     * The entity_test_mulrev entity storage.
     *
     * @var \Drupal\Core\Entity\EntityStorageInterface
     */
    protected $storage;
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() : void {
        parent::setUp();
        $this->installEntitySchema('entity_test_mulrev');
        $this->installConfig([
            'language',
        ]);
        $figures = $this->randomMachineName();
        $greetings = $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, entity_type: $field_storage->getTargetEntityTypeId());
            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) {
                    // @todo https://www.drupal.org/project/drupal/issues/3001920 Doing
                    //   [$field_name, $langcode, $values] = $units[$key]; causes
                    //   problems in PHP 7.3. Revert to better variable names once
                    //   https://bugs.php.net/bug.php?id=76937 is fixed.
                    $entity->getTranslation($units[$key][1])->{$units[$key][0]}[] = $units[$key][2];
                }
            }
            $entity->save();
        }
        $this->bundles = $bundles;
        $this->figures = $figures;
        $this->greetings = $greetings;
        $this->storage = $this->container
            ->get('entity_type.manager')
            ->getStorage('entity_test_mulrev');
    }
    
    /**
     * Tests basic functionality.
     */
    public function testEntityQuery() : void {
        $greetings = $this->greetings;
        $figures = $this->figures;
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery('OR')
            ->accessCheck(FALSE)
            ->exists($greetings, 'tr')
            ->condition("{$figures}.color", 'red')
            ->sort('id');
        $count_query = clone $query;
        $this->assertSame(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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE);
        $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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE);
        $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->storage
            ->getQuery()
            ->accessCheck(FALSE);
        $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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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();
        }
        // Test querying all revisions with a condition on the revision ID field.
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('revision_id', $first_entity->getRevisionId())
            ->allRevisions()
            ->execute();
        $this->assertCount(1, $this->queryResults);
        $this->assertEquals($first_entity->getRevisionId(), key($this->queryResults));
        // We changed the entity names, so the current revision should not match.
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('name.value', $old_name)
            ->execute();
        $this->assertResult();
        // Only if all revisions are queried, we find the old revision.
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition("{$greetings}.value", 'merhaba')
            ->execute();
        $this->assertResult();
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->assertSame($assert, $results);
        $results = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->assertSame(array_slice($assert, 4, 8, TRUE), $results);
        $results = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->assertSame(array_slice($assert, 4, 8, TRUE), $results);
        $results = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->assertSame($assert, $results);
        // Check that a query on the latest revisions without any condition returns
        // the correct results.
        $results = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->latestRevision()
            ->sort('id')
            ->sort('revision_id')
            ->execute();
        $expected = [
            1 => '1',
            2 => '2',
            3 => '3',
            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->assertSame($expected, $results);
    }
    
    /**
     * Tests sort().
     *
     * Warning: this is complicated.
     */
    public function testSort() : void {
        $greetings = $this->greetings;
        $figures = $this->figures;
        // Order up and down on a number.
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->sort('id')
            ->execute();
        $this->assertResult(range(1, 15));
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->sort('id', 'DESC')
            ->execute();
        $this->assertResult(range(15, 1));
        $query = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->assertSame(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',
        ]);
        $request->setSession(new Session(new MockArraySessionStorage()));
        \Drupal::getContainer()->get('request_stack')
            ->push($request);
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->sort("{$figures}.color", 'DESC')
            ->sort("{$greetings}.format", 'DESC')
            ->sort('id', 'DESC');
        $count_query = clone $query;
        $this->assertSame(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);
    }
    
    /**
     * Tests tablesort().
     */
    public function testTableSort() : void {
        // 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',
        ]);
        $request->setSession(new Session(new MockArraySessionStorage()));
        \Drupal::getContainer()->get('request_stack')
            ->push($request);
        $header = [
            'id' => [
                'data' => 'Id',
                'specifier' => 'id',
            ],
            'type' => [
                'data' => 'Type',
                'specifier' => 'type',
            ],
        ];
        $this->queryResults = array_values($this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->tableSort($header)
            ->execute();
        $this->assertResult(range(15, 1));
    }
    
    /**
     * Tests that count queries are separated across entity types.
     */
    public function testCount() : void {
        // 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();
        entity_test_create_bundle($bundle);
        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->container
            ->get('entity_type.manager')
            ->getStorage('entity_test')
            ->getQuery()
            ->accessCheck(FALSE)
            ->exists("{$field_name}.color")
            ->count()
            ->execute();
        $this->assertSame(0, $count);
    }
    
    /**
     * Tests that nested condition groups work as expected.
     */
    public function testNestedConditionGroups() : void {
        // 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->storage
            ->getQuery()
            ->accessCheck(FALSE);
        $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(4, 6, 12, 14);
    }
    
    /**
     * Tests that condition count returns expected number of conditions.
     */
    public function testConditionCount() : void {
        // Query for all entities of the first bundle that
        // have red as a color AND are triangle shaped.
        $query = $this->storage
            ->getQuery()
            ->accessCheck(FALSE);
        // Add an AND condition group with 2 conditions in it.
        $and_condition_group = $query->andConditionGroup()
            ->condition($this->figures . '.color', 'red')
            ->condition($this->figures . '.shape', 'triangle');
        // We added 2 conditions so count should be 2.
        $this->assertSame(2, $and_condition_group->count());
        // Add an OR condition group with 2 conditions in it.
        $or_condition_group = $query->orConditionGroup()
            ->condition($this->figures . '.color', 'red')
            ->condition($this->figures . '.shape', 'triangle');
        // We added 2 conditions so count should be 2.
        $this->assertSame(2, $or_condition_group->count());
    }
    
    /**
     * Tests queries with delta conditions.
     */
    public function testDelta() : void {
        $figures = $this->figures;
        // Test numeric delta value in field condition.
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition("{$figures}.1.color", 'red')
            ->sort('id')
            ->execute();
        // Delta 1 is not red.
        $this->assertResult();
        // Test on two different deltas.
        $query = $this->storage
            ->getQuery()
            ->accessCheck(FALSE);
        $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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition("{$figures}.%delta", 1)
            ->sort('id')
            ->execute();
        // Entity needs to have at least 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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition("id.0.value", [
            1,
            3,
            5,
        ], 'IN')
            ->sort('id')
            ->execute();
        $this->assertResult(1, 3, 5);
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->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->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition("id.%delta.value", [
            1,
            3,
            5,
        ], 'IN')
            ->sort('id')
            ->execute();
        $this->assertResult(1, 3, 5);
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition("id.%delta.value", [
            1,
            3,
            5,
        ], 'IN')
            ->condition("id.%delta", 0, '=')
            ->sort('id')
            ->execute();
        $this->assertResult(1, 3, 5);
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition("id.%delta.value", [
            1,
            3,
            5,
        ], 'IN')
            ->condition("id.%delta", 1, '=')
            ->sort('id')
            ->execute();
        $this->assertResult();
    }
    
    /**
     * @internal
     */
    protected function assertResult() : void {
        $assert = [];
        $expected = func_get_args();
        if ($expected && is_array($expected[0])) {
            $expected = $expected[0];
        }
        foreach ($expected as $binary) {
            $assert[$binary] = strval($binary);
        }
        $this->assertSame($assert, $this->queryResults);
    }
    
    /**
     * @internal
     */
    protected function assertRevisionResult(array $keys, array $expected) : void {
        $assert = [];
        foreach ($expected as $key => $binary) {
            $assert[$keys[$key]] = strval($binary);
        }
        $this->assertSame($assert, $this->queryResults);
    }
    
    /**
     * @internal
     */
    protected function assertBundleOrder(string $order) : void {
        // This loop is for bundle1 entities.
        for ($i = 1; $i <= 15; $i += 2) {
            $ok = TRUE;
            $index1 = array_search($i, $this->queryResults);
            $this->assertNotFalse($index1, "{$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");
        }
    }
    
    /**
     * Tests adding a tag and metadata to the Entity query object.
     *
     * The tags and metadata should propagate to the SQL query object.
     */
    public function testMetaData() : void {
        field_test_memorize();
        $query = $this->storage
            ->getQuery()
            ->accessCheck(FALSE);
        $query->addTag('efq_metadata_test')
            ->addMetaData('foo', 'bar')
            ->execute();
        $mem = field_test_memorize();
        $this->assertEquals('bar', $mem['field_test_query_efq_metadata_test_alter'][0], 'Tag and metadata propagated to the SQL query object.');
    }
    
    /**
     * Tests case sensitive and in-sensitive query conditions.
     */
    public function testCaseSensitivity() : void {
        $bundle = $this->randomMachineName();
        entity_test_create_bundle($bundle, entity_type: 'entity_test_mulrev');
        $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' => mb_strtoupper($string),
                'lowercase' => mb_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 = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase'])
            ->execute();
        $this->assertCount(1, $result, 'Case insensitive, lowercase');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase'])
            ->execute();
        $this->assertCount(1, $result, 'Case insensitive, uppercase');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'])
            ->execute();
        $this->assertCount(1, $result, 'Case insensitive, mixed.');
        // Check the case sensitive field, = operator.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase'])
            ->execute();
        $this->assertCount(0, $result, 'Case sensitive, lowercase.');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase'])
            ->execute();
        $this->assertCount(0, $result, 'Case sensitive, uppercase.');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'])
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, exact match.');
        // Check the case insensitive field, IN operator.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', [
            $fixtures[0]['lowercase'] . $fixtures[1]['lowercase'],
        ], 'IN')
            ->execute();
        $this->assertCount(1, $result, 'Case insensitive, lowercase');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', [
            $fixtures[0]['uppercase'] . $fixtures[1]['uppercase'],
        ], 'IN')
            ->execute();
        $this->assertCount(1, $result, 'Case insensitive, uppercase');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', [
            $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'],
        ], 'IN')
            ->execute();
        $this->assertCount(1, $result, 'Case insensitive, mixed');
        // Check the case sensitive field, IN operator.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', [
            $fixtures[0]['lowercase'] . $fixtures[1]['lowercase'],
        ], 'IN')
            ->execute();
        $this->assertCount(0, $result, 'Case sensitive, lowercase');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', [
            $fixtures[0]['uppercase'] . $fixtures[1]['uppercase'],
        ], 'IN')
            ->execute();
        $this->assertCount(0, $result, 'Case sensitive, uppercase');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', [
            $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'],
        ], 'IN')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, mixed');
        // Check the case insensitive field, STARTS_WITH operator.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', $fixtures[0]['lowercase'], 'STARTS_WITH')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, lowercase.');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', $fixtures[0]['uppercase'], 'STARTS_WITH')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, exact match.');
        // Check the case sensitive field, STARTS_WITH operator.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', $fixtures[0]['lowercase'], 'STARTS_WITH')
            ->execute();
        $this->assertCount(0, $result, 'Case sensitive, lowercase.');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', $fixtures[0]['uppercase'], 'STARTS_WITH')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, exact match.');
        // Check the case insensitive field, ENDS_WITH operator.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', $fixtures[1]['lowercase'], 'ENDS_WITH')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, lowercase.');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', $fixtures[1]['uppercase'], 'ENDS_WITH')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, exact match.');
        // Check the case sensitive field, ENDS_WITH operator.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', $fixtures[1]['lowercase'], 'ENDS_WITH')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, lowercase.');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', $fixtures[1]['uppercase'], 'ENDS_WITH')
            ->execute();
        $this->assertCount(0, $result, 'Case sensitive, exact match.');
        // Check the case insensitive field, CONTAINS operator, use the inner 8
        // characters of the uppercase and lowercase strings.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', mb_substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, lowercase.');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_ci', mb_strtolower(mb_substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, exact match.');
        // Check the case sensitive field, CONTAINS operator.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', mb_substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS')
            ->execute();
        $this->assertCount(1, $result, 'Case sensitive, lowercase.');
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('field_cs', mb_strtolower(mb_substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS')
            ->execute();
        $this->assertCount(0, $result, 'Case sensitive, exact match.');
    }
    
    /**
     * Tests base fields with multiple columns.
     */
    public function testBaseFieldMultipleColumns() : void {
        $this->enableModules([
            'taxonomy',
        ]);
        $this->installEntitySchema('taxonomy_term');
        Vocabulary::create([
            'vid' => 'tags',
        ]);
        $term1 = Term::create([
            'name' => $this->randomMachineName(),
            'vid' => 'tags',
            'description' => [
                'value' => 'description1',
                'format' => 'format1',
            ],
        ]);
        $term1->save();
        $term2 = Term::create([
            'name' => $this->randomMachineName(),
            'vid' => 'tags',
            'description' => [
                'value' => 'description2',
                'format' => 'format2',
            ],
        ]);
        $term2->save();
        // Test that the properties can be queried directly.
        $ids = $this->container
            ->get('entity_type.manager')
            ->getStorage('taxonomy_term')
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('description.value', 'description1')
            ->execute();
        $this->assertCount(1, $ids);
        $this->assertEquals($term1->id(), reset($ids));
        $ids = $this->container
            ->get('entity_type.manager')
            ->getStorage('taxonomy_term')
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('description.format', 'format1')
            ->execute();
        $this->assertCount(1, $ids);
        $this->assertEquals($term1->id(), reset($ids));
        // Test that the main property is queried if no property is specified.
        $ids = $this->container
            ->get('entity_type.manager')
            ->getStorage('taxonomy_term')
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('description', 'description1')
            ->execute();
        $this->assertCount(1, $ids);
        $this->assertEquals($term1->id(), reset($ids));
    }
    
    /**
     * Tests pending revisions.
     */
    public function testPendingRevisions() : void {
        // Ensure entity 14 is returned.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->execute();
        $this->assertCount(1, $result);
        // 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 = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->execute();
        $this->assertCount(1, $result);
        // Verify that field conditions on the default and pending revision are
        // work as expected.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->condition("{$this->figures}.color", $current_values[0]['color'])
            ->execute();
        $this->assertEquals([
            14 => '14',
        ], $result);
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->condition("{$this->figures}.color", 'red')
            ->allRevisions()
            ->execute();
        $this->assertEquals([
            16 => '14',
        ], $result);
        // Add another pending revision on the same entity and repeat the checks.
        $entity->setNewRevision(TRUE);
        $entity->isDefaultRevision(FALSE);
        $entity->{$this->figures}
            ->setValue([
            'color' => 'red',
            'shape' => 'square',
        ]);
        $entity->save();
        // A non-revisioned entity query should still return entity 14.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->execute();
        $this->assertCount(1, $result);
        $this->assertSame([
            14 => '14',
        ], $result);
        // Now check an entity query on the latest revision.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->latestRevision()
            ->execute();
        $this->assertCount(1, $result);
        $this->assertSame([
            17 => '14',
        ], $result);
        // Verify that field conditions on the default and pending revision still
        // work as expected.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->condition("{$this->figures}.color", $current_values[0]['color'])
            ->execute();
        $this->assertSame([
            14 => '14',
        ], $result);
        // Now there are two revisions with same value for the figure color.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->condition("{$this->figures}.color", 'red')
            ->allRevisions()
            ->execute();
        $this->assertSame([
            16 => '14',
            17 => '14',
        ], $result);
        // Check that querying for the latest revision returns the correct one.
        $result = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('id', [
            14,
        ], 'IN')
            ->condition("{$this->figures}.color", 'red')
            ->latestRevision()
            ->execute();
        $this->assertSame([
            17 => '14',
        ], $result);
    }
    
    /**
     * Tests SQL inject of condition field.
     *
     * This covers a database driver's EntityQuery\Condition class.
     */
    public function testInjectionInCondition() : void {
        $this->expectException(\Exception::class);
        $this->queryResults = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->condition('1 ; -- ', [
            0,
            1,
        ], 'IN')
            ->sort('id')
            ->execute();
    }
    
    /**
     * Tests that EntityQuery works when querying the same entity from two fields.
     */
    public function testWithTwoEntityReferenceFieldsToSameEntityType() : void {
        // 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');
        $storage = $this->container
            ->get('entity_type.manager')
            ->getStorage('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 = $storage->getQuery()
            ->accessCheck(FALSE)
            ->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 = $storage->getQuery()
            ->accessCheck(FALSE)
            ->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 = $storage->getQuery()
            ->accessCheck(FALSE)
            ->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));
    }
    
    /**
     * Test the entity query alter hooks are invoked.
     *
     * Hook functions in field_test.module add additional conditions to the query
     * removing entities with specific ids.
     */
    public function testAlterHook() : void {
        $basicQuery = $this->storage
            ->getQuery()
            ->accessCheck(FALSE)
            ->exists($this->greetings, 'tr')
            ->condition($this->figures . ".color", 'red')
            ->sort('id');
        // Verify assumptions about the unaltered result.
        $query = clone $basicQuery;
        $this->queryResults = $query->execute();
        $this->assertResult(5, 7, 13, 15);
        // field_test_entity_query_alter() removes the entity with id '5'.
        $query = clone $basicQuery;
        $this->queryResults = $query->addTag('entity_query_alter_hook_test')
            ->execute();
        $this->assertResult(7, 13, 15);
        // field_test_entity_query_entity_test_mulrev_alter() removes the
        // entity with id '7'.
        $query = clone $basicQuery;
        $this->queryResults = $query->addTag('entity_query_entity_test_mulrev_alter_hook_test')
            ->execute();
        $this->assertResult(5, 13, 15);
        // field_test_entity_query_tag__entity_query_alter_tag_test_alter() removes
        // the entity with id '13'.
        $query = clone $basicQuery;
        $this->queryResults = $query->addTag('entity_query_alter_tag_test')
            ->execute();
        $this->assertResult(5, 7, 15);
        // field_test_entity_query_tag__entity_test_mulrev__entity_query_
        // entity_test_mulrev_alter_tag_test_alter()
        // removes the entity with id '15'.
        $query = clone $basicQuery;
        $this->queryResults = $query->addTag('entity_query_entity_test_mulrev_alter_tag_test')
            ->execute();
        $this->assertResult(5, 7, 13);
    }
    
    /**
     * Tests entity queries with condition on the revision metadata keys.
     */
    public function testConditionOnRevisionMetadataKeys() : void {
        $this->installModule('entity_test_revlog');
        $this->installEntitySchema('entity_test_revlog');
        
        /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
        $entity_type_manager = $this->container
            ->get('entity_type.manager');
        
        /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
        $entity_type = $entity_type_manager->getDefinition('entity_test_revlog');
        
        /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
        $storage = $entity_type_manager->getStorage('entity_test_revlog');
        $revision_created_timestamp = time();
        $revision_created_field_name = $entity_type->getRevisionMetadataKey('revision_created');
        $entity = $storage->create([
            'type' => 'entity_test',
            $revision_created_field_name => $revision_created_timestamp,
        ]);
        $entity->save();
        // Query only the default revision.
        $result = $storage->getQuery()
            ->accessCheck(FALSE)
            ->condition($revision_created_field_name, $revision_created_timestamp)
            ->execute();
        $this->assertCount(1, $result);
        $this->assertEquals($entity->id(), reset($result));
        // Query all revisions.
        $result = $storage->getQuery()
            ->accessCheck(FALSE)
            ->condition($revision_created_field_name, $revision_created_timestamp)
            ->allRevisions()
            ->execute();
        $this->assertCount(1, $result);
        $this->assertEquals($entity->id(), reset($result));
    }
    
    /**
     * Tests __toString().
     */
    public function testToString() : void {
        $query = $this->storage
            ->getQuery()
            ->accessCheck(FALSE);
        $group_blue = $query->andConditionGroup()
            ->condition("{$this->figures}.color", [
            'blue',
        ], 'IN');
        $group_red = $query->andConditionGroup()
            ->condition("{$this->figures}.color", [
            'red',
        ], 'IN');
        $null_group = $query->andConditionGroup()
            ->notExists("{$this->figures}.color");
        $this->queryResults = $query->condition($group_blue)
            ->condition($group_red)
            ->condition($null_group)
            ->sort('id');
        $figures = $this->figures;
        // Matching the SQL statement against an hardcoded statement leads to
        // failures with database drivers that override the
        // Drupal\Core\Database\Query\Select class. We build a dynamic query via
        // the db API to check that its SQL matches the one generated by the
        // EntityQuery. This way we ensure that the database driver is free to
        // create its own comparable SQL statement.
        $connection = Database::getConnection();
        $expected = $connection->select("entity_test_mulrev", "base_table");
        $expected->addField("base_table", "revision_id", "revision_id");
        $expected->addField("base_table", "id", "id");
        $expected->join("entity_test_mulrev__{$figures}", "entity_test_mulrev__{$figures}", '[entity_test_mulrev__' . $figures . '].[entity_id] = [base_table].[id]');
        $expected->join("entity_test_mulrev__{$figures}", "entity_test_mulrev__{$figures}_2", '[entity_test_mulrev__' . $figures . '_2].[entity_id] = [base_table].[id]');
        $expected->addJoin("LEFT", "entity_test_mulrev__{$figures}", "entity_test_mulrev__{$figures}_3", '[entity_test_mulrev__' . $figures . '_3].[entity_id] = [base_table].[id]');
        $expected->condition("entity_test_mulrev__{$figures}.{$figures}_color", [
            "blue",
        ], "IN");
        $expected->condition("entity_test_mulrev__{$figures}_2.{$figures}_color", [
            "red",
        ], "IN");
        $expected->isNull("entity_test_mulrev__{$figures}_3.{$figures}_color");
        $expected->orderBy("base_table.id");
        // Apply table prefixes and quote identifiers for the expected SQL.
        $expected_string = $connection->prefixTables((string) $expected);
        $expected_string = $connection->quoteIdentifiers($expected_string);
        // Resolve placeholders in the expected SQL to their values.
        $quoted = [];
        foreach ($expected->getArguments() as $key => $value) {
            $quoted[$key] = $connection->quote($value);
        }
        $expected_string = strtr($expected_string, $quoted);
        $this->assertSame($expected_string, (string) $query);
    }
    
    /**
     * Test the accessCheck method is called.
     */
    public function testAccessCheckSpecified() : void {
        $this->expectException(QueryException::class);
        $this->expectExceptionMessage('Entity queries must explicitly set whether the query should be access checked or not. See Drupal\\Core\\Entity\\Query\\QueryInterface::accessCheck().');
        // We are purposely testing an entity query without access check, so we need
        // to tell PHPStan to ignore this.
        // @phpstan-ignore-next-line
        $this->storage
            ->getQuery()
            ->execute();
    }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Member alias Overriden Title Overrides
AssertContentTrait::$content protected property The current raw content.
AssertContentTrait::$drupalSettings protected property The drupalSettings value from the current raw $content.
AssertContentTrait::$elements protected property The XML structure parsed from the current raw $content. 1
AssertContentTrait::$plainTextContent protected property The plain-text content of raw $content (text nodes).
AssertContentTrait::assertEscaped protected function Passes if the raw text IS found escaped on the loaded page, fail otherwise.
AssertContentTrait::assertField protected function Asserts that a field exists with the given name or ID.
AssertContentTrait::assertFieldById protected function Asserts that a field exists with the given ID and value.
AssertContentTrait::assertFieldByName protected function Asserts that a field exists with the given name and value.
AssertContentTrait::assertFieldByXPath protected function Asserts that a field exists in the current page by the given XPath.
AssertContentTrait::assertFieldChecked protected function Asserts that a checkbox field in the current page is checked.
AssertContentTrait::assertFieldsByValue protected function Asserts that a field exists in the current page with a given Xpath result.
AssertContentTrait::assertLink protected function Passes if a link with the specified label is found.
AssertContentTrait::assertLinkByHref protected function Passes if a link containing a given href (part) is found.
AssertContentTrait::assertNoDuplicateIds protected function Asserts that each HTML ID is used for just a single element.
AssertContentTrait::assertNoEscaped protected function Passes if raw text IS NOT found escaped on loaded page, fail otherwise.
AssertContentTrait::assertNoField protected function Asserts that a field does not exist with the given name or ID.
AssertContentTrait::assertNoFieldById protected function Asserts that a field does not exist with the given ID and value.
AssertContentTrait::assertNoFieldByName protected function Asserts that a field does not exist with the given name and value.
AssertContentTrait::assertNoFieldByXPath protected function Asserts that a field does not exist or its value does not match, by XPath.
AssertContentTrait::assertNoFieldChecked protected function Asserts that a checkbox field in the current page is not checked.
AssertContentTrait::assertNoLink protected function Passes if a link with the specified label is not found.
AssertContentTrait::assertNoLinkByHref protected function Passes if a link containing a given href (part) is not found.
AssertContentTrait::assertNoLinkByHrefInMainRegion protected function Passes if a link containing a given href is not found in the main region.
AssertContentTrait::assertNoOption protected function Asserts that a select option in the current page does not exist.
AssertContentTrait::assertNoOptionSelected protected function Asserts that a select option in the current page is not checked.
AssertContentTrait::assertNoPattern protected function Triggers a pass if the perl regex pattern is not found in raw content.
AssertContentTrait::assertNoRaw protected function Passes if the raw text is NOT found on the loaded page, fail otherwise.
AssertContentTrait::assertNoText protected function Passes if the page (with HTML stripped) does not contains the text.
AssertContentTrait::assertNoTitle protected function Pass if the page title is not the given string.
AssertContentTrait::assertNoUniqueText protected function Passes if the text is found MORE THAN ONCE on the text version of the page.
AssertContentTrait::assertOption protected function Asserts that a select option in the current page exists.
AssertContentTrait::assertOptionByText protected function Asserts that a select option with the visible text exists.
AssertContentTrait::assertOptionSelected protected function Asserts that a select option in the current page is checked.
AssertContentTrait::assertOptionSelectedWithDrupalSelector protected function Asserts that a select option in the current page is checked.
AssertContentTrait::assertOptionWithDrupalSelector protected function Asserts that a select option in the current page exists.
AssertContentTrait::assertPattern protected function Triggers a pass if the Perl regex pattern is found in the raw content.
AssertContentTrait::assertRaw protected function Passes if the raw text IS found on the loaded page, fail otherwise.
AssertContentTrait::assertText protected function Passes if the page (with HTML stripped) contains the text.
AssertContentTrait::assertTextHelper protected function Helper for assertText and assertNoText.
AssertContentTrait::assertTextPattern protected function Asserts that a Perl regex pattern is found in the plain-text content.
AssertContentTrait::assertThemeOutput protected function Asserts themed output.
AssertContentTrait::assertTitle protected function Pass if the page title is the given string.
AssertContentTrait::assertUniqueText protected function Passes if the text is found ONLY ONCE on the text version of the page.
AssertContentTrait::assertUniqueTextHelper protected function Helper for assertUniqueText and assertNoUniqueText.
AssertContentTrait::buildXPathQuery protected function Builds an XPath query.
AssertContentTrait::constructFieldXpath protected function Helper: Constructs an XPath for the given set of attributes and value.
AssertContentTrait::cssSelect protected function Searches elements using a CSS selector in the raw content.
AssertContentTrait::getAllOptions protected function Get all option elements, including nested options, in a select.
AssertContentTrait::getDrupalSettings protected function Gets the value of drupalSettings for the currently-loaded page.
AssertContentTrait::getRawContent protected function Gets the current raw content.
AssertContentTrait::getSelectedItem protected function Get the selected value from a select field.
AssertContentTrait::getTextContent protected function Retrieves the plain-text content from the current raw content.
AssertContentTrait::parse protected function Parse content returned from curlExec using DOM and SimpleXML.
AssertContentTrait::removeWhiteSpace protected function Removes all white-space between HTML tags from the raw content.
AssertContentTrait::setDrupalSettings protected function Sets the value of drupalSettings for the currently-loaded page.
AssertContentTrait::setRawContent protected function Sets the raw content (e.g. HTML).
AssertContentTrait::xpath protected function Performs an xpath search on the contents of the internal browser.
ConfigTestTrait::configImporter protected function Returns a ConfigImporter object to import test configuration.
ConfigTestTrait::copyConfig protected function Copies configuration objects from source storage to target storage.
EntityKernelTestBase::$entityTypeManager protected property The entity type manager service. 1
EntityKernelTestBase::$generatedIds protected property A list of generated identifiers.
EntityKernelTestBase::$state protected property The state service.
EntityKernelTestBase::createUser protected function Creates a user.
EntityKernelTestBase::generateRandomEntityId protected function Generates a random ID avoiding collisions.
EntityKernelTestBase::getHooksInfo protected function Returns the entity_test hook invocation info.
EntityKernelTestBase::installModule protected function Installs a module and refreshes services.
EntityKernelTestBase::refreshServices protected function Refresh services. 1
EntityKernelTestBase::reloadEntity protected function Reloads the given entity from the storage and returns it.
EntityKernelTestBase::uninstallModule protected function Uninstalls a module and refreshes services.
EntityQueryTest::$bundles protected property A list of bundle machine names created for this test.
EntityQueryTest::$figures public property Field name for the figures field.
EntityQueryTest::$greetings public property Field name for the greetings field.
EntityQueryTest::$modules protected static property Modules to install. Overrides EntityKernelTestBase::$modules
EntityQueryTest::$queryResults protected property
EntityQueryTest::$storage protected property The entity_test_mulrev entity storage.
EntityQueryTest::assertBundleOrder protected function @internal
EntityQueryTest::assertResult protected function @internal
EntityQueryTest::assertRevisionResult protected function @internal
EntityQueryTest::setUp protected function Overrides EntityKernelTestBase::setUp
EntityQueryTest::testAccessCheckSpecified public function Test the accessCheck method is called.
EntityQueryTest::testAlterHook public function Test the entity query alter hooks are invoked.
EntityQueryTest::testBaseFieldMultipleColumns public function Tests base fields with multiple columns.
EntityQueryTest::testCaseSensitivity public function Tests case sensitive and in-sensitive query conditions.
EntityQueryTest::testConditionCount public function Tests that condition count returns expected number of conditions.
EntityQueryTest::testConditionOnRevisionMetadataKeys public function Tests entity queries with condition on the revision metadata keys.
EntityQueryTest::testCount public function Tests that count queries are separated across entity types.
EntityQueryTest::testDelta public function Tests queries with delta conditions.
EntityQueryTest::testEntityQuery public function Tests basic functionality.
EntityQueryTest::testInjectionInCondition public function Tests SQL inject of condition field.
EntityQueryTest::testMetaData public function Tests adding a tag and metadata to the Entity query object.
EntityQueryTest::testNestedConditionGroups public function Tests that nested condition groups work as expected.
EntityQueryTest::testPendingRevisions public function Tests pending revisions.
EntityQueryTest::testSort public function Tests sort().
EntityQueryTest::testTableSort public function Tests tablesort().
EntityQueryTest::testToString public function Tests __toString().
EntityQueryTest::testWithTwoEntityReferenceFieldsToSameEntityType public function Tests that EntityQuery works when querying the same entity from two fields.
EntityReferenceFieldCreationTrait::createEntityReferenceField protected function Creates a field of an entity reference field storage on the specified bundle.
ExtensionListTestTrait::getModulePath protected function Gets the path for the specified module.
ExtensionListTestTrait::getThemePath protected function Gets the path for the specified theme.
KernelTestBase::$backupGlobals protected property Back up and restore any global variables that may be changed by tests.
KernelTestBase::$backupStaticAttributes protected property Back up and restore static class properties that may be changed by tests.
KernelTestBase::$backupStaticAttributesBlacklist protected property Contains a few static class properties for performance.
KernelTestBase::$classLoader protected property
KernelTestBase::$configImporter protected property @todo Move into Config test base class. 6
KernelTestBase::$configSchemaCheckerExclusions protected static property An array of config object names that are excluded from schema checking. 3
KernelTestBase::$container protected property
KernelTestBase::$databasePrefix protected property
KernelTestBase::$keyValue protected property The key_value service that must persist between container rebuilds.
KernelTestBase::$preserveGlobalState protected property Do not forward any global state from the parent process to the processes
that run the actual tests.
KernelTestBase::$root protected property The app root.
KernelTestBase::$runTestInSeparateProcess protected property Kernel tests are run in separate processes because they allow autoloading
of code from extensions. Running the test in a separate process isolates
this behavior from other tests. Subclasses should not override this
property.
KernelTestBase::$siteDirectory protected property
KernelTestBase::$strictConfigSchema protected property Set to TRUE to strict check all configuration saved. 9
KernelTestBase::$usesSuperUserAccessPolicy protected property Set to TRUE to make user 1 a super user. 7
KernelTestBase::$vfsRoot protected property The virtual filesystem root directory.
KernelTestBase::assertPostConditions protected function 1
KernelTestBase::bootEnvironment protected function Bootstraps a basic test environment.
KernelTestBase::bootKernel protected function Bootstraps a kernel for a test. 1
KernelTestBase::config protected function Configuration accessor for tests. Returns non-overridden configuration.
KernelTestBase::disableModules protected function Disables modules for this test.
KernelTestBase::enableModules protected function Enables modules for this test. 1
KernelTestBase::getConfigSchemaExclusions protected function Gets the config schema exclusions for this test.
KernelTestBase::getDatabaseConnectionInfo protected function Returns the Database connection info to be used for this test. 2
KernelTestBase::getDatabasePrefix public function
KernelTestBase::getExtensionsForModules private function Returns Extension objects for $modules to install.
KernelTestBase::getModulesToEnable private static function Returns the modules to install for this test.
KernelTestBase::initFileCache protected function Initializes the FileCache component.
KernelTestBase::installConfig protected function Installs default configuration for a given list of modules.
KernelTestBase::installEntitySchema protected function Installs the storage schema for a specific entity type.
KernelTestBase::installSchema protected function Installs database tables from a module schema definition.
KernelTestBase::register public function Registers test-specific services. Overrides ServiceProviderInterface::register 27
KernelTestBase::render protected function Renders a render array. 1
KernelTestBase::setInstallProfile protected function Sets the install profile and rebuilds the container to update it.
KernelTestBase::setSetting protected function Sets an in-memory Settings variable.
KernelTestBase::setUpBeforeClass public static function 1
KernelTestBase::setUpFilesystem protected function Sets up the filesystem, so things like the file directory. 2
KernelTestBase::stop Deprecated protected function Stops test execution.
KernelTestBase::tearDown protected function 6
KernelTestBase::tearDownCloseDatabaseConnection public function @after
KernelTestBase::vfsDump protected function Dumps the current state of the virtual filesystem to STDOUT.
KernelTestBase::__get public function
KernelTestBase::__sleep public function Prevents serializing any properties.
PhpUnitWarnings::$deprecationWarnings private static property Deprecation warnings from PHPUnit to raise with @trigger_error().
PhpUnitWarnings::addWarning public function Converts PHPUnit deprecation warnings to E_USER_DEPRECATED.
RandomGeneratorTrait::getRandomGenerator protected function Gets the random generator for the utility methods.
RandomGeneratorTrait::randomMachineName protected function Generates a unique random string containing letters and numbers.
RandomGeneratorTrait::randomObject public function Generates a random PHP object.
RandomGeneratorTrait::randomString public function Generates a pseudo-random string of ASCII characters of codes 32 to 126.
RandomGeneratorTrait::randomStringValidate Deprecated public function Callback for random string validation.
StorageCopyTrait::replaceStorageContents protected static function Copy the configuration from one storage to another and remove stale items.
TestRequirementsTrait::checkModuleRequirements Deprecated private function Checks missing module requirements.
TestRequirementsTrait::checkRequirements Deprecated protected function Check module requirements for the Drupal use case.
TestRequirementsTrait::getDrupalRoot protected static function Returns the Drupal root directory.
UserCreationTrait::checkPermissions protected function Checks whether a given list of permission names is valid. Aliased as: drupalCheckPermissions
UserCreationTrait::createAdminRole protected function Creates an administrative role. Aliased as: drupalCreateAdminRole
UserCreationTrait::createRole protected function Creates a role with specified permissions. Aliased as: drupalCreateRole
UserCreationTrait::createUser protected function Create a user with a given set of permissions. Aliased as: drupalCreateUser
UserCreationTrait::grantPermissions protected function Grant permissions to a user role. Aliased as: drupalGrantPermissions
UserCreationTrait::setCurrentUser protected function Switch the current logged in user. Aliased as: drupalSetCurrentUser
UserCreationTrait::setUpCurrentUser protected function Creates a random user account and sets it as current user. Aliased as: drupalSetUpCurrentUser

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.