/** * 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. * @throws \Exception * Rethrown exception if the table recreation fails. */ protected function updateSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { if (!$this->storage->countFieldData($original, 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 { // Since there is no data we may be switching from a dedicated table // to a schema table schema, hence we should use the proper API. $this->performFieldSchemaOperation('delete', $original); $this->performFieldSchemaOperation('create', $storage_definition); } catch (\Exception $e) { if ($this->database->supportsTransactionalDDL()) { $transaction->rollback(); } else { // Recreate original schema. $this->createSharedTableSchema($original); } throw $e; } } else { if ($this->hasColumnChanges($storage_definition, $original)) { throw new FieldStorageDefinitionUpdateForbiddenException('The SQL storage cannot change the schema for an existing field (' . $storage_definition->getName() . ' in ' . $storage_definition->getTargetEntityTypeId() . ' entity) with data.'); } $updated_field_name = $storage_definition->getName(); $table_mapping = $this->storage->getTableMapping(); $column_names = $table_mapping->getColumnNames($updated_field_name); $schema_handler = $this->database->schema(); // Iterate over the mapped table to find the ones that host the deleted // field schema. $original_schema = $this->loadFieldSchemaData($original); $schema = array(); foreach ($table_mapping->getTableNames() as $table_name) { foreach ($table_mapping->getFieldNames($table_name) as $field_name) { if ($field_name == $updated_field_name) { $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names); // Handle NOT NULL constraints. foreach ($schema[$table_name]['fields'] as $column_name => $specifier) { $not_null = !empty($specifier['not null']); $original_not_null = !empty($original_schema[$table_name]['fields'][$column_name]['not null']); if ($not_null !== $original_not_null) { if ($not_null && $this->hasNullFieldPropertyData($table_name, $column_name)) { throw new EntityStorageException("The {$column_name} column cannot have NOT NULL constraints as it holds NULL values."); } $column_schema = $original_schema[$table_name]['fields'][$column_name]; $column_schema['not null'] = $not_null; $schema_handler->changeField($table_name, $field_name, $field_name, $column_schema); } } // Drop original indexes and unique keys. if (!empty($original_schema[$table_name]['indexes'])) { foreach ($original_schema[$table_name]['indexes'] as $name => $specifier) { $schema_handler->dropIndex($table_name, $name); } } if (!empty($original_schema[$table_name]['unique keys'])) { foreach ($original_schema[$table_name]['unique keys'] as $name => $specifier) { $schema_handler->dropUniqueKey($table_name, $name); } } // Create new indexes and unique keys. if (!empty($schema[$table_name]['indexes'])) { foreach ($schema[$table_name]['indexes'] as $name => $specifier) { // Check if the index exists because it might already have been // created as part of the earlier entity type update event. $this->addIndex($table_name, $name, $specifier, $schema[$table_name]); } } if (!empty($schema[$table_name]['unique keys'])) { foreach ($schema[$table_name]['unique keys'] as $name => $specifier) { $schema_handler->addUniqueKey($table_name, $name, $specifier); } } // After deleting the field schema skip to the next table. break; } } } $this->saveFieldSchemaData($storage_definition, $schema); } }
/** * 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. * @throws \Exception * Rethrown exception if the table recreation fails. */ protected function updateSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { if (!$this->storage->countFieldData($original, 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 { // Since there is no data we may be switching from a dedicated table // to a schema table schema, hence we should use the proper API. $this->performFieldSchemaOperation('delete', $original); $this->performFieldSchemaOperation('create', $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."); } $updated_field_name = $storage_definition->getName(); $table_mapping = $this->storage->getTableMapping(); $column_names = $table_mapping->getColumnNames($updated_field_name); $schema_handler = $this->database->schema(); // Iterate over the mapped table to find the ones that host the deleted // field schema. $original_schema = $this->loadFieldSchemaData($original); $schema = array(); foreach ($table_mapping->getTableNames() as $table_name) { foreach ($table_mapping->getFieldNames($table_name) as $field_name) { if ($field_name == $updated_field_name) { $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names); // Drop original indexes and unique keys. if (!empty($original_schema[$table_name]['indexes'])) { foreach ($original_schema[$table_name]['indexes'] as $name => $specifier) { $schema_handler->dropIndex($table_name, $name); } } if (!empty($original_schema[$table_name]['unique keys'])) { foreach ($original_schema[$table_name]['unique keys'] as $name => $specifier) { $schema_handler->dropUniqueKey($table_name, $name); } } // Create new indexes and unique keys. if (!empty($schema[$table_name]['indexes'])) { foreach ($schema[$table_name]['indexes'] as $name => $specifier) { $schema_handler->addIndex($table_name, $name, $specifier); } } if (!empty($schema[$table_name]['unique keys'])) { foreach ($schema[$table_name]['unique keys'] as $name => $specifier) { $schema_handler->addUniqueKey($table_name, $name, $specifier); } } // After deleting the field schema skip to the next table. break; } } } $this->saveFieldSchemaData($storage_definition, $schema); } }