/** * {@inheritdoc} */ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { if (!$storage_definition->hasData()) { // There is no data. Re-create the tables completely. 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 { $original_schema = $this->_fieldSqlSchema($original); foreach ($original_schema as $name => $table) { $this->database->schema()->dropTable($name, $table); } $schema = $this->_fieldSqlSchema($storage_definition); foreach ($schema as $name => $table) { $this->database->schema()->createTable($name, $table); } } catch (\Exception $e) { if ($this->database->supportsTransactionalDDL()) { $transaction->rollback(); } else { // Recreate tables. $original_schema = $this->_fieldSqlSchema($original); foreach ($original_schema as $name => $table) { if (!$this->database->schema()->tableExists($name)) { $this->database->schema()->createTable($name, $table); } } } 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."); } // There is data, so there are no column changes. Drop all the prior // indexes and create all the new ones, except for all the priors that // exist unchanged. $table = static::_fieldTableName($original); $revision_table = static::_fieldRevisionTableName($original); $schema = $storage_definition->getSchema(); $original_schema = $original->getSchema(); foreach ($original_schema['indexes'] as $name => $columns) { if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) { $real_name = static::_fieldIndexName($storage_definition, $name); $this->database->schema()->dropIndex($table, $real_name); $this->database->schema()->dropIndex($revision_table, $real_name); } } $table = static::_fieldTableName($storage_definition); $revision_table = static::_fieldRevisionTableName($storage_definition); foreach ($schema['indexes'] as $name => $columns) { if (!isset($original_schema['indexes'][$name]) || $columns != $original_schema['indexes'][$name]) { $real_name = static::_fieldIndexName($storage_definition, $name); $real_columns = array(); 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)) { $real_columns[] = array(static::_fieldColumnName($storage_definition, $column_name[0]), $column_name[1]); } else { $real_columns[] = static::_fieldColumnName($storage_definition, $column_name); } } $this->database->schema()->addIndex($table, $real_name, $real_columns); $this->database->schema()->addIndex($revision_table, $real_name, $real_columns); } } } }
/** * 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); } }