Pull merge.
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Entity / EntityQueryTest.php
1 <?php
2
3 namespace Drupal\KernelTests\Core\Entity;
4
5 use Drupal\entity_test\Entity\EntityTest;
6 use Drupal\entity_test\Entity\EntityTestMulRev;
7 use Drupal\field\Entity\FieldConfig;
8 use Drupal\field\Entity\FieldStorageConfig;
9 use Drupal\language\Entity\ConfigurableLanguage;
10 use Drupal\taxonomy\Entity\Term;
11 use Drupal\taxonomy\Entity\Vocabulary;
12 use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
13 use Symfony\Component\HttpFoundation\Request;
14
15 /**
16  * Tests Entity Query functionality.
17  *
18  * @group Entity
19  */
20 class EntityQueryTest extends EntityKernelTestBase {
21
22   use EntityReferenceTestTrait;
23
24   /**
25    * Modules to enable.
26    *
27    * @var array
28    */
29   public static $modules = ['field_test', 'language'];
30
31   /**
32    * @var array
33    */
34   protected $queryResults;
35
36   /**
37    * A list of bundle machine names created for this test.
38    *
39    * @var string[]
40    */
41   protected $bundles;
42
43   /**
44    * Field name for the greetings field.
45    *
46    * @var string
47    */
48   public $greetings;
49
50   /**
51    * Field name for the figures field.
52    *
53    * @var string
54    */
55   public $figures;
56
57   /**
58    * The entity_test_mulrev entity storage.
59    *
60    * @var \Drupal\Core\Entity\EntityStorageInterface
61    */
62   protected $storage;
63
64   protected function setUp() {
65     parent::setUp();
66
67     $this->installEntitySchema('entity_test_mulrev');
68
69     $this->installConfig(['language']);
70
71     $figures = mb_strtolower($this->randomMachineName());
72     $greetings = mb_strtolower($this->randomMachineName());
73     foreach ([$figures => 'shape', $greetings => 'text'] as $field_name => $field_type) {
74       $field_storage = FieldStorageConfig::create([
75         'field_name' => $field_name,
76         'entity_type' => 'entity_test_mulrev',
77         'type' => $field_type,
78         'cardinality' => 2,
79       ]);
80       $field_storage->save();
81       $field_storages[] = $field_storage;
82     }
83     $bundles = [];
84     for ($i = 0; $i < 2; $i++) {
85       // For the sake of tablesort, make sure the second bundle is higher than
86       // the first one. Beware: MySQL is not case sensitive.
87       do {
88         $bundle = $this->randomMachineName();
89       } while ($bundles && strtolower($bundles[0]) >= strtolower($bundle));
90       entity_test_create_bundle($bundle);
91       foreach ($field_storages as $field_storage) {
92         FieldConfig::create([
93           'field_storage' => $field_storage,
94           'bundle' => $bundle,
95         ])->save();
96       }
97       $bundles[] = $bundle;
98     }
99     // Each unit is a list of field name, langcode and a column-value array.
100     $units[] = [$figures, 'en', [
101         'color' => 'red',
102         'shape' => 'triangle',
103       ],
104     ];
105     $units[] = [$figures, 'en', [
106         'color' => 'blue',
107         'shape' => 'circle',
108       ],
109     ];
110     // To make it easier to test sorting, the greetings get formats according
111     // to their langcode.
112     $units[] = [$greetings, 'tr', [
113         'value' => 'merhaba',
114         'format' => 'format-tr',
115       ],
116     ];
117     $units[] = [$greetings, 'pl', [
118         'value' => 'siema',
119         'format' => 'format-pl',
120       ],
121     ];
122     // Make these languages available to the greetings field.
123     ConfigurableLanguage::createFromLangcode('tr')->save();
124     ConfigurableLanguage::createFromLangcode('pl')->save();
125     // Calculate the cartesian product of the unit array by looking at the
126     // bits of $i and add the unit at the bits that are 1. For example,
127     // decimal 13 is binary 1101 so unit 3,2 and 0 will be added to the
128     // entity.
129     for ($i = 1; $i <= 15; $i++) {
130       $entity = EntityTestMulRev::create([
131         'type' => $bundles[$i & 1],
132         'name' => $this->randomMachineName(),
133         'langcode' => 'en',
134       ]);
135       // Make sure the name is set for every language that we might create.
136       foreach (['tr', 'pl'] as $langcode) {
137         $entity->addTranslation($langcode)->name = $this->randomMachineName();
138       }
139       foreach (array_reverse(str_split(decbin($i))) as $key => $bit) {
140         if ($bit) {
141           // @todo https://www.drupal.org/project/drupal/issues/3001920 Doing
142           //   list($field_name, $langcode, $values) = $units[$key]; causes
143           //   problems in PHP 7.3. Revert to better variable names once
144           //   https://bugs.php.net/bug.php?id=76937 is fixed.
145           $entity->getTranslation($units[$key][1])->{$units[$key][0]}[] = $units[$key][2];
146         }
147       }
148       $entity->save();
149     }
150     $this->bundles = $bundles;
151     $this->figures = $figures;
152     $this->greetings = $greetings;
153     $this->storage = $this->container->get('entity_type.manager')->getStorage('entity_test_mulrev');
154   }
155
156   /**
157    * Test basic functionality.
158    */
159   public function testEntityQuery() {
160     $greetings = $this->greetings;
161     $figures = $this->figures;
162     $this->queryResults = $this->storage
163       ->getQuery()
164       ->exists($greetings, 'tr')
165       ->condition("$figures.color", 'red')
166       ->sort('id')
167       ->execute();
168     // As unit 0 was the red triangle and unit 2 was the turkish greeting,
169     // bit 0 and bit 2 needs to be set.
170     $this->assertResult(5, 7, 13, 15);
171
172     $query = $this->storage
173       ->getQuery('OR')
174       ->exists($greetings, 'tr')
175       ->condition("$figures.color", 'red')
176       ->sort('id');
177     $count_query = clone $query;
178     $this->assertEqual(12, $count_query->count()->execute());
179     $this->queryResults = $query->execute();
180     // Now bit 0 (1, 3, 5, 7, 9, 11, 13, 15) or bit 2 (4, 5, 6, 7, 12, 13, 14,
181     // 15) needs to be set.
182     $this->assertResult(1, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15);
183
184     // Test cloning of query conditions.
185     $query = $this->storage
186       ->getQuery()
187       ->condition("$figures.color", 'red')
188       ->sort('id');
189     $cloned_query = clone $query;
190     $cloned_query
191       ->condition("$figures.shape", 'circle');
192     // Bit 0 (1, 3, 5, 7, 9, 11, 13, 15) needs to be set.
193     $this->queryResults = $query->execute();
194     $this->assertResult(1, 3, 5, 7, 9, 11, 13, 15);
195     // No red color has a circle shape.
196     $this->queryResults = $cloned_query->execute();
197     $this->assertResult();
198
199     $query = $this->storage->getQuery();
200     $group = $query->orConditionGroup()
201       ->exists($greetings, 'tr')
202       ->condition("$figures.color", 'red');
203     $this->queryResults = $query
204       ->condition($group)
205       ->condition("$greetings.value", 'sie', 'STARTS_WITH')
206       ->sort('revision_id')
207       ->execute();
208     // Bit 3 and (bit 0 or 2) -- the above 8 part of the above.
209     $this->assertResult(9, 11, 12, 13, 14, 15);
210
211     // No figure has both the colors blue and red at the same time.
212     $this->queryResults = $this->storage
213       ->getQuery()
214       ->condition("$figures.color", 'blue')
215       ->condition("$figures.color", 'red')
216       ->sort('id')
217       ->execute();
218     $this->assertResult();
219
220     // But an entity might have a red and a blue figure both.
221     $query = $this->storage->getQuery();
222     $group_blue = $query->andConditionGroup()->condition("$figures.color", 'blue');
223     $group_red = $query->andConditionGroup()->condition("$figures.color", 'red');
224     $this->queryResults = $query
225       ->condition($group_blue)
226       ->condition($group_red)
227       ->sort('revision_id')
228       ->execute();
229     // Unit 0 and unit 1, so bits 0 1.
230     $this->assertResult(3, 7, 11, 15);
231
232     // Do the same test but with IN operator.
233     $query = $this->storage->getQuery();
234     $group_blue = $query->andConditionGroup()->condition("$figures.color", ['blue'], 'IN');
235     $group_red = $query->andConditionGroup()->condition("$figures.color", ['red'], 'IN');
236     $this->queryResults = $query
237       ->condition($group_blue)
238       ->condition($group_red)
239       ->sort('id')
240       ->execute();
241     // Unit 0 and unit 1, so bits 0 1.
242     $this->assertResult(3, 7, 11, 15);
243
244     // An entity might have either red or blue figure.
245     $this->queryResults = $this->storage
246       ->getQuery()
247       ->condition("$figures.color", ['blue', 'red'], 'IN')
248       ->sort('id')
249       ->execute();
250     // Bit 0 or 1 is on.
251     $this->assertResult(1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15);
252
253     $this->queryResults = $this->storage
254       ->getQuery()
255       ->exists("$figures.color")
256       ->notExists("$greetings.value")
257       ->sort('id')
258       ->execute();
259     // Bit 0 or 1 is on but 2 and 3 are not.
260     $this->assertResult(1, 2, 3);
261     // Now update the 'merhaba' string to xsiemax which is not a meaningful
262     // word but allows us to test revisions and string operations.
263     $ids = $this->storage
264       ->getQuery()
265       ->condition("$greetings.value", 'merhaba')
266       ->sort('id')
267       ->execute();
268     $entities = EntityTestMulRev::loadMultiple($ids);
269     $first_entity = reset($entities);
270     $old_name = $first_entity->name->value;
271     foreach ($entities as $entity) {
272       $entity->setNewRevision();
273       $entity->getTranslation('tr')->$greetings->value = 'xsiemax';
274       $entity->name->value .= 'x';
275       $entity->save();
276     }
277     // We changed the entity names, so the current revision should not match.
278     $this->queryResults = $this->storage
279       ->getQuery()
280       ->condition('name.value', $old_name)
281       ->execute();
282     $this->assertResult();
283     // Only if all revisions are queried, we find the old revision.
284     $this->queryResults = $this->storage
285       ->getQuery()
286       ->condition('name.value', $old_name)
287       ->allRevisions()
288       ->sort('revision_id')
289       ->execute();
290     $this->assertRevisionResult([$first_entity->id()], [$first_entity->id()]);
291     // When querying current revisions, this string is no longer found.
292     $this->queryResults = $this->storage
293       ->getQuery()
294       ->condition("$greetings.value", 'merhaba')
295       ->execute();
296     $this->assertResult();
297     $this->queryResults = $this->storage
298       ->getQuery()
299       ->condition("$greetings.value", 'merhaba')
300       ->allRevisions()
301       ->sort('revision_id')
302       ->execute();
303     // The query only matches the original revisions.
304     $this->assertRevisionResult([4, 5, 6, 7, 12, 13, 14, 15], [4, 5, 6, 7, 12, 13, 14, 15]);
305     $results = $this->storage
306       ->getQuery()
307       ->condition("$greetings.value", 'siema', 'CONTAINS')
308       ->sort('id')
309       ->execute();
310     // This matches both the original and new current revisions, multiple
311     // revisions are returned for some entities.
312     $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'];
313     $this->assertIdentical($results, $assert);
314     $results = $this->storage
315       ->getQuery()
316       ->condition("$greetings.value", 'siema', 'STARTS_WITH')
317       ->sort('revision_id')
318       ->execute();
319     // Now we only get the ones that originally were siema, entity id 8 and
320     // above.
321     $this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
322     $results = $this->storage
323       ->getQuery()
324       ->condition("$greetings.value", 'a', 'ENDS_WITH')
325       ->sort('revision_id')
326       ->execute();
327     // It is very important that we do not get the ones which only have
328     // xsiemax despite originally they were merhaba, ie. ended with a.
329     $this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
330     $results = $this->storage
331       ->getQuery()
332       ->condition("$greetings.value", 'a', 'ENDS_WITH')
333       ->allRevisions()
334       ->sort('id')
335       ->sort('revision_id')
336       ->execute();
337     // Now we get everything.
338     $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'];
339     $this->assertIdentical($results, $assert);
340
341     // Check that a query on the latest revisions without any condition returns
342     // the correct results.
343     $results = $this->storage
344       ->getQuery()
345       ->latestRevision()
346       ->sort('id')
347       ->sort('revision_id')
348       ->execute();
349     $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'];
350     $this->assertSame($expected, $results);
351   }
352
353   /**
354    * Test sort().
355    *
356    * Warning: this is complicated.
357    */
358   public function testSort() {
359     $greetings = $this->greetings;
360     $figures = $this->figures;
361     // Order up and down on a number.
362     $this->queryResults = $this->storage
363       ->getQuery()
364       ->sort('id')
365       ->execute();
366     $this->assertResult(range(1, 15));
367     $this->queryResults = $this->storage
368       ->getQuery()
369       ->sort('id', 'DESC')
370       ->execute();
371     $this->assertResult(range(15, 1));
372     $query = $this->storage
373       ->getQuery()
374       ->sort("$figures.color")
375       ->sort("$greetings.format")
376       ->sort('id');
377     // As we do not have any conditions, here are the possible colors and
378     // language codes, already in order, with the first occurrence of the
379     // entity id marked with *:
380     // 8  NULL pl *
381     // 12 NULL pl *
382
383     // 4  NULL tr *
384     // 12 NULL tr
385
386     // 2  blue NULL *
387     // 3  blue NULL *
388
389     // 10 blue pl *
390     // 11 blue pl *
391     // 14 blue pl *
392     // 15 blue pl *
393
394     // 6  blue tr *
395     // 7  blue tr *
396     // 14 blue tr
397     // 15 blue tr
398
399     // 1  red  NULL
400     // 3  red  NULL
401
402     // 9  red  pl *
403     // 11 red  pl
404     // 13 red  pl *
405     // 15 red  pl
406
407     // 5  red  tr *
408     // 7  red  tr
409     // 13 red  tr
410     // 15 red  tr
411     $count_query = clone $query;
412     $this->assertEqual(15, $count_query->count()->execute());
413     $this->queryResults = $query->execute();
414     $this->assertResult(8, 12, 4, 2, 3, 10, 11, 14, 15, 6, 7, 1, 9, 13, 5);
415
416     // Test the pager by setting element #1 to page 2 with a page size of 4.
417     // Results will be #8-12 from above.
418     $request = Request::createFromGlobals();
419     $request->query->replace([
420       'page' => '0,2',
421     ]);
422     \Drupal::getContainer()->get('request_stack')->push($request);
423     $this->queryResults = $this->storage
424       ->getQuery()
425       ->sort("$figures.color")
426       ->sort("$greetings.format")
427       ->sort('id')
428       ->pager(4, 1)
429       ->execute();
430     $this->assertResult(15, 6, 7, 1);
431
432     // Now test the reversed order.
433     $query = $this->storage
434       ->getQuery()
435       ->sort("$figures.color", 'DESC')
436       ->sort("$greetings.format", 'DESC')
437       ->sort('id', 'DESC');
438     $count_query = clone $query;
439     $this->assertEqual(15, $count_query->count()->execute());
440     $this->queryResults = $query->execute();
441     $this->assertResult(15, 13, 7, 5, 11, 9, 3, 1, 14, 6, 10, 2, 12, 4, 8);
442   }
443
444   /**
445    * Test tablesort().
446    */
447   public function testTableSort() {
448     // While ordering on bundles do not give us a definite order, we can still
449     // assert that all entities from one bundle are after the other as the
450     // order dictates.
451     $request = Request::createFromGlobals();
452     $request->query->replace([
453       'sort' => 'asc',
454       'order' => 'Type',
455     ]);
456     \Drupal::getContainer()->get('request_stack')->push($request);
457
458     $header = [
459       'id' => ['data' => 'Id', 'specifier' => 'id'],
460       'type' => ['data' => 'Type', 'specifier' => 'type'],
461     ];
462
463     $this->queryResults = array_values($this->storage
464       ->getQuery()
465       ->tableSort($header)
466       ->execute());
467     $this->assertBundleOrder('asc');
468
469     $request->query->add([
470       'sort' => 'desc',
471     ]);
472     \Drupal::getContainer()->get('request_stack')->push($request);
473
474     $header = [
475       'id' => ['data' => 'Id', 'specifier' => 'id'],
476       'type' => ['data' => 'Type', 'specifier' => 'type'],
477     ];
478     $this->queryResults = array_values($this->storage
479       ->getQuery()
480       ->tableSort($header)
481       ->execute());
482     $this->assertBundleOrder('desc');
483
484     // Ordering on ID is definite, however.
485     $request->query->add([
486       'order' => 'Id',
487     ]);
488     \Drupal::getContainer()->get('request_stack')->push($request);
489     $this->queryResults = $this->storage
490       ->getQuery()
491       ->tableSort($header)
492       ->execute();
493     $this->assertResult(range(15, 1));
494   }
495
496   /**
497    * Test that count queries are separated across entity types.
498    */
499   public function testCount() {
500     // Create a field with the same name in a different entity type.
501     $field_name = $this->figures;
502     $field_storage = FieldStorageConfig::create([
503       'field_name' => $field_name,
504       'entity_type' => 'entity_test',
505       'type' => 'shape',
506       'cardinality' => 2,
507       'translatable' => TRUE,
508     ]);
509     $field_storage->save();
510     $bundle = $this->randomMachineName();
511     FieldConfig::create([
512       'field_storage' => $field_storage,
513       'bundle' => $bundle,
514     ])->save();
515
516     $entity = EntityTest::create([
517       'id' => 1,
518       'type' => $bundle,
519     ]);
520     $entity->enforceIsNew();
521     $entity->save();
522
523     // As the single entity of this type we just saved does not have a value
524     // in the color field, the result should be 0.
525     $count = $this->container->get('entity_type.manager')
526       ->getStorage('entity_test')
527       ->getQuery()
528       ->exists("$field_name.color")
529       ->count()
530       ->execute();
531     $this->assertFalse($count);
532   }
533
534   /**
535    * Tests that nested condition groups work as expected.
536    */
537   public function testNestedConditionGroups() {
538     // Query for all entities of the first bundle that have either a red
539     // triangle as a figure or the Turkish greeting as a greeting.
540     $query = $this->storage->getQuery();
541
542     $first_and = $query->andConditionGroup()
543       ->condition($this->figures . '.color', 'red')
544       ->condition($this->figures . '.shape', 'triangle');
545     $second_and = $query->andConditionGroup()
546       ->condition($this->greetings . '.value', 'merhaba')
547       ->condition($this->greetings . '.format', 'format-tr');
548
549     $or = $query->orConditionGroup()
550       ->condition($first_and)
551       ->condition($second_and);
552
553     $this->queryResults = $query
554       ->condition($or)
555       ->condition('type', reset($this->bundles))
556       ->sort('id')
557       ->execute();
558
559     $this->assertResult(4, 6, 12, 14);
560   }
561
562   /**
563    * Tests that condition count returns expected number of conditions.
564    */
565   public function testConditionCount() {
566     // Query for all entities of the first bundle that
567     // have red as a colour AND are triangle shaped.
568     $query = $this->storage->getQuery();
569
570     // Add an AND condition group with 2 conditions in it.
571     $and_condition_group = $query->andConditionGroup()
572       ->condition($this->figures . '.color', 'red')
573       ->condition($this->figures . '.shape', 'triangle');
574
575     // We added 2 conditions so count should be 2.
576     $this->assertEqual($and_condition_group->count(), 2);
577
578     // Add an OR condition group with 2 conditions in it.
579     $or_condition_group = $query->orConditionGroup()
580       ->condition($this->figures . '.color', 'red')
581       ->condition($this->figures . '.shape', 'triangle');
582
583     // We added 2 conditions so count should be 2.
584     $this->assertEqual($or_condition_group->count(), 2);
585   }
586
587   /**
588    * Test queries with delta conditions.
589    */
590   public function testDelta() {
591     $figures = $this->figures;
592     // Test numeric delta value in field condition.
593     $this->queryResults = $this->storage
594       ->getQuery()
595       ->condition("$figures.0.color", 'red')
596       ->sort('id')
597       ->execute();
598     // As unit 0 at delta 0 was the red triangle bit 0 needs to be set.
599     $this->assertResult(1, 3, 5, 7, 9, 11, 13, 15);
600
601     $this->queryResults = $this->storage
602       ->getQuery()
603       ->condition("$figures.1.color", 'red')
604       ->sort('id')
605       ->execute();
606     // Delta 1 is not red.
607     $this->assertResult();
608
609     // Test on two different deltas.
610     $query = $this->storage->getQuery();
611     $or = $query->andConditionGroup()
612       ->condition("$figures.0.color", 'red')
613       ->condition("$figures.1.color", 'blue');
614     $this->queryResults = $query
615       ->condition($or)
616       ->sort('id')
617       ->execute();
618     $this->assertResult(3, 7, 11, 15);
619
620     // Test the delta range condition.
621     $this->queryResults = $this->storage
622       ->getQuery()
623       ->condition("$figures.%delta.color", ['blue', 'red'], 'IN')
624       ->condition("$figures.%delta", [0, 1], 'IN')
625       ->sort('id')
626       ->execute();
627     // Figure delta 0 or 1 can be blue or red, this matches a lot of entities.
628     $this->assertResult(1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15);
629
630     // Test the delta range condition without conditions on the value.
631     $this->queryResults = $this->storage
632       ->getQuery()
633       ->condition("$figures.%delta", 1)
634       ->sort('id')
635       ->execute();
636     // Entity needs to have at least two figures.
637     $this->assertResult(3, 7, 11, 15);
638
639     // Numeric delta on single value base field should return results only if
640     // the first item is being targeted.
641     $this->queryResults = $this->storage
642       ->getQuery()
643       ->condition("id.0.value", [1, 3, 5], 'IN')
644       ->sort('id')
645       ->execute();
646     $this->assertResult(1, 3, 5);
647     $this->queryResults = $this->storage
648       ->getQuery()
649       ->condition("id.1.value", [1, 3, 5], 'IN')
650       ->sort('id')
651       ->execute();
652     $this->assertResult();
653
654     // Delta range condition on single value base field should return results
655     // only if just the field value is targeted.
656     $this->queryResults = $this->storage
657       ->getQuery()
658       ->condition("id.%delta.value", [1, 3, 5], 'IN')
659       ->sort('id')
660       ->execute();
661     $this->assertResult(1, 3, 5);
662     $this->queryResults = $this->storage
663       ->getQuery()
664       ->condition("id.%delta.value", [1, 3, 5], 'IN')
665       ->condition("id.%delta", 0, '=')
666       ->sort('id')
667       ->execute();
668     $this->assertResult(1, 3, 5);
669     $this->queryResults = $this->storage
670       ->getQuery()
671       ->condition("id.%delta.value", [1, 3, 5], 'IN')
672       ->condition("id.%delta", 1, '=')
673       ->sort('id')
674       ->execute();
675     $this->assertResult();
676
677   }
678
679   protected function assertResult() {
680     $assert = [];
681     $expected = func_get_args();
682     if ($expected && is_array($expected[0])) {
683       $expected = $expected[0];
684     }
685     foreach ($expected as $binary) {
686       $assert[$binary] = strval($binary);
687     }
688     $this->assertIdentical($this->queryResults, $assert);
689   }
690
691   protected function assertRevisionResult($keys, $expected) {
692     $assert = [];
693     foreach ($expected as $key => $binary) {
694       $assert[$keys[$key]] = strval($binary);
695     }
696     $this->assertIdentical($this->queryResults, $assert);
697     return $assert;
698   }
699
700   protected function assertBundleOrder($order) {
701     // This loop is for bundle1 entities.
702     for ($i = 1; $i <= 15; $i += 2) {
703       $ok = TRUE;
704       $index1 = array_search($i, $this->queryResults);
705       $this->assertNotIdentical($index1, FALSE, "$i found at $index1.");
706       // This loop is for bundle2 entities.
707       for ($j = 2; $j <= 15; $j += 2) {
708         if ($ok) {
709           if ($order == 'asc') {
710             $ok = $index1 > array_search($j, $this->queryResults);
711           }
712           else {
713             $ok = $index1 < array_search($j, $this->queryResults);
714           }
715         }
716       }
717       $this->assertTrue($ok, "$i is after all entities in bundle2");
718     }
719   }
720
721   /**
722    * Test adding a tag and metadata to the Entity query object.
723    *
724    * The tags and metadata should propagate to the SQL query object.
725    */
726   public function testMetaData() {
727     $query = $this->storage->getQuery();
728     $query
729       ->addTag('efq_metadata_test')
730       ->addMetaData('foo', 'bar')
731       ->execute();
732
733     global $efq_test_metadata;
734     $this->assertEqual($efq_test_metadata, 'bar', 'Tag and metadata propagated to the SQL query object.');
735   }
736
737   /**
738    * Test case sensitive and in-sensitive query conditions.
739    */
740   public function testCaseSensitivity() {
741     $bundle = $this->randomMachineName();
742
743     $field_storage = FieldStorageConfig::create([
744       'field_name' => 'field_ci',
745       'entity_type' => 'entity_test_mulrev',
746       'type' => 'string',
747       'cardinality' => 1,
748       'translatable' => FALSE,
749       'settings' => [
750         'case_sensitive' => FALSE,
751       ],
752     ]);
753     $field_storage->save();
754
755     FieldConfig::create([
756       'field_storage' => $field_storage,
757       'bundle' => $bundle,
758     ])->save();
759
760     $field_storage = FieldStorageConfig::create([
761       'field_name' => 'field_cs',
762       'entity_type' => 'entity_test_mulrev',
763       'type' => 'string',
764       'cardinality' => 1,
765       'translatable' => FALSE,
766       'settings' => [
767         'case_sensitive' => TRUE,
768       ],
769     ]);
770     $field_storage->save();
771
772     FieldConfig::create([
773       'field_storage' => $field_storage,
774       'bundle' => $bundle,
775     ])->save();
776
777     $fixtures = [];
778
779     for ($i = 0; $i < 2; $i++) {
780       // If the last 4 of the string are all numbers, then there is no
781       // difference between upper and lowercase and the case sensitive CONTAINS
782       // test will fail. Ensure that can not happen by appending a non-numeric
783       // character. See https://www.drupal.org/node/2397297.
784       $string = $this->randomMachineName(7) . 'a';
785       $fixtures[] = [
786         'original' => $string,
787         'uppercase' => mb_strtoupper($string),
788         'lowercase' => mb_strtolower($string),
789       ];
790     }
791
792     EntityTestMulRev::create([
793       'type' => $bundle,
794       'name' => $this->randomMachineName(),
795       'langcode' => 'en',
796       'field_ci' => $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'],
797       'field_cs' => $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'],
798     ])->save();
799
800     // Check the case insensitive field, = operator.
801     $result = $this->storage
802       ->getQuery()
803       ->condition('field_ci', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase'])
804       ->execute();
805     $this->assertIdentical(count($result), 1, 'Case insensitive, lowercase');
806
807     $result = $this->storage
808       ->getQuery()
809       ->condition('field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase'])
810       ->execute();
811     $this->assertIdentical(count($result), 1, 'Case insensitive, uppercase');
812
813     $result = $this->storage
814       ->getQuery()
815       ->condition('field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'])
816       ->execute();
817     $this->assertIdentical(count($result), 1, 'Case insensitive, mixed.');
818
819     // Check the case sensitive field, = operator.
820     $result = $this->storage
821       ->getQuery()
822       ->condition('field_cs', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase'])
823       ->execute();
824     $this->assertIdentical(count($result), 0, 'Case sensitive, lowercase.');
825
826     $result = $this->storage
827       ->getQuery()
828       ->condition('field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase'])
829       ->execute();
830     $this->assertIdentical(count($result), 0, 'Case sensitive, uppercase.');
831
832     $result = $this->storage
833       ->getQuery()
834       ->condition('field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'])
835       ->execute();
836     $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
837
838     // Check the case insensitive field, IN operator.
839     $result = $this->storage
840       ->getQuery()
841       ->condition('field_ci', [$fixtures[0]['lowercase'] . $fixtures[1]['lowercase']], 'IN')
842       ->execute();
843     $this->assertIdentical(count($result), 1, 'Case insensitive, lowercase');
844
845     $result = $this->storage
846       ->getQuery()
847       ->condition('field_ci', [$fixtures[0]['uppercase'] . $fixtures[1]['uppercase']], 'IN')->execute();
848     $this->assertIdentical(count($result), 1, 'Case insensitive, uppercase');
849
850     $result = $this->storage
851       ->getQuery()
852       ->condition('field_ci', [$fixtures[0]['uppercase'] . $fixtures[1]['lowercase']], 'IN')
853       ->execute();
854     $this->assertIdentical(count($result), 1, 'Case insensitive, mixed');
855
856     // Check the case sensitive field, IN operator.
857     $result = $this->storage
858       ->getQuery()
859       ->condition('field_cs', [$fixtures[0]['lowercase'] . $fixtures[1]['lowercase']], 'IN')
860       ->execute();
861     $this->assertIdentical(count($result), 0, 'Case sensitive, lowercase');
862
863     $result = $this->storage
864       ->getQuery()
865       ->condition('field_cs', [$fixtures[0]['uppercase'] . $fixtures[1]['uppercase']], 'IN')
866       ->execute();
867     $this->assertIdentical(count($result), 0, 'Case sensitive, uppercase');
868
869     $result = $this->storage
870       ->getQuery()
871       ->condition('field_cs', [$fixtures[0]['uppercase'] . $fixtures[1]['lowercase']], 'IN')
872       ->execute();
873     $this->assertIdentical(count($result), 1, 'Case sensitive, mixed');
874
875     // Check the case insensitive field, STARTS_WITH operator.
876     $result = $this->storage
877       ->getQuery()
878       ->condition('field_ci', $fixtures[0]['lowercase'], 'STARTS_WITH')
879       ->execute();
880     $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
881
882     $result = $this->storage
883       ->getQuery()
884       ->condition('field_ci', $fixtures[0]['uppercase'], 'STARTS_WITH')
885       ->execute();
886     $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
887
888     // Check the case sensitive field, STARTS_WITH operator.
889     $result = $this->storage
890       ->getQuery()
891       ->condition('field_cs', $fixtures[0]['lowercase'], 'STARTS_WITH')
892       ->execute();
893     $this->assertIdentical(count($result), 0, 'Case sensitive, lowercase.');
894
895     $result = $this->storage
896       ->getQuery()
897       ->condition('field_cs', $fixtures[0]['uppercase'], 'STARTS_WITH')
898       ->execute();
899     $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
900
901     // Check the case insensitive field, ENDS_WITH operator.
902     $result = $this->storage
903       ->getQuery()
904       ->condition('field_ci', $fixtures[1]['lowercase'], 'ENDS_WITH')
905       ->execute();
906     $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
907
908     $result = $this->storage
909       ->getQuery()
910       ->condition('field_ci', $fixtures[1]['uppercase'], 'ENDS_WITH')
911       ->execute();
912     $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
913
914     // Check the case sensitive field, ENDS_WITH operator.
915     $result = $this->storage
916       ->getQuery()
917       ->condition('field_cs', $fixtures[1]['lowercase'], 'ENDS_WITH')
918       ->execute();
919     $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
920
921     $result = $this->storage
922       ->getQuery()
923       ->condition('field_cs', $fixtures[1]['uppercase'], 'ENDS_WITH')
924       ->execute();
925     $this->assertIdentical(count($result), 0, 'Case sensitive, exact match.');
926
927     // Check the case insensitive field, CONTAINS operator, use the inner 8
928     // characters of the uppercase and lowercase strings.
929     $result = $this->storage
930       ->getQuery()
931       ->condition('field_ci', mb_substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS')
932       ->execute();
933     $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
934
935     $result = $this->storage
936       ->getQuery()
937       ->condition('field_ci', mb_strtolower(mb_substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS')
938       ->execute();
939     $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
940
941     // Check the case sensitive field, CONTAINS operator.
942     $result = $this->storage
943       ->getQuery()
944       ->condition('field_cs', mb_substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS')
945       ->execute();
946     $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
947
948     $result = $this->storage
949       ->getQuery()
950       ->condition('field_cs', mb_strtolower(mb_substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS')
951       ->execute();
952     $this->assertIdentical(count($result), 0, 'Case sensitive, exact match.');
953
954   }
955
956   /**
957    * Test base fields with multiple columns.
958    */
959   public function testBaseFieldMultipleColumns() {
960     $this->enableModules(['taxonomy']);
961     $this->installEntitySchema('taxonomy_term');
962
963     Vocabulary::create(['vid' => 'tags']);
964
965     $term1 = Term::create([
966       'name' => $this->randomMachineName(),
967       'vid' => 'tags',
968       'description' => [
969         'value' => $this->randomString(),
970         'format' => 'format1',
971       ],
972     ]);
973     $term1->save();
974
975     $term2 = Term::create([
976       'name' => $this->randomMachineName(),
977       'vid' => 'tags',
978       'description' => [
979         'value' => $this->randomString(),
980         'format' => 'format2',
981       ],
982     ]);
983     $term2->save();
984
985     $ids = $this->container->get('entity_type.manager')
986       ->getStorage('taxonomy_term')
987       ->getQuery()
988       ->condition('description.format', 'format1')
989       ->execute();
990
991     $this->assertEqual(count($ids), 1);
992     $this->assertEqual($term1->id(), reset($ids));
993   }
994
995   /**
996    * Test pending revisions.
997    */
998   public function testPendingRevisions() {
999     // Ensure entity 14 is returned.
1000     $result = $this->storage
1001       ->getQuery()
1002       ->condition('id', [14], 'IN')
1003       ->execute();
1004     $this->assertEqual(count($result), 1);
1005
1006     // Set a revision on entity 14 that isn't the current default.
1007     $entity = EntityTestMulRev::load(14);
1008     $current_values = $entity->{$this->figures}->getValue();
1009
1010     $entity->setNewRevision(TRUE);
1011     $entity->isDefaultRevision(FALSE);
1012     $entity->{$this->figures}->setValue([
1013       'color' => 'red',
1014       'shape' => 'square',
1015     ]);
1016     $entity->save();
1017
1018     // Entity query should still return entity 14.
1019     $result = $this->storage
1020       ->getQuery()
1021       ->condition('id', [14], 'IN')
1022       ->execute();
1023     $this->assertEqual(count($result), 1);
1024
1025     // Verify that field conditions on the default and pending revision are
1026     // work as expected.
1027     $result = $this->storage
1028       ->getQuery()
1029       ->condition('id', [14], 'IN')
1030       ->condition("$this->figures.color", $current_values[0]['color'])
1031       ->execute();
1032     $this->assertEqual($result, [14 => '14']);
1033     $result = $this->storage
1034       ->getQuery()
1035       ->condition('id', [14], 'IN')
1036       ->condition("$this->figures.color", 'red')
1037       ->allRevisions()
1038       ->execute();
1039     $this->assertEqual($result, [16 => '14']);
1040
1041     // Add another pending revision on the same entity and repeat the checks.
1042     $entity->setNewRevision(TRUE);
1043     $entity->isDefaultRevision(FALSE);
1044     $entity->{$this->figures}->setValue([
1045       'color' => 'red',
1046       'shape' => 'square',
1047     ]);
1048     $entity->save();
1049
1050     // A non-revisioned entity query should still return entity 14.
1051     $result = $this->storage
1052       ->getQuery()
1053       ->condition('id', [14], 'IN')
1054       ->execute();
1055     $this->assertCount(1, $result);
1056     $this->assertSame([14 => '14'], $result);
1057
1058     // Now check an entity query on the latest revision.
1059     $result = $this->storage
1060       ->getQuery()
1061       ->condition('id', [14], 'IN')
1062       ->latestRevision()
1063       ->execute();
1064     $this->assertCount(1, $result);
1065     $this->assertSame([17 => '14'], $result);
1066
1067     // Verify that field conditions on the default and pending revision still
1068     // work as expected.
1069     $result = $this->storage
1070       ->getQuery()
1071       ->condition('id', [14], 'IN')
1072       ->condition("$this->figures.color", $current_values[0]['color'])
1073       ->execute();
1074     $this->assertSame([14 => '14'], $result);
1075
1076     // Now there are two revisions with same value for the figure color.
1077     $result = $this->storage
1078       ->getQuery()
1079       ->condition('id', [14], 'IN')
1080       ->condition("$this->figures.color", 'red')
1081       ->allRevisions()
1082       ->execute();
1083     $this->assertSame([16 => '14', 17 => '14'], $result);
1084
1085     // Check that querying for the latest revision returns the correct one.
1086     $result = $this->storage
1087       ->getQuery()
1088       ->condition('id', [14], 'IN')
1089       ->condition("$this->figures.color", 'red')
1090       ->latestRevision()
1091       ->execute();
1092     $this->assertSame([17 => '14'], $result);
1093   }
1094
1095   /**
1096    * Test against SQL inject of condition field. This covers a
1097    * database driver's EntityQuery\Condition class.
1098    */
1099   public function testInjectionInCondition() {
1100     try {
1101       $this->queryResults = $this->storage
1102         ->getQuery()
1103         ->condition('1 ; -- ', [0, 1], 'IN')
1104         ->sort('id')
1105         ->execute();
1106       $this->fail('SQL Injection attempt in Entity Query condition in operator should result in an exception.');
1107     }
1108     catch (\Exception $e) {
1109       $this->pass('SQL Injection attempt in Entity Query condition in operator should result in an exception.');
1110     }
1111   }
1112
1113   /**
1114    * Tests that EntityQuery works when querying the same entity from two fields.
1115    */
1116   public function testWithTwoEntityReferenceFieldsToSameEntityType() {
1117     // Create two entity reference fields referring 'entity_test' entities.
1118     $this->createEntityReferenceField('entity_test', 'entity_test', 'ref1', $this->randomMachineName(), 'entity_test');
1119     $this->createEntityReferenceField('entity_test', 'entity_test', 'ref2', $this->randomMachineName(), 'entity_test');
1120
1121     $storage = $this->container->get('entity_type.manager')
1122       ->getStorage('entity_test');
1123
1124     // Create two entities to be referred.
1125     $ref1 = EntityTest::create(['type' => 'entity_test']);
1126     $ref1->save();
1127     $ref2 = EntityTest::create(['type' => 'entity_test']);
1128     $ref2->save();
1129
1130     // Create a main entity referring the previous created entities.
1131     $entity = EntityTest::create([
1132       'type' => 'entity_test',
1133       'ref1' => $ref1->id(),
1134       'ref2' => $ref2->id(),
1135     ]);
1136     $entity->save();
1137
1138     // Check that works when referring with "{$field_name}".
1139     $result = $storage->getQuery()
1140       ->condition('type', 'entity_test')
1141       ->condition('ref1', $ref1->id())
1142       ->condition('ref2', $ref2->id())
1143       ->execute();
1144     $this->assertCount(1, $result);
1145     $this->assertEquals($entity->id(), reset($result));
1146
1147     // Check that works when referring with "{$field_name}.target_id".
1148     $result = $storage->getQuery()
1149       ->condition('type', 'entity_test')
1150       ->condition('ref1.target_id', $ref1->id())
1151       ->condition('ref2.target_id', $ref2->id())
1152       ->execute();
1153     $this->assertCount(1, $result);
1154     $this->assertEquals($entity->id(), reset($result));
1155
1156     // Check that works when referring with "{$field_name}.entity.id".
1157     $result = $storage->getQuery()
1158       ->condition('type', 'entity_test')
1159       ->condition('ref1.entity.id', $ref1->id())
1160       ->condition('ref2.entity.id', $ref2->id())
1161       ->execute();
1162     $this->assertCount(1, $result);
1163     $this->assertEquals($entity->id(), reset($result));
1164   }
1165
1166   /**
1167    * Tests entity queries with condition on the revision metadata keys.
1168    */
1169   public function testConditionOnRevisionMetadataKeys() {
1170     $this->installModule('entity_test_revlog');
1171     $this->installEntitySchema('entity_test_revlog');
1172
1173     /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
1174     $entity_type_manager = $this->container->get('entity_type.manager');
1175     /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
1176     $entity_type = $entity_type_manager->getDefinition('entity_test_revlog');
1177     /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
1178     $storage = $entity_type_manager->getStorage('entity_test_revlog');
1179
1180     $revision_created_timestamp = time();
1181     $revision_created_field_name = $entity_type->getRevisionMetadataKey('revision_created');
1182     $entity = $storage->create([
1183       'type' => 'entity_test',
1184       $revision_created_field_name => $revision_created_timestamp,
1185     ]);
1186     $entity->save();
1187
1188     // Query only the default revision.
1189     $result = $storage->getQuery()
1190       ->condition($revision_created_field_name, $revision_created_timestamp)
1191       ->execute();
1192     $this->assertCount(1, $result);
1193     $this->assertEquals($entity->id(), reset($result));
1194
1195     // Query all revisions.
1196     $result = $storage->getQuery()
1197       ->condition($revision_created_field_name, $revision_created_timestamp)
1198       ->allRevisions()
1199       ->execute();
1200     $this->assertCount(1, $result);
1201     $this->assertEquals($entity->id(), reset($result));
1202   }
1203
1204 }