summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2014-09-19 06:57:48 (GMT)
committerAlex Pott2014-09-19 06:57:48 (GMT)
commitf27fd1f59877f7e16441c88193425242d3e0faff (patch)
tree21dfbb853c1b22ba491b74e836b08c97cddec254
parentfb4e8cdf2f3f0953420668d4e7e88ef31b6b8611 (diff)
Issue #2337927 by effulgentsia, plach, fago: Fixed SqlContentEntityStorage::onFieldStorageDefinition(Create|Update|Delete)() is broken for base fields.
-rw-r--r--core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php9
-rw-r--r--core/lib/Drupal/Core/Entity/EntityManager.php2
-rw-r--r--core/lib/Drupal/Core/Entity/Schema/FieldableEntityStorageSchemaInterface.php8
-rw-r--r--core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php54
-rw-r--r--core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php634
-rw-r--r--core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php1170
-rw-r--r--core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php6
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleHandler.php22
-rw-r--r--core/lib/Drupal/Core/Field/BaseFieldDefinition.php17
-rw-r--r--core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php12
-rw-r--r--core/modules/aggregator/src/FeedStorageSchema.php37
-rw-r--r--core/modules/aggregator/src/ItemStorageSchema.php32
-rw-r--r--core/modules/block_content/src/BlockContentStorageSchema.php25
-rw-r--r--core/modules/comment/src/CommentStorageSchema.php46
-rw-r--r--core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.module6
-rw-r--r--core/modules/field/src/Entity/FieldStorageConfig.php21
-rw-r--r--core/modules/file/src/FileStorageSchema.php39
-rw-r--r--core/modules/node/src/NodeStorageSchema.php72
-rw-r--r--core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php49
-rw-r--r--core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php501
-rw-r--r--core/modules/system/src/Tests/Entity/EntitySchemaTest.php143
-rw-r--r--core/modules/system/src/Tests/Entity/EntityUnitTestBase.php31
-rw-r--r--core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php13
-rw-r--r--core/modules/system/src/Tests/Entity/FieldTranslationSqlStorageTest.php27
-rw-r--r--core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.info.yml8
-rw-r--r--core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.install41
-rw-r--r--core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.module69
-rw-r--r--core/modules/system/tests/modules/entity_schema_test/entity_schema_test.info.yml8
-rw-r--r--core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module79
-rw-r--r--core/modules/system/tests/modules/entity_test/entity_test.module18
-rw-r--r--core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUpdate.php56
-rw-r--r--core/modules/system/tests/modules/entity_test/src/EntityTestStorageSchema.php41
-rw-r--r--core/modules/system/tests/modules/entity_test/src/FieldStorageDefinition.php30
-rw-r--r--core/modules/taxonomy/src/TermStorageSchema.php47
-rw-r--r--core/modules/user/src/UserStorageSchema.php54
-rw-r--r--core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php28
-rw-r--r--core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php331
-rw-r--r--core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php99
38 files changed, 2877 insertions, 1008 deletions
diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
index 09fcf36..9c2c893 100644
--- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
@@ -189,8 +189,13 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
// Detect updated field storage definitions.
foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
// @todo Support non-storage-schema-changing definition updates too:
- // https://www.drupal.org/node/2336895.
- if ($this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
+ // https://www.drupal.org/node/2336895. So long as we're checking
+ // based on schema change requirements rather than definition
+ // equality, skip the check if the entity type itself needs to be
+ // updated, since that can affect the schema of all fields, so we
+ // want to process that update first without reporting false
+ // positives here.
+ if (!isset($change_list[$entity_type_id]['entity_type']) && $this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
$field_changes[$field_name] = static::DEFINITION_UPDATED;
}
}
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index a2aedf3..66ef2a6 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -1165,7 +1165,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
* {@inheritdoc}
*/
public function getLastInstalledFieldStorageDefinitions($entity_type_id) {
- return $this->installedDefinitions->get($entity_type_id . '.field_storage_definitions');
+ return $this->installedDefinitions->get($entity_type_id . '.field_storage_definitions', array());
}
/**
diff --git a/core/lib/Drupal/Core/Entity/Schema/FieldableEntityStorageSchemaInterface.php b/core/lib/Drupal/Core/Entity/Schema/FieldableEntityStorageSchemaInterface.php
index 759fb6e..c20d9d1 100644
--- a/core/lib/Drupal/Core/Entity/Schema/FieldableEntityStorageSchemaInterface.php
+++ b/core/lib/Drupal/Core/Entity/Schema/FieldableEntityStorageSchemaInterface.php
@@ -67,4 +67,12 @@ interface FieldableEntityStorageSchemaInterface extends EntityStorageSchemaInter
*/
public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original);
+ /**
+ * Performs final cleanup after all data of a field has been purged.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field being purged.
+ */
+ public function finalizePurge(FieldStorageDefinitionInterface $storage_definition);
+
}
diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
index 4e1546b..f992081 100644
--- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
+++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
@@ -15,7 +15,7 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface;
class DefaultTableMapping implements TableMappingInterface {
/**
- * A list of field storage definitions that are available for this mapping.
+ * The field storage definitions of this mapping.
*
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
*/
@@ -101,7 +101,16 @@ class DefaultTableMapping implements TableMappingInterface {
$this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], array_values($this->getColumnNames($field_name)));
}
- $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], $this->getExtraColumns($table_name));
+ // There is just one field for each dedicated storage table, thus
+ // $field_name can only refer to it.
+ if (isset($field_name) && $this->requiresDedicatedTableStorage($this->fieldStorageDefinitions[$field_name])) {
+ // Unlike in shared storage tables, in dedicated ones field columns are
+ // positioned last.
+ $this->allColumns[$table_name] = array_merge($this->getExtraColumns($table_name), $this->allColumns[$table_name]);
+ }
+ else {
+ $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], $this->getExtraColumns($table_name));
+ }
}
return $this->allColumns[$table_name];
}
@@ -180,6 +189,47 @@ class DefaultTableMapping implements TableMappingInterface {
}
/**
+ * Checks whether the given field can be stored in a shared table.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ *
+ * @return bool
+ * TRUE if the field can be stored in a dedicated table, FALSE otherwise.
+ */
+ public function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
+ return !$storage_definition->hasCustomStorage() && $storage_definition->isBaseField() && !$storage_definition->isMultiple();
+ }
+
+ /**
+ * Checks whether the given field has to be stored in a dedicated table.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ *
+ * @return bool
+ * TRUE if the field can be stored in a dedicated table, FALSE otherwise.
+ */
+ public function requiresDedicatedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
+ return !$storage_definition->hasCustomStorage() && !$this->allowsSharedTableStorage($storage_definition);
+ }
+
+ /**
+ * Returns a list of dedicated table names for this mapping.
+ *
+ * @return string[]
+ * An array of table names.
+ */
+ public function getDedicatedTableNames() {
+ $table_mapping = $this;
+ $definitions = array_filter($this->fieldStorageDefinitions, function($definition) use ($table_mapping) { return $table_mapping->requiresDedicatedTableStorage($definition); });
+ $data_tables = array_map(function($definition) use ($table_mapping) { return $table_mapping->getDedicatedDataTableName($definition); }, $definitions);
+ $revision_tables = array_map(function($definition) use ($table_mapping) { return $table_mapping->getDedicatedRevisionTableName($definition); }, $definitions);
+ $dedicated_tables = array_merge(array_values($data_tables), array_values($revision_tables));
+ return $dedicated_tables;
+ }
+
+ /**
* {@inheritdoc}
*/
public function getReservedColumns() {
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index e88025c..1ccd508 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -17,12 +17,12 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Entity\Schema\FieldableEntityStorageSchemaInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
+use Drupal\field\FieldStorageConfigInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -33,9 +33,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*
* The class uses \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema
* internally in order to automatically generate the database schema based on
- * the defined base fields. Entity types can override
- * SqlContentEntityStorage::getSchema() to customize the generated
- * schema; e.g., to add additional indexes.
+ * the defined base fields. Entity types can override the schema handler to
+ * customize the generated schema; e.g., to add additional indexes.
*
* @ingroup entity_api
*/
@@ -107,11 +106,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
protected $entityManager;
/**
- * The entity schema handler.
+ * The entity type's storage schema object.
*
* @var \Drupal\Core\Entity\Schema\EntityStorageSchemaInterface
*/
- protected $schemaHandler;
+ protected $storageSchema;
/**
* Cache backend.
@@ -157,15 +156,27 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
*/
public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache) {
parent::__construct($entity_type);
-
$this->database = $database;
$this->entityManager = $entity_manager;
$this->cacheBackend = $cache;
+ $this->initTableLayout();
+ }
+
+ /**
+ * Initializes table name variables.
+ */
+ protected function initTableLayout() {
+ // Reset table field values to ensure changes in the entity type definition
+ // are correctly reflected in the table layout.
+ $this->tableMapping = NULL;
+ $this->revisionKey = NULL;
+ $this->revisionTable = NULL;
+ $this->dataTable = NULL;
+ $this->revisionDataTable = NULL;
// @todo Remove table names from the entity type definition in
// https://drupal.org/node/2232465
$this->baseTable = $this->entityType->getBaseTable() ?: $this->entityTypeId;
-
$revisionable = $this->entityType->isRevisionable();
if ($revisionable) {
$this->revisionKey = $this->entityType->getKey('revision') ?: 'revision_id';
@@ -223,32 +234,57 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
}
/**
- * Gets the schema handler for this entity storage.
+ * Returns the entity type's storage schema object.
*
* @return \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema
- * The schema handler.
+ * The schema object.
*/
- protected function schemaHandler() {
- if (!isset($this->schemaHandler)) {
- $schema_handler_class = $this->entityType->getHandlerClass('storage_schema') ?: 'Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema';
- $this->schemaHandler = new $schema_handler_class($this->entityManager, $this->entityType, $this, $this->database);
+ protected function getStorageSchema() {
+ if (!isset($this->storageSchema)) {
+ $class = $this->entityType->getHandlerClass('storage_schema') ?: 'Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema';
+ $this->storageSchema = new $class($this->entityManager, $this->entityType, $this, $this->database);
}
- return $this->schemaHandler;
+ return $this->storageSchema;
}
/**
- * {@inheritdoc}
+ * Updates the wrapped entity type definition.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The update entity type.
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
+ * See https://www.drupal.org/node/2274017.
*/
- public function getTableMapping() {
- if (!isset($this->tableMapping)) {
+ public function setEntityType(EntityTypeInterface $entity_type) {
+ if ($this->entityType->id() == $entity_type->id()) {
+ $this->entityType = $entity_type;
+ $this->initTableLayout();
+ }
+ else {
+ throw new EntityStorageException(String::format('Unsupported entity type @id', array('@id' => $entity_type->id())));
+ }
+ }
- $definitions = array_filter($this->getFieldStorageDefinitions(), function (FieldStorageDefinitionInterface $definition) {
- // @todo Remove the check for FieldDefinitionInterface::isMultiple() when
- // multiple-value base fields are supported in
- // https://drupal.org/node/2248977.
- return !$definition->hasCustomStorage() && !$definition->isMultiple();
+ /**
+ * {@inheritdoc}
+ */
+ public function getTableMapping(array $storage_definitions = NULL) {
+ $table_mapping = $this->tableMapping;
+
+ // If we are using our internal storage definitions, which is our main use
+ // case, we can statically cache the computed table mapping. If a new set
+ // of field storage definitions is passed, for instance when comparing old
+ // and new storage schema, we compute the table mapping without caching.
+ // @todo Clean-up this in https://www.drupal.org/node/2274017 so we can
+ // easily instantiate a new table mapping whenever needed.
+ if (!isset($this->tableMapping) || $storage_definitions) {
+ $definitions = $storage_definitions ?: $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
+ $table_mapping = new DefaultTableMapping($definitions);
+
+ $definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
+ return $table_mapping->allowsSharedTableStorage($definition);
});
- $this->tableMapping = new DefaultTableMapping($definitions);
$key_fields = array_values(array_filter(array($this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey)));
$all_fields = array_keys($definitions);
@@ -272,16 +308,16 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$translatable = $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
@@ -290,7 +326,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
// 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
@@ -306,13 +342,13 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
// 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
@@ -321,20 +357,45 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
->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);
- $this->tableMapping
+ $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, array($this->langcodeKey));
+ $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
// "revision_table.langcode = data_table.langcode".
->setExtraColumns($this->revisionDataTable, array('default_langcode'));
}
+
+ // Add dedicated tables.
+ $definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
+ return $table_mapping->requiresDedicatedTableStorage($definition);
+ });
+ $extra_columns = array(
+ 'bundle',
+ 'deleted',
+ 'entity_id',
+ 'revision_id',
+ 'langcode',
+ 'delta',
+ );
+ foreach ($definitions as $field_name => $definition) {
+ foreach (array($table_mapping->getDedicatedDataTableName($definition), $table_mapping->getDedicatedRevisionTableName($definition)) as $table_name) {
+ $table_mapping->setFieldNames($table_name, array($field_name));
+ $table_mapping->setExtraColumns($table_name, $extra_columns);
+ }
+ }
+
+ // Cache the computed table mapping only if we are using our internal
+ // storage definitions.
+ if (!$storage_definitions) {
+ $this->tableMapping = $table_mapping;
+ }
}
- return $this->tableMapping;
+ return $table_mapping;
}
/**
@@ -581,7 +642,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$table_mapping = $this->getTableMapping();
$translations = array();
if ($this->revisionDataTable) {
- $data_fields = array_diff_key($table_mapping->getFieldNames($this->revisionDataTable), $table_mapping->getFieldNames($this->baseTable));
+ $data_fields = array_diff($table_mapping->getFieldNames($this->revisionDataTable), $table_mapping->getFieldNames($this->baseTable));
}
else {
$data_fields = $table_mapping->getFieldNames($this->dataTable);
@@ -1150,7 +1211,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$definitions[$bundle] = $this->entityManager->getFieldDefinitions($this->entityTypeId, $bundle);
foreach ($definitions[$bundle] as $field_name => $field_definition) {
$storage_definition = $field_definition->getFieldStorageDefinition();
- if ($this->usesDedicatedTable($storage_definition)) {
+ if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
$storage_definitions[$field_name] = $storage_definition;
}
}
@@ -1225,7 +1286,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
foreach ($this->entityManager->getFieldDefinitions($entity_type, $bundle) as $field_name => $field_definition) {
$storage_definition = $field_definition->getFieldStorageDefinition();
- if (!$this->usesDedicatedTable($storage_definition)) {
+ if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
continue;
}
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
@@ -1240,10 +1301,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
->condition('entity_id', $id)
->execute();
}
- $this->database->delete($revision_name)
- ->condition('entity_id', $id)
- ->condition('revision_id', $vid)
- ->execute();
+ if ($this->entityType->isRevisionable()) {
+ $this->database->delete($revision_name)
+ ->condition('entity_id', $id)
+ ->condition('revision_id', $vid)
+ ->execute();
+ }
}
// Prepare the multi-insert query.
@@ -1253,7 +1316,9 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$columns[] = $table_mapping->getFieldColumnName($storage_definition, $column);
}
$query = $this->database->insert($table_name)->fields($columns);
- $revision_query = $this->database->insert($revision_name)->fields($columns);
+ if ($this->entityType->isRevisionable()) {
+ $revision_query = $this->database->insert($revision_name)->fields($columns);
+ }
$langcodes = $field_definition->isTranslatable() ? $translation_langcodes : array($default_langcode);
foreach ($langcodes as $langcode) {
@@ -1276,7 +1341,9 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$record[$column_name] = !empty($attributes['serialize']) ? serialize($item->$column) : $item->$column;
}
$query->values($record);
- $revision_query->values($record);
+ if ($this->entityType->isRevisionable()) {
+ $revision_query->values($record);
+ }
if ($storage_definition->getCardinality() != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && ++$delta_count == $storage_definition->getCardinality()) {
break;
@@ -1291,7 +1358,9 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
if ($entity->isDefaultRevision()) {
$query->execute();
}
- $revision_query->execute();
+ if ($this->entityType->isRevisionable()) {
+ $revision_query->execute();
+ }
}
}
}
@@ -1306,7 +1375,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$table_mapping = $this->getTableMapping();
foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
$storage_definition = $field_definition->getFieldStorageDefinition();
- if (!$this->usesDedicatedTable($storage_definition)) {
+ if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
continue;
}
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
@@ -1314,9 +1383,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$this->database->delete($table_name)
->condition('entity_id', $entity->id())
->execute();
- $this->database->delete($revision_name)
- ->condition('entity_id', $entity->id())
- ->execute();
+ if ($this->entityType->isRevisionable()) {
+ $this->database->delete($revision_name)
+ ->condition('entity_id', $entity->id())
+ ->execute();
+ }
}
}
@@ -1332,7 +1403,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$table_mapping = $this->getTableMapping();
foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
$storage_definition = $field_definition->getFieldStorageDefinition();
- if (!$this->usesDedicatedTable($storage_definition)) {
+ if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
continue;
}
$revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
@@ -1345,184 +1416,108 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
}
/**
- * Returns whether the field uses a dedicated table for storage.
- *
- * @param FieldStorageDefinitionInterface $definition
- * The field storage definition.
- *
- * @return bool
- * Whether the field uses a dedicated table for storage.
- */
- protected function usesDedicatedTable(FieldStorageDefinitionInterface $definition) {
- // Everything that is not provided by the entity type is stored in a
- // dedicated table.
- return $definition->getProvider() != $this->entityType->getProvider() && !$definition->hasCustomStorage();
- }
-
- /**
* {@inheritdoc}
*/
public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
- return $this->schemaHandler()->requiresEntityStorageSchemaChanges($entity_type, $original);
+ return $this->getStorageSchema()->requiresEntityStorageSchemaChanges($entity_type, $original);
}
/**
* {@inheritdoc}
*/
public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
- return $this->schemaHandler()->requiresFieldStorageSchemaChanges($storage_definition, $original);
+ return $this->getStorageSchema()->requiresFieldStorageSchemaChanges($storage_definition, $original);
}
/**
* {@inheritdoc}
*/
public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
- return $this->schemaHandler()->requiresEntityDataMigration($entity_type, $original);
+ return $this->getStorageSchema()->requiresEntityDataMigration($entity_type, $original);
}
/**
* {@inheritdoc}
*/
public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
- return $this->schemaHandler()->requiresFieldDataMigration($storage_definition, $original);
+ return $this->getStorageSchema()->requiresFieldDataMigration($storage_definition, $original);
}
/**
* {@inheritdoc}
*/
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
- $this->schemaHandler()->onEntityTypeCreate($entity_type);
+ $this->getStorageSchema()->onEntityTypeCreate($entity_type);
}
/**
* {@inheritdoc}
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
- $this->schemaHandler()->onEntityTypeUpdate($entity_type, $original);
+ // Ensure we have an updated entity type definition.
+ $this->entityType = $entity_type;
+ // The table layout may have changed depending on the new entity type
+ // definition.
+ $this->initTableLayout();
+ // Let the schema handler adapt to possible table layout changes.
+ $this->getStorageSchema()->onEntityTypeUpdate($entity_type, $original);
}
/**
* {@inheritdoc}
*/
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
- $this->schemaHandler()->onEntityTypeDelete($entity_type);
+ $this->getStorageSchema()->onEntityTypeDelete($entity_type);
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
- $schema = $this->_fieldSqlSchema($storage_definition);
- foreach ($schema as $name => $table) {
- $this->database->schema()->createTable($name, $table);
- }
+ // If we are adding a field stored in a shared table we need to recompute
+ // the table mapping.
+ // @todo This does not belong here. Remove it once we are able to generate a
+ // fresh table mapping in the schema handler. See
+ // https://www.drupal.org/node/2274017.
+ if ($this->getTableMapping()->allowsSharedTableStorage($storage_definition)) {
+ $this->tableMapping = NULL;
+ }
+ $this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition);
}
/**
* {@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_mapping = $this->getTableMapping();
- $table = $table_mapping->getDedicatedDataTableName($original);
- $revision_table = $table_mapping->getDedicatedRevisionTableName($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 = $table_mapping->getDedicatedDataTableName($storage_definition);
- $revision_table = $table_mapping->getDedicatedRevisionTableName($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(
- $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
- $column_name[1],
- );
- }
- else {
- $real_columns[] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
- }
- }
- $this->database->schema()->addIndex($table, $real_name, $real_columns);
- $this->database->schema()->addIndex($revision_table, $real_name, $real_columns);
- }
- }
- }
+ $this->getStorageSchema()->onFieldStorageDefinitionUpdate($storage_definition, $original);
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
- $table_mapping = $this->getTableMapping();
+ $table_mapping = $this->getTableMapping(
+ $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
+ );
- // Mark all data associated with the field for deletion.
- $table = $table_mapping->getDedicatedDataTableName($storage_definition);
- $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
- $this->database->update($table)
- ->fields(array('deleted' => 1))
- ->execute();
+ // @todo Remove the FieldStorageConfigInterface check when non-configurable
+ // fields support purging: https://www.drupal.org/node/2282119.
+ if ($storage_definition instanceof FieldStorageConfigInterface && $table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+ // Mark all data associated with the field for deletion.
+ $table = $table_mapping->getDedicatedDataTableName($storage_definition);
+ $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+ $this->database->update($table)
+ ->fields(array('deleted' => 1))
+ ->execute();
+ if ($this->entityType->isRevisionable()) {
+ $this->database->update($revision_table)
+ ->fields(array('deleted' => 1))
+ ->execute();
+ }
+ }
- // Move the table to a unique name while the table contents are being
- // deleted.
- $new_table = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE);
- $revision_new_table = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
- $this->database->schema()->renameTable($table, $new_table);
- $this->database->schema()->renameTable($revision_table, $revision_new_table);
+ // Update the field schema.
+ $this->getStorageSchema()->onFieldStorageDefinitionDelete($storage_definition);
}
/**
@@ -1532,16 +1527,20 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$table_mapping = $this->getTableMapping();
$storage_definition = $field_definition->getFieldStorageDefinition();
// Mark field data as deleted.
- $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
- $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
- $this->database->update($table_name)
- ->fields(array('deleted' => 1))
- ->condition('bundle', $field_definition->getBundle())
- ->execute();
- $this->database->update($revision_name)
- ->fields(array('deleted' => 1))
- ->condition('bundle', $field_definition->getBundle())
- ->execute();
+ if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+ $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
+ $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+ $this->database->update($table_name)
+ ->fields(array('deleted' => 1))
+ ->condition('bundle', $field_definition->getBundle())
+ ->execute();
+ if ($this->entityType->isRevisionable()) {
+ $this->database->update($revision_name)
+ ->fields(array('deleted' => 1))
+ ->condition('bundle', $field_definition->getBundle())
+ ->execute();
+ }
+ }
}
/**
@@ -1560,7 +1559,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
foreach ($field_definitions as $field_definition) {
$storage_definition = $field_definition->getFieldStorageDefinition();
- if ($this->usesDedicatedTable($storage_definition)) {
+ if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
$is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
$revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
@@ -1568,10 +1567,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
->fields(array('bundle' => $bundle_new))
->condition('bundle', $bundle)
->execute();
- $this->database->update($revision_name)
- ->fields(array('bundle' => $bundle_new))
- ->condition('bundle', $bundle)
- ->execute();
+ if ($this->entityType->isRevisionable()) {
+ $this->database->update($revision_name)
+ ->fields(array('bundle' => $bundle_new))
+ ->condition('bundle', $bundle)
+ ->execute();
+ }
}
}
}
@@ -1649,20 +1650,18 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$this->database->delete($table_name)
->condition('revision_id', $revision_id)
->execute();
- $this->database->delete($revision_name)
- ->condition('revision_id', $revision_id)
- ->execute();
+ if ($this->entityType->isRevisionable()) {
+ $this->database->delete($revision_name)
+ ->condition('revision_id', $revision_id)
+ ->execute();
+ }
}
/**
* {@inheritdoc}
*/
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) {
- $table_mapping = $this->getTableMapping();
- $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE);
- $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
- $this->database->schema()->dropTable($table_name);
- $this->database->schema()->dropTable($revision_name);
+ $this->getStorageSchema()->finalizePurge($storage_definition);
}
/**
@@ -1671,23 +1670,49 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
public function countFieldData($storage_definition, $as_bool = FALSE) {
$table_mapping = $this->getTableMapping();
- $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);
+ 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);
+ }
+ elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
+ $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($storage_definition->getName() . '__' . $column_name);
+ }
+ $query->condition($or);
+ }
+ else {
+ $query->isNotNull($storage_definition->getName());
+ }
+ $query
+ ->fields('t', array($this->idKey))
+ ->distinct(TRUE);
+ }
+
+ // @todo Find a way to count field data also for fields having custom
+ // storage. See https://www.drupal.org/node/2337753.
+ $count = 0;
+ if (isset($query)) {
+ // 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();
}
- $count = $query->countQuery()->execute()->fetchField();
return $as_bool ? (bool) $count : (int) $count;
}
@@ -1701,200 +1726,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
* Whether the field has been already deleted.
*/
protected function storageDefinitionIsDeleted(FieldStorageDefinitionInterface $storage_definition) {
- return !array_key_exists($storage_definition->getName(), $this->entityManager->getFieldStorageDefinitions($this->entityTypeId));
- }
-
- /**
- * Gets the SQL table schema.
- *
- * @private Calling this function circumvents the entity system and is
- * strongly discouraged. This function is not considered part of the public
- * API and modules relying on it might break even in minor releases.
- *
- * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
- * The field storage definition.
- * @param array $schema
- * The field schema array. Mandatory for upgrades, omit otherwise.
- * @param bool $deleted
- * (optional) Whether the schema of the table holding the values of a
- * deleted field should be returned.
- *
- * @return array
- * The same as a hook_schema() implementation for the data and the
- * revision tables.
- *
- * @see hook_schema()
- */
- public static function _fieldSqlSchema(FieldStorageDefinitionInterface $storage_definition, array $schema = NULL, $deleted = FALSE) {
- $table_mapping = new DefaultTableMapping(array());
-
- $description_current = "Data storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
- $description_revision = "Revision archive storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
-
- $entity_type_id = $storage_definition->getTargetEntityTypeId();
- $entity_manager = \Drupal::entityManager();
- $entity_type = $entity_manager->getDefinition($entity_type_id);
- $definitions = $entity_manager->getBaseFieldDefinitions($entity_type_id);
-
- // Define the entity ID schema based on the field definitions.
- $id_definition = $definitions[$entity_type->getKey('id')];
- if ($id_definition->getType() == 'integer') {
- $id_schema = array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'description' => 'The entity id this data is attached to',
- );
- }
- else {
- $id_schema = array(
- 'type' => 'varchar',
- 'length' => 128,
- 'not null' => TRUE,
- 'description' => 'The entity id this data is attached to',
- );
- }
-
- // Define the revision ID schema, default to integer if there is no revision
- // ID.
- $revision_id_definition = $entity_type->hasKey('revision') ? $definitions[$entity_type->getKey('revision')] : NULL;
- if (!$revision_id_definition || $revision_id_definition->getType() == 'integer') {
- $revision_id_schema = array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => FALSE,
- 'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
- );
- }
- else {
- $revision_id_schema = array(
- 'type' => 'varchar',
- 'length' => 128,
- 'not null' => FALSE,
- 'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
- );
- }
-
- $current = array(
- 'description' => $description_current,
- 'fields' => array(
- 'bundle' => array(
- 'type' => 'varchar',
- 'length' => 128,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
- ),
- 'deleted' => array(
- 'type' => 'int',
- 'size' => 'tiny',
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'A boolean indicating whether this data item has been deleted'
- ),
- 'entity_id' => $id_schema,
- 'revision_id' => $revision_id_schema,
- 'langcode' => array(
- 'type' => 'varchar',
- 'length' => 32,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The language code for this data item.',
- ),
- 'delta' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'description' => 'The sequence number for this data item, used for multi-value fields',
- ),
- ),
- 'primary key' => array('entity_id', 'deleted', 'delta', 'langcode'),
- 'indexes' => array(
- 'bundle' => array('bundle'),
- 'deleted' => array('deleted'),
- 'entity_id' => array('entity_id'),
- 'revision_id' => array('revision_id'),
- 'langcode' => array('langcode'),
- ),
- );
-
- if (!$schema) {
- $schema = $storage_definition->getSchema();
- }
-
- // Add field columns.
- foreach ($schema['columns'] as $column_name => $attributes) {
- $real_name = $table_mapping->getFieldColumnName($storage_definition, $column_name);
- $current['fields'][$real_name] = $attributes;
- }
-
- // Add unique keys.
- foreach ($schema['unique keys'] as $unique_key_name => $columns) {
- $real_name = static::_fieldIndexName($storage_definition, $unique_key_name);
- foreach ($columns as $column_name) {
- $current['unique keys'][$real_name][] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
- }
- }
-
- // Add indexes.
- foreach ($schema['indexes'] as $index_name => $columns) {
- $real_name = static::_fieldIndexName($storage_definition, $index_name);
- 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)) {
- $current['indexes'][$real_name][] = array(
- $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
- $column_name[1],
- );
- }
- else {
- $current['indexes'][$real_name][] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
- }
- }
- }
-
- // Add foreign keys.
- foreach ($schema['foreign keys'] as $specifier => $specification) {
- $real_name = static::_fieldIndexName($storage_definition, $specifier);
- $current['foreign keys'][$real_name]['table'] = $specification['table'];
- foreach ($specification['columns'] as $column_name => $referenced) {
- $sql_storage_column = $table_mapping->getFieldColumnName($storage_definition, $column_name);
- $current['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced;
- }
- }
-
- // Construct the revision table.
- $revision = $current;
- $revision['description'] = $description_revision;
- $revision['primary key'] = array('entity_id', 'revision_id', 'deleted', 'delta', 'langcode');
- $revision['fields']['revision_id']['not null'] = TRUE;
- $revision['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
-
- return array(
- $table_mapping->getDedicatedDataTableName($storage_definition) => $current,
- $table_mapping->getDedicatedRevisionTableName($storage_definition) => $revision,
- );
- }
-
- /**
- * Generates an index name for a field data table.
- *
- * @private Calling this function circumvents the entity system and is
- * strongly discouraged. This function is not considered part of the public
- * API and modules relying on it might break even in minor releases.
- *
- * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
- * The field storage definition.
- * @param string $index
- * The name of the index.
- *
- * @return string
- * A string containing a generated index name for a field data table that is
- * unique among all other fields.
- */
- public static function _fieldIndexName(FieldStorageDefinitionInterface $storage_definition, $index) {
- return $storage_definition->getName() . '_' . $index;
+ return !array_key_exists($storage_definition->getName(), $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityTypeId));
}
}
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index ca61630..de6910f 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -13,15 +13,30 @@ use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Entity\Schema\FieldableEntityStorageSchemaInterface;
+use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\field\FieldStorageConfigInterface;
/**
* Defines a schema handler that supports revisionable, translatable entities.
+ *
+ * Entity types may extend this class and optimize the generated schema for all
+ * entity base tables by overriding getEntitySchema() for cross-field
+ * optimizations and getSharedTableFieldSchema() for optimizations applying to
+ * a single field.
*/
class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInterface {
/**
+ * The entity manager.
+ *
+ * @var \Drupal\Core\Entity\EntityManagerInterface
+ */
+ protected $entityManager;
+
+ /**
* The entity type this schema builder is responsible for.
*
* @var \Drupal\Core\Entity\ContentEntityTypeInterface
@@ -36,6 +51,14 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
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\Sql\SqlContentEntityStorage
@@ -57,6 +80,13 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
protected $database;
/**
+ * The key-value collection for tracking installed storage schema.
+ *
+ * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+ */
+ protected $installedStorageSchema;
+
+ /**
* Constructs a SqlContentEntityStorageSchema.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
@@ -69,6 +99,7 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
* The database connection to be used.
*/
public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, SqlContentEntityStorage $storage, Connection $database) {
+ $this->entityManager = $entity_manager;
$this->entityType = $entity_type;
$this->fieldStorageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id());
$this->storage = $storage;
@@ -76,25 +107,71 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
}
/**
+ * Returns the keyvalue collection for tracking the installed schema.
+ *
+ * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+ *
+ * @todo Inject this dependency in the constructor once this class can be
+ * instantiated as a regular entity handler:
+ * https://www.drupal.org/node/2332857.
+ */
+ protected function installedStorageSchema() {
+ if (!isset($this->installedStorageSchema)) {
+ $this->installedStorageSchema = \Drupal::keyValue('entity.storage_schema.sql');
+ }
+ return $this->installedStorageSchema;
+ }
+
+ /**
* {@inheritdoc}
*/
public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
return
$entity_type->getStorageClass() != $original->getStorageClass() ||
- $entity_type->getKeys() != $original->getKeys() ||
$entity_type->isRevisionable() != $original->isRevisionable() ||
- $entity_type->isTranslatable() != $original->isTranslatable();
+ $entity_type->isTranslatable() != $original->isTranslatable() ||
+ // Detect changes in key or index definitions.
+ $this->getEntitySchemaData($entity_type, $this->getEntitySchema($entity_type, TRUE)) != $this->loadEntitySchemaData($original);
}
/**
* {@inheritdoc}
*/
public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
- return
+ $table_mapping = $this->storage->getTableMapping();
+
+ if (
$storage_definition->hasCustomStorage() != $original->hasCustomStorage() ||
$storage_definition->getSchema() != $original->getSchema() ||
$storage_definition->isRevisionable() != $original->isRevisionable() ||
- $storage_definition->isTranslatable() != $original->isTranslatable();
+ $storage_definition->isTranslatable() != $original->isTranslatable() ||
+ $table_mapping->allowsSharedTableStorage($storage_definition) != $table_mapping->allowsSharedTableStorage($original) ||
+ $table_mapping->requiresDedicatedTableStorage($storage_definition) != $table_mapping->requiresDedicatedTableStorage($original)
+ ) {
+ return TRUE;
+ }
+
+ if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+ return $this->getDedicatedTableSchema($storage_definition) != $this->loadFieldSchemaData($original);
+ }
+ elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
+ $field_name = $storage_definition->getName();
+ $schema = array();
+ foreach (array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames()) as $table_name) {
+ if (in_array($field_name, $table_mapping->getFieldNames($table_name))) {
+ $column_names = $table_mapping->getColumnNames($storage_definition->getName());
+ $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
+ }
+ }
+ return $schema != $this->loadFieldSchemaData($original);
+ }
+ else {
+ // The field has custom storage, so we don't know if a schema change is
+ // needed or not, but since per the initial checks earlier in this
+ // function, nothing about the definition changed that we manage, we
+ // return FALSE.
+ return FALSE;
+ }
}
/**
@@ -117,23 +194,14 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
// @todo Ask the old storage handler rather than assuming:
// https://www.drupal.org/node/2335879.
$entity_type->getStorageClass() != $original_storage_class ||
- !$this->tableIsEmpty($this->storage->getBaseTable());
+ !$this->isTableEmpty($this->storage->getBaseTable());
}
/**
* {@inheritdoc}
*/
public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
- // If the base table is empty, there are no entities, and therefore, no
- // field data that we care about preserving.
- // @todo We might be returning TRUE here in cases where it would be safe
- // to return FALSE (for example, if the field is in a dedicated table
- // and that table is empty), and thereby preventing automatic updates
- // that should be possible, but determining that requires refactoring
- // SqlContentEntityStorage::_fieldSqlSchema(), and in the meantime,
- // it's safer to return false positives than false negatives:
- // https://www.drupal.org/node/1498720.
- return !$this->tableIsEmpty($this->storage->getBaseTable());
+ return !$this->storage->countFieldData($original, TRUE);
}
/**
@@ -142,12 +210,31 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
$this->checkEntityType($entity_type);
$schema_handler = $this->database->schema();
+
+ // Create entity tables.
$schema = $this->getEntitySchema($entity_type, TRUE);
foreach ($schema as $table_name => $table_schema) {
if (!$schema_handler->tableExists($table_name)) {
$schema_handler->createTable($table_name, $table_schema);
}
}
+
+ // 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) {
+ if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
+ $this->createDedicatedTableSchema($field_storage_definition);
+ }
+ elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
+ // The shared tables are already fully created, but we need to save the
+ // per-field schema definitions for later use.
+ $this->createSharedTableSchema($field_storage_definition, TRUE);
+ }
+ }
+
+ // Save data about entity indexes and keys.
+ $this->saveEntitySchemaData($entity_type, $schema);
}
/**
@@ -162,8 +249,13 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
return;
}
+ // If a migration is required, we can't proceed.
+ if ($this->requiresEntityDataMigration($entity_type, $original)) {
+ throw new EntityStorageException(String::format('The SQL storage cannot change the schema for an existing entity type with data.'));
+ }
+
// If we have no data just recreate the entity schema from scratch.
- if (!$this->requiresEntityDataMigration($entity_type, $original)) {
+ if ($this->isTableEmpty($this->storage->getBaseTable())) {
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.
@@ -184,9 +276,40 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
throw $e;
}
}
- // Otherwise, throw an exception.
else {
- throw new EntityStorageException(String::format('The SQL storage cannot change the schema for an existing entity type with data.'));
+ $schema_handler = $this->database->schema();
+
+ // Drop original indexes and unique keys.
+ foreach ($this->loadEntitySchemaData($entity_type) as $table_name => $schema) {
+ if (!empty($schema['indexes'])) {
+ foreach ($schema['indexes'] as $name => $specifier) {
+ $schema_handler->dropIndex($table_name, $name);
+ }
+ }
+ if (!empty($schema['unique keys'])) {
+ foreach ($schema['unique keys'] as $name => $specifier) {
+ $schema_handler->dropUniqueKey($table_name, $name);
+ }
+ }
+ }
+
+ // Create new indexes and unique keys.
+ $entity_schema = $this->getEntitySchema($entity_type, TRUE);
+ foreach ($this->getEntitySchemaData($entity_type, $entity_schema) as $table_name => $schema) {
+ if (!empty($schema['indexes'])) {
+ foreach ($schema['indexes'] as $name => $specifier) {
+ $schema_handler->addIndex($table_name, $name, $specifier);
+ }
+ }
+ if (!empty($schema['unique keys'])) {
+ foreach ($schema['unique keys'] as $name => $specifier) {
+ $schema_handler->addUniqueKey($table_name, $name, $specifier);
+ }
+ }
+ }
+
+ // Store the updated entity schema.
+ $this->saveEntitySchemaData($entity_type, $entity_schema);
}
}
@@ -196,38 +319,95 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
$this->checkEntityType($entity_type);
$schema_handler = $this->database->schema();
+ $actual_definition = $this->entityManager->getDefinition($entity_type->id());
+ // @todo Instead of switching the wrapped entity type, we should be able to
+ // instantiate a new table mapping for each entity type definition. See
+ // https://www.drupal.org/node/2274017.
+ $this->storage->setEntityType($entity_type);
+
+ // Delete entity tables.
foreach ($this->getEntitySchemaTables() as $table_name) {
if ($schema_handler->tableExists($table_name)) {
$schema_handler->dropTable($table_name);
}
}
+
+ // Delete dedicated field tables.
+ $field_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type->id());
+ $this->originalDefinitions = $field_storage_definitions;
+ $table_mapping = $this->storage->getTableMapping($field_storage_definitions);
+ foreach ($field_storage_definitions as $field_storage_definition) {
+ if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
+ $this->deleteDedicatedTableSchema($field_storage_definition);
+ }
+ }
+ $this->originalDefinitions = NULL;
+
+ $this->storage->setEntityType($actual_definition);
+
+ // Delete the entity schema.
+ $this->deleteEntitySchemaData($entity_type);
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
- // @todo Move implementation from
- // SqlContentEntityStorage::onFieldStorageDefinitionCreate()
- // into here: https://www.drupal.org/node/1498720
+ $this->performFieldSchemaOperation('create', $storage_definition);
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
- // @todo Move implementation from
- // SqlContentEntityStorage::onFieldStorageDefinitionUpdate()
- // into here: https://www.drupal.org/node/1498720
+ // 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;
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
- // @todo Move implementation from
- // SqlContentEntityStorage::onFieldStorageDefinitionDelete()
- // into here: https://www.drupal.org/node/1498720
+ // Only configurable fields currently support purging, so prevent deletion
+ // of ones we can't purge if they have existing data.
+ // @todo Add purging to all fields: https://www.drupal.org/node/2282119.
+ if (!($storage_definition instanceof FieldStorageConfigInterface) && $this->storage->countFieldData($storage_definition, TRUE)) {
+ throw new FieldStorageDefinitionUpdateForbiddenException('Unable to delete a field with data that can\'t be purged.');
+ }
+
+ // Retrieve a table mapping which contains the deleted field still.
+ $table_mapping = $this->storage->getTableMapping(
+ $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
+ );
+ if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+ // Move the table to a unique name while the table contents are being
+ // deleted.
+ $table = $table_mapping->getDedicatedDataTableName($storage_definition);
+ $new_table = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE);
+ $this->database->schema()->renameTable($table, $new_table);
+ if ($this->entityType->isRevisionable()) {
+ $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+ $revision_new_table = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
+ $this->database->schema()->renameTable($revision_table, $revision_new_table);
+ }
+ }
+
+ // @todo Remove when finalizePurge() is invoked from the outside for all
+ // fields: https://www.drupal.org/node/2282119.
+ if (!($storage_definition instanceof FieldStorageConfigInterface)) {
+ $this->performFieldSchemaOperation('delete', $storage_definition);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) {
+ $this->performFieldSchemaOperation('delete', $storage_definition);
}
/**
@@ -251,6 +431,12 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
/**
* Returns the entity schema for the specified entity type.
*
+ * Entity types may override this method in order to optimize the generated
+ * schema of the entity tables. However, only cross-field optimizations should
+ * be added here; e.g., an index spanning multiple fields. Optimizations that
+ * apply to a single field have to be added via
+ * SqlContentEntityStorageSchema::getSharedTableFieldSchema() instead.
+ *
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type definition.
* @param bool $reset
@@ -260,30 +446,54 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
* @return array
* A Schema API array describing the entity schema, excluding dedicated
* field tables.
+ *
+ * @throws \Drupal\Core\Field\FieldException
*/
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
+ $this->checkEntityType($entity_type);
$entity_type_id = $entity_type->id();
if (!isset($this->schema[$entity_type_id]) || $reset) {
- // Initialize the table schema.
+ // Back up the storage definition and replace it with the passed one.
+ // @todo Instead of switching the wrapped entity type, we should be able
+ // to instantiate a new table mapping for each entity type definition.
+ // See https://www.drupal.org/node/2274017.
+ $actual_definition = $this->entityManager->getDefinition($entity_type_id);
+ $this->storage->setEntityType($entity_type);
+
+ // Prepare basic information about the entity type.
$tables = $this->getEntitySchemaTables();
- $schema[$tables['base_table']] = $this->initializeBaseTable();
+
+ // Initialize the table schema.
+ $schema[$tables['base_table']] = $this->initializeBaseTable($entity_type);
if (isset($tables['revision_table'])) {
- $schema[$tables['revision_table']] = $this->initializeRevisionTable();
+ $schema[$tables['revision_table']] = $this->initializeRevisionTable($entity_type);
}
if (isset($tables['data_table'])) {
- $schema[$tables['data_table']] = $this->initializeDataTable();
+ $schema[$tables['data_table']] = $this->initializeDataTable($entity_type);
}
if (isset($tables['revision_data_table'])) {
- $schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable();
+ $schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable($entity_type);
}
+ // We need to act only on shared entity schema tables.
$table_mapping = $this->storage->getTableMapping();
- foreach ($table_mapping->getTableNames() as $table_name) {
- // Add the schema from field definitions.
+ $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] = array();
+ }
foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
- $column_names = $table_mapping->getColumnNames($field_name);
- $this->addFieldSchema($schema[$table_name], $field_name, $column_names);
+ if (!isset($storage_definitions[$field_name])) {
+ throw new FieldException(String::format('Field storage definition for "@field_name" could not be found.', array('@field_name' => $field_name)));
+ }
+ // Add the schema for base field definitions.
+ elseif ($table_mapping->allowsSharedTableStorage($storage_definitions[$field_name])) {
+ $column_names = $table_mapping->getColumnNames($field_name);
+ $storage_definition = $storage_definitions[$field_name];
+ $schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names));
+ }
}
// Add the schema for extra fields.
@@ -295,18 +505,21 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
}
// Process tables after having gathered field information.
- $this->processBaseTable($schema[$tables['base_table']]);
+ $this->processBaseTable($entity_type, $schema[$tables['base_table']]);
if (isset($tables['revision_table'])) {
- $this->processRevisionTable($schema[$tables['revision_table']]);
+ $this->processRevisionTable($entity_type, $schema[$tables['revision_table']]);
}
if (isset($tables['data_table'])) {
- $this->processDataTable($schema[$tables['data_table']]);
+ $this->processDataTable($entity_type, $schema[$tables['data_table']]);
}
if (isset($tables['revision_data_table'])) {
- $this->processRevisionDataTable($schema[$tables['revision_data_table']]);
+ $this->processRevisionDataTable($entity_type, $schema[$tables['revision_data_table']]);
}
$this->schema[$entity_type_id] = $schema;
+
+ // Restore the actual definition.
+ $this->storage->setEntityType($actual_definition);
}
return $this->schema[$entity_type_id];
@@ -328,55 +541,41 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
}
/**
- * Returns the schema for a single field definition.
+ * Returns entity schema definitions for index and key definitions.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type definition.
* @param array $schema
- * The table schema to add the field schema to, passed by reference.
- * @param string $field_name
- * The name of the field.
- * @param string[] $column_mapping
- * A mapping of field column names to database column names.
+ * The entity schema array.
+ *
+ * @return array
+ * A stripped down version of the $schema Schema API array containing, for
+ * each table, only the key and index definitions not derived from field
+ * storage definitions.
*/
- protected function addFieldSchema(array &$schema, $field_name, array $column_mapping) {
- $field_schema = $this->fieldStorageDefinitions[$field_name]->getSchema();
- $field_description = $this->fieldStorageDefinitions[$field_name]->getDescription();
-
- foreach ($column_mapping as $field_column_name => $schema_field_name) {
- $column_schema = $field_schema['columns'][$field_column_name];
+ protected function getEntitySchemaData(ContentEntityTypeInterface $entity_type, array $schema) {
+ $schema_data = array();
+ $entity_type_id = $entity_type->id();
+ $keys = array('indexes', 'unique keys');
+ $unused_keys = array_flip(array('description', 'fields', 'foreign keys'));
- $schema['fields'][$schema_field_name] = $column_schema;
- $schema['fields'][$schema_field_name]['description'] = $field_description;
- // Only entity keys are required.
- $keys = $this->entityType->getKeys() + array('langcode' => 'langcode');
- // The label is an entity key, but label fields are not necessarily
- // required.
- // 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
- // table is updated after the data has been inserted into the revision
- // table. For this reason the revision ID field cannot be marked as NOT
- // NULL.
- unset($keys['label'], $keys['revision']);
- // Key fields may not be NULL.
- if (in_array($field_name, $keys)) {
- $schema['fields'][$schema_field_name]['not null'] = TRUE;
+ foreach ($schema as $table_name => $table_schema) {
+ $table_schema = array_diff_key($table_schema, $unused_keys);
+ foreach ($keys as $key) {
+ // Exclude data generated from field storage definitions, we will check
+ // that separately.
+ if (!empty($table_schema[$key])) {
+ $data_keys = array_keys($table_schema[$key]);
+ $entity_keys = array_filter($data_keys, function ($key) use ($entity_type_id) {
+ return strpos($key, $entity_type_id . '_field__') !== 0;
+ });
+ $table_schema[$key] = array_intersect_key($table_schema[$key], array_flip($entity_keys));
+ }
}
+ $schema_data[$table_name] = array_filter($table_schema);
}
- if (!empty($field_schema['indexes'])) {
- $indexes = $this->getFieldIndexes($field_name, $field_schema, $column_mapping);
- $schema['indexes'] = array_merge($schema['indexes'], $indexes);
- }
-
- if (!empty($field_schema['unique keys'])) {
- $unique_keys = $this->getFieldUniqueKeys($field_name, $field_schema, $column_mapping);
- $schema['unique keys'] = array_merge($schema['unique keys'], $unique_keys);
- }
-
- if (!empty($field_schema['foreign keys'])) {
- $foreign_keys = $this->getFieldForeignKeys($field_name, $field_schema, $column_mapping);
- $schema['foreign keys'] = array_merge($schema['foreign keys'], $foreign_keys);
- }
+ return $schema_data;
}
/**
@@ -462,14 +661,14 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
* The ID of the entity type.
* @param string $field_name
* The name of the field.
- * @param string $key
- * The key of the field.
+ * @param string|null $key
+ * (optional) A further key to append to the name.
*
* @return string
* The field identifier name.
*/
- protected function getFieldSchemaIdentifierName($entity_type_id, $field_name, $key) {
- $real_key = "{$entity_type_id}_field__{$field_name}__{$key}";
+ protected function getFieldSchemaIdentifierName($entity_type_id, $field_name, $key = NULL) {
+ $real_key = isset($key) ? "{$entity_type_id}_field__{$field_name}__{$key}" : "{$entity_type_id}_field__{$field_name}";
// Limit the string to 48 characters, keeping a 16 characters margin for db
// prefixes.
if (strlen($real_key) > 48) {
@@ -536,24 +735,98 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
}
/**
+ * Loads stored schema data for the given entity type definition.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type definition.
+ *
+ * @return array
+ * The entity schema data array.
+ */
+ protected function loadEntitySchemaData(EntityTypeInterface $entity_type) {
+ return $this->installedStorageSchema()->get($entity_type->id() . '.entity_schema_data', array());
+ }
+
+ /**
+ * Stores schema data for the given entity type definition.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type definition.
+ * @param array $schema
+ * The entity schema data array.
+ */
+ protected function saveEntitySchemaData(EntityTypeInterface $entity_type, $schema) {
+ $data = $this->getEntitySchemaData($entity_type, $schema);
+ $this->installedStorageSchema()->set($entity_type->id() . '.entity_schema_data', $data);
+ }
+
+ /**
+ * Deletes schema data for the given entity type definition.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type definition.
+ */
+ protected function deleteEntitySchemaData(EntityTypeInterface $entity_type) {
+ $this->installedStorageSchema()->delete($entity_type->id() . '.entity_schema_data');
+ }
+
+ /**
+ * Loads stored schema data for the given field storage definition.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ *
+ * @return array
+ * The field schema data array.
+ */
+ protected function loadFieldSchemaData(FieldStorageDefinitionInterface $storage_definition) {
+ return $this->installedStorageSchema()->get($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), array());
+ }
+
+ /**
+ * Stores schema data for the given field storage definition.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ * @param array $schema
+ * The field schema data array.
+ */
+ protected function saveFieldSchemaData(FieldStorageDefinitionInterface $storage_definition, $schema) {
+ $this->installedStorageSchema()->set($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), $schema);
+ }
+
+ /**
+ * Deletes schema data for the given field storage definition.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ */
+ protected function deleteFieldSchemaData(FieldStorageDefinitionInterface $storage_definition) {
+ $this->installedStorageSchema()->delete($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName());
+ }
+
+ /**
* Initializes common information for a base table.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
+ *
* @return array
* A partial schema array for the base table.
*/
- protected function initializeBaseTable() {
- $entity_type_id = $this->entityType->id();
+ protected function initializeBaseTable(ContentEntityTypeInterface $entity_type) {
+ $entity_type_id = $entity_type->id();
$schema = array(
'description' => "The base table for $entity_type_id entities.",
- 'primary key' => array($this->entityType->getKey('id')),
+ 'primary key' => array($entity_type->getKey('id')),
'indexes' => array(),
'foreign keys' => array(),
);
- if ($this->entityType->hasKey('revision')) {
- $revision_key = $this->entityType->getKey('revision');
- $key_name = $this->getEntityIndexName($revision_key);
+ if ($entity_type->hasKey('revision')) {
+ $revision_key = $entity_type->getKey('revision');
+ $key_name = $this->getEntityIndexName($entity_type, $revision_key);
$schema['unique keys'][$key_name] = array($revision_key);
$schema['foreign keys'][$entity_type_id . '__revision'] = array(
'table' => $this->storage->getRevisionTable(),
@@ -569,27 +842,30 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
/**
* Initializes common information for a revision table.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
+ *
* @return array
* A partial schema array for the revision table.
*/
- protected function initializeRevisionTable() {
- $entity_type_id = $this->entityType->id();
- $id_key = $this->entityType->getKey('id');
- $revision_key = $this->entityType->getKey('revision');
+ protected function initializeRevisionTable(ContentEntityTypeInterface $entity_type) {
+ $entity_type_id = $entity_type->id();
+ $id_key = $entity_type->getKey('id');
+ $revision_key = $entity_type->getKey('revision');
$schema = array(
'description' => "The revision table for $entity_type_id entities.",
'primary key' => array($revision_key),
'indexes' => array(),
'foreign keys' => array(
- $entity_type_id . '__revisioned' => array(
+ $entity_type_id . '__revisioned' => array(
'table' => $this->storage->getBaseTable(),
'columns' => array($id_key => $id_key),
),
),
);
- $schema['indexes'][$this->getEntityIndexName($id_key)] = array($id_key);
+ $schema['indexes'][$this->getEntityIndexName($entity_type, $id_key)] = array($id_key);
$this->addTableDefaults($schema);
@@ -599,12 +875,15 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
/**
* Initializes common information for a data table.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
+ *
* @return array
* A partial schema array for the data table.
*/
- protected function initializeDataTable() {
- $entity_type_id = $this->entityType->id();
- $id_key = $this->entityType->getKey('id');
+ protected function initializeDataTable(ContentEntityTypeInterface $entity_type) {
+ $entity_type_id = $entity_type->id();
+ $id_key = $entity_type->getKey('id');
$schema = array(
'description' => "The data table for $entity_type_id entities.",
@@ -620,9 +899,9 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
),
);
- if ($this->entityType->hasKey('revision')) {
- $key = $this->entityType->getKey('revision');
- $schema['indexes'][$this->getEntityIndexName($key)] = array($key);
+ if ($entity_type->hasKey('revision')) {
+ $key = $entity_type->getKey('revision');
+ $schema['indexes'][$this->getEntityIndexName($entity_type, $key)] = array($key);
}
$this->addTableDefaults($schema);
@@ -633,13 +912,16 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
/**
* Initializes common information for a revision data table.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
+ *
* @return array
* A partial schema array for the revision data table.
*/
- protected function initializeRevisionDataTable() {
- $entity_type_id = $this->entityType->id();
- $id_key = $this->entityType->getKey('id');
- $revision_key = $this->entityType->getKey('revision');
+ protected function initializeRevisionDataTable(ContentEntityTypeInterface $entity_type) {
+ $entity_type_id = $entity_type->id();
+ $id_key = $entity_type->getKey('id');
+ $revision_key = $entity_type->getKey('revision');
$schema = array(
'description' => "The revision data table for $entity_type_id entities.",
@@ -682,51 +964,59 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
/**
* Processes the gathered schema for a base table.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
* @param array $schema
* The table schema, passed by reference.
*
* @return array
* A partial schema array for the base table.
*/
- protected function processBaseTable(array &$schema) {
- $this->processIdentifierSchema($schema, $this->entityType->getKey('id'));
+ protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) {
+ $this->processIdentifierSchema($schema, $entity_type->getKey('id'));
}
/**
* Processes the gathered schema for a base table.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
* @param array $schema
* The table schema, passed by reference.
*
* @return array
* A partial schema array for the base table.
*/
- protected function processRevisionTable(array &$schema) {
- $this->processIdentifierSchema($schema, $this->entityType->getKey('revision'));
+ protected function processRevisionTable(ContentEntityTypeInterface $entity_type, array &$schema) {
+ $this->processIdentifierSchema($schema, $entity_type->getKey('revision'));
}
/**
* Processes the gathered schema for a base table.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
* @param array $schema
* The table schema, passed by reference.
*
* @return array
* A partial schema array for the base table.
*/
- protected function processDataTable(array &$schema) {
+ protected function processDataTable(ContentEntityTypeInterface $entity_type, array &$schema) {
}
/**
* Processes the gathered schema for a base table.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
* @param array $schema
* The table schema, passed by reference.
*
* @return array
* A partial schema array for the base table.
*/
- protected function processRevisionDataTable(array &$schema) {
+ protected function processRevisionDataTable(ContentEntityTypeInterface $entity_type, array &$schema) {
}
/**
@@ -745,19 +1035,665 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
}
/**
+ * Performs the specified operation on a field.
+ *
+ * This figures out whether the field is stored in a dedicated or shared table
+ * and forwards the call to the proper handler.
+ *
+ * @param string $operation
+ * The name of the operation to be performed.
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
+ * (optional) The original field storage definition. This is relevant (and
+ * required) only for updates. Defaults to NULL.
+ */
+ protected function performFieldSchemaOperation($operation, FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original = NULL) {
+ $table_mapping = $this->storage->getTableMapping();
+ if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+ $this->{$operation . 'DedicatedTableSchema'}($storage_definition, $original);
+ }
+ elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
+ $this->{$operation . 'SharedTableSchema'}($storage_definition, $original);
+ }
+ }
+
+ /**
+ * Creates the schema for a field stored in a dedicated table.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The storage definition of the field being created.
+ */
+ protected function createDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition) {
+ $schema = $this->getDedicatedTableSchema($storage_definition);
+ foreach ($schema as $name => $table) {
+ $this->database->schema()->createTable($name, $table);
+ }
+ $this->saveFieldSchemaData($storage_definition, $schema);
+ }
+
+ /**
+ * Creates the schema for a field stored in a shared table.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The storage definition of the field being created.
+ * @param bool $only_save
+ * (optional) Whether to skip modification of database tables and only save
+ * the schema data for future comparison. For internal use only. This is
+ * used by onEntityTypeCreate() after it has already fully created the
+ * shared tables.
+ */
+ protected function createSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, $only_save = FALSE) {
+ $created_field_name = $storage_definition->getName();
+ $table_mapping = $this->storage->getTableMapping();
+ $column_names = $table_mapping->getColumnNames($created_field_name);
+ $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 will host the created
+ // field schema.
+ $schema = array();
+ foreach ($shared_table_names as $table_name) {
+ foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
+ if ($field_name == $created_field_name) {
+ // Create field columns.
+ $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
+ if (!$only_save) {
+ foreach ($schema[$table_name]['fields'] as $name => $specifier) {
+ $schema_handler->addField($table_name, $name, $specifier);
+ }
+ 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 creating the field schema skip to the next table.
+ break;
+ }
+ }
+ }
+ $this->saveFieldSchemaData($storage_definition, $schema);
+ }
+
+ /**
+ * Deletes the schema for a field stored in a dedicated table.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * 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, $deleted);
+ $this->database->schema()->dropTable($table_name);
+ if ($this->entityType->isRevisionable()) {
+ $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $deleted);
+ $this->database->schema()->dropTable($revision_name);
+ }
+ $this->deleteFieldSchemaData($storage_definition);
+ }
+
+ /**
+ * Deletes the schema for a field stored in a shared table.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The storage definition of the field being deleted.
+ */
+ protected function deleteSharedTableSchema(FieldStorageDefinitionInterface $storage_definition) {
+ $deleted_field_name = $storage_definition->getName();
+ $table_mapping = $this->storage->getTableMapping(
+ $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
+ );
+ $column_names = $table_mapping->getColumnNames($deleted_field_name);
+ $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 ($shared_table_names as $table_name) {
+ foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
+ if ($field_name == $deleted_field_name) {
+ $schema = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
+
+ // Drop indexes and unique keys first.
+ if (!empty($schema['indexes'])) {
+ foreach ($schema['indexes'] as $name => $specifier) {
+ $schema_handler->dropIndex($table_name, $name);
+ }
+ }
+ if (!empty($schema['unique keys'])) {
+ foreach ($schema['unique keys'] as $name => $specifier) {
+ $schema_handler->dropUniqueKey($table_name, $name);
+ }
+ }
+ // Drop columns.
+ foreach ($column_names as $column_name) {
+ $schema_handler->dropField($table_name, $column_name);
+ }
+ // After deleting the field schema skip to the next table.
+ break;
+ }
+ }
+ }
+
+ $this->deleteFieldSchemaData($storage_definition);
+ }
+
+ /**
+ * 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 updateDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
+ 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
+ // 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 shared table schema
+ // to a dedicated 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 tables.
+ $this->performFieldSchemaOperation('create', $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.");
+ }
+ // 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_mapping = $this->storage->getTableMapping();
+ $table = $table_mapping->getDedicatedDataTableName($original);
+ $revision_table = $table_mapping->getDedicatedRevisionTableName($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 = $this->getFieldIndexName($storage_definition, $name);
+ $this->database->schema()->dropIndex($table, $real_name);
+ $this->database->schema()->dropIndex($revision_table, $real_name);
+ }
+ }
+ $table = $table_mapping->getDedicatedDataTableName($storage_definition);
+ $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
+ foreach ($schema['indexes'] as $name => $columns) {
+ if (!isset($original_schema['indexes'][$name]) || $columns != $original_schema['indexes'][$name]) {
+ $real_name = $this->getFieldIndexName($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(
+ $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
+ $column_name[1],
+ );
+ }
+ else {
+ $real_columns[] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
+ }
+ }
+ $this->database->schema()->addIndex($table, $real_name, $real_columns);
+ $this->database->schema()->addIndex($revision_table, $real_name, $real_columns);
+ }
+ }
+ $this->saveFieldSchemaData($storage_definition, $this->getDedicatedTableSchema($storage_definition));
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Returns the schema for a single field definition.
+ *
+ * Entity types may override this method in order to optimize the generated
+ * schema for given field. While all optimizations that apply to a single
+ * field have to be added here, all cross-field optimizations should be via
+ * SqlContentEntityStorageSchema::getEntitySchema() instead; e.g.,
+ * an index spanning multiple fields.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The storage definition of the field whose schema has to be returned.
+ * @param string $table_name
+ * The name of the table columns will be added to.
+ * @param string[] $column_mapping
+ * A mapping of field column names to database column names.
+ *
+ * @return array
+ * The schema definition for the table with the following keys:
+ * - fields: The schema definition for the each field columns.
+ * - indexes: The schema definition for the indexes.
+ * - unique keys: The schema definition for the unique keys.
+ * - foreign keys: The schema definition for the foreign keys.
+ *
+ * @throws \Drupal\Core\Field\FieldException
+ * Exception thrown if the schema contains reserved column names.
+ */
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = array();
+ $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())) {
+ throw new FieldException(format_string('Illegal field column names on @field_name', array('@field_name' => $storage_definition->getName())));
+ }
+
+ $field_name = $storage_definition->getName();
+ $field_description = $storage_definition->getDescription();
+
+ foreach ($column_mapping as $field_column_name => $schema_field_name) {
+ $column_schema = $field_schema['columns'][$field_column_name];
+
+ $schema['fields'][$schema_field_name] = $column_schema;
+ $schema['fields'][$schema_field_name]['description'] = $field_description;
+ // Only entity keys are required.
+ $keys = $this->entityType->getKeys() + array('langcode' => 'langcode');
+ // The label is an entity key, but label fields are not necessarily
+ // required.
+ // 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
+ // table is updated after the data has been inserted into the revision
+ // table. For this reason the revision ID field cannot be marked as NOT
+ // NULL.
+ unset($keys['label'], $keys['revision']);
+ // Key fields may not be NULL.
+ if (in_array($field_name, $keys)) {
+ $schema['fields'][$schema_field_name]['not null'] = TRUE;
+ }
+ }
+
+ if (!empty($field_schema['indexes'])) {
+ $schema['indexes'] = $this->getFieldIndexes($field_name, $field_schema, $column_mapping);
+ }
+
+ if (!empty($field_schema['unique keys'])) {
+ $schema['unique keys'] = $this->getFieldUniqueKeys($field_name, $field_schema, $column_mapping);
+ }
+
+ if (!empty($field_schema['foreign keys'])) {
+ $schema['foreign keys'] = $this->getFieldForeignKeys($field_name, $field_schema, $column_mapping);
+ }
+
+ return $schema;
+ }
+
+ /**
+ * Adds an index for the specified field to the given schema definition.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The storage definition of the field for which an index should be added.
+ * @param array $schema
+ * A reference to the schema array to be updated.
+ * @param bool $not_null
+ * (optional) Whether to also add a 'not null' constraint to the column
+ * being indexed. Doing so improves index performance. Defaults to FALSE,
+ * in case the field needs to support NULL values.
+ * @param int $size
+ * (optional) The index size. Defaults to no limit.
+ */
+ protected function addSharedTableFieldIndex(FieldStorageDefinitionInterface $storage_definition, &$schema, $not_null = FALSE, $size = NULL) {
+ $name = $storage_definition->getName();
+ $real_key = $this->getFieldSchemaIdentifierName($storage_definition->getTargetEntityTypeId(), $name);
+ $schema['indexes'][$real_key] = array($size ? array($name, $size) : $name);
+ if ($not_null) {
+ $schema['fields'][$name]['not null'] = TRUE;
+ }
+ }
+
+ /**
+ * Adds a unique key for the specified field to the given schema definition.
+ *
+ * Also adds a 'not null' constraint, because many databases do not reliably
+ * support unique keys on null columns.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The storage definition of the field to which to add a unique key.
+ * @param array $schema
+ * A reference to the schema array to be updated.
+ */
+ protected function addSharedTableFieldUniqueKey(FieldStorageDefinitionInterface $storage_definition, &$schema) {
+ $name = $storage_definition->getName();
+ $real_key = $this->getFieldSchemaIdentifierName($storage_definition->getTargetEntityTypeId(), $name);
+ $schema['unique keys'][$real_key] = array($name);
+ $schema['fields'][$name]['not null'] = TRUE;
+ }
+
+ /**
+ * Adds a foreign key for the specified field to the given schema definition.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The storage definition of the field to which to add a foreign key.
+ * @param array $schema
+ * A reference to the schema array to be updated.
+ * @param string $foreign_table
+ * The foreign table.
+ * @param string $foreign_column
+ * The foreign column.
+ */
+ protected function addSharedTableFieldForeignKey(FieldStorageDefinitionInterface $storage_definition, &$schema, $foreign_table, $foreign_column) {
+ $name = $storage_definition->getName();
+ $real_key = $this->getFieldSchemaIdentifierName($storage_definition->getTargetEntityTypeId(), $name);
+ $schema['foreign keys'][$real_key] = array(
+ 'table' => $foreign_table,
+ 'columns' => array($name => $foreign_column),
+ );
+ }
+
+ /**
+ * Returns the SQL schema for a dedicated table.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * (optional) The entity type definition. Defaults to the one returned by
+ * the entity manager.
+ *
+ * @return array
+ * The schema definition for the table with the following keys:
+ * - fields: The schema definition for the each field columns.
+ * - indexes: The schema definition for the indexes.
+ * - unique keys: The schema definition for the unique keys.
+ * - foreign keys: The schema definition for the foreign keys.
+ *
+ * @throws \Drupal\Core\Field\FieldException
+ * Exception thrown if the schema contains reserved column names.
+ *
+ * @see hook_schema()
+ */
+ protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, ContentEntityTypeInterface $entity_type = NULL) {
+ $description_current = "Data storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
+ $description_revision = "Revision archive storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
+
+ $id_definition = $this->fieldStorageDefinitions[$this->entityType->getKey('id')];
+ if ($id_definition->getType() == 'integer') {
+ $id_schema = array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The entity id this data is attached to',
+ );
+ }
+ else {
+ $id_schema = array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'description' => 'The entity id this data is attached to',
+ );
+ }
+
+ // Define the revision ID schema.
+ if (!$this->entityType->isRevisionable()) {
+ $revision_id_schema = $id_schema;
+ $revision_id_schema['description'] = 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id';
+ }
+ elseif ($this->fieldStorageDefinitions[$this->entityType->getKey('revision')]->getType() == 'integer') {
+ $revision_id_schema = array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The entity revision id this data is attached to',
+ );
+ }
+ else {
+ $revision_id_schema = array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'description' => 'The entity revision id this data is attached to',
+ );
+ }
+
+ $data_schema = array(
+ 'description' => $description_current,
+ 'fields' => array(
+ 'bundle' => array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
+ ),
+ 'deleted' => array(
+ 'type' => 'int',
+ 'size' => 'tiny',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'A boolean indicating whether this data item has been deleted'
+ ),
+ 'entity_id' => $id_schema,
+ 'revision_id' => $revision_id_schema,
+ 'langcode' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The language code for this data item.',
+ ),
+ 'delta' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The sequence number for this data item, used for multi-value fields',
+ ),
+ ),
+ 'primary key' => array('entity_id', 'deleted', 'delta', 'langcode'),
+ 'indexes' => array(
+ 'bundle' => array('bundle'),
+ 'deleted' => array('deleted'),
+ 'entity_id' => array('entity_id'),
+ 'revision_id' => array('revision_id'),
+ 'langcode' => array('langcode'),
+ ),
+ );
+
+ // Check that the schema does not include forbidden column names.
+ $schema = $storage_definition->getSchema();
+ $table_mapping = $this->storage->getTableMapping();
+ if (array_intersect(array_keys($schema['columns']), $table_mapping->getReservedColumns())) {
+ throw new FieldException(format_string('Illegal field column names on @field_name', array('@field_name' => $storage_definition->getName())));
+ }
+
+ // Add field columns.
+ foreach ($schema['columns'] as $column_name => $attributes) {
+ $real_name = $table_mapping->getFieldColumnName($storage_definition, $column_name);
+ $data_schema['fields'][$real_name] = $attributes;
+ }
+
+ // Add indexes.
+ foreach ($schema['indexes'] as $index_name => $columns) {
+ $real_name = $this->getFieldIndexName($storage_definition, $index_name);
+ 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)) {
+ $data_schema['indexes'][$real_name][] = array(
+ $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
+ $column_name[1],
+ );
+ }
+ else {
+ $data_schema['indexes'][$real_name][] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
+ }
+ }
+ }
+
+ // Add foreign keys.
+ foreach ($schema['foreign keys'] as $specifier => $specification) {
+ $real_name = $this->getFieldIndexName($storage_definition, $specifier);
+ $data_schema['foreign keys'][$real_name]['table'] = $specification['table'];
+ foreach ($specification['columns'] as $column_name => $referenced) {
+ $sql_storage_column = $table_mapping->getFieldColumnName($storage_definition, $column_name);
+ $data_schema['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced;
+ }
+ }
+
+ $dedicated_table_schema = array($table_mapping->getDedicatedDataTableName($storage_definition) => $data_schema);
+
+ // If the entity type is revisionable, construct the revision table.
+ $entity_type = $entity_type ?: $this->entityType;
+ if ($entity_type->isRevisionable()) {
+ $revision_schema = $data_schema;
+ $revision_schema['description'] = $description_revision;
+ $revision_schema['primary key'] = array('entity_id', 'revision_id', 'deleted', 'delta', 'langcode');
+ $revision_schema['fields']['revision_id']['not null'] = TRUE;
+ $revision_schema['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
+ $dedicated_table_schema += array($table_mapping->getDedicatedRevisionTableName($storage_definition) => $revision_schema);
+ }
+
+ return $dedicated_table_schema;
+ }
+
+ /**
* Returns the name to be used for the given entity index.
*
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+ * The entity type.
* @param string $index
* The index column name.
*
* @return string
* The index name.
*/
- protected function getEntityIndexName($index) {
- return $this->entityType->id() . '__' . $index;
+ protected function getEntityIndexName(ContentEntityTypeInterface $entity_type, $index) {
+ return $entity_type->id() . '__' . $index;
}
/**
+ * Generates an index name for a field data table.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ * @param string $index
+ * The name of the index.
+ *
+ * @return string
+ * A string containing a generated index name for a field data table that is
+ * unique among all other fields.
+ */
+ protected function getFieldIndexName(FieldStorageDefinitionInterface $storage_definition, $index) {
+ return $storage_definition->getName() . '_' . $index;
+ }
+ /**
* Checks whether a database table is non-existent or empty.
*
* Empty tables can be dropped and recreated without data loss.
@@ -768,7 +1704,7 @@ class SqlContentEntityStorageSchema implements FieldableEntityStorageSchemaInter
* @return bool
* TRUE if the table is empty, FALSE otherwise.
*/
- protected function tableIsEmpty($table_name) {
+ protected function isTableEmpty($table_name) {
return !$this->database->schema()->tableExists($table_name) ||
!$this->database->select($table_name)
->countQuery()
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php
index b0a9b05..984d05e 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php
@@ -17,9 +17,13 @@ interface SqlEntityStorageInterface extends EntityStorageInterface {
/**
* Gets a table mapping for the entity's SQL tables.
*
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
+ * (optional) An array of field storage definitions to be used to compute
+ * the table mapping. Defaults to the ones provided by the entity manager.
+ *
* @return \Drupal\Core\Entity\Sql\TableMappingInterface
* A table mapping object for the entity's tables.
*/
- public function getTableMapping();
+ public function getTableMapping(array $storage_definitions = NULL);
}
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 0917e42..92e9748 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -821,6 +821,17 @@ class ModuleHandler implements ModuleHandlerInterface {
$version = max(max($versions), $version);
}
+ // Notify the entity manager that this module's entity types are new,
+ // so that it can notify all interested handlers. For example, a
+ // SQL-based storage handler can use this as an opportunity to create
+ // the necessary database tables.
+ $entity_manager = \Drupal::entityManager();
+ foreach ($entity_manager->getDefinitions() as $entity_type) {
+ if ($entity_type->getProvider() == $module) {
+ $entity_manager->onEntityTypeCreate($entity_type);
+ }
+ }
+
// Install default configuration of the module.
$config_installer = \Drupal::service('config.installer');
if ($sync_status) {
@@ -844,17 +855,6 @@ class ModuleHandler implements ModuleHandlerInterface {
}
drupal_set_installed_schema_version($module, $version);
- // Notify the entity manager that this module's entity types are new,
- // so that it can notify all interested handlers. For example, a
- // SQL-based storage handler can use this as an opportunity to create
- // the necessary database tables.
- $entity_manager = \Drupal::entityManager();
- foreach ($entity_manager->getDefinitions() as $entity_type) {
- if ($entity_type->getProvider() == $module) {
- $entity_manager->onEntityTypeCreate($entity_type);
- }
- }
-
// Record the fact that it was installed.
$modules_installed[] = $module;
diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
index 20b31ad..efddd23 100644
--- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
+++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
@@ -553,11 +553,6 @@ class BaseFieldDefinition extends ListDataDefinition implements FieldDefinitionI
'foreign keys' => array(),
);
- // Check that the schema does not include forbidden column names.
- if (array_intersect(array_keys($schema['columns']), static::getReservedColumns())) {
- throw new FieldException('Illegal field type columns.');
- }
-
// Merge custom indexes with those specified by the field type. Custom
// indexes prevail.
$schema['indexes'] = $this->indexes + $schema['indexes'];
@@ -583,19 +578,17 @@ class BaseFieldDefinition extends ListDataDefinition implements FieldDefinitionI
}
/**
- * A list of columns that can not be used as field type columns.
- *
- * @return array
+ * {@inheritdoc}
*/
- public static function getReservedColumns() {
- return array('deleted');
+ public function hasCustomStorage() {
+ return !empty($this->definition['custom_storage']) || $this->isComputed();
}
/**
* {@inheritdoc}
*/
- public function hasCustomStorage() {
- return !empty($this->definition['custom_storage']) || $this->isComputed();
+ public function isBaseField() {
+ return TRUE;
}
/**
diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
index 10f090e..6ace3fa 100644
--- a/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
@@ -298,6 +298,18 @@ interface FieldStorageDefinitionInterface {
public function hasCustomStorage();
/**
+ * Determines whether the field is a base field.
+ *
+ * Base fields are not specific to a given bundle or a set of bundles. This
+ * excludes configurable fields, as they are always attached to a specific
+ * bundle.
+ *
+ * @return bool
+ * Whether the field is a base field.
+ */
+ public function isBaseField();
+
+ /**
* Returns a unique identifier for the field.
*
* @return string
diff --git a/core/modules/aggregator/src/FeedStorageSchema.php b/core/modules/aggregator/src/FeedStorageSchema.php
index bb197bc..d251ed5 100644
--- a/core/modules/aggregator/src/FeedStorageSchema.php
+++ b/core/modules/aggregator/src/FeedStorageSchema.php
@@ -7,8 +7,8 @@
namespace Drupal\aggregator;
-use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the feed schema handler.
@@ -18,22 +18,25 @@ class FeedStorageSchema extends SqlContentEntityStorageSchema {
/**
* {@inheritdoc}
*/
- protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
- $schema = parent::getEntitySchema($entity_type, $reset);
-
- // Marking the respective fields as NOT NULL makes the indexes more
- // performant.
- $schema['aggregator_feed']['fields']['url']['not null'] = TRUE;
- $schema['aggregator_feed']['fields']['queued']['not null'] = TRUE;
- $schema['aggregator_feed']['fields']['title']['not null'] = TRUE;
-
- $schema['aggregator_feed']['indexes'] += array(
- 'aggregator_feed__url' => array(array('url', 255)),
- 'aggregator_feed__queued' => array('queued'),
- );
- $schema['aggregator_feed']['unique keys'] += array(
- 'aggregator_feed__title' => array('title'),
- );
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+ $field_name = $storage_definition->getName();
+
+ if ($table_name == 'aggregator_feed') {
+ switch ($field_name) {
+ case 'url':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE, 255);
+ break;
+
+ case 'queued':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+
+ case 'title':
+ $this->addSharedTableFieldUniqueKey($storage_definition, $schema);
+ break;
+ }
+ }
return $schema;
}
diff --git a/core/modules/aggregator/src/ItemStorageSchema.php b/core/modules/aggregator/src/ItemStorageSchema.php
index ae9af84..9688f67 100644
--- a/core/modules/aggregator/src/ItemStorageSchema.php
+++ b/core/modules/aggregator/src/ItemStorageSchema.php
@@ -9,6 +9,7 @@ namespace Drupal\aggregator;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the item schema handler.
@@ -18,22 +19,21 @@ class ItemStorageSchema extends SqlContentEntityStorageSchema {
/**
* {@inheritdoc}
*/
- protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
- $schema = parent::getEntitySchema($entity_type, $reset);
-
- // Marking the respective fields as NOT NULL makes the indexes more
- // performant.
- $schema['aggregator_item']['fields']['timestamp']['not null'] = TRUE;
-
- $schema['aggregator_item']['indexes'] += array(
- 'aggregator_item__timestamp' => array('timestamp'),
- );
- $schema['aggregator_item']['foreign keys'] += array(
- 'aggregator_item__aggregator_feed' => array(
- 'table' => 'aggregator_feed',
- 'columns' => array('fid' => 'fid'),
- ),
- );
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+ $field_name = $storage_definition->getName();
+
+ if ($table_name == 'aggregator_item') {
+ switch ($field_name) {
+ case 'timestamp':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+
+ case 'fid':
+ $this->addSharedTableFieldForeignKey($storage_definition, $schema, 'aggregator_feed', 'fid');
+ break;
+ }
+ }
return $schema;
}
diff --git a/core/modules/block_content/src/BlockContentStorageSchema.php b/core/modules/block_content/src/BlockContentStorageSchema.php
index 18295d3..c1674ae 100644
--- a/core/modules/block_content/src/BlockContentStorageSchema.php
+++ b/core/modules/block_content/src/BlockContentStorageSchema.php
@@ -9,6 +9,7 @@ namespace Drupal\block_content;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the block content schema handler.
@@ -21,10 +22,6 @@ class BlockContentStorageSchema extends SqlContentEntityStorageSchema {
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset);
- // Marking the respective fields as NOT NULL makes the indexes more
- // performant.
- $schema['block_content_field_data']['fields']['info']['not null'] = TRUE;
-
$schema['block_content_field_data']['unique keys'] += array(
'block_content__info' => array('info', 'langcode'),
);
@@ -32,4 +29,24 @@ class BlockContentStorageSchema extends SqlContentEntityStorageSchema {
return $schema;
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+ $field_name = $storage_definition->getName();
+
+ if ($table_name == 'block_content_field_data') {
+ switch ($field_name) {
+ case 'info':
+ // Improves the performance of the block_content__info index defined
+ // in getEntitySchema().
+ $schema['fields'][$field_name]['not null'] = TRUE;
+ break;
+ }
+ }
+
+ return $schema;
+ }
+
}
diff --git a/core/modules/comment/src/CommentStorageSchema.php b/core/modules/comment/src/CommentStorageSchema.php
index 7b9a03c..9196f9e 100644
--- a/core/modules/comment/src/CommentStorageSchema.php
+++ b/core/modules/comment/src/CommentStorageSchema.php
@@ -9,6 +9,7 @@ namespace Drupal\comment;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the comment schema handler.
@@ -21,13 +22,6 @@ class CommentStorageSchema extends SqlContentEntityStorageSchema {
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset);
- // Marking the respective fields as NOT NULL makes the indexes more
- // performant.
- $schema['comment_field_data']['fields']['created']['not null'] = TRUE;
- $schema['comment_field_data']['fields']['thread']['not null'] = TRUE;
-
- unset($schema['comment_field_data']['indexes']['comment_field__pid__target_id']);
- unset($schema['comment_field_data']['indexes']['comment_field__entity_id__target_id']);
$schema['comment_field_data']['indexes'] += array(
'comment__status_pid' => array('pid', 'status'),
'comment__num_new' => array(
@@ -45,16 +39,40 @@ class CommentStorageSchema extends SqlContentEntityStorageSchema {
'comment_type',
'default_langcode',
),
- 'comment__created' => array('created'),
- );
- $schema['comment_field_data']['foreign keys'] += array(
- 'comment__author' => array(
- 'table' => 'users',
- 'columns' => array('uid' => 'uid'),
- ),
);
return $schema;
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+ $field_name = $storage_definition->getName();
+
+ if ($table_name == 'comment_field_data') {
+ // Remove unneeded indexes.
+ unset($schema['indexes']['comment_field__pid__target_id']);
+ unset($schema['indexes']['comment_field__entity_id__target_id']);
+
+ switch ($field_name) {
+ case 'thread':
+ // Improves the performance of the comment__num_new index defined
+ // in getEntitySchema().
+ $schema['fields'][$field_name]['not null'] = TRUE;
+ break;
+
+ case 'created':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+
+ case 'uid':
+ $this->addSharedTableFieldForeignKey($storage_definition, $schema, 'users', 'uid');
+ }
+ }
+
+ return $schema;
+ }
+
}
diff --git a/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.module b/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.module
index 70311f1..116a326 100644
--- a/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.module
+++ b/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.module
@@ -13,15 +13,11 @@ use Drupal\Core\Field\BaseFieldDefinition;
function contact_storage_test_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
if ($entity_type->id() == 'contact_message') {
$fields = array();
+
$fields['id'] = BaseFieldDefinition::create('integer')
->setLabel(t('Message ID'))
->setDescription(t('The message ID.'))
->setReadOnly(TRUE)
- // Explicitly set this to 'contact' so that
- // SqlContentEntityStorage::usesDedicatedTable() doesn't attempt to
- // put the ID in a dedicated table.
- // @todo Remove when https://www.drupal.org/node/1498720 is in.
- ->setProvider('contact')
->setSetting('unsigned', TRUE);
return $fields;
diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php
index 128ac01..4f346c5 100644
--- a/core/modules/field/src/Entity/FieldStorageConfig.php
+++ b/core/modules/field/src/Entity/FieldStorageConfig.php
@@ -424,11 +424,6 @@ class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigI
'foreign keys' => array(),
);
- // Check that the schema does not include forbidden column names.
- if (array_intersect(array_keys($schema['columns']), static::getReservedColumns())) {
- throw new FieldException(String::format('Illegal field type @field_type on @field_name.', array('@field_type' => $this->type, '@field_name' => $this->name)));
- }
-
// Merge custom indexes with those specified by the field type. Custom
// indexes prevail.
$schema['indexes'] = $this->indexes + $schema['indexes'];
@@ -449,6 +444,13 @@ class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigI
/**
* {@inheritdoc}
*/
+ public function isBaseField() {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function getColumns() {
$schema = $this->getSchema();
// A typical use case for the method is to iterate on the columns, while
@@ -608,15 +610,6 @@ class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigI
}
/**
- * A list of columns that can not be used as field type columns.
- *
- * @return array
- */
- public static function getReservedColumns() {
- return array('deleted');
- }
-
- /**
* Determines whether a field has any data.
*
* @return bool
diff --git a/core/modules/file/src/FileStorageSchema.php b/core/modules/file/src/FileStorageSchema.php
index f223e7b..9c66e66 100644
--- a/core/modules/file/src/FileStorageSchema.php
+++ b/core/modules/file/src/FileStorageSchema.php
@@ -7,8 +7,8 @@
namespace Drupal\file;
-use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the file schema handler.
@@ -18,24 +18,25 @@ class FileStorageSchema extends SqlContentEntityStorageSchema {
/**
* {@inheritdoc}
*/
- protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
- $schema = parent::getEntitySchema($entity_type, $reset);
-
- // Marking the respective fields as NOT NULL makes the indexes more
- // performant.
- $schema['file_managed']['fields']['status']['not null'] = TRUE;
- $schema['file_managed']['fields']['changed']['not null'] = TRUE;
- $schema['file_managed']['fields']['uri']['not null'] = TRUE;
-
- // @todo There should be a 'binary' field type or setting.
- $schema['file_managed']['fields']['uri']['binary'] = TRUE;
- $schema['file_managed']['indexes'] += array(
- 'file__status' => array('status'),
- 'file__changed' => array('changed'),
- );
- $schema['file_managed']['unique keys'] += array(
- 'file__uri' => array('uri'),
- );
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+ $field_name = $storage_definition->getName();
+
+ if ($table_name == 'file_managed') {
+ switch ($field_name) {
+ case 'status':
+ case 'changed':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+
+ case 'uri':
+ $this->addSharedTableFieldUniqueKey($storage_definition, $schema, TRUE);
+ // @todo There should be a 'binary' field type or setting:
+ // https://www.drupal.org/node/2068655.
+ $schema['fields'][$field_name]['binary'] = TRUE;
+ break;
+ }
+ }
return $schema;
}
diff --git a/core/modules/node/src/NodeStorageSchema.php b/core/modules/node/src/NodeStorageSchema.php
index 4bd15c0..4d02d3b 100644
--- a/core/modules/node/src/NodeStorageSchema.php
+++ b/core/modules/node/src/NodeStorageSchema.php
@@ -9,6 +9,7 @@ namespace Drupal\node;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the node schema handler.
@@ -23,31 +24,11 @@ class NodeStorageSchema extends SqlContentEntityStorageSchema {
// Marking the respective fields as NOT NULL makes the indexes more
// performant.
- $schema['node_field_data']['fields']['changed']['not null'] = TRUE;
- $schema['node_field_data']['fields']['created']['not null'] = TRUE;
$schema['node_field_data']['fields']['default_langcode']['not null'] = TRUE;
- $schema['node_field_data']['fields']['promote']['not null'] = TRUE;
- $schema['node_field_data']['fields']['status']['not null'] = TRUE;
- $schema['node_field_data']['fields']['sticky']['not null'] = TRUE;
- $schema['node_field_data']['fields']['title']['not null'] = TRUE;
$schema['node_field_revision']['fields']['default_langcode']['not null'] = TRUE;
- // @todo Revisit index definitions in https://drupal.org/node/2015277.
- $schema['node_revision']['indexes'] += array(
- 'node__langcode' => array('langcode'),
- );
- $schema['node_revision']['foreign keys'] += array(
- 'node__revision_author' => array(
- 'table' => 'users',
- 'columns' => array('revision_uid' => 'uid'),
- ),
- );
-
$schema['node_field_data']['indexes'] += array(
- 'node__changed' => array('changed'),
- 'node__created' => array('created'),
'node__default_langcode' => array('default_langcode'),
- 'node__langcode' => array('langcode'),
'node__frontpage' => array('promote', 'status', 'sticky', 'created'),
'node__status_type' => array('status', 'type', 'nid'),
'node__title_type' => array('title', array('type', 4)),
@@ -55,10 +36,59 @@ class NodeStorageSchema extends SqlContentEntityStorageSchema {
$schema['node_field_revision']['indexes'] += array(
'node__default_langcode' => array('default_langcode'),
- 'node__langcode' => array('langcode'),
);
return $schema;
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+ $field_name = $storage_definition->getName();
+
+ if ($table_name == 'node_revision') {
+ switch ($field_name) {
+ case 'langcode':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+
+ case 'revision_uid':
+ $this->addSharedTableFieldForeignKey($storage_definition, $schema, 'users', 'uid');
+ break;
+ }
+ }
+
+ if ($table_name == 'node_field_data') {
+ switch ($field_name) {
+ case 'promote':
+ case 'status':
+ case 'sticky':
+ case 'title':
+ // Improves the performance of the indexes defined
+ // in getEntitySchema().
+ $schema['fields'][$field_name]['not null'] = TRUE;
+ break;
+
+ case 'changed':
+ case 'created':
+ case 'langcode':
+ // @todo Revisit index definitions: https://drupal.org/node/2015277.
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+ }
+ }
+
+ if ($table_name == 'node_field_revision') {
+ switch ($field_name) {
+ case 'langcode':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+ }
+ }
+
+ return $schema;
+ }
+
}
diff --git a/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php b/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
index 755527b..0866f7a 100644
--- a/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
@@ -15,6 +15,13 @@ namespace Drupal\system\Tests\Entity;
class EntityBundleFieldTest extends EntityUnitTestBase {
/**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('entity_schema_test');
+
+ /**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
@@ -40,58 +47,41 @@ class EntityBundleFieldTest extends EntityUnitTestBase {
}
/**
- * Tests the custom bundle field creation and deletion.
- */
- public function testCustomBundleFieldCreateDelete() {
- // Install the module which adds the field.
- $this->moduleHandler->install(array('entity_bundle_field_test'), FALSE);
- $definition = $this->entityManager->getFieldDefinitions('entity_test', 'custom')['custom_field'];
- $this->assertNotNull($definition, 'Field definition found.');
-
- // Make sure the table has been created.
- /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
- $table_mapping = $this->entityManager->getStorage('entity_test')->getTableMapping();
- $table = $table_mapping->getDedicatedDataTableName($definition);
- $this->assertTrue($this->database->schema()->tableExists($table), 'Table created');
- $this->moduleHandler->uninstall(array('entity_bundle_field_test'), FALSE);
- $this->assertFalse($this->database->schema()->tableExists($table), 'Table dropped');
- }
-
- /**
* Tests making use of a custom bundle field.
*/
public function testCustomBundleFieldUsage() {
+ entity_test_create_bundle('custom');
+
// Check that an entity with bundle entity_test does not have the custom
// field.
- $this->moduleHandler->install(array('entity_bundle_field_test'), FALSE);
$storage = $this->entityManager->getStorage('entity_test');
$entity = $storage->create([
'type' => 'entity_test',
]);
- $this->assertFalse($entity->hasField('custom_field'));
+ $this->assertFalse($entity->hasField('custom_bundle_field'));
// Check that the custom bundle has the defined custom field and check
// saving and deleting of custom field data.
$entity = $storage->create([
'type' => 'custom',
]);
- $this->assertTrue($entity->hasField('custom_field'));
- $entity->custom_field->value = 'swanky';
+ $this->assertTrue($entity->hasField('custom_bundle_field'));
+ $entity->custom_bundle_field->value = 'swanky';
$entity->save();
$storage->resetCache();
$entity = $storage->load($entity->id());
- $this->assertEqual($entity->custom_field->value, 'swanky', 'Entity was saved correct.y');
+ $this->assertEqual($entity->custom_bundle_field->value, 'swanky', 'Entity was saved correctly');
- $entity->custom_field->value = 'cozy';
+ $entity->custom_bundle_field->value = 'cozy';
$entity->save();
$storage->resetCache();
$entity = $storage->load($entity->id());
- $this->assertEqual($entity->custom_field->value, 'cozy', 'Entity was updated correctly.');
+ $this->assertEqual($entity->custom_bundle_field->value, 'cozy', 'Entity was updated correctly.');
$entity->delete();
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $storage->getTableMapping();
- $table = $table_mapping->getDedicatedDataTableName($entity->getFieldDefinition('custom_field'));
+ $table = $table_mapping->getDedicatedDataTableName($entity->getFieldDefinition('custom_bundle_field'));
$result = $this->database->select($table, 'f')
->fields('f')
->condition('f.entity_id', $entity->id())
@@ -100,11 +90,11 @@ class EntityBundleFieldTest extends EntityUnitTestBase {
// Create another entity to test that values are marked as deleted when a
// bundle is deleted.
- $entity = $storage->create(['type' => 'custom', 'custom_field' => 'new']);
+ $entity = $storage->create(['type' => 'custom', 'custom_bundle_field' => 'new']);
$entity->save();
entity_test_delete_bundle('custom');
- $table = $table_mapping->getDedicatedDataTableName($entity->getFieldDefinition('custom_field'));
+ $table = $table_mapping->getDedicatedDataTableName($entity->getFieldDefinition('custom_bundle_field'));
$result = $this->database->select($table, 'f')
->condition('f.entity_id', $entity->id())
->condition('deleted', 1)
@@ -112,7 +102,8 @@ class EntityBundleFieldTest extends EntityUnitTestBase {
->execute();
$this->assertEqual(1, $result->fetchField(), 'Field data has been deleted');
- // @todo Test field purge and table deletion once supported.
+ // @todo Test field purge and table deletion once supported. See
+ // https://www.drupal.org/node/2282119.
// $this->assertFalse($this->database->schema()->tableExists($table), 'Custom field table was deleted');
}
diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
index 655aadb..752a1f3 100644
--- a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
@@ -8,6 +8,9 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\entity_test\FieldStorageDefinition;
/**
* Tests EntityDefinitionUpdateManager functionality.
@@ -37,17 +40,18 @@ class EntityDefinitionUpdateTest extends EntityUnitTestBase {
parent::setUp();
$this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager');
$this->database = $this->container->get('database');
+
+ // Install every entity type's schema that wasn't installed in the parent
+ // method.
+ foreach (array_diff_key($this->entityManager->getDefinitions(), array_flip(array('user', 'entity_test'))) as $entity_type_id => $entity_type) {
+ $this->installEntitySchema($entity_type_id);
+ }
}
/**
* Tests when no definition update is needed.
*/
public function testNoUpdates() {
- // Install every entity type's schema.
- foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
- $this->installEntitySchema($entity_type_id);
- }
-
// Ensure that the definition update manager reports no updates.
$this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that no updates are needed.');
$this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), array(), 'EntityDefinitionUpdateManager reports an empty change summary.');
@@ -60,50 +64,375 @@ class EntityDefinitionUpdateTest extends EntityUnitTestBase {
/**
* Tests updating entity schema when there are no existing entities.
*/
- public function testUpdateWithoutData() {
- // Install every entity type's schema. Start with entity_test_rev not
- // supporting revisions, and ensure its revision table isn't created.
- $this->state->set('entity_test.entity_test_rev.disable_revisable', TRUE);
- $this->entityManager->clearCachedDefinitions();
- foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
- $this->installEntitySchema($entity_type_id);
- }
- $this->assertFalse($this->database->schema()->tableExists('entity_test_rev_revision'), 'Revision table not created for entity_test_rev.');
+ public function testEntityTypeUpdateWithoutData() {
+ // The 'entity_test_update' entity type starts out non-revisionable, so
+ // ensure the revision table hasn't been created during setUp().
+ $this->assertFalse($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table not created for entity_test_update.');
- // Restore entity_test_rev back to supporting revisions and ensure the
- // definition update manager reports that an update is needed.
- $this->state->delete('entity_test.entity_test_rev.disable_revisable');
+ // Update it to be revisionable and ensure the definition update manager
+ // reports that an update is needed.
+ $this->updateEntityTypeToRevisionable();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
- 'entity_test_rev' => array(
- t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_rev')->getLabel())),
+ 'entity_test_update' => array(
+ t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel())),
),
);
- $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.');
// Run the update and ensure the revision table is created.
$this->entityDefinitionUpdateManager->applyUpdates();
- $this->assertTrue($this->database->schema()->tableExists('entity_test_rev_revision'), 'Revision table created for entity_test_rev.');
+ $this->assertTrue($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table created for entity_test_update.');
}
/**
* Tests updating entity schema when there are existing entities.
*/
- public function testUpdateWithData() {
- // Install every entity type's schema. Start with entity_test_rev not
- // supporting revisions.
- $this->state->set('entity_test.entity_test_rev.disable_revisable', TRUE);
- $this->entityManager->clearCachedDefinitions();
- foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
- $this->installEntitySchema($entity_type_id);
+ public function testEntityTypeUpdateWithData() {
+ // Save an entity.
+ $this->entityManager->getStorage('entity_test_update')->create()->save();
+
+ // Update the entity type to be revisionable and try to apply the update.
+ // It's expected to throw an exception.
+ $this->updateEntityTypeToRevisionable();
+ try {
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->fail('EntityStorageException thrown when trying to apply an update that requires data migration.');
}
+ catch (EntityStorageException $e) {
+ $this->pass('EntityStorageException thrown when trying to apply an update that requires data migration.');
+ }
+ }
+ /**
+ * Tests creating, updating, and deleting a base field if no entities exist.
+ */
+ public function testBaseFieldCreateUpdateDeleteWithoutData() {
+ // Add a base field, ensure the update manager reports it, and the update
+ // creates its schema.
+ $this->addBaseField();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Create the %field_name field.', array('%field_name' => t('A new base field'))),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
+
+ // Add an index on the base field, ensure the update manager reports it,
+ // and the update creates it.
+ $this->addBaseFieldIndex();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Update the %field_name field.', array('%field_name' => t('A new base field'))),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index created.');
+
+ // Remove the above index, ensure the update manager reports it, and the
+ // update deletes it.
+ $this->removeBaseFieldIndex();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Update the %field_name field.', array('%field_name' => t('A new base field'))),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index deleted.');
+
+ // Update the type of the base field from 'string' to 'text', ensure the
+ // update manager reports it, and the update adjusts the schema
+ // accordingly.
+ $this->modifyBaseField();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Update the %field_name field.', array('%field_name' => t('A new base field'))),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Original column deleted in shared table for new_base_field.');
+ $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__value'), 'Value column created in shared table for new_base_field.');
+ $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__format'), 'Format column created in shared table for new_base_field.');
+
+ // Remove the base field, ensure the update manager reports it, and the
+ // update deletes the schema.
+ $this->removeBaseField();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Delete the %field_name field.', array('%field_name' => t('A new base field'))),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_value'), 'Value column deleted from shared table for new_base_field.');
+ $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_format'), 'Format column deleted from shared table for new_base_field.');
+ }
+
+ /**
+ * Tests creating, updating, and deleting a bundle field if no entities exist.
+ */
+ public function testBundleFieldCreateUpdateDeleteWithoutData() {
+ // Add a bundle field, ensure the update manager reports it, and the update
+ // creates its schema.
+ $this->addBundleField();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Create the %field_name field.', array('%field_name' => t('A new bundle field'))),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
+
+ // Update the type of the base field from 'string' to 'text', ensure the
+ // update manager reports it, and the update adjusts the schema
+ // accordingly.
+ $this->modifyBundleField();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Update the %field_name field.', array('%field_name' => t('A new bundle field'))),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertTrue($this->database->schema()->fieldExists('entity_test_update__new_bundle_field', 'new_bundle_field_format'), 'Format column created in dedicated table for new_base_field.');
+
+ // Remove the bundle field, ensure the update manager reports it, and the
+ // update deletes the schema.
+ $this->removeBundleField();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Delete the %field_name field.', array('%field_name' => t('A new bundle field'))),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
+ }
+
+ /**
+ * Tests creating and deleting a base field if entities exist.
+ *
+ * This tests deletion when there are existing entities, but not existing data
+ * for the field being deleted.
+ *
+ * @see testBaseFieldDeleteWithExistingData()
+ */
+ public function testBaseFieldCreateDeleteWithExistingEntities() {
+ // Save an entity.
+ $name = $this->randomString();
+ $entity = $this->entityManager->getStorage('entity_test_update')->create(array('name' => $name));
+ $entity->save();
+
+ // Add a base field and run the update. Ensure the base field's column is
+ // created and the prior saved entity data is still there.
+ $this->addBaseField();
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
+ $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
+ $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
+
+ // Remove the base field and run the update. Ensure the base field's column
+ // is deleted and the prior saved entity data is still there.
+ $this->removeBaseField();
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.');
+ $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
+ $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
+ }
+
+ /**
+ * Tests creating and deleting a bundle field if entities exist.
+ *
+ * This tests deletion when there are existing entities, but not existing data
+ * for the field being deleted.
+ *
+ * @see testBundleFieldDeleteWithExistingData()
+ */
+ public function testBundleFieldCreateDeleteWithExistingEntities() {
// Save an entity.
- $this->entityManager->getStorage('entity_test_rev')->create()->save();
+ $name = $this->randomString();
+ $entity = $this->entityManager->getStorage('entity_test_update')->create(array('name' => $name));
+ $entity->save();
+
+ // Add a bundle field and run the update. Ensure the bundle field's table
+ // is created and the prior saved entity data is still there.
+ $this->addBundleField();
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
+ $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
+ $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
+
+ // Remove the base field and run the update. Ensure the bundle field's
+ // table is deleted and the prior saved entity data is still there.
+ $this->removeBundleField();
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
+ $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
+ $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
+ }
+
+ /**
+ * Tests deleting a base field when it has existing data.
+ */
+ public function testBaseFieldDeleteWithExistingData() {
+ // Add the base field and run the update.
+ $this->addBaseField();
+ $this->entityDefinitionUpdateManager->applyUpdates();
+
+ // Save an entity with the base field populated.
+ $this->entityManager->getStorage('entity_test_update')->create(array('new_base_field' => 'foo'))->save();
- // Restore entity_test_rev back to supporting revisions and try to apply
- // the update. It's expected to throw an exception.
- $this->state->delete('entity_test.entity_test_rev.disable_revisable');
+ // Remove the base field and apply updates. It's expected to throw an
+ // exception.
+ // @todo Revisit that expectation once purging is implemented for
+ // all fields: https://www.drupal.org/node/2282119.
+ $this->removeBaseField();
+ try {
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
+ }
+ catch (FieldStorageDefinitionUpdateForbiddenException $e) {
+ $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
+ }
+ }
+
+ /**
+ * Tests deleting a bundle field when it has existing data.
+ */
+ public function testBundleFieldDeleteWithExistingData() {
+ // Add the bundle field and run the update.
+ $this->addBundleField();
+ $this->entityDefinitionUpdateManager->applyUpdates();
+
+ // Save an entity with the bundle field populated.
+ entity_test_create_bundle('custom');
+ $this->entityManager->getStorage('entity_test_update')->create(array('type' => 'test_bundle', 'new_bundle_field' => 'foo'))->save();
+
+ // Remove the bundle field and apply updates. It's expected to throw an
+ // exception.
+ // @todo Revisit that expectation once purging is implemented for
+ // all fields: https://www.drupal.org/node/2282119.
+ $this->removeBundleField();
+ try {
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
+ }
+ catch (FieldStorageDefinitionUpdateForbiddenException $e) {
+ $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
+ }
+ }
+
+ /**
+ * Tests updating a base field when it has existing data.
+ */
+ public function testBaseFieldUpdateWithExistingData() {
+ // Add the base field and run the update.
+ $this->addBaseField();
+ $this->entityDefinitionUpdateManager->applyUpdates();
+
+ // Save an entity with the base field populated.
+ $this->entityManager->getStorage('entity_test_update')->create(array('new_base_field' => 'foo'))->save();
+
+ // Change the field's field type and apply updates. It's expected to
+ // throw an exception.
+ $this->modifyBaseField();
+ try {
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
+ }
+ catch (FieldStorageDefinitionUpdateForbiddenException $e) {
+ $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
+ }
+ }
+
+ /**
+ * Tests updating a bundle field when it has existing data.
+ */
+ public function testBundleFieldUpdateWithExistingData() {
+ // Add the bundle field and run the update.
+ $this->addBundleField();
+ $this->entityDefinitionUpdateManager->applyUpdates();
+
+ // Save an entity with the bundle field populated.
+ entity_test_create_bundle('custom');
+ $this->entityManager->getStorage('entity_test_update')->create(array('type' => 'test_bundle', 'new_bundle_field' => 'foo'))->save();
+
+ // Change the field's field type and apply updates. It's expected to
+ // throw an exception.
+ $this->modifyBundleField();
+ try {
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
+ }
+ catch (FieldStorageDefinitionUpdateForbiddenException $e) {
+ $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
+ }
+ }
+
+ /**
+ * Tests creating and deleting a multi-field index when there are no existing entities.
+ */
+ public function testEntityIndexCreateDeleteWithoutData() {
+ // Add an entity index and ensure the update manager reports that as an
+ // update to the entity type.
+ $this->addEntityIndex();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel())),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+
+ // Run the update and ensure the new index is created.
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
+
+ // Remove the index and ensure the update manager reports that as an
+ // update to the entity type.
+ $this->removeEntityIndex();
+ $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
+ $expected = array(
+ 'entity_test_update' => array(
+ t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel())),
+ ),
+ );
+ $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+
+ // Run the update and ensure the index is deleted.
+ $this->entityDefinitionUpdateManager->applyUpdates();
+ $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
+ }
+
+ /**
+ * Tests creating a multi-field index when there are existing entities.
+ */
+ public function testEntityIndexCreateWithData() {
+ // Save an entity.
+ $name = $this->randomString();
+ $entity = $this->entityManager->getStorage('entity_test_update')->create(array('name' => $name));
+ $entity->save();
+
+ // Add an entity index, run the update. For now, it's expected to throw an
+ // exception.
+ // @todo Improve SqlContentEntityStorageSchema::requiresEntityDataMigration()
+ // to return FALSE when only index changes are required, so that it can be
+ // applied on top of existing data: https://www.drupal.org/node/2340993.
+ $this->addEntityIndex();
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->fail('EntityStorageException thrown when trying to apply an update that requires data migration.');
@@ -113,4 +442,112 @@ class EntityDefinitionUpdateTest extends EntityUnitTestBase {
}
}
+ /**
+ * Updates the 'entity_test_update' entity type to revisionable.
+ */
+ protected function updateEntityTypeToRevisionable() {
+ $entity_type = clone $this->entityManager->getDefinition('entity_test_update');
+ $keys = $entity_type->getKeys();
+ $keys['revision'] = 'revision_id';
+ $entity_type->set('entity_keys', $keys);
+ $this->state->set('entity_test_update.entity_type', $entity_type);
+ $this->entityManager->clearCachedDefinitions();
+ }
+
+ /**
+ * Adds a new base field to the 'entity_test_update' entity type.
+ *
+ * @param string $type
+ * (optional) The field type for the new field. Defaults to 'string'.
+ */
+ protected function addBaseField($type = 'string') {
+ $definitions['new_base_field'] = BaseFieldDefinition::create($type)
+ ->setName('new_base_field')
+ ->setLabel(t('A new base field'));
+ $this->state->set('entity_test_update.additional_base_field_definitions', $definitions);
+ $this->entityManager->clearCachedDefinitions();
+ }
+
+ /**
+ * Modifies the new base field from 'string' to 'text'.
+ */
+ protected function modifyBaseField() {
+ $this->addBaseField('text');
+ }
+
+ /**
+ * Removes the new base field from the 'entity_test_update' entity type.
+ */
+ protected function removeBaseField() {
+ $this->state->delete('entity_test_update.additional_base_field_definitions');
+ $this->entityManager->clearCachedDefinitions();
+ }
+
+ /**
+ * Adds a single-field index to the base field.
+ */
+ protected function addBaseFieldIndex() {
+ $this->state->set('entity_test_update.additional_field_index.entity_test_update.new_base_field', TRUE);
+ $this->entityManager->clearCachedDefinitions();
+ }
+
+ /**
+ * Removes the index added in addBaseFieldIndex().
+ */
+ protected function removeBaseFieldIndex() {
+ $this->state->delete('entity_test_update.additional_field_index.entity_test_update.new_base_field');
+ $this->entityManager->clearCachedDefinitions();
+ }
+
+ /**
+ * Adds a new bundle field to the 'entity_test_update' entity type.
+ *
+ * @param string $type
+ * (optional) The field type for the new field. Defaults to 'string'.
+ */
+ protected function addBundleField($type = 'string') {
+ $definitions['new_bundle_field'] = FieldStorageDefinition::create($type)
+ ->setName('new_bundle_field')
+ ->setLabel(t('A new bundle field'))
+ ->setTargetEntityTypeId('entity_test_update');
+ $this->state->set('entity_test_update.additional_field_storage_definitions', $definitions);
+ $this->state->set('entity_test_update.additional_bundle_field_definitions.test_bundle', $definitions);
+ $this->entityManager->clearCachedDefinitions();
+ }
+
+ /**
+ * Modifies the new bundle field from 'string' to 'text'.
+ */
+ protected function modifyBundleField() {
+ $this->addBundleField('text');
+ }
+
+ /**
+ * Removes the new bundle field from the 'entity_test_update' entity type.
+ */
+ protected function removeBundleField() {
+ $this->state->delete('entity_test_update.additional_field_storage_definitions');
+ $this->state->delete('entity_test_update.additional_bundle_field_definitions.test_bundle');
+ $this->entityManager->clearCachedDefinitions();
+ }
+
+ /**
+ * Adds an index to the 'entity_test_update' entity type's base table.
+ *
+ * @see \Drupal\entity_test\EntityTestStorageSchema::getEntitySchema().
+ */
+ protected function addEntityIndex() {
+ $indexes = array(
+ 'entity_test_update__new_index' => array('name', 'user_id'),
+ );
+ $this->state->set('entity_test_update.additional_entity_indexes', $indexes);
+ }
+
+ /**
+ * Removes the index added in addEntityIndex().
+ */
+ protected function removeEntityIndex() {
+ $this->state->delete('entity_test_update.additional_entity_indexes');
+ }
+
}
diff --git a/core/modules/system/src/Tests/Entity/EntitySchemaTest.php b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
new file mode 100644
index 0000000..bdf08e3
--- /dev/null
+++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\EntitySchemaTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\Component\Utility\String;
+
+/**
+ * Tests adding a custom bundle field.
+ */
+class EntitySchemaTest extends EntityUnitTestBase {
+
+ /**
+ * The database connection used.
+ *
+ * @var \Drupal\Core\Database\Connection
+ */
+ protected $database;
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('menu_link');
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity Schema',
+ 'description' => 'Tests entity field schema API for base and bundle fields.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ parent::setUp();
+ $this->installSchema('user', array('users_data'));
+ $this->installSchema('system', array('router'));
+ $this->database = $this->container->get('database');
+ }
+
+ /**
+ * Tests the custom bundle field creation and deletion.
+ */
+ public function testCustomFieldCreateDelete() {
+ // Install the module which adds the field.
+ $this->installModule('entity_schema_test');
+ $this->entityManager->clearCachedDefinitions();
+ $storage_definitions = $this->entityManager->getFieldStorageDefinitions('entity_test');
+ $this->assertNotNull($storage_definitions['custom_base_field'], 'Base field definition found.');
+ $this->assertNotNull($storage_definitions['custom_bundle_field'], 'Bundle field definition found.');
+
+ // Make sure the field schema can be created.
+ $this->entityManager->onFieldStorageDefinitionCreate($storage_definitions['custom_base_field']);
+ $this->entityManager->onFieldStorageDefinitionCreate($storage_definitions['custom_bundle_field']);
+ /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+ $table_mapping = $this->entityManager->getStorage('entity_test')->getTableMapping();
+ $base_table = current($table_mapping->getTableNames());
+ $base_column = current($table_mapping->getColumnNames('custom_base_field'));
+ $this->assertTrue($this->database->schema()->fieldExists($base_table, $base_column), 'Table column created');
+ $table = $table_mapping->getDedicatedDataTableName($storage_definitions['custom_bundle_field']);
+ $this->assertTrue($this->database->schema()->tableExists($table), 'Table created');
+
+ // Make sure the field schema can be deleted.
+ $this->uninstallModule('entity_schema_test');
+ $this->entityManager->onFieldStorageDefinitionDelete($storage_definitions['custom_base_field']);
+ $this->entityManager->onFieldStorageDefinitionDelete($storage_definitions['custom_bundle_field']);
+ $this->assertFalse($this->database->schema()->fieldExists($base_table, $base_column), 'Table column dropped');
+ $this->assertFalse($this->database->schema()->tableExists($table), 'Table dropped');
+ }
+
+ /**
+ * Updates the entity type definition.
+ *
+ * @param bool $alter
+ * Whether the original definition should be altered or not.
+ */
+ protected function updateEntityType($alter) {
+ $entity_test_id = 'entity_test';
+ $original = $this->entityManager->getDefinition($entity_test_id);
+ $this->entityManager->clearCachedDefinitions();
+ $this->state->set('entity_schema_update', $alter);
+ $entity_type = $this->entityManager->getDefinition($entity_test_id);
+ $this->entityManager->onEntityTypeUpdate($entity_type, $original);
+ }
+
+ /**
+ * Tests that entity schema responds to changes in the entity type definition.
+ */
+ public function testEntitySchemaUpdate() {
+ $this->installModule('entity_schema_test');
+ $storage_definitions = $this->entityManager->getFieldStorageDefinitions('entity_test');
+ $this->entityManager->onFieldStorageDefinitionCreate($storage_definitions['custom_base_field']);
+ $this->entityManager->onFieldStorageDefinitionCreate($storage_definitions['custom_bundle_field']);
+ $schema_handler = $this->database->schema();
+ $tables = array('entity_test', 'entity_test_revision', 'entity_test_field_data', 'entity_test_field_revision');
+ $dedicated_tables = array('entity_test__custom_bundle_field', 'entity_test_revision__custom_bundle_field');
+
+ // Initially only the base table and the dedicated field data table should
+ // exist.
+ foreach ($tables as $index => $table) {
+ $this->assertEqual($schema_handler->tableExists($table), !$index, String::format('Entity schema correct for the @table table.', array('@table' => $table)));
+ }
+ $this->assertTrue($schema_handler->tableExists($dedicated_tables[0]), String::format('Field schema correct for the @table table.', array('@table' => $table)));
+
+ // Update the entity type definition and check that the entity schema now
+ // supports translations and revisions.
+ $this->updateEntityType(TRUE);
+ foreach ($tables as $table) {
+ $this->assertTrue($schema_handler->tableExists($table), String::format('Entity schema correct for the @table table.', array('@table' => $table)));
+ }
+ foreach ($dedicated_tables as $table) {
+ $this->assertTrue($schema_handler->tableExists($table), String::format('Field schema correct for the @table table.', array('@table' => $table)));
+ }
+
+ // Revert changes and check that the entity schema now does not support
+ // neither translations nor revisions.
+ $this->updateEntityType(FALSE);
+ foreach ($tables as $index => $table) {
+ $this->assertEqual($schema_handler->tableExists($table), !$index, String::format('Entity schema correct for the @table table.', array('@table' => $table)));
+ }
+ $this->assertTrue($schema_handler->tableExists($dedicated_tables[0]), String::format('Field schema correct for the @table table.', array('@table' => $table)));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function refreshServices() {
+ parent::refreshServices();
+ $this->database = $this->container->get('database');
+ }
+
+}
diff --git a/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php b/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php
index 243f48d..ada31f2 100644
--- a/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php
+++ b/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php
@@ -111,4 +111,35 @@ abstract class EntityUnitTestBase extends DrupalUnitTestBase {
return $hooks;
}
+ /**
+ * Installs a module and refreshes services.
+ *
+ * @param string $module
+ * The module to install.
+ */
+ protected function installModule($module) {
+ $this->enableModules(array($module));
+ $this->refreshServices();
+ }
+
+ /**
+ * Uninstalls a module and refreshes services.
+ *
+ * @param string $module
+ * The module to uninstall.
+ */
+ protected function uninstallModule($module) {
+ $this->disableModules(array($module));
+ $this->refreshServices();
+ }
+
+ /**
+ * Refresh services.
+ */
+ protected function refreshServices() {
+ $this->container = \Drupal::getContainer();
+ $this->entityManager = $this->container->get('entity.manager');
+ $this->state = $this->container->get('state');
+ }
+
}
diff --git a/core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php b/core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php
index 6610958..9c3a870 100644
--- a/core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php
+++ b/core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php
@@ -8,7 +8,6 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Database\Database;
-use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\field\Entity\FieldStorageConfig;
@@ -460,19 +459,9 @@ class FieldSqlStorageTest extends EntityUnitTestBase {
// Reload the field schema after the update.
$schema = $field_storage->getSchema();
- // Retrieve the field definition and check that the foreign key is in place.
- $field_storage = FieldStorageConfig::loadByName('entity_test', $field_name);
+ // Check that the foreign key is in place.
$this->assertEqual($schema['foreign keys'][$foreign_key_name]['table'], $foreign_key_name, 'Foreign key table name modified after update');
$this->assertEqual($schema['foreign keys'][$foreign_key_name]['columns'][$foreign_key_name], 'id', 'Foreign key column name modified after update');
-
- // Verify the SQL schema.
- $schemas = SqlContentEntityStorage::_fieldSqlSchema($field_storage);
- $schema = $schemas[$this->table_mapping->getDedicatedDataTableName($field_storage)];
- $this->assertEqual(count($schema['foreign keys']), 1, 'There is 1 foreign key in the schema');
- $foreign_key = reset($schema['foreign keys']);
- $foreign_key_column = $this->table_mapping->getFieldColumnName($field_storage, $foreign_key_name);
- $this->assertEqual($foreign_key['table'], $foreign_key_name, 'Foreign key table name preserved in the schema');
- $this->assertEqual($foreign_key['columns'][$foreign_key_column], 'id', 'Foreign key column name preserved in the schema');
}
/**
diff --git a/core/modules/system/src/Tests/Entity/FieldTranslationSqlStorageTest.php b/core/modules/system/src/Tests/Entity/FieldTranslationSqlStorageTest.php
index 2874aed..c2a6a65 100644
--- a/core/modules/system/src/Tests/Entity/FieldTranslationSqlStorageTest.php
+++ b/core/modules/system/src/Tests/Entity/FieldTranslationSqlStorageTest.php
@@ -87,24 +87,19 @@ class FieldTranslationSqlStorageTest extends EntityLanguageTestBase {
foreach ($fields as $field_name) {
$field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
- $tables = array(
- $table_mapping->getDedicatedDataTableName($field_storage),
- $table_mapping->getDedicatedRevisionTableName($field_storage),
- );
+ $table = $table_mapping->getDedicatedDataTableName($field_storage);
- foreach ($tables as $table) {
- $record = \Drupal::database()
- ->select($table, 'f')
- ->fields('f')
- ->condition('f.entity_id', $id)
- ->condition('f.revision_id', $id)
- ->execute()
- ->fetchObject();
+ $record = \Drupal::database()
+ ->select($table, 'f')
+ ->fields('f')
+ ->condition('f.entity_id', $id)
+ ->condition('f.revision_id', $id)
+ ->execute()
+ ->fetchObject();
- if ($record->langcode != $langcode) {
- $status = FALSE;
- break;
- }
+ if ($record->langcode != $langcode) {
+ $status = FALSE;
+ break;
}
}
diff --git a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.info.yml b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.info.yml
deleted file mode 100644
index 6732090..0000000
--- a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.info.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-name: 'Entity bundle field test module'
-type: module
-description: 'Provides a bundle field to the test entity.'
-package: Testing
-version: VERSION
-core: 8.x
-dependencies:
- - entity_test
diff --git a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.install b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.install
deleted file mode 100644
index 6065425..0000000
--- a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.install
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update and uninstall functions.
- */
-
-/**
- * Implements hook_install().
- */
-function entity_bundle_field_test_install() {
- $manager = \Drupal::entityManager();
- // Notify the entity storage of our custom field.
- $definition = $manager->getFieldStorageDefinitions('entity_test')['custom_field'];
- $manager->getStorage('entity_test')->onFieldStorageDefinitionCreate($definition);
-
- // Create the custom bundle and put our bundle field on it.
- entity_test_create_bundle('custom');
- $definition = $manager->getFieldDefinitions('entity_test', 'custom')['custom_field'];
- $manager->getStorage('entity_test')->onFieldDefinitionCreate($definition);
-}
-
-/**
- * Implements hook_uninstall().
- */
-function entity_bundle_field_test_uninstall() {
- entity_bundle_field_test_is_uninstalling(TRUE);
- $manager = \Drupal::entityManager();
- // Notify the entity storage that our field is gone.
- $definition = $manager->getFieldDefinitions('entity_test', 'custom')['custom_field'];
- $manager->getStorage('entity_test')->onFieldDefinitionDelete($definition);
- $storage_definition = $manager->getFieldStorageDefinitions('entity_test')['custom_field'];
- $manager->getStorage('entity_test')->onFieldStorageDefinitionDelete($storage_definition);
- $manager->clearCachedFieldDefinitions();
-
- do {
- $count = $manager->getStorage('entity_test')->purgeFieldData($definition, 500);
- }
- while ($count != 0);
- $manager->getStorage('entity_test')->finalizePurge($definition);
-}
diff --git a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.module b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.module
deleted file mode 100644
index 7a39717..0000000
--- a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.module
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-
-/**
- * @file
- * Test module for the entity API providing a bundle field.
- */
-
-use Drupal\Core\Field\BaseFieldDefinition;
-
-/**
- * Tracks whether the module is currently being uninstalled.
- *
- * @param bool|null $value
- * (optional) If set, the value any subsequent calls should return.
- *
- * @return bool
- * Whether the module is currently uninstalling.
- */
-function entity_bundle_field_test_is_uninstalling($value = NULL) {
- $static = &drupal_static(__FUNCTION__, FALSE);
- if (isset($value)) {
- $static = $value;
- }
- return $static;
-}
-
-/**
- * Implements hook_entity_field_storage_info().
- */
-function entity_bundle_field_test_entity_field_storage_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
- if ($entity_type->id() == 'entity_test' && !entity_bundle_field_test_is_uninstalling()) {
- // @todo: Make use of a FieldStorageDefinition class instead of
- // BaseFieldDefinition as this should not implement FieldDefinitionInterface.
- // See https://drupal.org/node/2280639.
- $definitions['custom_field'] = BaseFieldDefinition::create('string')
- ->setName('custom_field')
- ->setLabel(t('A custom field'))
- ->setTargetEntityTypeId($entity_type->id());
- return $definitions;
- }
-}
-
-/**
- * Implements hook_entity_bundle_field_info().
- */
-function entity_bundle_field_test_entity_bundle_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
- if ($entity_type->id() == 'entity_test' && $bundle == 'custom' && !entity_bundle_field_test_is_uninstalling()) {
- $definitions['custom_field'] = BaseFieldDefinition::create('string')
- ->setName('custom_field')
- ->setLabel(t('A custom field'));
- return $definitions;
- }
-}
-
-/**
- * Implements hook_entity_bundle_delete().
- */
-function entity_bundle_field_test_entity_bundle_delete($entity_type_id, $bundle) {
- if ($entity_type_id == 'entity_test' && $bundle == 'custom') {
- // Notify the entity storage that our field is gone.
- $field_definition = BaseFieldDefinition::create('string')
- ->setTargetEntityTypeId($entity_type_id)
- ->setBundle($bundle)
- ->setName('custom_field')
- ->setLabel(t('A custom field'));
- \Drupal::entityManager()->getStorage('entity_test')
- ->onFieldDefinitionDelete($field_definition);
- }
-}
diff --git a/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.info.yml b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.info.yml
new file mode 100644
index 0000000..646717d
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.info.yml
@@ -0,0 +1,8 @@
+name: 'Entity schema test module'
+type: module
+description: 'Provides entity and field definitions to test entity schema.'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+ - entity_test
diff --git a/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module
new file mode 100644
index 0000000..206ffa2
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Test module for the entity API providing a bundle field.
+ */
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\entity_test\FieldStorageDefinition;
+use Drupal\entity_test\Entity\EntityTestMulRev;
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function entity_schema_test_entity_type_alter(array &$entity_types) {
+ if (\Drupal::state()->get('entity_schema_update')) {
+ $entity_type = $entity_types['entity_test'];
+ $entity_type->set('translatable', TRUE);
+ $entity_type->set('data_table', 'entity_test_field_data');
+ $keys = $entity_type->getKeys();
+ $keys['revision'] = 'revision_id';
+ $entity_type->set('entity_keys', $keys);
+ }
+}
+
+/**
+ * Implements hook_entity_base_field_info().
+ */
+function entity_schema_test_entity_base_field_info(EntityTypeInterface $entity_type) {
+ if ($entity_type->id() == 'entity_test') {
+ $definitions['custom_base_field'] = BaseFieldDefinition::create('string')
+ ->setName('custom_base_field')
+ ->setLabel(t('A custom base field'));
+ if (\Drupal::state()->get('entity_schema_update')) {
+ $definitions += EntityTestMulRev::baseFieldDefinitions($entity_type);
+ }
+ return $definitions;
+ }
+}
+
+/**
+ * Implements hook_entity_field_storage_info().
+ */
+function entity_schema_test_entity_field_storage_info(EntityTypeInterface $entity_type) {
+ if ($entity_type->id() == 'entity_test') {
+ $definitions['custom_bundle_field'] = FieldStorageDefinition::create('string')
+ ->setName('custom_bundle_field')
+ ->setLabel(t('A custom bundle field'))
+ ->setTargetEntityTypeId($entity_type->id());
+ return $definitions;
+ }
+}
+
+/**
+ * Implements hook_entity_bundle_field_info().
+ */
+function entity_schema_test_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle) {
+ if ($entity_type->id() == 'entity_test' && $bundle == 'custom') {
+ $definitions['custom_bundle_field'] = \Drupal::entityManager()->getFieldStorageDefinitions($entity_type->id())['custom_bundle_field'];
+ return $definitions;
+ }
+}
+
+/**
+ * Implements hook_entity_bundle_delete().
+ */
+function entity_schema_test_entity_bundle_delete($entity_type_id, $bundle) {
+ if ($entity_type_id == 'entity_test' && $bundle == 'custom') {
+ $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
+ $field_definitions = entity_schema_test_entity_bundle_field_info($entity_type, $bundle);
+ $field_definitions['custom_bundle_field']
+ ->setTargetEntityTypeId($entity_type_id)
+ ->setBundle($bundle);
+ // Notify the entity storage that our field is gone.
+ \Drupal::entityManager()->getStorage($entity_type_id)
+ ->onFieldDefinitionDelete($field_definitions['custom_bundle_field']);
+ }
+}
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 63e289a..d5b08fa 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -73,13 +73,8 @@ function entity_test_entity_type_alter(array &$entity_types) {
}
}
- // Optionally allow testing an entity type definition being updated from
- // revisable to not or vice versa.
- if (\Drupal::state()->get('entity_test.entity_test_rev.disable_revisable')) {
- $keys = $entity_types['entity_test_rev']->getKeys();
- unset($keys['revision']);
- $entity_types['entity_test_rev']->set('entity_keys', $keys);
- }
+ // Allow entity_test_update tests to override the entity type definition.
+ $entity_types['entity_test_update'] = \Drupal::state()->get('entity_test_update.entity_type', $entity_types['entity_test_update']);
}
/**
@@ -97,6 +92,15 @@ function entity_test_entity_base_field_info_alter(&$fields, EntityTypeInterface
}
/**
+ * Implements hook_entity_field_storage_info().
+ */
+function entity_test_entity_field_storage_info(EntityTypeInterface $entity_type) {
+ if ($entity_type->id() == 'entity_test_update') {
+ return \Drupal::state()->get('entity_test_update.additional_field_storage_definitions', array());
+ }
+}
+
+/**
* Creates a new bundle for entity_test entities.
*
* @param string $bundle
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUpdate.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUpdate.php
new file mode 100644
index 0000000..9e792c7
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUpdate.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\Entity\EntityTestUpdate.
+ */
+
+namespace Drupal\entity_test\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Defines the test entity class for testing definition updates.
+ *
+ * This entity type starts out non-revisionable by lacking a "revision_id" key,
+ * but during an update test, can be made revisionable by adding that key.
+ *
+ * @ContentEntityType(
+ * id = "entity_test_update",
+ * label = @Translation("Test entity update"),
+ * handlers = {
+ * "storage_schema" = "Drupal\entity_test\EntityTestStorageSchema"
+ * },
+ * base_table = "entity_test_update",
+ * revision_table = "entity_test_update_revision",
+ * fieldable = TRUE,
+ * persistent_cache = FALSE,
+ * entity_keys = {
+ * "id" = "id",
+ * "uuid" = "uuid",
+ * "bundle" = "type",
+ * "label" = "name"
+ * }
+ * )
+ */
+class EntityTestUpdate extends EntityTestRev {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+ $fields = parent::baseFieldDefinitions($entity_type);
+ $fields += \Drupal::state()->get('entity_test_update.additional_base_field_definitions', array());
+ return $fields;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
+ $fields = parent::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions);
+ $fields += \Drupal::state()->get('entity_test_update.additional_bundle_field_definitions.' . $bundle, array());
+ return $fields;
+ }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestStorageSchema.php b/core/modules/system/tests/modules/entity_test/src/EntityTestStorageSchema.php
new file mode 100644
index 0000000..09e2ab1
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/EntityTestStorageSchema.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\EntityTestStorageSchema.
+ */
+
+namespace Drupal\entity_test;
+
+use Drupal\Core\Entity\ContentEntityTypeInterface;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+
+/**
+ * Defines the entity_test_update storage_schema handler.
+ */
+class EntityTestStorageSchema extends SqlContentEntityStorageSchema {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
+ $schema = parent::getEntitySchema($entity_type, $reset);
+ $schema['entity_test_update']['indexes'] += \Drupal::state()->get('entity_test_update.additional_entity_indexes', array());
+ return $schema;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+
+ if (\Drupal::state()->get('entity_test_update.additional_field_index.' . $table_name . '.' . $storage_definition->getName())) {
+ $this->addSharedTableFieldIndex($storage_definition, $schema);
+ }
+
+ return $schema;
+ }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/src/FieldStorageDefinition.php b/core/modules/system/tests/modules/entity_test/src/FieldStorageDefinition.php
new file mode 100644
index 0000000..6a2757c
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/FieldStorageDefinition.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\FieldStorageDefinition.
+ */
+
+namespace Drupal\entity_test;
+
+use Drupal\Core\Field\BaseFieldDefinition;
+
+/**
+ * A custom field storage definition class.
+ *
+ * For convenience we extend from BaseFieldDefinition although this should not
+ * implement FieldDefinitionInterface.
+ *
+ * @todo Provide and make use of a proper FieldStorageDefinition class instead:
+ * https://drupal.org/node/2280639.
+ */
+class FieldStorageDefinition extends BaseFieldDefinition {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isBaseField() {
+ return FALSE;
+ }
+
+}
diff --git a/core/modules/taxonomy/src/TermStorageSchema.php b/core/modules/taxonomy/src/TermStorageSchema.php
index 83e3457..301bf46 100644
--- a/core/modules/taxonomy/src/TermStorageSchema.php
+++ b/core/modules/taxonomy/src/TermStorageSchema.php
@@ -9,6 +9,7 @@ namespace Drupal\taxonomy;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the term schema handler.
@@ -21,20 +22,10 @@ class TermStorageSchema extends SqlContentEntityStorageSchema {
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset = FALSE);
- if (isset($schema['taxonomy_term_field_data'])) {
- // Marking the respective fields as NOT NULL makes the indexes more
- // performant.
- $schema['taxonomy_term_field_data']['fields']['weight']['not null'] = TRUE;
- $schema['taxonomy_term_field_data']['fields']['name']['not null'] = TRUE;
-
- unset($schema['taxonomy_term_field_data']['indexes']['taxonomy_term_field__vid__target_id']);
- unset($schema['taxonomy_term_field_data']['indexes']['taxonomy_term_field__description__format']);
- $schema['taxonomy_term_field_data']['indexes'] += array(
- 'taxonomy_term__tree' => array('vid', 'weight', 'name'),
- 'taxonomy_term__vid_name' => array('vid', 'name'),
- 'taxonomy_term__name' => array('name'),
- );
- }
+ $schema['taxonomy_term_field_data']['indexes'] += array(
+ 'taxonomy_term__tree' => array('vid', 'weight', 'name'),
+ 'taxonomy_term__vid_name' => array('vid', 'name'),
+ );
$schema['taxonomy_term_hierarchy'] = array(
'description' => 'Stores the hierarchical relationship between terms.',
@@ -116,4 +107,32 @@ class TermStorageSchema extends SqlContentEntityStorageSchema {
return $schema;
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+ $field_name = $storage_definition->getName();
+
+ if ($table_name == 'taxonomy_term_field_data') {
+ // Remove unneeded indexes.
+ unset($schema['indexes']['taxonomy_term_field__vid__target_id']);
+ unset($schema['indexes']['taxonomy_term_field__description__format']);
+
+ switch ($field_name) {
+ case 'weight':
+ // Improves the performance of the taxonomy_term__tree index defined
+ // in getEntitySchema().
+ $schema['fields'][$field_name]['not null'] = TRUE;
+ break;
+
+ case 'name':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+ }
+ }
+
+ return $schema;
+ }
+
}
diff --git a/core/modules/user/src/UserStorageSchema.php b/core/modules/user/src/UserStorageSchema.php
index 0229db2..48690f7 100644
--- a/core/modules/user/src/UserStorageSchema.php
+++ b/core/modules/user/src/UserStorageSchema.php
@@ -9,6 +9,7 @@ namespace Drupal\user;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Defines the user schema handler.
@@ -21,20 +22,6 @@ class UserStorageSchema extends SqlContentEntityStorageSchema {
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset);
- // The "users" table does not use serial identifiers.
- $schema['users']['fields']['uid']['type'] = 'int';
-
- // Marking the respective fields as NOT NULL makes the indexes more
- // performant.
- $schema['users_field_data']['fields']['access']['not null'] = TRUE;
- $schema['users_field_data']['fields']['created']['not null'] = TRUE;
- $schema['users_field_data']['fields']['name']['not null'] = TRUE;
-
- $schema['users_field_data']['indexes'] += array(
- 'user__access' => array('access'),
- 'user__created' => array('created'),
- 'user__mail' => array('mail'),
- );
$schema['users_field_data']['unique keys'] += array(
'user__name' => array('name', 'langcode'),
);
@@ -71,4 +58,43 @@ class UserStorageSchema extends SqlContentEntityStorageSchema {
return $schema;
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function processIdentifierSchema(&$schema, $key) {
+ // The "users" table does not use serial identifiers.
+ if ($key != $this->entityType->getKey('id')) {
+ parent::processIdentifierSchema($schema, $key);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+ $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+ $field_name = $storage_definition->getName();
+
+ if ($table_name == 'users_field_data') {
+ switch ($field_name) {
+ case 'name':
+ // Improves the performance of the user__name index defined
+ // in getEntitySchema().
+ $schema['fields'][$field_name]['not null'] = TRUE;
+ break;
+
+ case 'mail':
+ $this->addSharedTableFieldIndex($storage_definition, $schema);
+ break;
+
+ case 'access':
+ case 'created':
+ $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+ break;
+ }
+ }
+
+ return $schema;
+ }
+
}
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
index 18802c74..e647387 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
@@ -53,11 +53,11 @@ class DefaultTableMappingTest extends UnitTestCase {
*/
public function testGetAllColumns() {
// Set up single-column and multi-column definitions.
- $definitions['id'] = $this->setUpDefinition(['value']);
- $definitions['name'] = $this->setUpDefinition(['value']);
- $definitions['type'] = $this->setUpDefinition(['value']);
- $definitions['description'] = $this->setUpDefinition(['value', 'format']);
- $definitions['owner'] = $this->setUpDefinition([
+ $definitions['id'] = $this->setUpDefinition('id', ['value']);
+ $definitions['name'] = $this->setUpDefinition('name', ['value']);
+ $definitions['type'] = $this->setUpDefinition('type', ['value']);
+ $definitions['description'] = $this->setUpDefinition('description', ['value', 'format']);
+ $definitions['owner'] = $this->setUpDefinition('owner', [
'target_id',
'target_revision_id',
]);
@@ -188,17 +188,17 @@ class DefaultTableMappingTest extends UnitTestCase {
* @covers ::getColumnNames()
*/
public function testGetColumnNames() {
- $definitions['test'] = $this->setUpDefinition([]);
+ $definitions['test'] = $this->setUpDefinition('test', []);
$table_mapping = new DefaultTableMapping($definitions);
$expected = [];
$this->assertSame($expected, $table_mapping->getColumnNames('test'));
- $definitions['test'] = $this->setUpDefinition(['value']);
+ $definitions['test'] = $this->setUpDefinition('test', ['value']);
$table_mapping = new DefaultTableMapping($definitions);
$expected = ['value' => 'test'];
$this->assertSame($expected, $table_mapping->getColumnNames('test'));
- $definitions['test'] = $this->setUpDefinition(['value', 'format']);
+ $definitions['test'] = $this->setUpDefinition('test', ['value', 'format']);
$table_mapping = new DefaultTableMapping($definitions);
$expected = ['value' => 'test__value', 'format' => 'test__format'];
$this->assertSame($expected, $table_mapping->getColumnNames('test'));
@@ -237,13 +237,21 @@ class DefaultTableMappingTest extends UnitTestCase {
/**
* Sets up a field storage definition for the test.
*
+ * @param string $name
+ * The field name.
* @param array $column_names
* An array of column names for the storage definition.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- protected function setUpDefinition(array $column_names) {
- $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+ protected function setUpDefinition($name, array $column_names) {
+ $definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
+ $definition->expects($this->any())
+ ->method('isBaseField')
+ ->will($this->returnValue(TRUE));
+ $definition->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue($name));
$definition->expects($this->any())
->method('getColumns')
->will($this->returnValue(array_fill_keys($column_names, [])));
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
index 3109adc..788744f 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
@@ -8,8 +8,8 @@
namespace Drupal\Tests\Core\Entity\Sql;
use Drupal\Core\Entity\ContentEntityType;
-use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Core\Entity\Sql\DefaultTableMapping;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Tests\UnitTestCase;
/**
@@ -47,11 +47,11 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
protected $storageDefinitions;
/**
- * The content entity schema handler used in this test.
+ * The storage schema handler used in this test.
*
* @var \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema.
*/
- protected $schemaHandler;
+ protected $storageSchema;
/**
* {@inheritdoc}
@@ -372,7 +372,7 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
),
);
- $this->setUpEntitySchemaHandler($expected);
+ $this->setUpStorageSchema($expected);
$table_mapping = new DefaultTableMapping($this->storageDefinitions);
$table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
@@ -382,7 +382,7 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
->method('getTableMapping')
->will($this->returnValue($table_mapping));
- $this->schemaHandler->onEntityTypeCreate($this->entityType);
+ $this->storageSchema->onEntityTypeCreate($this->entityType);
}
/**
@@ -472,7 +472,7 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
),
);
- $this->setUpEntitySchemaHandler($expected);
+ $this->setUpStorageSchema($expected);
$table_mapping = new DefaultTableMapping($this->storageDefinitions);
$table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
@@ -482,7 +482,7 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
->method('getTableMapping')
->will($this->returnValue($table_mapping));
- $this->schemaHandler->onEntityTypeCreate($this->entityType);
+ $this->storageSchema->onEntityTypeCreate($this->entityType);
}
/**
@@ -562,7 +562,7 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
),
);
- $this->setUpEntitySchemaHandler($expected);
+ $this->setUpStorageSchema($expected);
$table_mapping = new DefaultTableMapping($this->storageDefinitions);
$table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
@@ -572,7 +572,7 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
->method('getTableMapping')
->will($this->returnValue($table_mapping));
- $this->schemaHandler->onEntityTypeCreate($this->entityType);
+ $this->storageSchema->onEntityTypeCreate($this->entityType);
}
/**
@@ -746,7 +746,7 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
),
);
- $this->setUpEntitySchemaHandler($expected);
+ $this->setUpStorageSchema($expected);
$table_mapping = new DefaultTableMapping($this->storageDefinitions);
$table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
@@ -758,11 +758,301 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
->method('getTableMapping')
->will($this->returnValue($table_mapping));
- $this->schemaHandler->onEntityTypeCreate($this->entityType);
+ $this->storageSchema->onEntityTypeCreate($this->entityType);
+ }
+
+ /**
+ * Tests the schema for a field dedicated table.
+ *
+ * @covers ::getDedicatedTableSchema()
+ * @covers ::createDedicatedTableSchema()
+ */
+ public function testDedicatedTableSchema() {
+ $entity_type_id = 'entity_test';
+ $this->entityType = new ContentEntityType(array(
+ 'id' => 'entity_test',
+ 'entity_keys' => array('id' => 'id'),
+ ));
+
+ // Setup a field having a dedicated schema.
+ $field_name = $this->getRandomGenerator()->name();
+ $this->setUpStorageDefinition($field_name, array(
+ 'columns' => array(
+ 'shape' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => FALSE,
+ ),
+ 'color' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => FALSE,
+ ),
+ ),
+ 'foreign keys' => array(
+ 'color' => array(
+ 'table' => 'color',
+ 'columns' => array(
+ 'color' => 'id'
+ ),
+ ),
+ ),
+ 'unique keys' => array(),
+ 'indexes' => array(),
+ ));
+
+ $field_storage = $this->storageDefinitions[$field_name];
+ $field_storage
+ ->expects($this->any())
+ ->method('getType')
+ ->will($this->returnValue('shape'));
+ $field_storage
+ ->expects($this->any())
+ ->method('getTargetEntityTypeId')
+ ->will($this->returnValue($entity_type_id));
+ $field_storage
+ ->expects($this->any())
+ ->method('isMultiple')
+ ->will($this->returnValue(TRUE));
+
+ $this->storageDefinitions['id']
+ ->expects($this->any())
+ ->method('getType')
+ ->will($this->returnValue('integer'));
+
+ $expected = array(
+ $entity_type_id . '__' . $field_name => array(
+ 'description' => "Data storage for $entity_type_id field $field_name.",
+ 'fields' => array(
+ 'bundle' => array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => true,
+ 'default' => '',
+ 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
+ ),
+ 'deleted' => array(
+ 'type' => 'int',
+ 'size' => 'tiny',
+ 'not null' => true,
+ 'default' => 0,
+ 'description' => 'A boolean indicating whether this data item has been deleted',
+ ),
+ 'entity_id' => array(
+ 'type' => 'int',
+ 'unsigned' => true,
+ 'not null' => true,
+ 'description' => 'The entity id this data is attached to',
+ ),
+ 'revision_id' => array(
+ 'type' => 'int',
+ 'unsigned' => true,
+ 'not null' => true,
+ 'description' => 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id',
+ ),
+ 'langcode' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => true,
+ 'default' => '',
+ 'description' => 'The language code for this data item.',
+ ),
+ 'delta' => array(
+ 'type' => 'int',
+ 'unsigned' => true,
+ 'not null' => true,
+ 'description' => 'The sequence number for this data item, used for multi-value fields',
+ ),
+ $field_name . '_shape' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => false,
+ ),
+ $field_name . '_color' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => false,
+ ),
+ ),
+ 'primary key' => array('entity_id', 'deleted', 'delta', 'langcode'),
+ 'indexes' => array(
+ 'bundle' => array('bundle'),
+ 'deleted' => array('deleted'),
+ 'entity_id' => array('entity_id'),
+ 'revision_id' => array('revision_id'),
+ 'langcode' => array('langcode'),
+ ),
+ 'foreign keys' => array(
+ $field_name . '_color' => array(
+ 'table' => 'color',
+ 'columns' => array(
+ $field_name . '_color' => 'id',
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $this->setUpStorageSchema($expected);
+
+ $table_mapping = new DefaultTableMapping($this->storageDefinitions);
+ $table_mapping->setFieldNames($entity_type_id, array_keys($this->storageDefinitions));
+ $table_mapping->setExtraColumns($entity_type_id, array('default_langcode'));
+
+ $this->storage->expects($this->any())
+ ->method('getTableMapping')
+ ->will($this->returnValue($table_mapping));
+
+ $this->storageSchema->onFieldStorageDefinitionCreate($field_storage);
}
/**
- * Sets up the schema handler.
+ * Tests the schema for a field dedicated table for an entity with a string identifier.
+ *
+ * @covers ::getDedicatedTableSchema()
+ * @covers ::createDedicatedTableSchema()
+ */
+ public function testDedicatedTableSchemaForEntityWithStringIdentifier() {
+ $entity_type_id = 'entity_test';
+ $this->entityType = new ContentEntityType(array(
+ 'id' => 'entity_test',
+ 'entity_keys' => array('id' => 'id'),
+ ));
+
+ // Setup a field having a dedicated schema.
+ $field_name = $this->getRandomGenerator()->name();
+ $this->setUpStorageDefinition($field_name, array(
+ 'columns' => array(
+ 'shape' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => FALSE,
+ ),
+ 'color' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => FALSE,
+ ),
+ ),
+ 'foreign keys' => array(
+ 'color' => array(
+ 'table' => 'color',
+ 'columns' => array(
+ 'color' => 'id'
+ ),
+ ),
+ ),
+ 'unique keys' => array(),
+ 'indexes' => array(),
+ ));
+
+ $field_storage = $this->storageDefinitions[$field_name];
+ $field_storage
+ ->expects($this->any())
+ ->method('getType')
+ ->will($this->returnValue('shape'));
+ $field_storage
+ ->expects($this->any())
+ ->method('getTargetEntityTypeId')
+ ->will($this->returnValue($entity_type_id));
+ $field_storage
+ ->expects($this->any())
+ ->method('isMultiple')
+ ->will($this->returnValue(TRUE));
+
+ $this->storageDefinitions['id']
+ ->expects($this->any())
+ ->method('getType')
+ ->will($this->returnValue('string'));
+
+ $expected = array(
+ $entity_type_id . '__' . $field_name => array(
+ 'description' => "Data storage for $entity_type_id field $field_name.",
+ 'fields' => array(
+ 'bundle' => array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => true,
+ 'default' => '',
+ 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
+ ),
+ 'deleted' => array(
+ 'type' => 'int',
+ 'size' => 'tiny',
+ 'not null' => true,
+ 'default' => 0,
+ 'description' => 'A boolean indicating whether this data item has been deleted',
+ ),
+ 'entity_id' => array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => true,
+ 'description' => 'The entity id this data is attached to',
+ ),
+ 'revision_id' => array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => true,
+ 'description' => 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id',
+ ),
+ 'langcode' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => true,
+ 'default' => '',
+ 'description' => 'The language code for this data item.',
+ ),
+ 'delta' => array(
+ 'type' => 'int',
+ 'unsigned' => true,
+ 'not null' => true,
+ 'description' => 'The sequence number for this data item, used for multi-value fields',
+ ),
+ $field_name . '_shape' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => false,
+ ),
+ $field_name . '_color' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => false,
+ ),
+ ),
+ 'primary key' => array('entity_id', 'deleted', 'delta', 'langcode'),
+ 'indexes' => array(
+ 'bundle' => array('bundle'),
+ 'deleted' => array('deleted'),
+ 'entity_id' => array('entity_id'),
+ 'revision_id' => array('revision_id'),
+ 'langcode' => array('langcode'),
+ ),
+ 'foreign keys' => array(
+ $field_name . '_color' => array(
+ 'table' => 'color',
+ 'columns' => array(
+ $field_name . '_color' => 'id',
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $this->setUpStorageSchema($expected);
+
+ $table_mapping = new DefaultTableMapping($this->storageDefinitions);
+ $table_mapping->setFieldNames($entity_type_id, array_keys($this->storageDefinitions));
+ $table_mapping->setExtraColumns($entity_type_id, array('default_langcode'));
+
+ $this->storage->expects($this->any())
+ ->method('getTableMapping')
+ ->will($this->returnValue($table_mapping));
+
+ $this->storageSchema->onFieldStorageDefinitionCreate($field_storage);
+ }
+
+ /**
+ * Sets up the storage schema object to test.
*
* This uses the field definitions set in $this->storageDefinitions.
*
@@ -770,7 +1060,7 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
* (optional) An associative array describing the expected entity schema to
* be created. Defaults to expecting nothing.
*/
- protected function setUpEntitySchemaHandler(array $expected = array()) {
+ protected function setUpStorageSchema(array $expected = array()) {
$this->entityManager->expects($this->any())
->method('getDefinition')
->with($this->entityType->id())
@@ -812,7 +1102,15 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
->method('schema')
->will($this->returnValue($db_schema_handler));
- $this->schemaHandler = new SqlContentEntityStorageSchema($this->entityManager, $this->entityType, $this->storage, $connection);
+ $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+ $this->storageSchema = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
+ ->setConstructorArgs(array($this->entityManager, $this->entityType, $this->storage, $connection))
+ ->setMethods(array('installedStorageSchema'))
+ ->getMock();
+ $this->storageSchema
+ ->expects($this->any())
+ ->method('installedStorageSchema')
+ ->will($this->returnValue($key_value));
}
/**
@@ -826,7 +1124,10 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase {
*/
public function setUpStorageDefinition($field_name, array $schema) {
$this->storageDefinitions[$field_name] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
- // getDescription() is called once for each table.
+ $this->storageDefinitions[$field_name]->expects($this->any())
+ ->method('isBaseField')
+ ->will($this->returnValue(TRUE));
+ // getName() is called once for each table.
$this->storageDefinitions[$field_name]->expects($this->any())
->method('getName')
->will($this->returnValue($field_name));
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
index 24b90d3..6a6cef5 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
@@ -280,10 +280,7 @@ class SqlContentEntityStorageTest extends UnitTestCase {
),
);
- $this->fieldDefinitions['id'] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
- $this->fieldDefinitions['id']->expects($this->any())
- ->method('getName')
- ->will($this->returnValue('id'));
+ $this->fieldDefinitions = $this->mockFieldDefinitions(array('id'));
$this->fieldDefinitions['id']->expects($this->once())
->method('getColumns')
->will($this->returnValue($columns));
@@ -338,14 +335,22 @@ class SqlContentEntityStorageTest extends UnitTestCase {
$storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
->setConstructorArgs(array($this->entityType, $this->connection, $this->entityManager, $this->cache))
- ->setMethods(array('schemaHandler'))
+ ->setMethods(array('getStorageSchema'))
->getMock();
- $schema_handler = new SqlContentEntityStorageSchema($this->entityManager, $this->entityType, $storage, $this->connection);
+ $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+ $schema_handler = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
+ ->setConstructorArgs(array($this->entityManager, $this->entityType, $storage, $this->connection))
+ ->setMethods(array('installedStorageSchema', 'createSharedTableSchema'))
+ ->getMock();
+ $schema_handler
+ ->expects($this->any())
+ ->method('installedStorageSchema')
+ ->will($this->returnValue($key_value));
$storage
->expects($this->any())
- ->method('schemaHandler')
+ ->method('getStorageSchema')
->will($this->returnValue($schema_handler));
$storage->onEntityTypeCreate($this->entityType);
@@ -922,83 +927,6 @@ class SqlContentEntityStorageTest extends UnitTestCase {
}
/**
- * Tests field SQL schema generation for an entity with a string identifier.
- *
- * @covers ::_fieldSqlSchema()
- */
- public function testFieldSqlSchemaForEntityWithStringIdentifier() {
- $field_type_manager = $this->getMock('Drupal\Core\Field\FieldTypePluginManagerInterface');
-
- $this->container->set('plugin.manager.field.field_type', $field_type_manager);
- $this->container->set('entity.manager', $this->entityManager);
-
- $this->entityType->expects($this->any())
- ->method('getKey')
- ->will($this->returnValueMap(array(
- array('id', 'id'),
- array('revision', 'revision'),
- )));
- $this->entityType->expects($this->once())
- ->method('hasKey')
- ->with('revision')
- ->will($this->returnValue(TRUE));
-
- $field_type_manager->expects($this->exactly(2))
- ->method('getDefaultSettings')
- ->will($this->returnValue(array()));
- $field_type_manager->expects($this->exactly(2))
- ->method('getDefaultInstanceSettings')
- ->will($this->returnValue(array()));
-
- $this->fieldDefinitions['id'] = BaseFieldDefinition::create('string')
- ->setName('id');
- $this->fieldDefinitions['revision'] = BaseFieldDefinition::create('string')
- ->setName('revision');
-
- $this->entityManager->expects($this->any())
- ->method('getDefinition')
- ->with('test_entity')
- ->will($this->returnValue($this->entityType));
- $this->entityManager->expects($this->any())
- ->method('getBaseFieldDefinitions')
- ->will($this->returnValue($this->fieldDefinitions));
-
- // Define a field definition for a test_field field.
- $field_storage = $this->getMock('\Drupal\field\FieldStorageConfigInterface');
- $field_storage->deleted = FALSE;
-
- $field_storage->expects($this->any())
- ->method('getName')
- ->will($this->returnValue('test_field'));
-
- $field_storage->expects($this->any())
- ->method('getTargetEntityTypeId')
- ->will($this->returnValue('test_entity'));
-
- $field_schema = array(
- 'columns' => array(
- 'value' => array(
- 'type' => 'varchar',
- 'length' => 10,
- 'not null' => FALSE,
- ),
- ),
- 'unique keys' => array(),
- 'indexes' => array(),
- 'foreign keys' => array(),
- );
- $field_storage->expects($this->any())
- ->method('getSchema')
- ->will($this->returnValue($field_schema));
-
- $schema = SqlContentEntityStorage::_fieldSqlSchema($field_storage);
-
- // Make sure that the entity_id schema field if of type varchar.
- $this->assertEquals($schema['test_entity__test_field']['fields']['entity_id']['type'], 'varchar');
- $this->assertEquals($schema['test_entity__test_field']['fields']['revision_id']['type'], 'varchar');
- }
-
- /**
* @covers ::create()
*/
public function testCreate() {
@@ -1071,6 +999,9 @@ class SqlContentEntityStorageTest extends UnitTestCase {
$definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
// Assign common method return values.
+ $methods += array(
+ 'isBaseField' => TRUE,
+ );
foreach ($methods as $method => $result) {
$definition
->expects($this->any())