Issue #3024728 by amateescu, plach: Preserve the backup tables after an entity type schema conversion process
(cherry picked from commit d938b0a8ec
)
8.7.x
parent
cd16c34cd5
commit
d0b603465c
|
@ -17,6 +17,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
|
|||
use Drupal\Core\Field\FieldException;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* Defines a schema handler that supports revisionable, translatable entities.
|
||||
|
@ -95,6 +96,13 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
*/
|
||||
protected $installedStorageSchema;
|
||||
|
||||
/**
|
||||
* The key-value collection for tracking entity update backup repository.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
*/
|
||||
protected $updateBackupRepository;
|
||||
|
||||
/**
|
||||
* The deleted fields repository.
|
||||
*
|
||||
|
@ -163,6 +171,23 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
return $this->deletedFieldsRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key/value collection for tracking the entity update backups.
|
||||
*
|
||||
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
* A key/value collection.
|
||||
*
|
||||
* @todo Inject this dependency in the constructor once this class can be
|
||||
* instantiated as a regular entity handler.
|
||||
* @see https://www.drupal.org/node/2332857
|
||||
*/
|
||||
protected function updateBackupRepository() {
|
||||
if (!isset($this->updateBackupRepository)) {
|
||||
$this->updateBackupRepository = \Drupal::keyValue('entity.update_backup');
|
||||
}
|
||||
return $this->updateBackupRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the table mapping with updated definitions.
|
||||
*
|
||||
|
@ -432,7 +457,11 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
$sandbox['temporary_table_mapping'] = $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions, $temporary_prefix);
|
||||
$sandbox['new_table_mapping'] = $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions);
|
||||
$sandbox['original_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions);
|
||||
$sandbox['backup_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions, 'old_');
|
||||
|
||||
$backup_prefix = static::getTemporaryTableMappingPrefix($original, $original_field_storage_definitions, 'old_');
|
||||
$sandbox['backup_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions, $backup_prefix);
|
||||
$sandbox['backup_prefix_key'] = substr($backup_prefix, 4);
|
||||
$sandbox['backup_request_time'] = \Drupal::time()->getRequestTime();
|
||||
|
||||
// Create temporary tables based on the new entity type and field storage
|
||||
// definitions.
|
||||
|
@ -554,11 +583,22 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
}
|
||||
|
||||
// At this point the update process either finished successfully or any
|
||||
// error has been thrown already, so we can drop the backup entity tables.
|
||||
// @todo Decide whether we should keep these tables around.
|
||||
// @see https://www.drupal.org/project/drupal/issues/3024728
|
||||
foreach ($backup_table_names as $original_table_name => $backup_table_name) {
|
||||
$this->database->schema()->dropTable($backup_table_name);
|
||||
// error has been thrown already. We can either keep the backup tables in
|
||||
// place or drop them.
|
||||
if (Settings::get('entity_update_backup', TRUE)) {
|
||||
$backup_key = $sandbox['backup_prefix_key'];
|
||||
$backup = [
|
||||
'entity_type' => $original,
|
||||
'field_storage_definitions' => $original_field_storage_definitions,
|
||||
'table_mapping' => $backup_table_mapping,
|
||||
'request_time' => $sandbox['backup_request_time'],
|
||||
];
|
||||
$this->updateBackupRepository()->set("{$original->id()}.$backup_key", $backup);
|
||||
}
|
||||
else {
|
||||
foreach ($backup_table_names as $original_table_name => $backup_table_name) {
|
||||
$this->database->schema()->dropTable($backup_table_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -582,13 +622,15 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
* An entity type definition.
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storage_definitions
|
||||
* An array of field storage definitions.
|
||||
* @param string $first_prefix_part
|
||||
* (optional) The first part of the prefix. Defaults to 'tmp_'.
|
||||
*
|
||||
* @return string
|
||||
* A temporary table mapping prefix.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function getTemporaryTableMappingPrefix(EntityTypeInterface $entity_type, array $field_storage_definitions) {
|
||||
public static function getTemporaryTableMappingPrefix(EntityTypeInterface $entity_type, array $field_storage_definitions, $first_prefix_part = 'tmp_') {
|
||||
// Construct a unique prefix based on the contents of the entity type and
|
||||
// field storage definitions.
|
||||
$prefix_parts[] = spl_object_hash($entity_type);
|
||||
|
@ -598,7 +640,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
$prefix_parts[] = \Drupal::time()->getRequestTime();
|
||||
$hash = hash('sha256', implode('', $prefix_parts));
|
||||
|
||||
return 'tmp_' . substr($hash, 0, 6);
|
||||
return $first_prefix_part . substr($hash, 0, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -165,6 +165,9 @@ class FieldableEntityDefinitionUpdateTest extends EntityKernelTestBase {
|
|||
// Check that we can still save new entities after the schema has been
|
||||
// updated.
|
||||
$this->insertData($new_rev, $new_mul);
|
||||
|
||||
// Check that the backup tables have been kept in place.
|
||||
$this->assertBackupTables();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -569,10 +572,25 @@ class FieldableEntityDefinitionUpdateTest extends EntityKernelTestBase {
|
|||
$this->assertFalse($database_schema->tableExists($entity_type->getRevisionDataTable()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the backup tables have been kept after a successful update.
|
||||
*/
|
||||
protected function assertBackupTables() {
|
||||
$backups = \Drupal::keyValue('entity.update_backup')->getAll();
|
||||
$backup = reset($backups);
|
||||
|
||||
$schema = $this->database->schema();
|
||||
foreach ($backup['table_mapping']->getTableNames() as $table_name) {
|
||||
$this->assertTrue($schema->tableExists($table_name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a failed entity schema update preserves the existing data.
|
||||
*/
|
||||
public function testFieldableEntityTypeUpdatesErrorHandling() {
|
||||
$schema = $this->database->schema();
|
||||
|
||||
// First, convert the entity type to be translatable for better coverage and
|
||||
// insert some initial data.
|
||||
$entity_type = $this->getUpdatedEntityTypeDefinition(FALSE, TRUE);
|
||||
|
@ -581,6 +599,12 @@ class FieldableEntityDefinitionUpdateTest extends EntityKernelTestBase {
|
|||
$this->assertEntityTypeSchema(FALSE, TRUE);
|
||||
$this->insertData(FALSE, TRUE);
|
||||
|
||||
$tables = $schema->findTables('old_%');
|
||||
$this->assertCount(3, $tables);
|
||||
foreach ($tables as $table) {
|
||||
$schema->dropTable($table);
|
||||
}
|
||||
|
||||
$original_entity_type = $this->lastInstalledSchemaRepository->getLastInstalledDefinition('entity_test_update');
|
||||
$original_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update');
|
||||
|
||||
|
@ -640,14 +664,19 @@ class FieldableEntityDefinitionUpdateTest extends EntityKernelTestBase {
|
|||
$this->assertEquals($original_field_schema_data, $new_field_schema_data);
|
||||
|
||||
// Check that temporary tables have been removed.
|
||||
$schema = $this->database->schema();
|
||||
$temporary_table_names = $storage->getCustomTableMapping($new_entity_type, $new_storage_definitions, 'tmp_')->getTableNames();
|
||||
$current_table_names = $storage->getCustomTableMapping($new_entity_type, $new_storage_definitions)->getTableNames();
|
||||
foreach (array_combine($temporary_table_names, $current_table_names) as $temp_table_name => $table_name) {
|
||||
$tables = $schema->findTables('tmp_%');
|
||||
$this->assertCount(0, $tables);
|
||||
|
||||
$current_table_names = $storage->getCustomTableMapping($original_entity_type, $original_storage_definitions)->getTableNames();
|
||||
foreach ($current_table_names as $table_name) {
|
||||
$this->assertTrue($schema->tableExists($table_name));
|
||||
$this->assertFalse($schema->tableExists($temp_table_name));
|
||||
}
|
||||
|
||||
// Check that backup tables do not exist anymore, since they were
|
||||
// restored/renamed.
|
||||
$tables = $schema->findTables('old_%');
|
||||
$this->assertCount(0, $tables);
|
||||
|
||||
// Check that the original tables still exist and their data is intact.
|
||||
$this->assertTrue($schema->tableExists('entity_test_update'));
|
||||
$this->assertTrue($schema->tableExists('entity_test_update_data'));
|
||||
|
@ -723,4 +752,37 @@ class FieldableEntityDefinitionUpdateTest extends EntityKernelTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the removal of the backup tables after a successful update.
|
||||
*/
|
||||
public function testFieldableEntityTypeUpdatesRemoveBackupTables() {
|
||||
$schema = $this->database->schema();
|
||||
|
||||
// Convert the entity type to be revisionable.
|
||||
$entity_type = $this->getUpdatedEntityTypeDefinition(TRUE, FALSE);
|
||||
$field_storage_definitions = $this->getUpdatedFieldStorageDefinitions(TRUE, FALSE);
|
||||
$this->entityDefinitionUpdateManager->updateFieldableEntityType($entity_type, $field_storage_definitions);
|
||||
|
||||
// Check that backup tables are kept by default.
|
||||
$tables = $schema->findTables('old_%');
|
||||
$this->assertCount(3, $tables);
|
||||
foreach ($tables as $table) {
|
||||
$schema->dropTable($table);
|
||||
}
|
||||
|
||||
// Make the entity update process drop the backup tables after a successful
|
||||
// update.
|
||||
$settings = Settings::getAll();
|
||||
$settings['entity_update_backup'] = FALSE;
|
||||
new Settings($settings);
|
||||
|
||||
$entity_type = $this->getUpdatedEntityTypeDefinition(TRUE, TRUE);
|
||||
$field_storage_definitions = $this->getUpdatedFieldStorageDefinitions(TRUE, TRUE);
|
||||
$this->entityDefinitionUpdateManager->updateFieldableEntityType($entity_type, $field_storage_definitions);
|
||||
|
||||
// Check that backup tables have been dropped.
|
||||
$tables = $schema->findTables('old_%');
|
||||
$this->assertCount(0, $tables);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -754,6 +754,15 @@ $settings['file_scan_ignore_directories'] = [
|
|||
*/
|
||||
$settings['entity_update_batch_size'] = 50;
|
||||
|
||||
/**
|
||||
* Entity update backup.
|
||||
*
|
||||
* This is used to inform the entity storage handler that the backup tables as
|
||||
* well as the original entity type and field storage definitions should be
|
||||
* retained after a successful entity update process.
|
||||
*/
|
||||
$settings['entity_update_backup'] = TRUE;
|
||||
|
||||
/**
|
||||
* Load local development override configuration, if available.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue