123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- <?php
- namespace Drupal\Core\Entity\Sql;
- use Drupal\Core\Entity\ContentEntityTypeInterface;
- use Drupal\Core\Field\FieldStorageDefinitionInterface;
- /**
- * Defines a default table mapping class.
- */
- class DefaultTableMapping implements TableMappingInterface {
- /**
- * The entity type definition.
- *
- * @var \Drupal\Core\Entity\ContentEntityTypeInterface
- */
- protected $entityType;
- /**
- * The field storage definitions of this mapping.
- *
- * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
- */
- protected $fieldStorageDefinitions = [];
- /**
- * A list of field names per table.
- *
- * This corresponds to the return value of
- * TableMappingInterface::getFieldNames() except that this variable is
- * additionally keyed by table name.
- *
- * @var array[]
- */
- protected $fieldNames = [];
- /**
- * A list of database columns which store denormalized data per table.
- *
- * This corresponds to the return value of
- * TableMappingInterface::getExtraColumns() except that this variable is
- * additionally keyed by table name.
- *
- * @var array[]
- */
- protected $extraColumns = [];
- /**
- * A mapping of column names per field name.
- *
- * This corresponds to the return value of
- * TableMappingInterface::getColumnNames() except that this variable is
- * additionally keyed by field name.
- *
- * This data is derived from static::$storageDefinitions, but is stored
- * separately to avoid repeated processing.
- *
- * @var array[]
- */
- protected $columnMapping = [];
- /**
- * A list of all database columns per table.
- *
- * This corresponds to the return value of
- * TableMappingInterface::getAllColumns() except that this variable is
- * additionally keyed by table name.
- *
- * This data is derived from static::$storageDefinitions, static::$fieldNames,
- * and static::$extraColumns, but is stored separately to avoid repeated
- * processing.
- *
- * @var array[]
- */
- protected $allColumns = [];
- /**
- * Constructs a DefaultTableMapping.
- *
- * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
- * The entity type definition.
- * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
- * A list of field storage definitions that should be available for the
- * field columns of this table mapping.
- */
- public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
- $this->entityType = $entity_type;
- $this->fieldStorageDefinitions = $storage_definitions;
- }
- /**
- * {@inheritdoc}
- */
- public function getTableNames() {
- return array_unique(array_merge(array_keys($this->fieldNames), array_keys($this->extraColumns)));
- }
- /**
- * {@inheritdoc}
- */
- public function getAllColumns($table_name) {
- if (!isset($this->allColumns[$table_name])) {
- $this->allColumns[$table_name] = [];
- foreach ($this->getFieldNames($table_name) as $field_name) {
- $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], array_values($this->getColumnNames($field_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];
- }
- /**
- * {@inheritdoc}
- */
- public function getFieldNames($table_name) {
- if (isset($this->fieldNames[$table_name])) {
- return $this->fieldNames[$table_name];
- }
- return [];
- }
- /**
- * {@inheritdoc}
- */
- public function getFieldTableName($field_name) {
- $result = NULL;
- if (isset($this->fieldStorageDefinitions[$field_name])) {
- // Since a field may be stored in more than one table, we inspect tables
- // in order of relevance: the data table if present is the main place
- // where field data is stored, otherwise the base table is responsible for
- // storing field data. Revision metadata is an exception as it's stored
- // only in the revision table.
- // @todo The table mapping itself should know about entity tables. See
- // https://www.drupal.org/node/2274017.
- /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
- $storage = \Drupal::entityManager()->getStorage($this->entityType->id());
- $storage_definition = $this->fieldStorageDefinitions[$field_name];
- $table_names = [
- $storage->getDataTable(),
- $storage->getBaseTable(),
- $storage->getRevisionTable(),
- $this->getDedicatedDataTableName($storage_definition),
- ];
- // Collect field columns.
- $field_columns = [];
- foreach (array_keys($storage_definition->getColumns()) as $property_name) {
- $field_columns[] = $this->getFieldColumnName($storage_definition, $property_name);
- }
- foreach (array_filter($table_names) as $table_name) {
- $columns = $this->getAllColumns($table_name);
- // We assume finding one field column belonging to the mapping is enough
- // to identify the field table.
- if (array_intersect($columns, $field_columns)) {
- $result = $table_name;
- break;
- }
- }
- }
- if (!isset($result)) {
- throw new SqlContentEntityStorageException("Table information not available for the '$field_name' field.");
- }
- return $result;
- }
- /**
- * {@inheritdoc}
- */
- public function getColumnNames($field_name) {
- if (!isset($this->columnMapping[$field_name])) {
- $this->columnMapping[$field_name] = [];
- if (isset($this->fieldStorageDefinitions[$field_name]) && !$this->fieldStorageDefinitions[$field_name]->hasCustomStorage()) {
- foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
- $this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($this->fieldStorageDefinitions[$field_name], $property_name);
- }
- }
- }
- return $this->columnMapping[$field_name];
- }
- /**
- * {@inheritdoc}
- */
- public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $property_name) {
- $field_name = $storage_definition->getName();
- if ($this->allowsSharedTableStorage($storage_definition)) {
- $column_name = count($storage_definition->getColumns()) == 1 ? $field_name : $field_name . '__' . $property_name;
- }
- elseif ($this->requiresDedicatedTableStorage($storage_definition)) {
- if ($property_name == TableMappingInterface::DELTA) {
- $column_name = 'delta';
- }
- else {
- $column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name;
- }
- }
- else {
- throw new SqlContentEntityStorageException("Column information not available for the '$field_name' field.");
- }
- return $column_name;
- }
- /**
- * Adds field columns for a table to the table mapping.
- *
- * @param string $table_name
- * The name of the table to add the field column for.
- * @param string[] $field_names
- * A list of field names to add the columns for.
- *
- * @return $this
- */
- public function setFieldNames($table_name, array $field_names) {
- $this->fieldNames[$table_name] = $field_names;
- // Force the re-computation of the column list.
- unset($this->allColumns[$table_name]);
- return $this;
- }
- /**
- * {@inheritdoc}
- */
- public function getExtraColumns($table_name) {
- if (isset($this->extraColumns[$table_name])) {
- return $this->extraColumns[$table_name];
- }
- return [];
- }
- /**
- * Adds a extra columns for a table to the table mapping.
- *
- * @param string $table_name
- * The name of table to add the extra columns for.
- * @param string[] $column_names
- * The list of column names.
- *
- * @return $this
- */
- public function setExtraColumns($table_name, array $column_names) {
- $this->extraColumns[$table_name] = $column_names;
- // Force the re-computation of the column list.
- unset($this->allColumns[$table_name]);
- return $this;
- }
- /**
- * 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() && !$storage_definition->isDeleted();
- }
- /**
- * 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);
- }
- /**
- * Gets 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() {
- return ['deleted'];
- }
- /**
- * Generates a table name for a field data table.
- *
- * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
- * The field storage definition.
- * @param bool $is_deleted
- * (optional) Whether the table name holding the values of a deleted field
- * should be returned.
- *
- * @return string
- * A string containing the generated name for the database table.
- */
- public function getDedicatedDataTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
- if ($is_deleted) {
- // When a field is a deleted, the table is renamed to
- // {field_deleted_data_UNIQUE_STORAGE_ID}. To make sure we don't end up
- // with table names longer than 64 characters, we hash the unique storage
- // identifier and return the first 10 characters so we end up with a short
- // unique ID.
- return "field_deleted_data_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
- }
- else {
- return $this->generateFieldTableName($storage_definition, FALSE);
- }
- }
- /**
- * Generates a table name for a field revision archive table.
- *
- * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
- * The field storage definition.
- * @param bool $is_deleted
- * (optional) Whether the table name holding the values of a deleted field
- * should be returned.
- *
- * @return string
- * A string containing the generated name for the database table.
- */
- public function getDedicatedRevisionTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
- if ($is_deleted) {
- // When a field is a deleted, the table is renamed to
- // {field_deleted_revision_UNIQUE_STORAGE_ID}. To make sure we don't end
- // up with table names longer than 64 characters, we hash the unique
- // storage identifier and return the first 10 characters so we end up with
- // a short unique ID.
- return "field_deleted_revision_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
- }
- else {
- return $this->generateFieldTableName($storage_definition, TRUE);
- }
- }
- /**
- * Generates a safe and unambiguous field table name.
- *
- * The method accounts for a maximum table name length of 64 characters, and
- * takes care of disambiguation.
- *
- * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
- * The field storage definition.
- * @param bool $revision
- * TRUE for revision table, FALSE otherwise.
- *
- * @return string
- * The final table name.
- */
- protected function generateFieldTableName(FieldStorageDefinitionInterface $storage_definition, $revision) {
- $separator = $revision ? '_revision__' : '__';
- $table_name = $storage_definition->getTargetEntityTypeId() . $separator . $storage_definition->getName();
- // Limit the string to 48 characters, keeping a 16 characters margin for db
- // prefixes.
- if (strlen($table_name) > 48) {
- // Use a shorter separator, a truncated entity_type, and a hash of the
- // field storage unique identifier.
- $separator = $revision ? '_r__' : '__';
- // Truncate to the same length for the current and revision tables.
- $entity_type = substr($storage_definition->getTargetEntityTypeId(), 0, 34);
- $field_hash = substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
- $table_name = $entity_type . $separator . $field_hash;
- }
- return $table_name;
- }
- }
|