X-Git-Url: http://aleph1.co.uk/gitweb/?a=blobdiff_plain;f=web%2Fcore%2Flib%2FDrupal%2FCore%2FEntity%2FSql%2FSqlContentEntityStorage.php;h=48545d22ded94b4a7f6b651200196095f18458a5;hb=5b8bb166bfa98770daef9de5c127fc2e6ef02340;hp=29b8a9aa95ce1373883b7dd35c6732823f72d90d;hpb=af6d1fb995500ae68849458ee10d66abbdcfb252;p=yaffs-website diff --git a/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index 29b8a9aa9..48545d22d 100644 --- a/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -3,12 +3,14 @@ namespace Drupal\Core\Entity\Sql; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Database; use Drupal\Core\Database\DatabaseExceptionWrapper; use Drupal\Core\Database\SchemaException; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\ContentEntityStorageBase; +use Drupal\Core\Entity\ContentEntityTypeInterface; use Drupal\Core\Entity\EntityBundleListenerInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; @@ -132,7 +134,8 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt $container->get('database'), $container->get('entity.manager'), $container->get('cache.entity'), - $container->get('language_manager') + $container->get('language_manager'), + $container->get('entity.memory_cache') ); } @@ -160,9 +163,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt * The cache backend to be used. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. + * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache + * The memory cache backend to be used. */ - public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) { - parent::__construct($entity_type, $entity_manager, $cache); + public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache = NULL) { + parent::__construct($entity_type, $entity_manager, $cache, $memory_cache); $this->database = $database; $this->languageManager = $language_manager; $this->initTableLayout(); @@ -180,22 +185,21 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt $this->dataTable = NULL; $this->revisionDataTable = NULL; - // @todo Remove table names from the entity type definition in - // https://www.drupal.org/node/2232465. - $this->baseTable = $this->entityType->getBaseTable() ?: $this->entityTypeId; + $table_mapping = $this->getTableMapping(); + $this->baseTable = $table_mapping->getBaseTable(); $revisionable = $this->entityType->isRevisionable(); if ($revisionable) { $this->revisionKey = $this->entityType->getKey('revision') ?: 'revision_id'; - $this->revisionTable = $this->entityType->getRevisionTable() ?: $this->entityTypeId . '_revision'; + $this->revisionTable = $table_mapping->getRevisionTable(); } $translatable = $this->entityType->isTranslatable(); if ($translatable) { - $this->dataTable = $this->entityType->getDataTable() ?: $this->entityTypeId . '_field_data'; + $this->dataTable = $table_mapping->getDataTable(); $this->langcodeKey = $this->entityType->getKey('langcode'); $this->defaultLangcodeKey = $this->entityType->getKey('default_langcode'); } if ($revisionable && $translatable) { - $this->revisionDataTable = $this->entityType->getRevisionDataTable() ?: $this->entityTypeId . '_field_revision'; + $this->revisionDataTable = $table_mapping->getRevisionDataTable(); } } @@ -283,6 +287,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt */ public function setTableMapping(TableMappingInterface $table_mapping) { $this->tableMapping = $table_mapping; + + $this->baseTable = $table_mapping->getBaseTable(); + $this->revisionTable = $table_mapping->getRevisionTable(); + $this->dataTable = $table_mapping->getDataTable(); + $this->revisionDataTable = $table_mapping->getRevisionDataTable(); } /** @@ -301,117 +310,41 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt * {@inheritdoc} */ public function getTableMapping(array $storage_definitions = NULL) { - $table_mapping = $this->tableMapping; + // 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. + if ($storage_definitions) { + return $this->getCustomTableMapping($this->entityType, $storage_definitions); + } // 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) { - $table_mapping_class = $this->temporary ? TemporaryTableMapping::class : DefaultTableMapping::class; - $definitions = $storage_definitions ?: $this->entityManager->getFieldStorageDefinitions($this->entityTypeId); - /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping|\Drupal\Core\Entity\Sql\TemporaryTableMapping $table_mapping */ - $table_mapping = new $table_mapping_class($this->entityType, $definitions); - - $shared_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) { - return $table_mapping->allowsSharedTableStorage($definition); - }); - - $key_fields = array_values(array_filter([$this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey])); - $all_fields = array_keys($shared_table_definitions); - $revisionable_fields = array_keys(array_filter($shared_table_definitions, function (FieldStorageDefinitionInterface $definition) { - return $definition->isRevisionable(); - })); - // Make sure the key fields come first in the list of fields. - $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields)); - - // If the entity is revisionable, gather the fields that need to be put - // in the revision table. - $revisionable = $this->entityType->isRevisionable(); - $revision_metadata_fields = $revisionable ? array_values($this->entityType->getRevisionMetadataKeys()) : []; - - $translatable = $this->entityType->isTranslatable(); - if (!$revisionable && !$translatable) { - // The base layout stores all the base field values in the base table. - $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. - $table_mapping->setFieldNames($this->baseTable, array_diff($all_fields, $revision_metadata_fields)); - $revision_key_fields = [$this->idKey, $this->revisionKey]; - $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 - // other base field values are stored in the data table, no matter - // whether they are translatable or not. The data table holds also a - // 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. - $table_mapping - ->setFieldNames($this->baseTable, $key_fields) - ->setFieldNames($this->dataTable, array_values(array_diff($all_fields, [$this->uuidKey]))); - } - elseif ($revisionable && $translatable) { - // The revisionable multilingual layout stores key field values in the - // base table, except for language, which is stored in the revision - // table along with revision metadata. The revision data table holds - // data field values for all the revisionable fields and the data table - // 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. - $table_mapping->setFieldNames($this->baseTable, array_values($key_fields)); - - // 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, [$this->uuidKey], $revision_metadata_fields)); - $table_mapping->setFieldNames($this->dataTable, $data_fields); - - $revision_base_fields = array_merge([$this->idKey, $this->revisionKey, $this->langcodeKey], $revision_metadata_fields); - $table_mapping->setFieldNames($this->revisionTable, $revision_base_fields); - - $revision_data_key_fields = [$this->idKey, $this->revisionKey, $this->langcodeKey]; - $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$this->langcodeKey]); - $table_mapping->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields)); - } - - // Add dedicated tables. - $dedicated_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) { - return $table_mapping->requiresDedicatedTableStorage($definition); - }); - $extra_columns = [ - 'bundle', - 'deleted', - 'entity_id', - 'revision_id', - 'langcode', - 'delta', - ]; - foreach ($dedicated_table_definitions as $field_name => $definition) { - $tables = [$table_mapping->getDedicatedDataTableName($definition)]; - if ($revisionable && $definition->isRevisionable()) { - $tables[] = $table_mapping->getDedicatedRevisionTableName($definition); - } - foreach ($tables as $table_name) { - $table_mapping->setFieldNames($table_name, [$field_name]); - $table_mapping->setExtraColumns($table_name, $extra_columns); - } - } + // case, we can statically cache the computed table mapping. + if (!isset($this->tableMapping)) { + $storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId); - // Cache the computed table mapping only if we are using our internal - // storage definitions. - if (!$storage_definitions) { - $this->tableMapping = $table_mapping; - } + $this->tableMapping = $this->getCustomTableMapping($this->entityType, $storage_definitions); } - return $table_mapping; + return $this->tableMapping; + } + + /** + * Gets a table mapping for the specified entity type and storage definitions. + * + * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type + * An entity type definition. + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions + * An array of field storage definitions to be used to compute the table + * mapping. + * + * @return \Drupal\Core\Entity\Sql\TableMappingInterface + * A table mapping object for the entity's tables. + * + * @internal + */ + public function getCustomTableMapping(ContentEntityTypeInterface $entity_type, array $storage_definitions) { + $table_mapping_class = $this->temporary ? TemporaryTableMapping::class : DefaultTableMapping::class; + return $table_mapping_class::create($entity_type, $storage_definitions); } /** @@ -482,25 +415,46 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt return []; } + // Get the names of the fields that are stored in the base table and, if + // applicable, the revision table. Other entity data will be loaded in + // loadFromSharedTables() and loadFromDedicatedTables(). + $field_names = $this->tableMapping->getFieldNames($this->baseTable); + if ($this->revisionTable) { + $field_names = array_unique(array_merge($field_names, $this->tableMapping->getFieldNames($this->revisionTable))); + } + $values = []; foreach ($records as $id => $record) { $values[$id] = []; // Skip the item delta and item value levels (if possible) but let the // field assign the value as suiting. This avoids unnecessary array // hierarchies and saves memory here. - foreach ($record as $name => $value) { - // Handle columns named [field_name]__[column_name] (e.g for field types - // that store several properties). - if ($field_name = strstr($name, '__', TRUE)) { - $property_name = substr($name, strpos($name, '__') + 2); - $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = $value; + foreach ($field_names as $field_name) { + $field_columns = $this->tableMapping->getColumnNames($field_name); + // Handle field types that store several properties. + if (count($field_columns) > 1) { + foreach ($field_columns as $property_name => $column_name) { + if (property_exists($record, $column_name)) { + $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = $record->{$column_name}; + unset($record->{$column_name}); + } + } } + // Handle field types that store only one property. else { - // Handle columns named directly after the field (e.g if the field - // type only stores one property). - $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = $value; + $column_name = reset($field_columns); + if (property_exists($record, $column_name)) { + $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = $record->{$column_name}; + unset($record->{$column_name}); + } } } + + // Handle additional record entries that are not provided by an entity + // field, such as 'isDefaultRevision'. + foreach ($record as $name => $value) { + $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = $value; + } } // Initialize translations array. @@ -564,7 +518,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt // Some fields can have more then one columns in the data table so // column names are needed. foreach ($data_fields as $data_field) { - // \Drupal\Core\Entity\Sql\TableMappingInterface:: getColumNames() + // \Drupal\Core\Entity\Sql\TableMappingInterface::getColumnNames() // returns an array keyed by property names so remove the keys // before array_merge() to avoid losing data with fields having the // same columns i.e. value. @@ -706,7 +660,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt * A SelectQuery object for loading the entity. */ protected function buildQuery($ids, $revision_ids = FALSE) { - $query = $this->database->select($this->entityType->getBaseTable(), 'base'); + $query = $this->database->select($this->baseTable, 'base'); $query->addTag($this->entityTypeId . '_load_multiple'); @@ -784,7 +738,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt protected function doDeleteFieldItems($entities) { $ids = array_keys($entities); - $this->database->delete($this->entityType->getBaseTable()) + $this->database->delete($this->baseTable) ->condition($this->idKey, $ids, 'IN') ->execute(); @@ -867,10 +821,13 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt if ($update) { $default_revision = $entity->isDefaultRevision(); if ($default_revision) { + // Remove the ID from the record to enable updates on SQL variants + // that prevent updating serial columns, for example, mssql. + unset($record->{$this->idKey}); $this->database ->update($this->baseTable) ->fields((array) $record) - ->condition($this->idKey, $record->{$this->idKey}) + ->condition($this->idKey, $entity->get($this->idKey)->value) ->execute(); } if ($this->revisionTable) { @@ -879,11 +836,15 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt } else { $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable); + // Remove the revision ID from the record to enable updates on SQL + // variants that prevent updating serial columns, for example, + // mssql. + unset($record->{$this->revisionKey}); $entity->preSaveRevision($this, $record); $this->database ->update($this->revisionTable) ->fields((array) $record) - ->condition($this->revisionKey, $record->{$this->revisionKey}) + ->condition($this->revisionKey, $entity->getRevisionId()) ->execute(); } } @@ -1105,24 +1066,26 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt $record->{$this->revisionKey} = $insert_id; } if ($entity->isDefaultRevision()) { - $this->database->update($this->entityType->getBaseTable()) + $this->database->update($this->baseTable) ->fields([$this->revisionKey => $record->{$this->revisionKey}]) ->condition($this->idKey, $record->{$this->idKey}) ->execute(); } + // Make sure to update the new revision key for the entity. + $entity->{$this->revisionKey}->value = $record->{$this->revisionKey}; } else { + // Remove the revision ID from the record to enable updates on SQL + // variants that prevent updating serial columns, for example, + // mssql. + unset($record->{$this->revisionKey}); $this->database ->update($this->revisionTable) ->fields((array) $record) - ->condition($this->revisionKey, $record->{$this->revisionKey}) + ->condition($this->revisionKey, $entity->getRevisionId()) ->execute(); } - - // Make sure to update the new revision key for the entity. - $entity->{$this->revisionKey}->value = $record->{$this->revisionKey}; - - return $record->{$this->revisionKey}; + return $entity->getRevisionId(); } /** @@ -1462,14 +1425,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt * {@inheritdoc} */ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) { - // 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->wrapSchemaException(function () use ($storage_definition) { $this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition); }); @@ -1694,16 +1649,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) { // Ascertain the table this field is mapped too. $field_name = $storage_definition->getName(); - try { - $table_name = $table_mapping->getFieldTableName($field_name); - } - catch (SqlContentEntityStorageException $e) { - // This may happen when changing field storage schema, since we are not - // able to use a table mapping matching the passed storage definition. - // @todo Revisit this once we are able to instantiate the table mapping - // properly. See https://www.drupal.org/node/2274017. - $table_name = $this->dataTable ?: $this->baseTable; - } + $table_name = $table_mapping->getFieldTableName($field_name); $query = $this->database->select($table_name, 't'); $or = $query->orConditionGroup(); foreach (array_keys($storage_definition->getColumns()) as $property_name) {