diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index 2249180..6e365f6 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -250,7 +250,7 @@ public function getTableMapping() { if (!isset($this->tableMapping)) { $this->tableMapping = new DefaultTableMapping($this->storageDefinitions); - $key_fields = array_filter(array($this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey)); + $key_fields = array_values(array_filter(array($this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey))); $all_fields = array_keys($this->storageDefinitions); $revisionable_fields = array_keys(array_filter($this->storageDefinitions, function (FieldStorageDefinitionInterface $definition) { return $definition->isRevisionable(); @@ -266,11 +266,11 @@ public function getTableMapping() { // https://drupal.org/node/2248991. $revision_metadata_fields = array_intersect(array('revision_timestamp', 'revision_uid', 'log'), $all_fields); + $revisionable = $this->entityType->hasKey('revision'); // @todo Remove the data table check once all entity types are using // entity query and we have a views data controller. See: // - https://drupal.org/node/2068325 // - https://drupal.org/node/1740492 - $revisionable = $this->entityType->hasKey('revision'); $translatable = $this->entityType->getDataTable() && $this->entityType->isTranslatable(); if (!$revisionable && !$translatable) { // The base layout stores all the base field values in the base table. @@ -295,7 +295,7 @@ public function getTableMapping() { // the data table. $this->tableMapping ->setFieldNames($this->baseTable, $key_fields) - ->setFieldNames($this->dataTable, array_diff($all_fields, array($this->uuidKey))) + ->setFieldNames($this->dataTable, array_values(array_diff($all_fields, array($this->uuidKey)))) // Add the denormalized 'default_langcode' field to the mapping. Its // value is identical to the query expression // "base_table.langcode = data_table.langcode" @@ -309,12 +309,12 @@ public function getTableMapping() { // holds the data field values for all non-revisionable fields. The data // field values of revisionable fields are denormalized in the data // table, as well. - $this->tableMapping->setFieldNames($this->baseTable, array_diff($key_fields, array($this->langcodeKey))); + $this->tableMapping->setFieldNames($this->baseTable, array_values(array_diff($key_fields, array($this->langcodeKey)))); // Like in the multilingual, non-revisionable case the UUID is not // in the data table. Additionally, do not store revision metadata // fields in the data table. - $data_fields = array_diff($all_fields, array($this->uuidKey), $revision_metadata_fields); + $data_fields = array_values(array_diff($all_fields, array($this->uuidKey), $revision_metadata_fields)); $this->tableMapping ->setFieldNames($this->dataTable, $data_fields) // Add the denormalized 'default_langcode' field to the mapping. Its diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentDefaultFormatterCacheTagsTest.php index 037640a..da92ef6 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentDefaultFormatterCacheTagsTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentDefaultFormatterCacheTagsTest.php @@ -46,7 +46,8 @@ public function setUp() { $this->container->set('current_user', $this->createUser(array(), array('access comments'))); // Install tables and config needed to render comments. - $this->installSchema('comment', array('comment', 'comment_entity_statistics')); + $this->installSchema('comment', array('comment_entity_statistics')); + $this->installEntitySchema('comment'); $this->installConfig(array('system', 'filter')); // Comment rendering generates links, so build the router. diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php index 7e3c6e6..8a61fd1 100644 --- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php @@ -557,6 +557,371 @@ public function testGetTableMappingRevisionableWithFields(array $entity_keys) { } /** + * Tests getTableMapping() with a non-revisionable, translatable entity type. + * + * @param string[] $entity_keys + * A map of entity keys to use for the mocked entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + * + * @dataProvider providerTestGetTableMappingSimple() + */ + public function testGetTableMappingTranslatable(array $entity_keys) { + // This allows to re-use the data provider. + $entity_keys['langcode'] = 'langcode'; + + $this->entityType->expects($this->exactly(2)) + ->method('isTranslatable') + ->will($this->returnValue(TRUE)); + $this->entityType->expects($this->exactly(3)) + ->method('getDataTable') + ->will($this->returnValue('entity_test_field_data')); + $this->entityType->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap(array( + array('id', $entity_keys['id']), + array('uuid', $entity_keys['uuid']), + array('bundle', $entity_keys['bundle']), + ))); + + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + + $expected = array('entity_test', 'entity_test_field_data'); + $this->assertEquals($expected, $mapping->getTableNames()); + + $expected = array_values(array_filter($entity_keys)); + $actual = $mapping->getFieldNames('entity_test'); + $this->assertEquals($expected, $actual); + // The UUID is not stored on the data table. + $expected = array_values(array_filter(array( + $entity_keys['id'], + $entity_keys['bundle'], + $entity_keys['langcode'], + ))); + $actual = $mapping->getFieldNames('entity_test_field_data'); + $this->assertEquals($expected, $actual); + + $expected = array(); + $actual = $mapping->getExtraColumns('entity_test'); + $this->assertEquals($expected, $actual); + $expected = array('default_langcode'); + $actual = $mapping->getExtraColumns('entity_test_field_data'); + $this->assertEquals($expected, $actual); + } + + /** + * Tests getTableMapping() with a translatable entity type with fields. + * + * @param string[] $entity_keys + * A map of entity keys to use for the mocked entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + * + * @dataProvider providerTestGetTableMappingSimple() + */ + public function testGetTableMappingTranslatableWithFields(array $entity_keys) { + // This allows to re-use the data provider. + $entity_keys['langcode'] = 'langcode'; + + $base_field_names = array('title', 'description', 'owner'); + $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names); + + $definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $this->fieldDefinitions = array_fill_keys($field_names, $definition); + + $this->entityType->expects($this->exactly(2)) + ->method('isTranslatable') + ->will($this->returnValue(TRUE)); + $this->entityType->expects($this->exactly(3)) + ->method('getDataTable') + ->will($this->returnValue('entity_test_field_data')); + $this->entityType->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap(array( + array('id', $entity_keys['id']), + array('uuid', $entity_keys['uuid']), + array('bundle', $entity_keys['bundle']), + ))); + + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + + $expected = array('entity_test', 'entity_test_field_data'); + $this->assertEquals($expected, $mapping->getTableNames()); + + $expected = array_values(array_filter($entity_keys)); + $actual = $mapping->getFieldNames('entity_test'); + $this->assertEquals($expected, $actual); + // The UUID is not stored on the data table. + $expected = array_merge(array_filter(array( + $entity_keys['id'], + $entity_keys['bundle'], + $entity_keys['langcode'], + )), $base_field_names); + $actual = $mapping->getFieldNames('entity_test_field_data'); + $this->assertEquals($expected, $actual); + + $expected = array(); + $actual = $mapping->getExtraColumns('entity_test'); + $this->assertEquals($expected, $actual); + $expected = array('default_langcode'); + $actual = $mapping->getExtraColumns('entity_test_field_data'); + $this->assertEquals($expected, $actual); + } + + /** + * Tests getTableMapping() with a revisionable, translatable entity type. + * + * @param string[] $entity_keys + * A map of entity keys to use for the mocked entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + * + * @dataProvider providerTestGetTableMappingSimple() + */ + public function testGetTableMappingRevisionableTranslatable(array $entity_keys) { + // This allows to re-use the data provider. + $entity_keys = array( + 'id' => $entity_keys['id'], + 'revision' => 'test_revision', + 'bundle' => $entity_keys['bundle'], + 'uuid' => $entity_keys['uuid'], + 'langcode' => 'langcode', + ); + + $this->entityType->expects($this->exactly(2)) + ->method('hasKey') + ->with('revision') + ->will($this->returnValue(TRUE)); + $this->entityType->expects($this->exactly(2)) + ->method('isTranslatable') + ->will($this->returnValue(TRUE)); + $this->entityType->expects($this->exactly(3)) + ->method('getDataTable') + ->will($this->returnValue('entity_test_field_data')); + $this->entityType->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap(array( + array('id', $entity_keys['id']), + array('uuid', $entity_keys['uuid']), + array('bundle', $entity_keys['bundle']), + array('revision', $entity_keys['revision']), + ))); + + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + + $expected = array( + 'entity_test', + 'entity_test_field_data', + 'entity_test_revision', + 'entity_test_field_revision', + ); + $this->assertEquals($expected, $mapping->getTableNames()); + + // The language code is not stored on the base table, but on the revision + // table. + $expected = array_values(array_filter(array( + $entity_keys['id'], + $entity_keys['revision'], + $entity_keys['bundle'], + $entity_keys['uuid'], + ))); + $actual = $mapping->getFieldNames('entity_test'); + $this->assertEquals($expected, $actual); + // The revision table on the other hand does not store the bundle and the + // UUID. + $expected = array_values(array_filter(array( + $entity_keys['id'], + $entity_keys['revision'], + $entity_keys['langcode'], + ))); + $actual = $mapping->getFieldNames('entity_test_revision'); + $this->assertEquals($expected, $actual); + // The UUID is not stored on the data table. + $expected = array_values(array_filter(array( + $entity_keys['id'], + $entity_keys['revision'], + $entity_keys['bundle'], + $entity_keys['langcode'], + ))); + $actual = $mapping->getFieldNames('entity_test_field_data'); + $this->assertEquals($expected, $actual); + // The data revision also does not store the bundle. + $expected = array_values(array_filter(array( + $entity_keys['id'], + $entity_keys['revision'], + $entity_keys['langcode'], + ))); + $actual = $mapping->getFieldNames('entity_test_field_revision'); + $this->assertEquals($expected, $actual); + + $expected = array(); + $actual = $mapping->getExtraColumns('entity_test'); + $this->assertEquals($expected, $actual); + $actual = $mapping->getExtraColumns('entity_test_revision'); + $this->assertEquals($expected, $actual); + $expected = array('default_langcode'); + $actual = $mapping->getExtraColumns('entity_test_field_data'); + $this->assertEquals($expected, $actual); + $actual = $mapping->getExtraColumns('entity_test_field_revision'); + $this->assertEquals($expected, $actual); + } + + /** + * Tests getTableMapping() with a complex entity type with fields. + * + * @param string[] $entity_keys + * A map of entity keys to use for the mocked entity type. + * + * @covers ::__construct() + * @covers ::getTableMapping() + * + * @dataProvider providerTestGetTableMappingSimple() + */ + public function testGetTableMappingRevisionableTranslatableWithFields(array $entity_keys) { + // This allows to re-use the data provider. + $entity_keys = array( + 'id' => $entity_keys['id'], + 'revision' => 'test_revision', + 'bundle' => $entity_keys['bundle'], + 'uuid' => $entity_keys['uuid'], + 'langcode' => 'langcode', + ); + + // PHPUnit does not allow for multiple data providers. + $test_cases = array( + array(), + array('revision_timestamp'), + array('revision_uid'), + array('log'), + array('revision_timestamp', 'revision_uid'), + array('revision_timestamp', 'log'), + array('revision_uid', 'log'), + array('revision_timestamp', 'revision_uid', 'log'), + ); + foreach ($test_cases as $revision_metadata_field_names) { + $this->setUp(); + + $base_field_names = array('title'); + $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names); + + $definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $this->fieldDefinitions = array_fill_keys($field_names, $definition); + + $revisionable_field_names = array('description', 'owner'); + $definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + // isRevisionable() is only called once, but we re-use the same definition + // for all revisionable fields. + $definition->expects($this->any()) + ->method('isRevisionable') + ->will($this->returnValue(TRUE)); + $field_names = array_merge( + $field_names, + $revisionable_field_names + ); + $this->fieldDefinitions += array_fill_keys( + array_merge($revisionable_field_names, $revision_metadata_field_names), + $definition + ); + + $this->entityType->expects($this->exactly(2)) + ->method('hasKey') + ->with('revision') + ->will($this->returnValue(TRUE)); + $this->entityType->expects($this->exactly(2)) + ->method('isTranslatable') + ->will($this->returnValue(TRUE)); + $this->entityType->expects($this->exactly(3)) + ->method('getDataTable') + ->will($this->returnValue('entity_test_field_data')); + $this->entityType->expects($this->any()) + ->method('getKey') + ->will($this->returnValueMap(array( + array('id', $entity_keys['id']), + array('uuid', $entity_keys['uuid']), + array('bundle', $entity_keys['bundle']), + array('revision', $entity_keys['revision']), + ))); + + $this->setUpEntityStorage(); + + $mapping = $this->entityStorage->getTableMapping(); + + $expected = array( + 'entity_test', + 'entity_test_field_data', + 'entity_test_revision', + 'entity_test_field_revision', + ); + $this->assertEquals($expected, $mapping->getTableNames()); + + $expected = array( + 'entity_test', + 'entity_test_field_data', + 'entity_test_revision', + 'entity_test_field_revision', + ); + $this->assertEquals($expected, $mapping->getTableNames()); + + // The language code is not stored on the base table, but on the revision + // table. + $expected = array_values(array_filter(array( + $entity_keys['id'], + $entity_keys['revision'], + $entity_keys['bundle'], + $entity_keys['uuid'], + ))); + $actual = $mapping->getFieldNames('entity_test'); + $this->assertEquals($expected, $actual); + // The revision table on the other hand does not store the bundle and the + // UUID. + $expected = array_merge(array_filter(array( + $entity_keys['id'], + $entity_keys['revision'], + $entity_keys['langcode'], + )), $revision_metadata_field_names); + $actual = $mapping->getFieldNames('entity_test_revision'); + $this->assertEquals($expected, $actual); + // The UUID is not stored on the data table. + $expected = array_merge(array_filter(array( + $entity_keys['id'], + $entity_keys['revision'], + $entity_keys['bundle'], + $entity_keys['langcode'], + )), $base_field_names, $revisionable_field_names); + $actual = $mapping->getFieldNames('entity_test_field_data'); + $this->assertEquals($expected, $actual); + // The data revision also does not store the bundle. + $expected = array_merge(array_filter(array( + $entity_keys['id'], + $entity_keys['revision'], + $entity_keys['langcode'], + )), $revisionable_field_names); + $actual = $mapping->getFieldNames('entity_test_field_revision'); + $this->assertEquals($expected, $actual); + + $expected = array(); + $actual = $mapping->getExtraColumns('entity_test'); + $this->assertEquals($expected, $actual); + $actual = $mapping->getExtraColumns('entity_test_revision'); + $this->assertEquals($expected, $actual); + $expected = array('default_langcode'); + $actual = $mapping->getExtraColumns('entity_test_field_data'); + $this->assertEquals($expected, $actual); + $actual = $mapping->getExtraColumns('entity_test_field_revision'); + $this->assertEquals($expected, $actual); + } + } + + /** * Tests field SQL schema generation for an entity with a string identifier. * * @covers ::_fieldSqlSchema()