diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index abccc03..0ade256 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -1166,6 +1166,11 @@ protected function doDeleteFieldItemsRevision(EntityInterface $entity) { * {@inheritdoc} */ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) { + // If we are adding a field shared in a shared table we need to recompute + // the table mapping. + if ($this->getTableMapping()->allowsSharedTableStorage($storage_definition)) { + $this->tableMapping = NULL; + } $this->schemaHandler()->createFieldSchema($storage_definition); } diff --git a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php index 125e6f9..272a3ab 100644 --- a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php +++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php @@ -74,14 +74,18 @@ public function __construct(EntityManagerInterface $entity_manager, ContentEntit } /** - * {@inheritdoc} - */ - public function createFieldSchema(FieldStorageDefinitionInterface $storage_definition) { - $this->performSchemaOperation('create', $storage_definition); - } - - /** - * TODO + * Performs the specified operation on a field. + * + * This figures out whether the field is stored in a dedicated or shared table + * and forwards the call to the proper handler. + * + * @param string $operation + * The name of the operation to be performed. + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The field storage definition + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original + * (optional) The original field storage definition. This is relevant (and + * required) only for updates. Defaults to NULL. */ protected function performSchemaOperation($operation, FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original = NULL) { $table_mapping = $this->storage->getTableMapping(); @@ -94,7 +98,17 @@ protected function performSchemaOperation($operation, FieldStorageDefinitionInte } /** - * TODO + * {@inheritdoc} + */ + public function createFieldSchema(FieldStorageDefinitionInterface $storage_definition) { + $this->performSchemaOperation('create', $storage_definition); + } + + /** + * Creates the schema for a field stored in a dedicated table. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The storage definition of the field being created. */ protected function createDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition) { $schema = $this->getDedicatedTableSchema($storage_definition); @@ -104,10 +118,31 @@ protected function createDedicatedTableSchema(FieldStorageDefinitionInterface $s } /** - * TODO + * Creates the schema for a field stored in a shared table. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The storage definition of the field being created. */ protected function createSharedTableSchema(FieldStorageDefinitionInterface $storage_definition) { - // TODO + $created_field_name = $storage_definition->getName(); + $table_mapping = $this->storage->getTableMapping(); + $column_names = $table_mapping->getColumnNames($created_field_name); + $schema = $this->getSharedTableFieldSchema($storage_definition, $column_names); + $keys = array_diff_key($schema, array('fields' => FALSE)); + + // Iterate over the mapped table to find the ones that will host the created + // field schema. + foreach ($table_mapping->getTableNames() as $table_name) { + foreach ($table_mapping->getFieldNames($table_name) as $field_name) { + if ($field_name == $created_field_name) { + foreach ($schema['fields'] as $column_name => $specifier) { + $this->database->schema()->addField($table_name, $column_name, $specifier, $keys); + } + // After creating the field schema skip to the next table. + break; + } + } + } } /** @@ -115,6 +150,7 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor */ public function markFieldSchemaAsDeleted(FieldStorageDefinitionInterface $storage_definition) { $table_mapping = $this->storage->getTableMapping(); + // TODO Do we need this also for shared table storage? if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) { // Move the table to a unique name while the table contents are being // deleted. @@ -135,7 +171,10 @@ public function deleteFieldSchema(FieldStorageDefinitionInterface $storage_defin } /** - * {@inheritdoc} + * Deletes the schema for a field stored in a dedicated table. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The storage definition of the field being deleted. */ protected function deleteDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition) { $table_mapping = $this->storage->getTableMapping(); @@ -146,10 +185,43 @@ protected function deleteDedicatedTableSchema(FieldStorageDefinitionInterface $s } /** - * TODO + * Deletes the schema for a field stored in a shared table. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The storage definition of the field being deleted. */ protected function deleteSharedTableSchema(FieldStorageDefinitionInterface $storage_definition) { - // TODO + $deleted_field_name = $storage_definition->getName(); + $table_mapping = $this->storage->getTableMapping(); + $column_names = $table_mapping->getColumnNames($deleted_field_name); + $schema = $this->getSharedTableFieldSchema($storage_definition, $column_names); + $schema_handler = $this->database->schema(); + + // Iterate over the mapped table to find the ones that host the deleted + // field schema. + foreach ($table_mapping->getTableNames() as $table_name) { + foreach ($table_mapping->getFieldNames($table_name) as $field_name) { + if ($field_name == $deleted_field_name) { + // Drop indexes and unique keys first. + if (!empty($schema['indexes'])) { + foreach ($schema['indexes'] as $name => $specifier) { + $schema_handler->dropIndex($table_name, $name); + } + } + if (!empty($schema['unique keys'])) { + foreach ($schema['unique keys'] as $name => $specifier) { + $schema_handler->dropUniqueKey($table_name, $name); + } + } + // Drop columns. + foreach ($column_names as $column_name) { + $schema_handler->dropField($table_name, $column_name); + } + // After deleting the field schema skip to the next table. + break; + } + } + } } /** @@ -160,7 +232,15 @@ public function updateFieldSchema(FieldStorageDefinitionInterface $storage_defin } /** - * {@inheritdoc} + * Updates the schema for a field stored in a shared table. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The storage definition of the field being updated. + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original + * The original storage definition; i.e., the definition before the update. + * + * @throws \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException + * Thrown when the update to the field is forbidden. */ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { if (!$storage_definition->hasData()) { @@ -244,10 +324,82 @@ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $s } /** - * TODO + * Updates the schema for a field stored in a shared table. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The storage definition of the field being updated. + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original + * The original storage definition; i.e., the definition before the update. */ - protected function updateSharedTableSchema(FieldStorageDefinitionInterface $storage_definition) { - // TODO + protected function updateSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { + if (!$this->storage->countFieldData($storage_definition, TRUE)) { + if ($this->database->supportsTransactionalDDL()) { + // If the database supports transactional DDL, we can go ahead and rely + // on it. If not, we will have to rollback manually if something fails. + $transaction = $this->database->startTransaction(); + } + try { + $this->deleteSharedTableSchema($original); + $this->createSharedTableSchema($storage_definition); + } + catch (\Exception $e) { + if ($this->database->supportsTransactionalDDL()) { + $transaction->rollback(); + } + else { + // Recreate original schema. + $this->createSharedTableSchema($original); + } + throw $e; + } + } + else { + if ($storage_definition->getColumns() != $original->getColumns()) { + throw new FieldStorageDefinitionUpdateForbiddenException("The SQL storage cannot change the schema for an existing field with data."); + } + + $schema = array(); + $original_schema = array(); + $updated_field_name = $storage_definition->getName(); + $table_mapping = $this->storage->getTableMapping(); + $column_names = $table_mapping->getColumnNames($updated_field_name); + $original_schema = $this->getSharedTableFieldSchema($original, $column_names); + $schema = $this->getSharedTableFieldSchema($storage_definition, $column_names); + $schema_handler = $this->database->schema(); + + // Iterate over the mapped table to find the ones that host the deleted + // field schema. + foreach ($table_mapping->getTableNames() as $table_name) { + foreach ($table_mapping->getFieldNames($table_name) as $field_name) { + if ($field_name == $updated_field_name) { + // Drop original indexes and unique keys. + if (!empty($original_schema['indexes'])) { + foreach ($original_schema['indexes'] as $name => $specifier) { + $schema_handler->dropIndex($table_name, $name); + } + } + if (!empty($original_schema['unique keys'])) { + foreach ($original_schema['unique keys'] as $name => $specifier) { + $schema_handler->dropUniqueKey($table_name, $name); + } + } + // Create new indexes and unique keys. + if (!empty($schema['indexes'])) { + foreach ($schema['indexes'] as $name => $specifier) { + $schema_handler->addIndex($table_name, $name, $specifier); + } + } + if (!empty($schema['unique keys'])) { + foreach ($schema['unique keys'] as $name => $specifier) { + $schema_handler->addUniqueKey($table_name, $name, $specifier); + } + } + // After deleting the field schema skip to the next table. + break; + } + } + } + } } /** @@ -272,11 +424,15 @@ public function getSchema() { $table_mapping = $this->storage->getTableMapping(); foreach ($table_mapping->getTableNames() as $table_name) { + if (!isset($schema[$table_name])) { + $schema[$table_name] = array(); + } foreach ($table_mapping->getFieldNames($table_name) as $field_name) { // Add the schema for base field definitions. - if ($this->storage->getTableMapping()->allowsSharedTableStorage($this->fieldStorageDefinitions[$field_name])) { + if ($table_mapping->allowsSharedTableStorage($this->fieldStorageDefinitions[$field_name])) { $column_names = $table_mapping->getColumnNames($field_name); - $this->addFieldSchema($schema[$table_name], $field_name, $column_names); + $storage_definition = $this->fieldStorageDefinitions[$field_name]; + $schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $column_names)); } } @@ -324,22 +480,22 @@ protected function getTables() { /** * Returns the schema for a single field definition. * - * @param array $schema - * The table schema to add the field schema to, passed by reference. - * @param string $field_name - * The name of the field. + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The storage definition of the field whose schema has to be returned. * @param string[] $column_mapping * A mapping of field column names to database column names. */ - protected function addFieldSchema(array &$schema, $field_name, array $column_mapping) { - $field_schema = $this->fieldStorageDefinitions[$field_name]->getSchema(); + protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, array $column_mapping) { + $schema = array(); + $field_schema = $storage_definition->getSchema(); // Check that the schema does not include forbidden column names. if (array_intersect(array_keys($field_schema['columns']), $this->storage->getTableMapping()->getReservedColumns())) { throw new FieldException('Illegal field type columns.'); } - $field_description = $this->fieldStorageDefinitions[$field_name]->getDescription(); + $field_name = $storage_definition->getName(); + $field_description = $storage_definition->getDescription(); foreach ($column_mapping as $field_column_name => $schema_field_name) { $column_schema = $field_schema['columns'][$field_column_name]; @@ -364,19 +520,18 @@ protected function addFieldSchema(array &$schema, $field_name, array $column_map } if (!empty($field_schema['indexes'])) { - $indexes = $this->getFieldIndexes($field_name, $field_schema, $column_mapping); - $schema['indexes'] = array_merge($schema['indexes'], $indexes); + $schema['indexes'] = $this->getFieldIndexes($field_name, $field_schema, $column_mapping); } if (!empty($field_schema['unique keys'])) { - $unique_keys = $this->getFieldUniqueKeys($field_name, $field_schema, $column_mapping); - $schema['unique keys'] = array_merge($schema['unique keys'], $unique_keys); + $schema['unique keys'] = $this->getFieldUniqueKeys($field_name, $field_schema, $column_mapping); } if (!empty($field_schema['foreign keys'])) { - $foreign_keys = $this->getFieldForeignKeys($field_name, $field_schema, $column_mapping); - $schema['foreign keys'] = array_merge($schema['foreign keys'], $foreign_keys); + $schema['foreign keys'] = $this->getFieldForeignKeys($field_name, $field_schema, $column_mapping); } + + return $schema; } /** diff --git a/core/lib/Drupal/Core/Entity/Schema/EntitySchemaHandlerInterface.php b/core/lib/Drupal/Core/Entity/Schema/EntitySchemaHandlerInterface.php index d9d5efe..c7fe82d 100644 --- a/core/lib/Drupal/Core/Entity/Schema/EntitySchemaHandlerInterface.php +++ b/core/lib/Drupal/Core/Entity/Schema/EntitySchemaHandlerInterface.php @@ -15,38 +15,38 @@ interface EntitySchemaHandlerInterface extends EntitySchemaProviderInterface { /** - * TODO + * Creates the storage schema for the given field. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition - * The definition being created. + * The storage definition of the field being created. */ public function createFieldSchema(FieldStorageDefinitionInterface $storage_definition); /** - * TODO + * Marks the storage schema for the given field as deleted. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition - * The definition being created. + * The storage definition of the field being deleted. */ public function markFieldSchemaAsDeleted(FieldStorageDefinitionInterface $storage_definition); /** - * TODO + * Deletes the storage schema for the given field. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition - * The definition being created. + * The storage definition of the field being deleted. */ public function deleteFieldSchema(FieldStorageDefinitionInterface $storage_definition); /** - * TODO + * Updates the storage schema for the given field. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition - * The field being updated. + * The storage definition of the field being updated. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original * The original storage definition; i.e., the definition before the update. * - * @throws \Drupal\Core\Entity\Exception\StorageDefinitionUpdateForbiddenException + * @throws \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException * Thrown when the update to the field is forbidden. */ public function updateFieldSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original); diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php index 91c4624..b0a5522 100644 --- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php +++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php @@ -6,7 +6,7 @@ */ namespace Drupal\Core\Entity\Sql; -use Drupal\Core\Field\FieldDefinitionInterface; + use Drupal\Core\Field\FieldStorageDefinitionInterface; /** diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMappingInterface.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMappingInterface.php index 68f7f2a..a9e0ebf 100644 --- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMappingInterface.php +++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMappingInterface.php @@ -15,19 +15,37 @@ interface DefaultTableMappingInterface extends TableMappingInterface { /** - * TODO + * Returns a list of dedicated table names for this mapping. + * + * TODO Do we really need this or should we return everything in + * TableMappingInterface::getTableNames()? + * + * @return string[] + * An array of table names. */ - function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition); + function getDedicatedTableNames(); /** - * TODO + * Checks whether the given field can be stored in a shared table. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The field storage definition. + * + * @return bool + * TRUE if the field can be stored in a dedicated table, FALSE otherwise. */ - function requiresDedicatedTableStorage(FieldStorageDefinitionInterface $storage_definition); + function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition); /** - * TODO + * Checks whether the given field can be stored in a shared table. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition + * The field storage definition. + * + * @return bool + * TRUE if the field can be stored in a dedicated table, FALSE otherwise. */ - function getDedicatedTableNames(); + function requiresDedicatedTableStorage(FieldStorageDefinitionInterface $storage_definition); /** * A list of columns that can not be used as field type columns.