diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMultiValueBasefield.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMultiValueBasefield.php new file mode 100644 index 0000000..c1e0939 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMultiValueBasefield.php @@ -0,0 +1,44 @@ +setCardinality(2); + + return $fields; + } + +} diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php index cc7b7be..61def5a 100644 --- a/core/modules/views/src/EntityViewsData.php +++ b/core/modules/views/src/EntityViewsData.php @@ -58,6 +58,13 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac protected $fieldStorageDefinitions; /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** * Constructs an EntityViewsData object. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type @@ -112,6 +119,7 @@ public function getViewsData() { $data = []; $base_table = $this->entityType->getBaseTable() ?: $this->entityType->id(); + $views_revision_base_table = NULL; $revisionable = $this->entityType->isRevisionable(); $base_field = $this->entityType->getKey('id'); @@ -235,6 +243,7 @@ public function getViewsData() { // Load all typed data definitions of all fields. This should cover each of // the entity base, revision, data tables. $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id()); + /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ if ($table_mapping = $this->storage->getTableMapping($field_definitions)) { // Fetch all fields that can appear in both the base table and the data // table. @@ -257,6 +266,36 @@ public function getViewsData() { $this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]); } } + + foreach ($field_definitions as $field_definition) { + if ($table_mapping->requiresDedicatedTableStorage($field_definition->getFieldStorageDefinition())) { + $table = $table_mapping->getDedicatedDataTableName($field_definition->getFieldStorageDefinition()); + + $data[$table]['table']['group'] = $this->entityType->getLabel(); + $data[$table]['table']['provider'] = $this->entityType->getProvider(); + $data[$table]['table']['join'][$views_base_table] = [ + 'left_field' => $base_field, + 'field' => 'entity_id', + 'extra' => [ + ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE], + ], + ]; + + if ($revisionable) { + $revision_table = $table_mapping->getDedicatedRevisionTableName($field_definition->getFieldStorageDefinition()); + + $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]); + $data[$revision_table]['table']['provider'] = $this->entityType->getProvider(); + $data[$revision_table]['table']['join'][$views_revision_base_table] = [ + 'left_field' => $revision_field, + 'field' => 'entity_id', + 'extra' => [ + ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE], + ], + ]; + } + } + } } // Add the entity type key to each table generated. diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml new file mode 100644 index 0000000..478071d --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml @@ -0,0 +1,45 @@ +langcode: en +status: true +dependencies: + module: + - entity_test +id: test_entity_multivalue_basefield +label: '' +module: views +description: '' +tag: '' +base_table: entity_test_multivalue_basefield +base_field: id +core: '8' +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + fields: + id: + id: id + table: entity_test_multivalue_basefield + field: nid + relationship: none + plugin_id: field + entity_type: entity_test_multivalue_basefield + entity_field: id + name: + id: name + table: entity_test_multivalue_basefield__name + field: name + plugin_id: field + entity_type: entity_test_multivalue_basefield + entity_field: name + defaults: + fields: false + filters: false + sorts: + id: + id: id + table: entity_test_multivalue_basefield + field: id + order: asc diff --git a/core/modules/views/tests/src/Kernel/Entity/EntityViewsWithMultivalueBasefieldTest.php b/core/modules/views/tests/src/Kernel/Entity/EntityViewsWithMultivalueBasefieldTest.php new file mode 100644 index 0000000..edf0fb0 --- /dev/null +++ b/core/modules/views/tests/src/Kernel/Entity/EntityViewsWithMultivalueBasefieldTest.php @@ -0,0 +1,54 @@ +installEntitySchema('entity_test_multivalue_basefield'); + } + + /** + * Tests entity views with multivalue base fields. + */ + public function testView() { + EntityTestMultiValueBasefield::create([ + 'name' => 'test', + ])->save(); + EntityTestMultiValueBasefield::create([ + 'name' => ['test2', 'test3'], + ])->save(); + + $view = Views::getView('test_entity_multivalue_basefield'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['name' => ['test']], + ['name' => ['test2', 'test3']], + ], ['name' => 'name']); + } + +} diff --git a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php index c3e93ed..d5675a6 100644 --- a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php +++ b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\ContentEntityType; use Drupal\Core\Entity\EntityType; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\Sql\DefaultTableMapping; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem; @@ -161,6 +162,12 @@ protected function setupBaseFields(array $base_fields) { ->setTranslatable(TRUE) ->setSetting('max_length', 255); + // A base field with cardinality > 1 + $base_fields['string'] = BaseFieldDefinition::create('string') + ->setLabel('Strong') + ->setTranslatable(TRUE) + ->setCardinality(2); + foreach ($base_fields as $name => $base_field) { $base_field->setName($name); } @@ -377,6 +384,10 @@ protected function setupFieldStorageDefinition() { $homepage_field_storage_definition->expects($this->any()) ->method('getSchema') ->willReturn(UriItem::schema($homepage_field_storage_definition)); + $string_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $string_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(StringItem::schema($string_field_storage_definition)); // Setup the user_id entity reference field. $this->entityManager->expects($this->any()) @@ -412,6 +423,7 @@ protected function setupFieldStorageDefinition() { 'name' => $name_field_storage_definition, 'description' => $description_field_storage_definition, 'homepage' => $homepage_field_storage_definition, + 'string' => $string_field_storage_definition, 'user_id' => $user_id_field_storage_definition, 'revision_id' => $revision_id_field_storage_definition, ]); @@ -436,10 +448,12 @@ public function testBaseTableFields() { ['entity_test', $base_field_definitions], ])); // Setup the table mapping. - $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping = $this->getMockBuilder(DefaultTableMapping::class) + ->disableOriginalConstructor() + ->getMock(); $table_mapping->expects($this->any()) ->method('getTableNames') - ->willReturn(['entity_test']); + ->willReturn(['entity_test', 'entity_test__string']); $table_mapping->expects($this->any()) ->method('getColumnNames') ->willReturnMap([ @@ -451,12 +465,26 @@ public function testBaseTableFields() { ['description', ['value' => 'description__value', 'format' => 'description__format']], ['homepage', ['value' => 'homepage']], ['user_id', ['target_id' => 'user_id']], + ['string', ['value' => 'value']], ]); $table_mapping->expects($this->any()) ->method('getFieldNames') ->willReturnMap([ - ['entity_test', ['id', 'uuid', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']] + ['entity_test', ['id', 'uuid', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']], + ['entity_test__string', ['string']], ]); + $table_mapping->expects($this->any()) + ->method('requiresDedicatedTableStorage') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + return $base_field->getName() === 'string'; + }); + $table_mapping->expects($this->any()) + ->method('getDedicatedDataTableName') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + if ($base_field->getName() === 'string') { + return 'entity_test__string'; + } + }); $this->entityStorage->expects($this->once()) ->method('getTableMapping') @@ -493,6 +521,18 @@ public function testBaseTableFields() { $relationship = $data['entity_test']['user_id']['relationship']; $this->assertEquals('users_field_data', $relationship['base']); $this->assertEquals('uid', $relationship['base field']); + + $this->assertStringField($data['entity_test__string']['string']); + $this->assertField($data['entity_test__string']['string'], 'string'); + $this->assertEquals([ + 'left_field' => 'id', + 'field' => 'entity_id', + 'extra' => [[ + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ]], + ], $data['entity_test__string']['table']['join']['entity_test']); } /** @@ -530,10 +570,12 @@ public function testDataTableFields() { $this->viewsData->setEntityType($entity_type); // Setup the table mapping. - $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping = $this->getMockBuilder(DefaultTableMapping::class) + ->disableOriginalConstructor() + ->getMock(); $table_mapping->expects($this->any()) ->method('getTableNames') - ->willReturn(['entity_test_mul', 'entity_test_mul_property_data']); + ->willReturn(['entity_test_mul', 'entity_test_mul_property_data', 'entity_test_mul__string']); $table_mapping->expects($this->any()) ->method('getColumnNames') ->willReturnMap([ @@ -545,12 +587,14 @@ public function testDataTableFields() { ['description', ['value' => 'description__value', 'format' => 'description__format']], ['homepage', ['value' => 'homepage']], ['user_id', ['target_id' => 'user_id']], + ['string', ['value' => 'value']], ]); $table_mapping->expects($this->any()) ->method('getFieldNames') ->willReturnMap([ ['entity_test_mul', ['uuid']], ['entity_test_mul_property_data', ['id', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']], + ['entity_test_mul__string', ['string']], ]); $table_mapping->expects($this->any()) @@ -561,6 +605,18 @@ public function testDataTableFields() { } return 'entity_test_mul_property_data'; }); + $table_mapping->expects($this->any()) + ->method('requiresDedicatedTableStorage') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + return $base_field->getName() === 'string'; + }); + $table_mapping->expects($this->any()) + ->method('getDedicatedDataTableName') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + if ($base_field->getName() === 'string') { + return 'entity_test_mul__string'; + } + }); $this->entityStorage->expects($this->once()) ->method('getTableMapping') @@ -620,6 +676,18 @@ public function testDataTableFields() { $relationship = $data['entity_test_mul_property_data']['user_id']['relationship']; $this->assertEquals('users_field_data', $relationship['base']); $this->assertEquals('uid', $relationship['base field']); + + $this->assertStringField($data['entity_test_mul__string']['string']); + $this->assertField($data['entity_test_mul__string']['string'], 'string'); + $this->assertEquals([ + 'left_field' => 'id', + 'field' => 'entity_id', + 'extra' => [[ + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ]], + ], $data['entity_test_mul__string']['table']['join']['entity_test_mul']); } /** @@ -651,10 +719,12 @@ public function testRevisionTableFields() { $this->viewsData->setEntityType($entity_type); // Setup the table mapping. - $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping = $this->getMockBuilder(DefaultTableMapping::class) + ->disableOriginalConstructor() + ->getMock(); $table_mapping->expects($this->any()) ->method('getTableNames') - ->willReturn(['entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision']); + ->willReturn(['entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision', 'entity_test_mulrev__string', 'entity_test_mulrev_revision__string']); $table_mapping->expects($this->any()) ->method('getColumnNames') ->willReturnMap([ @@ -667,6 +737,7 @@ public function testRevisionTableFields() { ['homepage', ['value' => 'homepage']], ['user_id', ['target_id' => 'user_id']], ['revision_id', ['value' => 'id']], + ['string', ['value' => 'value']], ]); $table_mapping->expects($this->any()) ->method('getFieldNames') @@ -675,7 +746,29 @@ public function testRevisionTableFields() { ['entity_test_mulrev_revision', ['id', 'revision_id', 'langcode']], ['entity_test_mulrev_property_data', ['id', 'revision_id', 'langcode', 'name', 'description', 'homepage', 'user_id']], ['entity_test_mulrev_property_revision', ['id', 'revision_id', 'langcode', 'name', 'description', 'homepage', 'user_id']], + ['entity_test_mulrev__string', ['string']], + ['entity_test_mulrev_revision__string', ['string']], ]); + $table_mapping->expects($this->any()) + ->method('requiresDedicatedTableStorage') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + return $base_field->getName() === 'string'; + }); + $table_mapping->expects($this->any()) + ->method('getDedicatedDataTableName') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + if ($base_field->getName() === 'string') { + return 'entity_test_mulrev__string'; + } + }); + + $table_mapping->expects($this->any()) + ->method('getDedicatedRevisionTableName') + ->willReturnCallback(function (BaseFieldDefinition $base_field) { + if ($base_field->getName() === 'string') { + return 'entity_test_mulrev_revision__string'; + } + }); $table_mapping->expects($this->any()) ->method('getFieldTableName') @@ -768,6 +861,30 @@ public function testRevisionTableFields() { $relationship = $data['entity_test_mulrev_property_revision']['user_id']['relationship']; $this->assertEquals('users_field_data', $relationship['base']); $this->assertEquals('uid', $relationship['base field']); + + $this->assertStringField($data['entity_test_mulrev__string']['string']); + $this->assertField($data['entity_test_mulrev__string']['string'], 'string'); + $this->assertEquals([ + 'left_field' => 'id', + 'field' => 'entity_id', + 'extra' => [[ + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ]], + ], $data['entity_test_mulrev__string']['table']['join']['entity_test_mulrev_property_data']); + + $this->assertStringField($data['entity_test_mulrev_revision__string']['string']); + $this->assertField($data['entity_test_mulrev_revision__string']['string'], 'string'); + $this->assertEquals([ + 'left_field' => 'revision_id', + 'field' => 'entity_id', + 'extra' => [[ + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ]], + ], $data['entity_test_mulrev_revision__string']['table']['join']['entity_test_mulrev_property_revision']); } /**