diff --git a/core/lib/Drupal/Core/Entity/Sql/RevisionableSchemaConverter.php b/core/lib/Drupal/Core/Entity/Sql/RevisionableSchemaConverter.php index 584e361..0ddcfba 100644 --- a/core/lib/Drupal/Core/Entity/Sql/RevisionableSchemaConverter.php +++ b/core/lib/Drupal/Core/Entity/Sql/RevisionableSchemaConverter.php @@ -17,13 +17,6 @@ class RevisionableSchemaConverter { /** - * The entity type ID. - * - * @var string - */ - protected $entityTypeId; - - /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface @@ -54,8 +47,6 @@ class RevisionableSchemaConverter { /** * EntitySchemaUpdater constructor. * - * @param string $entity_type_id - * The entity type ID. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. * @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entity_definition_update_manager @@ -65,8 +56,7 @@ class RevisionableSchemaConverter { * @param \Drupal\Core\Database\Connection $database * Database connection. */ - public function __construct($entity_type_id, EntityTypeManagerInterface $entity_type_manager, EntityDefinitionUpdateManagerInterface $entity_definition_update_manager, EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository, Connection $database) { - $this->entityTypeId = $entity_type_id; + public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityDefinitionUpdateManagerInterface $entity_definition_update_manager, EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository, Connection $database) { $this->entityTypeManager = $entity_type_manager; $this->entityDefinitionUpdateManager = $entity_definition_update_manager; $this->lastInstalledSchemaRepository = $last_installed_schema_repository; @@ -74,88 +64,96 @@ public function __construct($entity_type_id, EntityTypeManagerInterface $entity_ } /** - * Converts the schema of an entity type with existing data to revisionable. + * Converts an entity type with existing data to be revisionable. * * @param array $sandbox * The sandbox array from a hook_update_N() implementation. - * @param array $schema - * The database schema array that we are updating to. This is usually - * generated by - * \Drupal\Core\Entity\Sql\RevisionableSchemaConverter::getFullEntitySchema(). - * @param string $revision_table - * The name of the revision table for the updated entity type. - * @param string $revision_data_table - * (optional) The name of the revision data table for the updated entity - * type. Defaults to NULL. + * @param string $entity_type_id + * The entity type ID. * @param string[] $fields_to_update * (optional) An array of field names that should be converted to be * revisionable. Note that the 'langcode' field, if present, is updated * automatically. Defaults to an empty array. */ - public function convertSchema(array &$sandbox, array $schema, $revision_key, $revision_table, $revision_data_table = NULL, array $fields_to_update = []) { - // If 'progress' is not set, then this will be the first run of the batch, - // so we need to initialize the updated entity type and field storage - // definitions and create the temporary tables. - if (!isset($sandbox['progress'])) { - $entity_type = $this->lastInstalledSchemaRepository->getLastInstalledDefinition($this->entityTypeId); - $storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($this->entityTypeId); + public function convertToRevisionable(array &$sandbox, $entity_type_id, array $fields_to_update = []) { + // @todo Put the site in maintenance mode at the beginning of this process. - $sandbox['entity_type'] = $this->updateEntityTypeDefinition($entity_type, $revision_key, $revision_table, $revision_data_table); - $sandbox['storage_definitions'] = $this->updateFieldStorageDefinitions($entity_type, $storage_definitions, $fields_to_update); + // If 'progress' is not set, then this will be the first run of the batch. + if (!isset($sandbox['progress'])) { + $original_entity_type = $this->lastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id); + $original_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id); + $sandbox['original_entity_type'] = $original_entity_type; + $sandbox['original_storage_definitions'] = $original_storage_definitions; - // @todo Instead of switching the wrapped entity type, we should be able - // to instantiate a new table mapping for each entity type definition. - // See https://www.drupal.org/node/2274017. - $storage = $this->entityTypeManager->getStorage($entity_type->id()); - $storage->setEntityType($entity_type); + /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */ + $storage = $this->entityTypeManager->getStorage($entity_type_id); + $storage->setEntityType($original_entity_type); /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ - $sandbox['table_mapping'] = $storage->getTableMapping($sandbox['storage_definitions']); + $table_mapping = $storage->getTableMapping($original_storage_definitions); + + // Rename the existing entity tables to temporary table names and keep a + // mapping of both old and new names. + $original_table_names = $table_mapping->getTableNames(); + $temporary_table_names = []; + foreach ($original_table_names as $table_name) { + $temp_table_name = TemporaryTableMapping::getTempTableName($table_name); + $temporary_table_names[$table_name] = $temp_table_name; + $this->database->schema()->renameTable($table_name, $temp_table_name); + } + $sandbox['temporary_table_names'] = $temporary_table_names; + + // Create a temporary table mapping object that will be used to retrieve + // data from the original entity tables. + $temporary_entity_type = clone $original_entity_type; + $temporary_entity_type->set('base_table', TemporaryTableMapping::getTempTableName($temporary_entity_type->getBaseTable())); + if ($temporary_entity_type->isTranslatable()) { + $temporary_entity_type->set('data_table', TemporaryTableMapping::getTempTableName($temporary_entity_type->getDataTable())); + } + $sandbox['temporary_entity_type'] = $temporary_entity_type; + $sandbox['temporary_table_mapping'] = $this->getTemporaryTableMapping($temporary_entity_type, $original_storage_definitions); + + // Updating the entity type definition is safe to do at this stage because + // its tables have been already moved to temporary locations, so it is + // considered as not having any data. + $this->entityTypeManager->useCaches(FALSE); + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); - // Create the new tables using temporary table names. - $this->createTables($schema); + // Instruct the entity schema handler that data migration is being handled + // independently. + $entity_type->requiresDataMigration = FALSE; + + $this->entityDefinitionUpdateManager->updateEntityType($entity_type); + $sandbox['entity_type'] = $entity_type; } // Copy over the existing data to the new temporary tables. $this->copyData($sandbox); - // If the data copying has finished successfully, we can drop the existing + // If the data copying has finished successfully, we can drop the temporary // tables and call the appropriate update mechanisms. if ($sandbox['#finished'] === 1) { - // Replace the original tables with the temporary ones. - $this->replaceTables($sandbox, $schema); - - // Update the entity type definition. - $this->lastInstalledSchemaRepository->setLastInstalledDefinition($sandbox['entity_type']); - $this->entityDefinitionUpdateManager->updateEntityType($sandbox['entity_type']); + // @todo Remove the temporary tables. // Update the field storage definitions. - static::getStorageSchemaHandler($sandbox['entity_type'])->updateFieldSchemaData($sandbox['storage_definitions']); - foreach ($sandbox['storage_definitions'] as $field_name => $storage_definition) { - if ($field_name === $sandbox['entity_type']->getKey('revision')) { - $this->entityDefinitionUpdateManager->installFieldStorageDefinition($storage_definition->getName(), $sandbox['entity_type']->id(), $sandbox['entity_type']->getProvider(), $storage_definition); - } - else { - $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition); - } - } + $this->updateFieldStorageDefinitions($sandbox['entity_type'], $sandbox['original_storage_definitions'], $fields_to_update); } } /** - * Copies existing data to new tables. + * Loads entities from the temporary storage and re-saves them to the new one. * * @param array $sandbox * The sandbox array from a hook_update_N() implementation. */ protected function copyData(array &$sandbox) { + /** @var \Drupal\Core\Entity\Sql\TemporaryTableMapping $temporary_table_mapping */ + $temporary_table_mapping = $sandbox['temporary_table_mapping']; + $temporary_entity_type = $sandbox['temporary_entity_type']; $entity_type = $sandbox['entity_type']; - $storage_definitions = $sandbox['storage_definitions']; - /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ - $table_mapping = $sandbox['table_mapping']; // If 'progress' is not set, then this will be the first run of the batch. - $base_table = $entity_type->getBaseTable(); + $base_table = $temporary_entity_type->getBaseTable(); if (!isset($sandbox['progress'])) { $sandbox['progress'] = 0; $sandbox['current_id'] = 0; @@ -165,153 +163,48 @@ protected function copyData(array &$sandbox) { ->fetchField(); } - $id = $entity_type->getKey('id'); + $id = $temporary_entity_type->getKey('id'); - // Get the next 50 entity IDs to migrate. + // Get the next 10 entity IDs to migrate. $entity_ids = $this->database->select($base_table) ->fields($base_table, [$id]) ->condition($id, $sandbox['current_id'], '>') - ->range(0, 50) + ->range(0, 10) ->execute() ->fetchAllKeyed(0, 0); - foreach ($entity_ids as $entity_id) { - $this->copySingleEntity($entity_type, $storage_definitions, $table_mapping, $entity_id); - $sandbox['progress']++; - $sandbox['current_id'] = $entity_id; - } - $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); - } + /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */ + $storage = $this->entityTypeManager->getStorage($temporary_entity_type->id()); + $storage->setEntityType($temporary_entity_type); + $storage->setTableMapping($temporary_table_mapping); - /** - * Gets field data for an entity table. - * - * @param string $table_name - * The entity table name. - * @param array $conditions - * An array of database conditions. - * - * @return array - * An array of field data for the specified entity table. - */ - protected function getTableData($table_name, array $conditions) { - $query = $this->database->select($table_name, 't') - ->fields('t'); + $temporary_entities = $storage->loadMultiple($entity_ids); - foreach ($conditions as $field => $value) { - $query->condition($field, $value); - } + // Now inject the updated entity type definition in the storage and re-save + // the entities. This will also reset the internal table mapping. + $storage->setEntityType($entity_type); - return $query->execute()->fetchAll(\PDO::FETCH_ASSOC); - } + foreach ($temporary_entities as $entity_id => $entity) { + $revision_id_key = $entity_type->getKey('revision'); - /** - * Copies field data for an entity. - * - * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type - * The entity type definition. - * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions - * An array of field storage definitions. - * @param \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping - * The table mapping. - * @param int|string $entity_id - * The entity ID. - */ - protected function copySingleEntity(ContentEntityTypeInterface $entity_type, array $storage_definitions, DefaultTableMapping $table_mapping, $entity_id) { - $id_key = $entity_type->getKey('id'); - $revision_key = $entity_type->getKey('revision'); + // Manually set the original entity since it cannot be read from the + // temporary storage anymore. + $entity->original = $entity; - // Copy data to the revision base table. - $base_table = $entity_type->getBaseTable(); - $revision_table = $entity_type->getRevisionTable(); - $revision_column_names = $table_mapping->getAllColumns($revision_table); - - $base_table_data = $this->getTableData($base_table, [$id_key => $entity_id]); - $this->database->insert($this->getTempTableName($base_table)) - ->fields((array) reset($base_table_data) + [$revision_key => $entity_id]) - ->execute(); - $this->database->insert($this->getTempTableName($revision_table)) - ->fields(array_intersect_key((array) reset($base_table_data) + [$revision_key => $entity_id], array_flip($revision_column_names))) - ->execute(); - - // Copy data to the revision data table. - if ($entity_type->isTranslatable()) { - $data_table = $entity_type->getDataTable(); - $revision_data_table = $entity_type->getRevisionDataTable(); - $data_table_column_names = $table_mapping->getAllColumns($data_table); - $revision_data_column_names = $table_mapping->getAllColumns($revision_data_table); - $data_table_data = $this->getTableData($data_table, [$id_key => $entity_id]); - - // Note: These values could be by language. - foreach ($data_table_data as $values) { - $primary_keys_data = [$id_key => $entity_id]; - $primary_keys_revision_data = [$revision_key => $entity_id]; - if ($entity_type->hasKey('langcode')) { - $langcode_key = $entity_type->getKey('langcode'); - $primary_keys_data[$langcode_key] = $values[$langcode_key]; - $primary_keys_revision_data[$langcode_key] = $values[$langcode_key]; - } - - $data = (array) $values + [$revision_key => $entity_id]; - $this->database->insert($this->getTempTableName($data_table)) - ->fields(array_intersect_key($data, array_flip($data_table_column_names))) - ->execute(); - - $this->database->insert($this->getTempTableName($revision_data_table)) - ->fields(array_intersect_key($data, array_flip($revision_data_column_names))) - ->execute(); - } - } + // Set the revision ID to be same as the entity ID. + $entity->set($revision_id_key, $entity_id); - // Copy data to the dedicated tables. - $dedicated_fields = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $field_storage) use ($table_mapping) { - return $table_mapping->requiresDedicatedTableStorage($field_storage); - }); + // Treat the entity as new in order to make the storage do an INSERT + // rather than an UPDATE. + $entity->enforceIsNew(TRUE); - // For copying the data of dedicated field tables we can simply use the - // "INSERT INTO ... SELECT * ..." syntax. - foreach ($dedicated_fields as $dedicated_field => $field_storage) { - $dedicated_table_name = $table_mapping->getFieldTableName($dedicated_field); - $dedicated_revision_table = $table_mapping->getDedicatedRevisionTableName($field_storage); - - $query = $this->database->select($dedicated_table_name, 'dt') - ->fields('dt') - ->condition('entity_id', $entity_id); - - $this->database->insert($this->getTempTableName($dedicated_table_name)) - ->from($query) - ->execute(); - $this->database->insert($this->getTempTableName($dedicated_revision_table)) - ->from($query) - ->execute(); - } - } + // Finally, save the entity in the new revisionable storage. + $storage->save($entity); - /** - * Updates the installed entity type definition. - * - * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type - * A content entity type definition. - * @param string $revision_key - * The value of the entity type revision key. - * @param string $revision_table - * The name of the revision table. - * @param string $revision_data_table - * (optional) The name of the revision data table. - * - * @return \Drupal\Core\Entity\ContentEntityTypeInterface - * The udpated entity type definition. - */ - protected function updateEntityTypeDefinition(ContentEntityTypeInterface $entity_type, $revision_key, $revision_table, $revision_data_table = NULL) { - $keys = $entity_type->getKeys(); - $keys['revision'] = $revision_key; - $entity_type->set('entity_keys', $keys); - $entity_type->set('revision_table', $revision_table); - if ($revision_data_table) { - $entity_type->set('revision_data_table', $revision_data_table); + $sandbox['progress']++; + $sandbox['current_id'] = $entity_id; } - - return $entity_type; + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); } /** @@ -329,7 +222,7 @@ protected function updateEntityTypeDefinition(ContentEntityTypeInterface $entity * An array of updated field storage definitions. */ protected function updateFieldStorageDefinitions(ContentEntityTypeInterface $entity_type, array $storage_definitions, array $fields_to_update = []) { - // Update the 'langcode' field manually, as it is configure in the base + // Update the 'langcode' field manually, as it is configured in the base // content entity field definitions. if ($entity_type->hasKey('langcode')) { $fields_to_update = array_merge([$entity_type->getKey('langcode')], $fields_to_update); @@ -340,154 +233,100 @@ protected function updateFieldStorageDefinitions(ContentEntityTypeInterface $ent // about base fields. if ($storage_definitions[$field_name]->isBaseField()) { $storage_definitions[$field_name]->setRevisionable(TRUE); + $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definitions[$field_name]); } } // Add the revision ID field. - $storage_definitions[$entity_type->getKey('revision')] = BaseFieldDefinition::create('integer') + $revision_field = BaseFieldDefinition::create('integer') ->setName($entity_type->getKey('revision')) ->setTargetEntityTypeId($entity_type->id()) ->setTargetBundle(NULL) ->setLabel(new TranslatableMarkup('Revision ID')) ->setReadOnly(TRUE) ->setSetting('unsigned', TRUE); + $this->entityDefinitionUpdateManager->installFieldStorageDefinition($revision_field->getName(), $entity_type->id(), $entity_type->getProvider(), $revision_field); - return $storage_definitions; - } - - /** - * Creates the entity tables with the new schema. - * - * @param array $schema - * The Schema API array. - */ - protected function createTables(array $schema) { - foreach ($schema as $table_name => $table_schema) { - if ($this->database->schema()->tableExists($this->getTempTableName($table_name))) { - $this->database->schema()->dropTable($this->getTempTableName($table_name)); - } - - $this->database->schema()->createTable($this->getTempTableName($table_name), $table_schema); - } - } - - /** - * Replaces the existing entity tables with the new ones. - * - * @param array $sandbox - * The sandbox array from a hook_update_N() implementation. - * @param array $schema - * The Schema API array. - */ - protected function replaceTables(array $sandbox, array $schema) { - /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ - $table_mapping = $sandbox['table_mapping']; - - // Delete all the existing entity tables. - $table_names = array_unique(array_merge($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames())); - foreach ($table_names as $table_name) { - $this->database->schema()->dropTable($table_name); - } - + $storage_definitions[$entity_type->getKey('revision')] = $revision_field; - // Rename the temporary tables to their actual table names. - foreach ($schema as $table_name => $table_schema) { - $this->database->schema()->renameTable($this->getTempTableName($table_name), $table_name); - } + return $storage_definitions; } /** - * Generates a temporary table name. + * Gets a temporary table mapping for the entity's SQL tables. * - * The method accounts for a maximum table name length of 64 characters. + * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type + * A content entity type definition. + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions + * An array of field storage definitions to be used to compute the table + * mapping. * - * @param string $table_name - * The initial table name. + * @return \Drupal\Core\Entity\Sql\TemporaryTableMapping + * A table mapping object for the entity's tables. * - * @return string - * The final table name. + * @todo Remove this when https://www.drupal.org/node/2274017 is fixed. */ - protected function getTempTableName($table_name) { - $prefix = 'rev_tmp_'; - $tmp_table_name = $prefix . $table_name; - - // Limit the string to 48 characters, keeping a 16 characters margin for db - // prefixes. - if (strlen($table_name) > 48) { - $short_table_name = substr($table_name, 0, 30); - $table_hash = substr(hash('sha256', $table_name), 0, 10); + protected function getTemporaryTableMapping(ContentEntityTypeInterface $entity_type, array $storage_definitions) { + $table_mapping = new TemporaryTableMapping($entity_type, $storage_definitions); - $tmp_table_name = $prefix . $short_table_name . $table_hash; + $translatable = $entity_type->isTranslatable(); + $base_table = $entity_type->getBaseTable(); + if ($translatable) { + $data_table = $entity_type->getDataTable(); } - return $tmp_table_name; - } - /** - * Returns the full schema definition for an entity type. - * - * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type - * A content entity type definition. - * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions - * An array of field storage definitions. - * - * @return array - * A Schema API array describing the entity schema, including dedicated - * field tables. - */ - public static function getFullEntitySchema(ContentEntityTypeInterface $entity_type, array $storage_definitions) { - $schema_handler = static::getStorageSchemaHandler($entity_type); - return $schema_handler->getFullEntitySchema($entity_type, $storage_definitions); - } + $id_key = $entity_type->getKey('id'); + $revision_key = $entity_type->getKey('revision'); + $bundle_key = $entity_type->getKey('bundle'); + $uuid_key = $entity_type->getKey('uuid'); + $langcode_key = $entity_type->getKey('langcode'); - /** - * Gets the entity type's storage schema object. - * - * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type - * A content entity type definition. - * - * @return \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema - * The schema handler object. - */ - protected static function getStorageSchemaHandler(ContentEntityTypeInterface $entity_type) { - $entity_manager = \Drupal::entityManager(); - $storage = $entity_manager->getStorage($entity_type->id()); - $database = \Drupal::database(); + $shared_table_definitions = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) { + return $table_mapping->allowsSharedTableStorage($definition); + }); - $class = $entity_type->getHandlerClass('storage_schema') ?: 'Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema'; - return new $class($entity_manager, $entity_type, $storage, $database); - } + $key_fields = array_values(array_filter(array($id_key, $revision_key, $bundle_key, $uuid_key, $langcode_key))); + $all_fields = array_keys($shared_table_definitions); + // Make sure the key fields come first in the list of fields. + $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields)); - /** - * Returns the representation of a variable with short style array syntax. - * - * @param mixed $var - * The variable to export. - * @param string $indent - * (optional) And optional string to use for indentation. Defaults to an - * empty string. - * - * @return mixed - * The variable representation when the return parameter is used and - * evaluates to TRUE. - */ - public static function varExportShortArraySyntax($var, $indent = '') { - switch (gettype($var)) { - case "string": - return "'" . addcslashes($var, "\\\$\"\r\n\t\v\f") . "'"; - case "array": - $indexed = array_keys($var) === range(0, count($var) - 1); - $r = []; - foreach ($var as $key => $value) { - $r[] = "$indent " - . ($indexed ? '' : self::varExportShortArraySyntax($key) . ' => ') - . self::varExportShortArraySyntax($value, "$indent "); - } - return "[\n" . implode(",\n", $r) . (!empty($r) ? ',': '') . "\n" . $indent . ']'; - case "boolean": - return $var ? "TRUE" : "FALSE"; - default: - return var_export($var, TRUE); + if (!$translatable) { + // The base layout stores all the base field values in the base table. + $table_mapping->setFieldNames($base_table, $all_fields); } + else { + // Multilingual layouts store key field values in the base table. The + // other base field values are stored in the data table, no matter + // whether they are translatable or not. The data table holds also a + // denormalized copy of the bundle field value to allow for more + // performant queries. This means that only the UUID is not stored on + // the data table. + $table_mapping + ->setFieldNames($base_table, $key_fields) + ->setFieldNames($data_table, array_values(array_diff($all_fields, array($uuid_key)))); + } + + // Add dedicated tables. + $dedicated_table_definitions = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) { + return $table_mapping->requiresDedicatedTableStorage($definition); + }); + $extra_columns = array( + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'langcode', + 'delta', + ); + foreach ($dedicated_table_definitions as $field_name => $definition) { + $tables = [$table_mapping->getDedicatedDataTableName($definition)]; + foreach ($tables as $table_name) { + $table_mapping->setFieldNames($table_name, array($field_name)); + $table_mapping->setExtraColumns($table_name, $extra_columns); + } + } + + return $table_mapping; } } diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index 0854220..8d13c5a 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -267,6 +267,18 @@ public function setEntityType(EntityTypeInterface $entity_type) { } /** + * Sets the wrapped table mapping definition. + * + * @param \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping + * The table mapping. + * + * @internal Only to be used internally by Entity API. + */ + public function setTableMapping(TableMappingInterface $table_mapping) { + $this->tableMapping = $table_mapping; + } + + /** * {@inheritdoc} */ public function getTableMapping(array $storage_definitions = NULL) { diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php index 6a9e4e1..5ea3880 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php @@ -274,6 +274,12 @@ protected function getSchemaFromStorageDefinition(FieldStorageDefinitionInterfac * {@inheritdoc} */ public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + // Check if the entity type specifies that data migration is being handled + // elsewhere. + if (isset($entity_type->requiresDataMigration) && !$entity_type->requiresDataMigration) { + return FALSE; + } + // If the original storage has existing entities, or it is impossible to // determine if that is the case, require entity data to be migrated. $original_storage_class = $original->getStorageClass(); @@ -1237,10 +1243,14 @@ protected function deleteDedicatedTableSchema(FieldStorageDefinitionInterface $s $deleted = !$this->originalDefinitions; $table_mapping = $this->storage->getTableMapping(); $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $deleted); - $this->database->schema()->dropTable($table_name); + if ($this->database->schema()->tableExists($table_name)) { + $this->database->schema()->dropTable($table_name); + } if ($this->entityType->isRevisionable()) { $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $deleted); - $this->database->schema()->dropTable($revision_name); + if ($this->database->schema()->tableExists($revision_name)) { + $this->database->schema()->dropTable($revision_name); + } } $this->deleteFieldSchemaData($storage_definition); } diff --git a/core/lib/Drupal/Core/Entity/Sql/TemporaryTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/TemporaryTableMapping.php new file mode 100644 index 0000000..b0f01e0 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Sql/TemporaryTableMapping.php @@ -0,0 +1,45 @@ + 48) { + $short_table_name = substr($table_name, 0, 34); + $table_hash = substr(hash('sha256', $table_name), 0, 10); + + $tmp_table_name = $prefix . $short_table_name . $table_hash; + } + return $tmp_table_name; + } + +} diff --git a/core/modules/system/tests/modules/entity_test_revisionable_schema_converter/entity_test_revisionable_schema_converter.install b/core/modules/system/tests/modules/entity_test_revisionable_schema_converter/entity_test_revisionable_schema_converter.install deleted file mode 100644 index 15ca20a..0000000 --- a/core/modules/system/tests/modules/entity_test_revisionable_schema_converter/entity_test_revisionable_schema_converter.install +++ /dev/null @@ -1,698 +0,0 @@ - [ - 'description' => 'The base table for entity_test_to_rev_conversion entities.', - 'primary key' => ['id',], - 'indexes' => [], - 'foreign keys' => [ - 'entity_test_to_rev_conversion__revision' => [ - 'table' => 'entity_test_to_rev_conversion_revision', - 'columns' => ['revision_id' => 'revision_id',], - ], - ], - 'unique keys' => [ - 'entity_test_to_rev_conversion__revision_id' => ['revision_id',], - 'entity_test_to_rev_conversion_field__uuid__value' => ['uuid',], - ], - 'fields' => [ - 'id' => [ - 'type' => 'serial', - 'unsigned' => TRUE, - 'size' => 'normal', - 'not null' => TRUE, - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'size' => 'normal', - 'not null' => FALSE, - ], - 'type' => [ - 'type' => 'varchar', - 'length' => 255, - 'binary' => FALSE, - 'not null' => TRUE, - ], - 'uuid' => [ - 'type' => 'varchar_ascii', - 'length' => 128, - 'binary' => FALSE, - 'not null' => TRUE, - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 12, - 'not null' => TRUE, - ], - ], - ], - 'entity_test_to_rev_conversion_revision' => [ - 'description' => 'The revision table for entity_test_to_rev_conversion entities.', - 'primary key' => ['revision_id',], - 'indexes' => ['entity_test_to_rev_conversion__id' => ['id',],], - 'foreign keys' => [ - 'entity_test_to_rev_conversion__revisioned' => [ - 'table' => 'entity_test_to_rev_conversion', - 'columns' => ['id' => 'id',], - ], - ], - 'fields' => [ - 'id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'size' => 'normal', - 'not null' => TRUE, - ], - 'revision_id' => [ - 'type' => 'serial', - 'unsigned' => TRUE, - 'size' => 'normal', - 'not null' => TRUE, - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 12, - 'not null' => TRUE, - ], - ], - 'unique keys' => [], - ], - 'entity_test_to_rev_conversion_field_data' => [ - 'description' => 'The data table for entity_test_to_rev_conversion entities.', - 'primary key' => ['id', 'langcode',], - 'indexes' => [ - 'entity_test_to_rev_conversion__id__default_langcode__langcode' => [ - 'id', - 'default_langcode', - 'langcode', - ], - 'entity_test_to_rev_conversion__revision_id' => ['revision_id',], - 'entity_test_to_rev_conversion__f655316315' => ['user_id',], - 'entity_test_to_rev_conversion__34eed4023e' => ['content_translation_uid',], - ], - 'foreign keys' => [ - 'entity_test_to_rev_conversion' => [ - 'table' => 'entity_test_to_rev_conversion', - 'columns' => ['id' => 'id',], - ], - 'entity_test_to_rev_conversion_field__test_multiple_properties__shape' => [ - 'table' => 'shape', - 'columns' => ['test_multiple_properties__shape' => 'id',], - ], - ], - 'fields' => [ - 'id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'size' => 'normal', - 'not null' => TRUE, - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'size' => 'normal', - 'not null' => TRUE, - ], - 'type' => [ - 'type' => 'varchar', - 'length' => 255, - 'binary' => FALSE, - 'not null' => TRUE, - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 12, - 'not null' => TRUE, - ], - 'name' => [ - 'type' => 'varchar', - 'length' => 32, - 'binary' => FALSE, - 'not null' => FALSE, - ], - 'created' => ['type' => 'int', 'not null' => FALSE,], - 'user_id' => [ - 'description' => 'The ID of the target entity.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - ], - 'test_single_property' => [ - 'type' => 'varchar', - 'length' => 255, - 'binary' => FALSE, - 'not null' => FALSE, - ], - 'test_multiple_properties__shape' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'test_multiple_properties__color' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'default_langcode' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - ], - 'content_translation_source' => [ - 'type' => 'varchar_ascii', - 'length' => 12, - 'not null' => FALSE, - ], - 'content_translation_outdated' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => FALSE, - ], - 'content_translation_uid' => [ - 'description' => 'The ID of the target entity.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - ], - 'content_translation_status' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => FALSE, - ], - 'content_translation_changed' => [ - 'type' => 'int', - 'not null' => FALSE, - ], - ], - 'unique keys' => [], - ], - 'entity_test_to_rev_conversion_field_revision' => [ - 'description' => 'The revision data table for entity_test_to_rev_conversion entities.', - 'primary key' => ['revision_id', 'langcode',], - 'indexes' => [ - 'entity_test_to_rev_conversion__id__default_langcode__langcode' => [ - 'id', - 'default_langcode', - 'langcode', - ], - 'entity_test_to_rev_conversion__34eed4023e' => ['content_translation_uid',], - ], - 'foreign keys' => [ - 'entity_test_to_rev_conversion' => [ - 'table' => 'entity_test_to_rev_conversion', - 'columns' => ['id' => 'id',], - ], - 'entity_test_to_rev_conversion__revision' => [ - 'table' => 'entity_test_to_rev_conversion_revision', - 'columns' => ['revision_id' => 'revision_id',], - ], - 'entity_test_to_rev_conversion_field__test_multiple_properties__shape' => [ - 'table' => 'shape', - 'columns' => ['test_multiple_properties__shape' => 'id',], - ], - ], - 'fields' => [ - 'id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'size' => 'normal', - 'not null' => TRUE, - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'size' => 'normal', - 'not null' => TRUE, - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 12, - 'not null' => TRUE, - ], - 'test_single_property' => [ - 'type' => 'varchar', - 'length' => 255, - 'binary' => FALSE, - 'not null' => FALSE, - ], - 'test_multiple_properties__shape' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'test_multiple_properties__color' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'default_langcode' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - ], - 'content_translation_source' => [ - 'type' => 'varchar_ascii', - 'length' => 12, - 'not null' => FALSE, - ], - 'content_translation_outdated' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => FALSE, - ], - 'content_translation_uid' => [ - 'description' => 'The ID of the target entity.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - ], - 'content_translation_status' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => FALSE, - ], - 'content_translation_changed' => [ - 'type' => 'int', - 'not null' => FALSE, - ], - ], - 'unique keys' => [], - ], - 'entity_test_to_rev_conversion__a37a3596ca' => [ - 'description' => 'Data storage for entity_test_to_rev_conversion field test_single_property_multiple_values.', - 'fields' => [ - 'bundle' => [ - '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' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'A boolean indicating whether this data item has been deleted', - ], - 'entity_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity id this data is attached to', - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity revision id this data is attached to', - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The language code for this data item.', - ], - 'delta' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The sequence number for this data item, used for multi-value fields', - ], - 'test_single_property_multiple_values_value' => [ - 'type' => 'varchar', - 'length' => 255, - 'binary' => FALSE, - 'not null' => TRUE, - ], - ], - 'primary key' => ['entity_id', 'deleted', 'delta', 'langcode',], - 'indexes' => [ - 'bundle' => ['bundle',], - 'revision_id' => ['revision_id',], - ], - ], - 'entity_test_to_rev_conversion_r__a37a3596ca' => [ - 'description' => 'Revision archive storage for entity_test_to_rev_conversion field test_single_property_multiple_values.', - 'fields' => [ - 'bundle' => [ - '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' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'A boolean indicating whether this data item has been deleted', - ], - 'entity_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity id this data is attached to', - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity revision id this data is attached to', - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The language code for this data item.', - ], - 'delta' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The sequence number for this data item, used for multi-value fields', - ], - 'test_single_property_multiple_values_value' => [ - 'type' => 'varchar', - 'length' => 255, - 'binary' => FALSE, - 'not null' => TRUE, - ], - ], - 'primary key' => [ - 'entity_id', - 'revision_id', - 'deleted', - 'delta', - 'langcode', - ], - 'indexes' => [ - 'bundle' => ['bundle',], - 'revision_id' => ['revision_id',], - ], - ], - 'entity_test_to_rev_conversion__26859c2758' => [ - 'description' => 'Data storage for entity_test_to_rev_conversion field test_multiple_properties_multiple_values.', - 'fields' => [ - 'bundle' => [ - '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' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'A boolean indicating whether this data item has been deleted', - ], - 'entity_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity id this data is attached to', - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity revision id this data is attached to', - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The language code for this data item.', - ], - 'delta' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The sequence number for this data item, used for multi-value fields', - ], - 'test_multiple_properties_multiple_values_shape' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'test_multiple_properties_multiple_values_color' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - ], - 'primary key' => ['entity_id', 'deleted', 'delta', 'langcode',], - 'indexes' => [ - 'bundle' => ['bundle',], - 'revision_id' => ['revision_id',], - ], - 'foreign keys' => [ - 'test_multiple_properties_multiple_values_shape' => [ - 'table' => 'shape', - 'columns' => ['test_multiple_properties_multiple_values_shape' => 'id',], - ], - ], - ], - 'entity_test_to_rev_conversion_r__26859c2758' => [ - 'description' => 'Revision archive storage for entity_test_to_rev_conversion field test_multiple_properties_multiple_values.', - 'fields' => [ - 'bundle' => [ - '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' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'A boolean indicating whether this data item has been deleted', - ], - 'entity_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity id this data is attached to', - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity revision id this data is attached to', - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The language code for this data item.', - ], - 'delta' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The sequence number for this data item, used for multi-value fields', - ], - 'test_multiple_properties_multiple_values_shape' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'test_multiple_properties_multiple_values_color' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - ], - 'primary key' => [ - 'entity_id', - 'revision_id', - 'deleted', - 'delta', - 'langcode', - ], - 'indexes' => [ - 'bundle' => ['bundle',], - 'revision_id' => ['revision_id',], - ], - 'foreign keys' => [ - 'test_multiple_properties_multiple_values_shape' => [ - 'table' => 'shape', - 'columns' => ['test_multiple_properties_multiple_values_shape' => 'id',], - ], - ], - ], - 'entity_test_to_rev_conversion__e37161fa29' => [ - 'description' => 'Data storage for entity_test_to_rev_conversion field field_test_configurable_field.', - 'fields' => [ - 'bundle' => [ - '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' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'A boolean indicating whether this data item has been deleted', - ], - 'entity_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity id this data is attached to', - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity revision id this data is attached to', - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The language code for this data item.', - ], - 'delta' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The sequence number for this data item, used for multi-value fields', - ], - 'field_test_configurable_field_shape' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'field_test_configurable_field_color' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - ], - 'primary key' => ['entity_id', 'deleted', 'delta', 'langcode',], - 'indexes' => [ - 'bundle' => ['bundle',], - 'revision_id' => ['revision_id',], - ], - 'foreign keys' => [ - 'field_test_configurable_field_shape' => [ - 'table' => 'shape', - 'columns' => ['field_test_configurable_field_shape' => 'id',], - ], - ], - ], - 'entity_test_to_rev_conversion_r__e37161fa29' => [ - 'description' => 'Revision archive storage for entity_test_to_rev_conversion field field_test_configurable_field.', - 'fields' => [ - 'bundle' => [ - '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' => [ - 'type' => 'int', - 'size' => 'tiny', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'A boolean indicating whether this data item has been deleted', - ], - 'entity_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity id this data is attached to', - ], - 'revision_id' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The entity revision id this data is attached to', - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The language code for this data item.', - ], - 'delta' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'description' => 'The sequence number for this data item, used for multi-value fields', - ], - 'field_test_configurable_field_shape' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'field_test_configurable_field_color' => [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - ], - 'primary key' => [ - 'entity_id', - 'revision_id', - 'deleted', - 'delta', - 'langcode', - ], - 'indexes' => [ - 'bundle' => ['bundle',], - 'revision_id' => ['revision_id',], - ], - 'foreign keys' => [ - 'field_test_configurable_field_shape' => [ - 'table' => 'shape', - 'columns' => ['field_test_configurable_field_shape' => 'id',], - ], - ], - ], - ]; - - $revisionableSchemaConverter = new \Drupal\Core\Entity\Sql\RevisionableSchemaConverter( - 'entity_test_to_rev_conversion', - \Drupal::entityTypeManager(), - \Drupal::entityDefinitionUpdateManager(), - \Drupal::service('entity.last_installed_schema.repository'), - \Drupal::database() - ); - - $revisionableSchemaConverter->convertSchema( - $sandbox, - $schema, - 'revision_id', - 'entity_test_to_rev_conversion_revision', - 'entity_test_to_rev_conversion_field_revision', - ['test_single_property', 'test_multiple_properties', 'test_single_property_multiple_values', 'test_multiple_properties_multiple_values'] - ); -} diff --git a/core/modules/system/tests/modules/entity_test_revisionable_schema_converter/entity_test_revisionable_schema_converter.post_update.php b/core/modules/system/tests/modules/entity_test_revisionable_schema_converter/entity_test_revisionable_schema_converter.post_update.php new file mode 100644 index 0000000..a4c5782 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test_revisionable_schema_converter/entity_test_revisionable_schema_converter.post_update.php @@ -0,0 +1,32 @@ +convertToRevisionable( + $sandbox, + 'entity_test_to_rev_conversion', + ['test_single_property', 'test_multiple_properties', 'test_single_property_multiple_values', 'test_multiple_properties_multiple_values']); +} + +/** + * @} End of "addtogroup updates-8.3.x". + */