/** * 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('revision_log'), array('revision_timestamp', 'revision_uid'), array('revision_timestamp', 'revision_log'), array('revision_uid', 'revision_log'), array('revision_timestamp', 'revision_uid', 'revision_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); $this->fieldDefinitions = $this->mockFieldDefinitions($field_names); $revisionable_field_names = array('description', 'owner'); $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, $revision_metadata_field_names), array('isRevisionable' => TRUE)); $this->entityType->expects($this->atLeastOnce())->method('isRevisionable')->will($this->returnValue(TRUE)); $this->entityType->expects($this->atLeastOnce())->method('isTranslatable')->will($this->returnValue(TRUE)); $this->entityType->expects($this->atLeastOnce())->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); } }
/** * Gets the SQL schema for a dedicated table. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition * The field storage definition. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type * (optional) The entity type definition. Defaults to the one returned by * the entity manager. * * @return array * The schema definition for the table with the following keys: * - fields: The schema definition for the each field columns. * - indexes: The schema definition for the indexes. * - unique keys: The schema definition for the unique keys. * - foreign keys: The schema definition for the foreign keys. * * @throws \Drupal\Core\Field\FieldException * Exception thrown if the schema contains reserved column names. * * @see hook_schema() */ protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, ContentEntityTypeInterface $entity_type = NULL) { $description_current = "Data storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}."; $description_revision = "Revision archive storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}."; $id_definition = $this->fieldStorageDefinitions[$this->entityType->getKey('id')]; if ($id_definition->getType() == 'integer') { $id_schema = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The entity id this data is attached to'); } else { $id_schema = array('type' => 'varchar_ascii', 'length' => 128, 'not null' => TRUE, 'description' => 'The entity id this data is attached to'); } // Define the revision ID schema. if (!$this->entityType->isRevisionable()) { $revision_id_schema = $id_schema; $revision_id_schema['description'] = 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id'; } elseif ($this->fieldStorageDefinitions[$this->entityType->getKey('revision')]->getType() == 'integer') { $revision_id_schema = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The entity revision id this data is attached to'); } else { $revision_id_schema = array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'description' => 'The entity revision id this data is attached to'); } $data_schema = array('description' => $description_current, 'fields' => array('bundle' => array('type' => 'varchar_ascii', 'length' => 128, 'not null' => TRUE, 'default' => '', 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance'), 'deleted' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0, 'description' => 'A boolean indicating whether this data item has been deleted'), 'entity_id' => $id_schema, 'revision_id' => $revision_id_schema, 'langcode' => array('type' => 'varchar_ascii', 'length' => 32, 'not null' => TRUE, 'default' => '', 'description' => 'The language code for this data item.'), 'delta' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The sequence number for this data item, used for multi-value fields')), 'primary key' => array('entity_id', 'deleted', 'delta', 'langcode'), 'indexes' => array('bundle' => array('bundle'), 'revision_id' => array('revision_id'))); // Check that the schema does not include forbidden column names. $schema = $storage_definition->getSchema(); $properties = $storage_definition->getPropertyDefinitions(); $table_mapping = $this->storage->getTableMapping(); if (array_intersect(array_keys($schema['columns']), $table_mapping->getReservedColumns())) { throw new FieldException("Illegal field column names on {$storage_definition->getName()}"); } // Add field columns. foreach ($schema['columns'] as $column_name => $attributes) { $real_name = $table_mapping->getFieldColumnName($storage_definition, $column_name); $data_schema['fields'][$real_name] = $attributes; // A dedicated table only contain rows for actual field values, and no // rows for entities where the field is empty. Thus, we can safely // enforce 'not null' on the columns for the field's required properties. $data_schema['fields'][$real_name]['not null'] = $properties[$column_name]->isRequired(); } // Add indexes. foreach ($schema['indexes'] as $index_name => $columns) { $real_name = $this->getFieldIndexName($storage_definition, $index_name); foreach ($columns as $column_name) { // Indexes can be specified as either a column name or an array with // column name and length. Allow for either case. if (is_array($column_name)) { $data_schema['indexes'][$real_name][] = array($table_mapping->getFieldColumnName($storage_definition, $column_name[0]), $column_name[1]); } else { $data_schema['indexes'][$real_name][] = $table_mapping->getFieldColumnName($storage_definition, $column_name); } } } // Add unique keys. foreach ($schema['unique keys'] as $index_name => $columns) { $real_name = $this->getFieldIndexName($storage_definition, $index_name); foreach ($columns as $column_name) { // Unique keys can be specified as either a column name or an array with // column name and length. Allow for either case. if (is_array($column_name)) { $data_schema['unique keys'][$real_name][] = array($table_mapping->getFieldColumnName($storage_definition, $column_name[0]), $column_name[1]); } else { $data_schema['unique keys'][$real_name][] = $table_mapping->getFieldColumnName($storage_definition, $column_name); } } } // Add foreign keys. foreach ($schema['foreign keys'] as $specifier => $specification) { $real_name = $this->getFieldIndexName($storage_definition, $specifier); $data_schema['foreign keys'][$real_name]['table'] = $specification['table']; foreach ($specification['columns'] as $column_name => $referenced) { $sql_storage_column = $table_mapping->getFieldColumnName($storage_definition, $column_name); $data_schema['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced; } } $dedicated_table_schema = array($table_mapping->getDedicatedDataTableName($storage_definition) => $data_schema); // If the entity type is revisionable, construct the revision table. $entity_type = $entity_type ?: $this->entityType; if ($entity_type->isRevisionable()) { $revision_schema = $data_schema; $revision_schema['description'] = $description_revision; $revision_schema['primary key'] = array('entity_id', 'revision_id', 'deleted', 'delta', 'langcode'); $revision_schema['fields']['revision_id']['not null'] = TRUE; $revision_schema['fields']['revision_id']['description'] = 'The entity revision id this data is attached to'; $dedicated_table_schema += array($table_mapping->getDedicatedRevisionTableName($storage_definition) => $revision_schema); } return $dedicated_table_schema; }