use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\FieldStorageConfigInterface;
return FALSE;
}
- return $this->getSchemaFromStorageDefinition($storage_definition) != $this->loadFieldSchemaData($original);
+ $current_schema = $this->getSchemaFromStorageDefinition($storage_definition);
+ $this->processFieldStorageSchema($current_schema);
+ $installed_schema = $this->loadFieldSchemaData($original);
+ $this->processFieldStorageSchema($installed_schema);
+
+ return $current_schema != $installed_schema;
}
/**
* {@inheritdoc}
*/
public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+ // Check if the entity type specifies that data migration is being handled
+ // elsewhere.
+ if ($entity_type->get('requires_data_migration') === FALSE) {
+ 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();
}
// Create dedicated field tables.
- $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type->id());
- $table_mapping = $this->storage->getTableMapping($field_storage_definitions);
- foreach ($field_storage_definitions as $field_storage_definition) {
+ $table_mapping = $this->storage->getTableMapping($this->fieldStorageDefinitions);
+ foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
$this->createDedicatedTableSchema($field_storage_definition);
}
// We need to act only on shared entity schema tables.
$table_mapping = $this->storage->getTableMapping();
$table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames());
- $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
foreach ($table_names as $table_name) {
if (!isset($schema[$table_name])) {
$schema[$table_name] = [];
}
foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
- if (!isset($storage_definitions[$field_name])) {
+ if (!isset($this->fieldStorageDefinitions[$field_name])) {
throw new FieldException("Field storage definition for '$field_name' could not be found.");
}
// Add the schema for base field definitions.
- elseif ($table_mapping->allowsSharedTableStorage($storage_definitions[$field_name])) {
+ elseif ($table_mapping->allowsSharedTableStorage($this->fieldStorageDefinitions[$field_name])) {
$column_names = $table_mapping->getColumnNames($field_name);
- $storage_definition = $storage_definitions[$field_name];
+ $storage_definition = $this->fieldStorageDefinitions[$field_name];
$schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names));
}
}
// Add an index for the 'published' entity key.
if (is_subclass_of($entity_type->getClass(), EntityPublishedInterface::class)) {
$published_key = $entity_type->getKey('published');
- if ($published_key && !$storage_definitions[$published_key]->hasCustomStorage()) {
+ if ($published_key && !$this->fieldStorageDefinitions[$published_key]->hasCustomStorage()) {
$published_field_table = $table_mapping->getFieldTableName($published_key);
$id_key = $entity_type->getKey('id');
if ($bundle_key = $entity_type->getKey('bundle')) {
// Collect all possible field schema identifiers for shared table fields.
// These will be used to detect entity schema data in the subsequent loop.
$field_schema_identifiers = [];
- $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
- $table_mapping = $this->storage->getTableMapping($storage_definitions);
- foreach ($storage_definitions as $field_name => $storage_definition) {
+ $table_mapping = $this->storage->getTableMapping($this->fieldStorageDefinitions);
+ foreach ($this->fieldStorageDefinitions as $field_name => $storage_definition) {
if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
// Make sure both base identifier names and suffixed names are listed.
$name = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name);
* The field schema data array.
*/
protected function saveFieldSchemaData(FieldStorageDefinitionInterface $storage_definition, $schema) {
+ $this->processFieldStorageSchema($schema);
$this->installedStorageSchema()->set($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), $schema);
}
unset($schema['fields'][$key]['default']);
}
+ /**
+ * Processes the schema for a field storage definition.
+ *
+ * @param array &$field_storage_schema
+ * An array that contains the schema data for a field storage definition.
+ */
+ protected function processFieldStorageSchema(array &$field_storage_schema) {
+ // Clean up some schema properties that should not be taken into account
+ // after a field storage has been created.
+ foreach ($field_storage_schema as $table_name => $table_schema) {
+ foreach ($table_schema['fields'] as $key => $schema) {
+ unset($field_storage_schema[$table_name]['fields'][$key]['initial']);
+ unset($field_storage_schema[$table_name]['fields'][$key]['initial_from_field']);
+ }
+ }
+ }
+
/**
* Performs the specified operation on a field.
*
$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);
+ $revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $deleted);
+ if ($this->database->schema()->tableExists($revision_table_name)) {
+ $this->database->schema()->dropTable($revision_table_name);
+ }
}
$this->deleteFieldSchemaData($storage_definition);
}
// involving them. Only indexes for which all columns exist are
// actually created.
$create = FALSE;
- $specifier_columns = array_map(function($item) { return is_string($item) ? $item : reset($item); }, $specifier);
+ $specifier_columns = array_map(function ($item) {
+ return is_string($item) ? $item : reset($item);
+ }, $specifier);
if (!isset($column_names) || array_intersect($specifier_columns, $column_names)) {
$create = TRUE;
foreach ($specifier_columns as $specifier_column_name) {
foreach ($index_keys as $key => $drop_method) {
if (!empty($schema[$key])) {
foreach ($schema[$key] as $name => $specifier) {
- $specifier_columns = array_map(function($item) { return is_string($item) ? $item : reset($item); }, $specifier);
+ $specifier_columns = array_map(function ($item) {
+ return is_string($item) ? $item : reset($item);
+ }, $specifier);
if (!isset($column_names) || array_intersect($specifier_columns, $column_names)) {
$schema_handler->{$drop_method}($table_name, $name);
}
* - foreign keys: The schema definition for the foreign keys.
*
* @throws \Drupal\Core\Field\FieldException
- * Exception thrown if the schema contains reserved column names.
+ * Exception thrown if the schema contains reserved column names or if the
+ * initial values definition is invalid.
*/
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
$schema = [];
+ $table_mapping = $this->storage->getTableMapping();
$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())) {
+ if (array_intersect(array_keys($field_schema['columns']), $table_mapping->getReservedColumns())) {
throw new FieldException("Illegal field column names on {$storage_definition->getName()}");
}
$field_name = $storage_definition->getName();
$base_table = $this->storage->getBaseTable();
+ // Define the initial values, if any.
+ $initial_value = $initial_value_from_field = [];
+ $storage_definition_is_new = empty($this->loadFieldSchemaData($storage_definition));
+ if ($storage_definition_is_new && $storage_definition instanceof BaseFieldDefinition && $table_mapping->allowsSharedTableStorage($storage_definition)) {
+ if (($initial_storage_value = $storage_definition->getInitialValue()) && !empty($initial_storage_value)) {
+ // We only support initial values for fields that are stored in shared
+ // tables (i.e. single-value fields).
+ // @todo Implement initial value support for multi-value fields in
+ // https://www.drupal.org/node/2883851.
+ $initial_value = reset($initial_storage_value);
+ }
+
+ if ($initial_value_field_name = $storage_definition->getInitialValueFromField()) {
+ // Check that the field used for populating initial values is valid. We
+ // must use the last installed version of that, as the new field might
+ // be created in an update function and the storage definition of the
+ // "from" field might get changed later.
+ $last_installed_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id());
+ if (!isset($last_installed_storage_definitions[$initial_value_field_name])) {
+ throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: The field $initial_value_field_name does not exist.");
+ }
+
+ if ($storage_definition->getType() !== $last_installed_storage_definitions[$initial_value_field_name]->getType()) {
+ throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: The field types do not match.");
+ }
+
+ if (!$table_mapping->allowsSharedTableStorage($last_installed_storage_definitions[$initial_value_field_name])) {
+ throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: Both fields have to be stored in the shared entity tables.");
+ }
+
+ $initial_value_from_field = $table_mapping->getColumnNames($initial_value_field_name);
+ }
+ }
+
// A shared table contains rows for entities where the field is empty
// (since other fields stored in the same table might not be empty), thus
// the only columns that can be 'not null' are those for required
- // properties of required fields. However, even those would break in the
- // case where a new field is added to a table that contains existing rows.
- // For now, we only hardcode 'not null' to a couple "entity keys", in order
- // to keep their indexes optimized.
- // @todo Revisit once we have support for 'initial' in
- // https://www.drupal.org/node/2346019.
+ // properties of required fields. For now, we only hardcode 'not null' to a
+ // few "entity keys", in order to keep their indexes optimized.
+ // @todo Fix this in https://www.drupal.org/node/2841291.
$not_null_keys = $this->entityType->getKeys();
- // Label fields are not necessarily required.
- unset($not_null_keys['label']);
+ // Label and the 'revision_translation_affected' fields are not necessarily
+ // required.
+ unset($not_null_keys['label'], $not_null_keys['revision_translation_affected']);
// Because entity ID and revision ID are both serial fields in the base and
// revision table respectively, the revision ID is not known yet, when
// inserting data into the base table. Instead the revision ID in the base
$schema['fields'][$schema_field_name] = $column_schema;
$schema['fields'][$schema_field_name]['not null'] = in_array($field_name, $not_null_keys);
+
+ // Use the initial value of the field storage, if available.
+ if ($initial_value && isset($initial_value[$field_column_name])) {
+ $schema['fields'][$schema_field_name]['initial'] = drupal_schema_get_field_value($column_schema, $initial_value[$field_column_name]);
+ }
+ elseif (!empty($initial_value_from_field)) {
+ $schema['fields'][$schema_field_name]['initial_from_field'] = $initial_value_from_field[$field_column_name];
+ }
}
if (!empty($field_schema['indexes'])) {