3 namespace Drupal\Core\Entity\Sql;
5 use Drupal\Core\Entity\ContentEntityTypeInterface;
6 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 * Defines a default table mapping class.
11 class DefaultTableMapping implements TableMappingInterface {
14 * The entity type definition.
16 * @var \Drupal\Core\Entity\ContentEntityTypeInterface
18 protected $entityType;
21 * The field storage definitions of this mapping.
23 * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
25 protected $fieldStorageDefinitions = [];
28 * A list of field names per table.
30 * This corresponds to the return value of
31 * TableMappingInterface::getFieldNames() except that this variable is
32 * additionally keyed by table name.
36 protected $fieldNames = [];
39 * A list of database columns which store denormalized data per table.
41 * This corresponds to the return value of
42 * TableMappingInterface::getExtraColumns() except that this variable is
43 * additionally keyed by table name.
47 protected $extraColumns = [];
50 * A mapping of column names per field name.
52 * This corresponds to the return value of
53 * TableMappingInterface::getColumnNames() except that this variable is
54 * additionally keyed by field name.
56 * This data is derived from static::$storageDefinitions, but is stored
57 * separately to avoid repeated processing.
61 protected $columnMapping = [];
64 * A list of all database columns per table.
66 * This corresponds to the return value of
67 * TableMappingInterface::getAllColumns() except that this variable is
68 * additionally keyed by table name.
70 * This data is derived from static::$storageDefinitions, static::$fieldNames,
71 * and static::$extraColumns, but is stored separately to avoid repeated
76 protected $allColumns = [];
79 * Constructs a DefaultTableMapping.
81 * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
82 * The entity type definition.
83 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
84 * A list of field storage definitions that should be available for the
85 * field columns of this table mapping.
87 public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
88 $this->entityType = $entity_type;
89 $this->fieldStorageDefinitions = $storage_definitions;
95 public function getTableNames() {
96 return array_unique(array_merge(array_keys($this->fieldNames), array_keys($this->extraColumns)));
102 public function getAllColumns($table_name) {
103 if (!isset($this->allColumns[$table_name])) {
104 $this->allColumns[$table_name] = [];
106 foreach ($this->getFieldNames($table_name) as $field_name) {
107 $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], array_values($this->getColumnNames($field_name)));
110 // There is just one field for each dedicated storage table, thus
111 // $field_name can only refer to it.
112 if (isset($field_name) && $this->requiresDedicatedTableStorage($this->fieldStorageDefinitions[$field_name])) {
113 // Unlike in shared storage tables, in dedicated ones field columns are
115 $this->allColumns[$table_name] = array_merge($this->getExtraColumns($table_name), $this->allColumns[$table_name]);
118 $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], $this->getExtraColumns($table_name));
121 return $this->allColumns[$table_name];
127 public function getFieldNames($table_name) {
128 if (isset($this->fieldNames[$table_name])) {
129 return $this->fieldNames[$table_name];
137 public function getFieldTableName($field_name) {
140 if (isset($this->fieldStorageDefinitions[$field_name])) {
141 // Since a field may be stored in more than one table, we inspect tables
142 // in order of relevance: the data table if present is the main place
143 // where field data is stored, otherwise the base table is responsible for
144 // storing field data. Revision metadata is an exception as it's stored
145 // only in the revision table.
146 // @todo The table mapping itself should know about entity tables. See
147 // https://www.drupal.org/node/2274017.
148 /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
149 $storage = \Drupal::entityManager()->getStorage($this->entityType->id());
150 $storage_definition = $this->fieldStorageDefinitions[$field_name];
152 $storage->getDataTable(),
153 $storage->getBaseTable(),
154 $storage->getRevisionTable(),
155 $this->getDedicatedDataTableName($storage_definition),
158 // Collect field columns.
160 foreach (array_keys($storage_definition->getColumns()) as $property_name) {
161 $field_columns[] = $this->getFieldColumnName($storage_definition, $property_name);
164 foreach (array_filter($table_names) as $table_name) {
165 $columns = $this->getAllColumns($table_name);
166 // We assume finding one field column belonging to the mapping is enough
167 // to identify the field table.
168 if (array_intersect($columns, $field_columns)) {
169 $result = $table_name;
175 if (!isset($result)) {
176 throw new SqlContentEntityStorageException("Table information not available for the '$field_name' field.");
185 public function getColumnNames($field_name) {
186 if (!isset($this->columnMapping[$field_name])) {
187 $this->columnMapping[$field_name] = [];
188 if (isset($this->fieldStorageDefinitions[$field_name]) && !$this->fieldStorageDefinitions[$field_name]->hasCustomStorage()) {
189 foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
190 $this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($this->fieldStorageDefinitions[$field_name], $property_name);
194 return $this->columnMapping[$field_name];
200 public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $property_name) {
201 $field_name = $storage_definition->getName();
203 if ($this->allowsSharedTableStorage($storage_definition)) {
204 $column_name = count($storage_definition->getColumns()) == 1 ? $field_name : $field_name . '__' . $property_name;
206 elseif ($this->requiresDedicatedTableStorage($storage_definition)) {
207 if ($property_name == TableMappingInterface::DELTA) {
208 $column_name = 'delta';
211 $column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name;
215 throw new SqlContentEntityStorageException("Column information not available for the '$field_name' field.");
222 * Adds field columns for a table to the table mapping.
224 * @param string $table_name
225 * The name of the table to add the field column for.
226 * @param string[] $field_names
227 * A list of field names to add the columns for.
231 public function setFieldNames($table_name, array $field_names) {
232 $this->fieldNames[$table_name] = $field_names;
233 // Force the re-computation of the column list.
234 unset($this->allColumns[$table_name]);
241 public function getExtraColumns($table_name) {
242 if (isset($this->extraColumns[$table_name])) {
243 return $this->extraColumns[$table_name];
249 * Adds a extra columns for a table to the table mapping.
251 * @param string $table_name
252 * The name of table to add the extra columns for.
253 * @param string[] $column_names
254 * The list of column names.
258 public function setExtraColumns($table_name, array $column_names) {
259 $this->extraColumns[$table_name] = $column_names;
260 // Force the re-computation of the column list.
261 unset($this->allColumns[$table_name]);
266 * Checks whether the given field can be stored in a shared table.
268 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
269 * The field storage definition.
272 * TRUE if the field can be stored in a dedicated table, FALSE otherwise.
274 public function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
275 return !$storage_definition->hasCustomStorage() && $storage_definition->isBaseField() && !$storage_definition->isMultiple() && !$storage_definition->isDeleted();
279 * Checks whether the given field has to be stored in a dedicated table.
281 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
282 * The field storage definition.
285 * TRUE if the field can be stored in a dedicated table, FALSE otherwise.
287 public function requiresDedicatedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
288 return !$storage_definition->hasCustomStorage() && !$this->allowsSharedTableStorage($storage_definition);
292 * Gets a list of dedicated table names for this mapping.
295 * An array of table names.
297 public function getDedicatedTableNames() {
298 $table_mapping = $this;
299 $definitions = array_filter($this->fieldStorageDefinitions, function ($definition) use ($table_mapping) {
300 return $table_mapping->requiresDedicatedTableStorage($definition);
302 $data_tables = array_map(function ($definition) use ($table_mapping) {
303 return $table_mapping->getDedicatedDataTableName($definition);
305 $revision_tables = array_map(function ($definition) use ($table_mapping) {
306 return $table_mapping->getDedicatedRevisionTableName($definition);
308 $dedicated_tables = array_merge(array_values($data_tables), array_values($revision_tables));
309 return $dedicated_tables;
315 public function getReservedColumns() {
320 * Generates a table name for a field data table.
322 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
323 * The field storage definition.
324 * @param bool $is_deleted
325 * (optional) Whether the table name holding the values of a deleted field
326 * should be returned.
329 * A string containing the generated name for the database table.
331 public function getDedicatedDataTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
333 // When a field is a deleted, the table is renamed to
334 // {field_deleted_data_UNIQUE_STORAGE_ID}. To make sure we don't end up
335 // with table names longer than 64 characters, we hash the unique storage
336 // identifier and return the first 10 characters so we end up with a short
338 return "field_deleted_data_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
341 return $this->generateFieldTableName($storage_definition, FALSE);
346 * Generates a table name for a field revision archive table.
348 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
349 * The field storage definition.
350 * @param bool $is_deleted
351 * (optional) Whether the table name holding the values of a deleted field
352 * should be returned.
355 * A string containing the generated name for the database table.
357 public function getDedicatedRevisionTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
359 // When a field is a deleted, the table is renamed to
360 // {field_deleted_revision_UNIQUE_STORAGE_ID}. To make sure we don't end
361 // up with table names longer than 64 characters, we hash the unique
362 // storage identifier and return the first 10 characters so we end up with
363 // a short unique ID.
364 return "field_deleted_revision_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
367 return $this->generateFieldTableName($storage_definition, TRUE);
372 * Generates a safe and unambiguous field table name.
374 * The method accounts for a maximum table name length of 64 characters, and
375 * takes care of disambiguation.
377 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
378 * The field storage definition.
379 * @param bool $revision
380 * TRUE for revision table, FALSE otherwise.
383 * The final table name.
385 protected function generateFieldTableName(FieldStorageDefinitionInterface $storage_definition, $revision) {
386 $separator = $revision ? '_revision__' : '__';
387 $table_name = $storage_definition->getTargetEntityTypeId() . $separator . $storage_definition->getName();
388 // Limit the string to 48 characters, keeping a 16 characters margin for db
390 if (strlen($table_name) > 48) {
391 // Use a shorter separator, a truncated entity_type, and a hash of the
392 // field storage unique identifier.
393 $separator = $revision ? '_r__' : '__';
394 // Truncate to the same length for the current and revision tables.
395 $entity_type = substr($storage_definition->getTargetEntityTypeId(), 0, 34);
396 $field_hash = substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
397 $table_name = $entity_type . $separator . $field_hash;