commit 6f7e110fe319f4cb399568feacfedea7be4b2da2 Author: Francesco Placella Date: Fri Aug 8 19:40:55 2014 +0200 DEV 1498720: Fixed switching between shared and dedicated field schema. diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index 2ff10c4..f276e8d 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -269,14 +269,15 @@ public function setEntityType(ContentEntityTypeInterface $entity_type) { /** * {@inheritdoc} */ - public function getTableMapping() { - if (!isset($this->tableMapping)) { - $storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId); + public function getTableMapping($storage_definitions = NULL) { + $table_mapping = $this->tableMapping; + + if (!isset($this->tableMapping) || $storage_definitions) { + $definitions = $storage_definitions ?: $this->entityManager->getFieldStorageDefinitions($this->entityTypeId); $base_field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityTypeId); - $table_mapping = new DefaultTableMapping($storage_definitions, $base_field_definitions); - $this->tableMapping = $table_mapping; + $table_mapping = new DefaultTableMapping($definitions, $base_field_definitions); - $definitions = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) { + $definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) { return $table_mapping->allowsSharedTableStorage($definition); }); @@ -306,16 +307,16 @@ public function getTableMapping() { $translatable = $this->entityType->getDataTable() && $this->entityType->isTranslatable(); if (!$revisionable && !$translatable) { // The base layout stores all the base field values in the base table. - $this->tableMapping->setFieldNames($this->baseTable, $all_fields); + $table_mapping->setFieldNames($this->baseTable, $all_fields); } elseif ($revisionable && !$translatable) { // The revisionable layout stores all the base field values in the base // table, except for revision metadata fields. Revisionable fields // denormalized in the base table but also stored in the revision table // together with the entity ID and the revision ID as identifiers. - $this->tableMapping->setFieldNames($this->baseTable, array_diff($all_fields, $revision_metadata_fields)); + $table_mapping->setFieldNames($this->baseTable, array_diff($all_fields, $revision_metadata_fields)); $revision_key_fields = array($this->idKey, $this->revisionKey); - $this->tableMapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields)); + $table_mapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields)); } elseif (!$revisionable && $translatable) { // Multilingual layouts store key field values in the base table. The @@ -324,7 +325,7 @@ public function getTableMapping() { // 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. - $this->tableMapping + $table_mapping ->setFieldNames($this->baseTable, $key_fields) ->setFieldNames($this->dataTable, array_values(array_diff($all_fields, array($this->uuidKey)))) // Add the denormalized 'default_langcode' field to the mapping. Its @@ -340,13 +341,13 @@ public function getTableMapping() { // holds the data field values for all non-revisionable fields. The data // field values of revisionable fields are denormalized in the data // table, as well. - $this->tableMapping->setFieldNames($this->baseTable, array_values(array_diff($key_fields, array($this->langcodeKey)))); + $table_mapping->setFieldNames($this->baseTable, array_values(array_diff($key_fields, array($this->langcodeKey)))); // Like in the multilingual, non-revisionable case the UUID is not // in the data table. Additionally, do not store revision metadata // fields in the data table. $data_fields = array_values(array_diff($all_fields, array($this->uuidKey), $revision_metadata_fields)); - $this->tableMapping + $table_mapping ->setFieldNames($this->dataTable, $data_fields) // Add the denormalized 'default_langcode' field to the mapping. Its // value is identical to the query expression @@ -355,11 +356,11 @@ public function getTableMapping() { ->setExtraColumns($this->dataTable, array('default_langcode')); $revision_base_fields = array_merge(array($this->idKey, $this->revisionKey, $this->langcodeKey), $revision_metadata_fields); - $this->tableMapping->setFieldNames($this->revisionTable, $revision_base_fields); + $table_mapping->setFieldNames($this->revisionTable, $revision_base_fields); $revision_data_key_fields = array($this->idKey, $this->revisionKey, $this->langcodeKey); $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, array($this->langcodeKey)); - $this->tableMapping + $table_mapping ->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields)) // Add the denormalized 'default_langcode' field to the mapping. Its // value is identical to the query expression @@ -368,7 +369,7 @@ public function getTableMapping() { } // Add dedicated tables. - $definitions = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) { + $definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) { return $table_mapping->requiresDedicatedTableStorage($definition); }); $extra_columns = array( @@ -385,9 +386,13 @@ public function getTableMapping() { $table_mapping->setExtraColumns($table_name, $extra_columns); } } + + if (!$storage_definitions) { + $this->tableMapping = $table_mapping; + } } - return $this->tableMapping; + return $table_mapping; } /** @@ -1614,25 +1619,54 @@ public function finalizePurge(FieldStorageDefinitionInterface $storage_definitio * {@inheritdoc} */ public function countFieldData($storage_definition, $as_bool = FALSE) { - $is_deleted = $this->storageDefinitionIsDeleted($storage_definition); $table_mapping = $this->getTableMapping(); - $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted); - $query = $this->database->select($table_name, 't'); - $or = $query->orConditionGroup(); - foreach ($storage_definition->getColumns() as $column_name => $data) { - $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name)); + if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) { + $is_deleted = $this->storageDefinitionIsDeleted($storage_definition); + $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted); + $query = $this->database->select($table_name, 't'); + $or = $query->orConditionGroup(); + foreach ($storage_definition->getColumns() as $column_name => $data) { + $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name)); + } + $query + ->condition($or) + ->fields('t', array('entity_id')) + ->distinct(TRUE); + // If we are performing the query just to check if the field has data + // limit the number of rows. + if ($as_bool) { + $query->range(0, 1); + } + $count = $query->countQuery()->execute()->fetchField(); } - $query - ->condition($or) - ->fields('t', array('entity_id')) - ->distinct(TRUE); - // If we are performing the query just to check if the field has data - // limit the number of rows. - if ($as_bool) { - $query->range(0, 1); + else { + if ($as_bool) { + $count = $this->hasData(); + } + else { + $data_table = $this->dataTable ?: $this->baseTable; + $query = $this->database->select($data_table, 't'); + $columns = $storage_definition->getColumns(); + if (count($columns) > 1) { + $or = $query->orConditionGroup(); + foreach ($columns as $column_name => $data) { + $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name)); + } + $query->condition($or); + } + else { + $query->isNotNull($storage_definition->getName()); + } + $count = $query + ->fields('t', array($this->idKey)) + ->distinct(TRUE) + ->countQuery() + ->execute() + ->fetchField(); + } } - $count = $query->countQuery()->execute()->fetchField(); + return $as_bool ? (bool) $count : (int) $count; } diff --git a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php index c7ad754..2c66986 100644 --- a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php +++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php @@ -44,6 +44,14 @@ class ContentEntitySchemaHandler implements ContentEntitySchemaHandlerInterface protected $fieldStorageDefinitions; /** + * The original storage field definitions for this entity type. Used during + * field schema updates. + * + * @var \Drupal\Core\Field\FieldDefinitionInterface[] + */ + protected $originalDefinitions; + + /** * The storage object for the given entity type. * * @var \Drupal\Core\Entity\ContentEntityDatabaseStorage @@ -504,10 +512,11 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor $column_names = $table_mapping->getColumnNames($created_field_name); $schema = $this->getSharedTableFieldSchema($storage_definition, $column_names); $keys = array_diff_key($schema, array('fields' => FALSE)); + $shared_table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames()); // Iterate over the mapped table to find the ones that will host the created // field schema. - foreach ($table_mapping->getTableNames() as $table_name) { + foreach ($shared_table_names as $table_name) { foreach ($table_mapping->getFieldNames($table_name) as $field_name) { if ($field_name == $created_field_name) { foreach ($schema['fields'] as $column_name => $specifier) { @@ -553,9 +562,13 @@ public function deleteFieldSchema(FieldStorageDefinitionInterface $storage_defin * The storage definition of the field being deleted. */ protected function deleteDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition) { + // When switching from dedicated to shared field table layout we need need + // to delete the field tables with their regular names. When this happens + // original definitions will be defined. + $deleted = !$this->originalDefinitions; $table_mapping = $this->storage->getTableMapping(); - $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE); - $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE); + $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $deleted); + $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $deleted); $this->database->schema()->dropTable($table_name); $this->database->schema()->dropTable($revision_name); } @@ -568,14 +581,15 @@ protected function deleteDedicatedTableSchema(FieldStorageDefinitionInterface $s */ protected function deleteSharedTableSchema(FieldStorageDefinitionInterface $storage_definition) { $deleted_field_name = $storage_definition->getName(); - $table_mapping = $this->storage->getTableMapping(); + $table_mapping = $this->storage->getTableMapping($this->originalDefinitions); $column_names = $table_mapping->getColumnNames($deleted_field_name); $schema = $this->getSharedTableFieldSchema($storage_definition, $column_names); $schema_handler = $this->database->schema(); + $shared_table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames()); // Iterate over the mapped table to find the ones that host the deleted // field schema. - foreach ($table_mapping->getTableNames() as $table_name) { + foreach ($shared_table_names as $table_name) { foreach ($table_mapping->getFieldNames($table_name) as $field_name) { if ($field_name == $deleted_field_name) { // Drop indexes and unique keys first. @@ -604,7 +618,12 @@ protected function deleteSharedTableSchema(FieldStorageDefinitionInterface $stor * {@inheritdoc} */ public function updateFieldSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { + // Store original definitions so that switching between shared and dedicated + // field table layout works. + $this->originalDefinitions = $this->fieldStorageDefinitions; + $this->originalDefinitions[$original->getName()] = $original; $this->performFieldSchemaOperation('update', $storage_definition, $original); + $this->originalDefinitions = NULL; } /** @@ -621,7 +640,7 @@ public function updateFieldSchema(FieldStorageDefinitionInterface $storage_defin * Rethrown exception if the table recreation fails. */ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { - if (!$storage_definition->hasData()) { + if (!$this->storage->countFieldData($original, TRUE)) { // 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 @@ -629,14 +648,10 @@ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $s $transaction = $this->database->startTransaction(); } try { - $original_schema = $this->getDedicatedTableSchema($original); - foreach ($original_schema as $name => $table) { - $this->database->schema()->dropTable($name, $table); - } - $schema = $this->getDedicatedTableSchema($storage_definition); - foreach ($schema as $name => $table) { - $this->database->schema()->createTable($name, $table); - } + // Since there is no data we may be switching from a shared table schema + // to a dedicated table schema, hence we should use the proper API. + $this->deleteFieldSchema($original); + $this->createFieldSchema($storage_definition); } catch (\Exception $e) { if ($this->database->supportsTransactionalDDL()) { @@ -644,12 +659,7 @@ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $s } else { // Recreate tables. - $original_schema = $this->getDedicatedTableSchema($original); - foreach ($original_schema as $name => $table) { - if (!$this->database->schema()->tableExists($name)) { - $this->database->schema()->createTable($name, $table); - } - } + $this->createFieldSchema($original); } throw $e; } @@ -715,15 +725,17 @@ protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $s * Rethrown exception if the table recreation fails. */ protected function updateSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { - if (!$this->storage->countFieldData($storage_definition, TRUE)) { + 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 { - $this->deleteSharedTableSchema($original); - $this->createSharedTableSchema($storage_definition); + // 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->deleteFieldSchema($original); + $this->createFieldSchema($storage_definition); } catch (\Exception $e) { if ($this->database->supportsTransactionalDDL()) {