3 namespace Drupal\Tests\views\Unit\Plugin\query;
5 use Drupal\Core\Entity\EntityInterface;
6 use Drupal\Core\Entity\EntityStorageInterface;
7 use Drupal\Core\Entity\EntityType;
8 use Drupal\Core\Entity\EntityTypeManagerInterface;
9 use Drupal\Core\Messenger\MessengerInterface;
10 use Drupal\Tests\UnitTestCase;
11 use Drupal\views\Plugin\views\query\DateSqlInterface;
12 use Drupal\views\Plugin\views\query\Sql;
13 use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
14 use Drupal\views\ResultRow;
15 use Drupal\views\ViewEntityInterface;
16 use Drupal\views\ViewExecutable;
17 use Drupal\views\ViewsData;
18 use Symfony\Component\DependencyInjection\ContainerBuilder;
21 * @coversDefaultClass \Drupal\views\Plugin\views\query\Sql
25 class SqlTest extends UnitTestCase {
28 * @covers ::getCacheTags
29 * @covers ::getAllEntities
31 public function testGetCacheTags() {
32 $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
33 $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
34 $date_sql = $this->prophesize(DateSqlInterface::class);
35 $messenger = $this->prophesize(MessengerInterface::class);
37 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
41 $view->result = $result;
43 // Add a row with an entity.
44 $row = new ResultRow();
45 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
46 $prophecy->getCacheTags()->willReturn(['entity_test:123']);
47 $entity = $prophecy->reveal();
48 $row->_entity = $entity;
51 $view->result = $result;
53 // Add a row with an entity and a relationship entity.
54 $row = new ResultRow();
55 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
56 $prophecy->getCacheTags()->willReturn(['entity_test:124']);
57 $entity = $prophecy->reveal();
58 $row->_entity = $entity;
60 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
61 $prophecy->getCacheTags()->willReturn(['entity_test:125']);
62 $entity = $prophecy->reveal();
63 $row->_relationship_entities[] = $entity;
64 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
65 $prophecy->getCacheTags()->willReturn(['entity_test:126']);
66 $entity = $prophecy->reveal();
67 $row->_relationship_entities[] = $entity;
70 $view->result = $result;
72 $this->assertEquals(['entity_test:123', 'entity_test:124', 'entity_test:125', 'entity_test:126'], $query->getCacheTags());
76 * @covers ::getCacheTags
77 * @covers ::getAllEntities
79 public function testGetCacheMaxAge() {
80 $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
81 $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
82 $date_sql = $this->prophesize(DateSqlInterface::class);
83 $messenger = $this->prophesize(MessengerInterface::class);
85 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
90 // Add a row with an entity.
91 $row = new ResultRow();
92 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
93 $prophecy->getCacheMaxAge()->willReturn(10);
94 $entity = $prophecy->reveal();
96 $row->_entity = $entity;
97 $view->result[] = $row;
99 // Add a row with an entity and a relationship entity.
100 $row = new ResultRow();
101 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
102 $prophecy->getCacheMaxAge()->willReturn(20);
103 $entity = $prophecy->reveal();
104 $row->_entity = $entity;
106 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
107 $prophecy->getCacheMaxAge()->willReturn(30);
108 $entity = $prophecy->reveal();
109 $row->_relationship_entities[] = $entity;
110 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
111 $prophecy->getCacheMaxAge()->willReturn(40);
112 $entity = $prophecy->reveal();
113 $row->_relationship_entities[] = $entity;
115 $this->assertEquals(10, $query->getCacheMaxAge());
119 * Sets up the views data in the container.
121 * @param \Drupal\views\ViewsData $views_data
124 protected function setupViewsData(ViewsData $views_data) {
125 $container = \Drupal::hasContainer() ? \Drupal::getContainer() : new ContainerBuilder();
126 $container->set('views.views_data', $views_data);
127 \Drupal::setContainer($container);
131 * Sets up the entity type manager in the container.
133 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
134 * The entity type manager.
136 protected function setupEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
137 $container = \Drupal::hasContainer() ? \Drupal::getContainer() : new ContainerBuilder();
138 $container->set('entity_type.manager', $entity_type_manager);
139 $container->set('entity.manager', $entity_type_manager);
140 \Drupal::setContainer($container);
144 * Sets up some test entity types and corresponding views data.
146 * @param \Drupal\Core\Entity\EntityInterface[][] $entities_by_type
147 * Test entities keyed by entity type and entity ID.
148 * @param \Drupal\Core\Entity\EntityInterface[][] $entity_revisions_by_type
149 * Test entities keyed by entity type and revision ID.
151 * @return \Prophecy\Prophecy\ObjectProphecy
153 protected function setupEntityTypes($entities_by_type = [], $entity_revisions_by_type = []) {
154 $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
155 $entity_type0 = new EntityType([
158 'base_table' => 'entity_first',
159 'revision_table' => 'entity_first__revision',
165 $entity_type1 = new EntityType([
168 'base_table' => 'entity_second',
169 'revision_table' => 'entity_second__revision',
176 $entity_type_manager->getDefinitions()->willReturn([
177 'first' => $entity_type0,
178 'second' => $entity_type1,
179 'base_table' => 'entity_second',
182 $entity_type_manager->getDefinition('first')->willReturn($entity_type0);
183 $entity_type_manager->getDefinition('second')->willReturn($entity_type1);
185 // Setup the views data corresponding to the entity types.
186 $views_data = $this->prophesize(ViewsData::class);
187 $views_data->get('entity_first')->willReturn([
189 'entity type' => 'first',
190 'entity revision' => FALSE,
193 $views_data->get('entity_first__revision')->willReturn([
195 'entity type' => 'first',
196 'entity revision' => TRUE,
199 $views_data->get('entity_second')->willReturn([
201 'entity type' => 'second',
202 'entity revision' => FALSE,
205 $views_data->get('entity_second__revision')->willReturn([
207 'entity type' => 'second',
208 'entity revision' => TRUE,
211 $views_data->get('entity_first_field_data')->willReturn([
213 'entity type' => 'first',
214 'entity revision' => FALSE,
217 $this->setupViewsData($views_data->reveal());
219 // Setup the loading of entities and entity revisions.
221 'first' => $this->prophesize(EntityStorageInterface::class),
222 'second' => $this->prophesize(EntityStorageInterface::class),
225 foreach ($entities_by_type as $entity_type_id => $entities) {
226 foreach ($entities as $entity_id => $entity) {
227 $entity_storages[$entity_type_id]->load($entity_id)->willReturn($entity);
229 $entity_storages[$entity_type_id]->loadMultiple(array_keys($entities))->willReturn($entities);
232 foreach ($entity_revisions_by_type as $entity_type_id => $entity_revisions) {
233 foreach ($entity_revisions as $revision_id => $revision) {
234 $entity_storages[$entity_type_id]->loadRevision($revision_id)->willReturn($revision);
238 $entity_type_manager->getStorage('first')->willReturn($entity_storages['first']);
239 $entity_type_manager->getStorage('second')->willReturn($entity_storages['second']);
241 $this->setupEntityTypeManager($entity_type_manager->reveal());
243 return $entity_type_manager;
247 * @covers ::loadEntities
248 * @covers ::assignEntitiesToResult
250 public function testLoadEntitiesWithEmptyResult() {
251 $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
252 $view_entity = $this->prophesize(ViewEntityInterface::class);
253 $view_entity->get('base_table')->willReturn('entity_first');
254 $view_entity->get('base_field')->willReturn('id');
255 $view->storage = $view_entity->reveal();
257 $entity_type_manager = $this->setupEntityTypes();
258 $date_sql = $this->prophesize(DateSqlInterface::class);
259 $messenger = $this->prophesize(MessengerInterface::class);
261 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
262 $query->view = $view;
265 $query->addField('entity_first', 'id', 'id');
266 $query->loadEntities($result);
267 $this->assertEmpty($result);
271 * @covers ::loadEntities
272 * @covers ::assignEntitiesToResult
274 public function testLoadEntitiesWithNoRelationshipAndNoRevision() {
275 $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
276 $view_entity = $this->prophesize(ViewEntityInterface::class);
277 $view_entity->get('base_table')->willReturn('entity_first');
278 $view_entity->get('base_field')->willReturn('id');
279 $view->storage = $view_entity->reveal();
283 1 => $this->prophesize(EntityInterface::class)->reveal(),
284 2 => $this->prophesize(EntityInterface::class)->reveal(),
287 $entity_type_manager = $this->setupEntityTypes($entities);
288 $date_sql = $this->prophesize(DateSqlInterface::class);
289 $messenger = $this->prophesize(MessengerInterface::class);
291 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
292 $query->view = $view;
295 $result[] = new ResultRow([
298 // Note: Let the same entity be returned multiple times, for example to
299 // support the translation usecase.
300 $result[] = new ResultRow([
303 $result[] = new ResultRow([
307 $query->addField('entity_first', 'id', 'id');
308 $query->loadEntities($result);
310 $this->assertSame($entities['first'][1], $result[0]->_entity);
311 $this->assertSame($entities['first'][2], $result[1]->_entity);
312 $this->assertSame($entities['first'][2], $result[2]->_entity);
316 * Create a view with a relationship.
318 protected function setupViewWithRelationships(ViewExecutable $view, $base = 'entity_second') {
319 // We don't use prophecy, because prophecy enforces methods.
320 $relationship = $this->getMockBuilder(RelationshipPluginBase::class)->disableOriginalConstructor()->getMock();
321 $relationship->definition['base'] = $base;
322 $relationship->tableAlias = $base;
323 $relationship->alias = $base;
325 $view->relationship[$base] = $relationship;
329 * @covers ::loadEntities
330 * @covers ::assignEntitiesToResult
332 public function testLoadEntitiesWithRelationship() {
333 // We don't use prophecy, because prophecy enforces methods.
334 $view = $this->getMockBuilder(ViewExecutable::class)->disableOriginalConstructor()->getMock();
335 $this->setupViewWithRelationships($view);
337 $view_entity = $this->prophesize(ViewEntityInterface::class);
338 $view_entity->get('base_table')->willReturn('entity_first');
339 $view_entity->get('base_field')->willReturn('id');
340 $view->storage = $view_entity->reveal();
344 1 => $this->prophesize(EntityInterface::class)->reveal(),
345 2 => $this->prophesize(EntityInterface::class)->reveal(),
348 11 => $this->prophesize(EntityInterface::class)->reveal(),
349 12 => $this->prophesize(EntityInterface::class)->reveal(),
352 $entity_type_manager = $this->setupEntityTypes($entities);
353 $date_sql = $this->prophesize(DateSqlInterface::class);
354 $messenger = $this->prophesize(MessengerInterface::class);
356 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
357 $query->view = $view;
360 $result[] = new ResultRow([
362 'entity_second__id' => 11,
364 // Provide an explicit NULL value, to test the case of a non required
366 $result[] = new ResultRow([
368 'entity_second__id' => NULL,
370 $result[] = new ResultRow([
372 'entity_second__id' => 12,
375 $query->addField('entity_first', 'id', 'id');
376 $query->addField('entity_second', 'id', 'entity_second__id');
377 $query->loadEntities($result);
379 $this->assertSame($entities['first'][1], $result[0]->_entity);
380 $this->assertSame($entities['first'][2], $result[1]->_entity);
381 $this->assertSame($entities['first'][2], $result[2]->_entity);
383 $this->assertSame($entities['second'][11], $result[0]->_relationship_entities['entity_second']);
384 $this->assertEquals([], $result[1]->_relationship_entities);
385 $this->assertSame($entities['second'][12], $result[2]->_relationship_entities['entity_second']);
389 * @covers ::loadEntities
390 * @covers ::assignEntitiesToResult
392 public function testLoadEntitiesWithNonEntityRelationship() {
393 // We don't use prophecy, because prophecy enforces methods.
394 $view = $this->getMockBuilder(ViewExecutable::class)->disableOriginalConstructor()->getMock();
395 $this->setupViewWithRelationships($view, 'entity_first_field_data');
397 $view_entity = $this->prophesize(ViewEntityInterface::class);
398 $view_entity->get('base_table')->willReturn('entity_first');
399 $view_entity->get('base_field')->willReturn('id');
400 $view->storage = $view_entity->reveal();
404 1 => $this->prophesize(EntityInterface::class)->reveal(),
405 2 => $this->prophesize(EntityInterface::class)->reveal(),
408 $entity_type_manager = $this->setupEntityTypes($entities);
409 $date_sql = $this->prophesize(DateSqlInterface::class);
410 $messenger = $this->prophesize(MessengerInterface::class);
412 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
413 $query->view = $view;
416 $result[] = new ResultRow([
419 $result[] = new ResultRow([
423 $query->addField('entity_first', 'id', 'id');
424 $query->loadEntities($result);
425 $entity_information = $query->getEntityTableInfo();
427 $this->assertSame($entities['first'][1], $result[0]->_entity);
428 $this->assertSame($entities['first'][2], $result[1]->_entity);
430 $this->assertEquals([], $result[0]->_relationship_entities);
431 $this->assertEquals([], $result[1]->_relationship_entities);
433 // This is an entity table and should be in $entity_information.
434 $this->assertContains('first', array_keys($entity_information));
435 // This is not an entity table and should not be in $entity_information.
436 $this->assertNotContains('entity_first_field_data__entity_first_field_data', array_keys($entity_information));
440 * @covers ::loadEntities
441 * @covers ::assignEntitiesToResult
443 public function testLoadEntitiesWithRevision() {
444 // We don't use prophecy, because prophecy enforces methods.
445 $view = $this->getMockBuilder(ViewExecutable::class)
446 ->disableOriginalConstructor()
449 $view_entity = $this->prophesize(ViewEntityInterface::class);
450 $view_entity->get('base_table')->willReturn('entity_first__revision');
451 $view_entity->get('base_field')->willReturn('vid');
452 $view->storage = $view_entity->reveal();
454 $entity_revisions = [
456 1 => $this->prophesize(EntityInterface::class)->reveal(),
457 3 => $this->prophesize(EntityInterface::class)->reveal(),
460 $entity_type_manager = $this->setupEntityTypes([], $entity_revisions);
461 $date_sql = $this->prophesize(DateSqlInterface::class);
462 $messenger = $this->prophesize(MessengerInterface::class);
464 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
465 $query->view = $view;
468 $result[] = new ResultRow([
471 $result[] = new ResultRow([
474 $result[] = new ResultRow([
478 $query->addField('entity_first__revision', 'vid', 'vid');
479 $query->loadEntities($result);
481 $this->assertSame($entity_revisions['first'][1], $result[0]->_entity);
482 $this->assertSame($entity_revisions['first'][1], $result[1]->_entity);
483 $this->assertSame($entity_revisions['first'][3], $result[2]->_entity);
487 * @covers ::loadEntities
488 * @covers ::assignEntitiesToResult
490 public function testLoadEntitiesWithRevisionOfSameEntityType() {
491 // We don't use prophecy, because prophecy enforces methods.
492 $view = $this->getMockBuilder(ViewExecutable::class)
493 ->disableOriginalConstructor()
495 $this->setupViewWithRelationships($view, 'entity_first__revision');
497 $view_entity = $this->prophesize(ViewEntityInterface::class);
498 $view_entity->get('base_table')->willReturn('entity_first');
499 $view_entity->get('base_field')->willReturn('id');
500 $view->storage = $view_entity->reveal();
504 1 => $this->prophesize(EntityInterface::class)->reveal(),
505 2 => $this->prophesize(EntityInterface::class)->reveal(),
508 $entity_revisions = [
510 1 => $this->prophesize(EntityInterface::class)->reveal(),
511 2 => $this->prophesize(EntityInterface::class)->reveal(),
512 3 => $this->prophesize(EntityInterface::class)->reveal(),
515 $entity_type_manager = $this->setupEntityTypes($entity, $entity_revisions);
516 $date_sql = $this->prophesize(DateSqlInterface::class);
517 $messenger = $this->prophesize(MessengerInterface::class);
519 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
520 $query->view = $view;
523 $result[] = new ResultRow([
525 'entity_first__revision__vid' => 1,
527 $result[] = new ResultRow([
529 'entity_first__revision__vid' => 2,
531 $result[] = new ResultRow([
533 'entity_first__revision__vid' => 3,
536 $query->addField('entity_first', 'id', 'id');
537 $query->addField('entity_first__revision', 'vid', 'entity_first__revision__vid');
538 $query->loadEntities($result);
540 $this->assertSame($entity['first'][1], $result[0]->_entity);
541 $this->assertSame($entity['first'][2], $result[1]->_entity);
542 $this->assertSame($entity['first'][2], $result[2]->_entity);
543 $this->assertSame($entity_revisions['first'][1], $result[0]->_relationship_entities['entity_first__revision']);
544 $this->assertSame($entity_revisions['first'][2], $result[1]->_relationship_entities['entity_first__revision']);
545 $this->assertSame($entity_revisions['first'][3], $result[2]->_relationship_entities['entity_first__revision']);
549 * @covers ::loadEntities
550 * @covers ::assignEntitiesToResult
552 public function testLoadEntitiesWithRelationshipAndRevision() {
553 // We don't use prophecy, because prophecy enforces methods.
554 $view = $this->getMockBuilder(ViewExecutable::class)->disableOriginalConstructor()->getMock();
555 $this->setupViewWithRelationships($view);
557 $view_entity = $this->prophesize(ViewEntityInterface::class);
558 $view_entity->get('base_table')->willReturn('entity_first__revision');
559 $view_entity->get('base_field')->willReturn('vid');
560 $view->storage = $view_entity->reveal();
564 11 => $this->prophesize(EntityInterface::class)->reveal(),
565 12 => $this->prophesize(EntityInterface::class)->reveal(),
568 $entity_revisions = [
570 1 => $this->prophesize(EntityInterface::class)->reveal(),
571 3 => $this->prophesize(EntityInterface::class)->reveal(),
574 $entity_type_manager = $this->setupEntityTypes($entities, $entity_revisions);
575 $date_sql = $this->prophesize(DateSqlInterface::class);
576 $messenger = $this->prophesize(MessengerInterface::class);
578 $query = new Sql([], 'sql', [], $entity_type_manager->reveal(), $date_sql->reveal(), $messenger->reveal());
579 $query->view = $view;
582 $result[] = new ResultRow([
584 'entity_second__id' => 11,
586 // Provide an explicit NULL value, to test the case of a non required
588 $result[] = new ResultRow([
590 'entity_second__id' => NULL,
592 $result[] = new ResultRow([
594 'entity_second__id' => 12,
597 $query->addField('entity_first__revision', 'vid', 'vid');
598 $query->addField('entity_second', 'id', 'entity_second__id');
599 $query->loadEntities($result);
601 $this->assertSame($entity_revisions['first'][1], $result[0]->_entity);
602 $this->assertSame($entity_revisions['first'][1], $result[1]->_entity);
603 $this->assertSame($entity_revisions['first'][3], $result[2]->_entity);
605 $this->assertSame($entities['second'][11], $result[0]->_relationship_entities['entity_second']);
606 $this->assertEquals([], $result[1]->_relationship_entities);
607 $this->assertSame($entities['second'][12], $result[2]->_relationship_entities['entity_second']);