Commit 1ab26517 authored by alexpott's avatar alexpott
Browse files

Issue #2183231 by tstoeckler, plach, jessebeach, fago, Berdir, xjm, mauzeh:...

Issue #2183231 by tstoeckler, plach, jessebeach, fago, Berdir, xjm, mauzeh: Make ContentEntityDatabaseStorage generate static database schemas for content entities.
parent 6352edf7
......@@ -555,14 +555,18 @@ protected static function invalidateTagsOnDelete(array $entities) {
* Acts on entities of which this entity is a bundle entity type.
*/
protected function onUpdateBundleEntity() {
// If this entity is a bundle entity type of another entity type, and we're
// updating an existing entity, and that other entity type has a view
// builder class, then invalidate the render cache of entities for which
// this entity is a bundle.
$bundle_of = $this->getEntityType()->getBundleOf();
$entity_manager = \Drupal::entityManager();
if ($bundle_of !== FALSE && $entity_manager->hasController($bundle_of, 'view_builder')) {
$entity_manager->getViewBuilder($bundle_of)->resetCache();
if ($bundle_of !== FALSE) {
// If this entity is a bundle entity type of another entity type, and we're
// updating an existing entity, and that other entity type has a view
// builder class, then invalidate the render cache of entities for which
// this entity is a bundle.
$entity_manager = $this->entityManager();
if ($entity_manager->hasController($bundle_of, 'view_builder')) {
$entity_manager->getViewBuilder($bundle_of)->resetCache();
}
// Entity bundle field definitions may depend on bundle settings.
$entity_manager->clearCachedFieldDefinitions();
}
}
......
......@@ -76,6 +76,7 @@ public function __construct(EntityTypeInterface $entity_type) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->idKey = $this->entityType->getKey('id');
$this->uuidKey = $this->entityType->getKey('uuid');
$this->entityClass = $this->entityType->getClass();
}
......
......@@ -12,6 +12,7 @@
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityDatabaseStorage;
use Drupal\Core\Entity\Query\QueryException;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\FieldConfigInterface;
......@@ -35,7 +36,6 @@ class Tables implements TablesInterface {
*/
protected $entityTables = array();
/**
* Field table array, key is table name, value is alias.
*
......@@ -45,11 +45,19 @@ class Tables implements TablesInterface {
*/
protected $fieldTables = array();
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManager
*/
protected $entityManager;
/**
* @param \Drupal\Core\Database\Query\SelectInterface $sql_query
*/
public function __construct(SelectInterface $sql_query) {
$this->sqlQuery = $sql_query;
$this->entityManager = \Drupal::entityManager();
}
/**
......@@ -57,7 +65,6 @@ public function __construct(SelectInterface $sql_query) {
*/
public function addField($field, $type, $langcode) {
$entity_type_id = $this->sqlQuery->getMetaData('entity_type');
$entity_manager = \Drupal::entityManager();
$age = $this->sqlQuery->getMetaData('age');
// This variable ensures grouping works correctly. For example:
// ->condition('tags', 2, '>')
......@@ -73,13 +80,13 @@ public function addField($field, $type, $langcode) {
// This will contain the definitions of the last specifier seen by the
// system.
$propertyDefinitions = array();
$entity_type = $entity_manager->getDefinition($entity_type_id);
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$field_storage_definitions = array();
// @todo Needed for menu links, make this implementation content entity
// specific after https://drupal.org/node/2256521.
if ($entity_type instanceof ContentEntityTypeInterface) {
$field_storage_definitions = $entity_manager->getFieldStorageDefinitions($entity_type_id);
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
}
for ($key = 0; $key <= $count; $key ++) {
// If there is revision support and only the current revision is being
......@@ -154,10 +161,10 @@ public function addField($field, $type, $langcode) {
$entity_tables = array();
if ($data_table = $entity_type->getDataTable()) {
$this->sqlQuery->addMetaData('simple_query', FALSE);
$entity_tables[$data_table] = drupal_get_schema($data_table);
$entity_tables[$data_table] = $this->getTableMapping($data_table, $entity_type_id);
}
$entity_base_table = $entity_type->getBaseTable();
$entity_tables[$entity_base_table] = drupal_get_schema($entity_base_table);
$entity_tables[$entity_base_table] = $this->getTableMapping($entity_base_table, $entity_type_id);
$sql_column = $specifier;
$table = $this->ensureEntityTable($index_prefix, $specifier, $type, $langcode, $base_table, $entity_id_field, $entity_tables);
}
......@@ -174,8 +181,8 @@ public function addField($field, $type, $langcode) {
if (isset($propertyDefinitions[$relationship_specifier]) && $field->getPropertyDefinition('entity')->getDataType() == 'entity_reference' ) {
// If it is, use the entity type.
$entity_type_id = $propertyDefinitions[$relationship_specifier]->getTargetDefinition()->getEntityTypeId();
$entity_type = $entity_manager->getDefinition($entity_type_id);
$field_storage_definitions = $entity_manager->getFieldStorageDefinitions($entity_type_id);
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
// Add the new entity base table using the table and sql column.
$join_condition= '%alias.' . $entity_type->getKey('id') . " = $table.$sql_column";
$base_table = $this->sqlQuery->leftJoin($entity_type->getBaseTable(), NULL, $join_condition);
......@@ -199,8 +206,8 @@ public function addField($field, $type, $langcode) {
* @throws \Drupal\Core\Entity\Query\QueryException
*/
protected function ensureEntityTable($index_prefix, $property, $type, $langcode, $base_table, $id_field, $entity_tables) {
foreach ($entity_tables as $table => $schema) {
if (isset($schema['fields'][$property])) {
foreach ($entity_tables as $table => $mapping) {
if (isset($mapping[$property])) {
if (!isset($this->entityTables[$index_prefix . $table])) {
$this->entityTables[$index_prefix . $table] = $this->addJoin($type, $table, "%alias.$id_field = $base_table.$id_field", $langcode);
}
......@@ -241,4 +248,27 @@ protected function addJoin($type, $table, $join_condition, $langcode) {
return $this->sqlQuery->addJoin($type, $table, NULL, $join_condition, $arguments);
}
/**
* Returns the schema for the given table.
*
* @param string $table
* The table name.
*
* @return array|bool
* The table field mapping for the given table or FALSE if not available.
*/
protected function getTableMapping($table, $entity_type_id) {
$storage = $this->entityManager->getStorage($entity_type_id);
if ($storage instanceof SqlEntityStorageInterface) {
$mapping = $storage->getTableMapping()->getAllColumns($table);
}
else {
// @todo Stop calling drupal_get_schema() once menu links are converted
// to the Entity Field API. See https://drupal.org/node/1842858.
$schema = drupal_get_schema($table);
$mapping = array_keys($schema['fields']);
}
return array_flip($mapping);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Schema\ContentEntitySchemaHandler.
*/
namespace Drupal\Core\Entity\Schema;
use Drupal\Core\Entity\ContentEntityDatabaseStorage;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
/**
* Defines a schema handler that supports revisionable, translatable entities.
*/
class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface {
/**
* The entity type this schema builder is responsible for.
*
* @var \Drupal\Core\Entity\ContentEntityTypeInterface
*/
protected $entityType;
/**
* The storage field definitions for this entity type.
*
* @var \Drupal\Core\Field\FieldDefinitionInterface[]
*/
protected $fieldStorageDefinitions;
/**
* The storage object for the given entity type.
*
* @var \Drupal\Core\Entity\ContentEntityDatabaseStorage
*/
protected $storage;
/**
* A static cache of the generated schema array.
*
* @var array
*/
protected $schema;
/**
* Constructs a ContentEntitySchemaHandler.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
* @param \Drupal\Core\Entity\ContentEntityDatabaseStorage $storage
* The storage of the entity type. This must be an SQL-based storage.
*/
public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, ContentEntityDatabaseStorage $storage) {
$this->entityType = $entity_type;
$this->fieldStorageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id());
$this->storage = $storage;
}
/**
* {@inheritdoc}
*/
public function getSchema() {
// Prepare basic information about the entity type.
$tables = $this->getTables();
if (!isset($this->schema[$this->entityType->id()])) {
// Initialize the table schema.
$schema[$tables['base_table']] = $this->initializeBaseTable();
if (isset($tables['revision_table'])) {
$schema[$tables['revision_table']] = $this->initializeRevisionTable();
}
if (isset($tables['data_table'])) {
$schema[$tables['data_table']] = $this->initializeDataTable();
}
if (isset($tables['revision_data_table'])) {
$schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable();
}
$table_mapping = $this->storage->getTableMapping();
foreach ($table_mapping->getTableNames() as $table_name) {
// Add the schema from field definitions.
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);
}
// Add the schema for extra fields.
foreach ($table_mapping->getExtraColumns($table_name) as $column_name) {
if ($column_name == 'default_langcode') {
$this->addDefaultLangcodeSchema($schema[$table_name]);
}
}
}
// Process tables after having gathered field information.
$this->processBaseTable($schema[$tables['base_table']]);
if (isset($tables['revision_table'])) {
$this->processRevisionTable($schema[$tables['revision_table']]);
}
if (isset($tables['data_table'])) {
$this->processDataTable($schema[$tables['data_table']]);
}
if (isset($tables['revision_data_table'])) {
$this->processRevisionDataTable($schema[$tables['revision_data_table']]);
}
$this->schema[$this->entityType->id()] = $schema;
}
return $this->schema[$this->entityType->id()];
}
/**
* Gets a list of entity type tables.
*
* @return array
* A list of entity type tables, keyed by table key.
*/
protected function getTables() {
return array_filter(array(
'base_table' => $this->storage->getBaseTable(),
'revision_table' => $this->storage->getRevisionTable(),
'data_table' => $this->storage->getDataTable(),
'revision_data_table' => $this->storage->getRevisionDataTable(),
));
}
/**
* Returns the schema for a single field 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.
*/
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];
$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'])) {
$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);
}
}
/**
* Returns an index schema array for a given field.
*
* @param string $field_name
* The name of the field.
* @param array $field_schema
* The schema of the field.
* @param string[] $column_mapping
* A mapping of field column names to database column names.
*
* @return array
* The schema definition for the indexes.
*/
protected function getFieldIndexes($field_name, array $field_schema, array $column_mapping) {
return $this->getFieldSchemaData($field_name, $field_schema, $column_mapping, 'indexes');
}
/**
* Returns a unique key schema array for a given field.
*
* @param string $field_name
* The name of the field.
* @param array $field_schema
* The schema of the field.
* @param string[] $column_mapping
* A mapping of field column names to database column names.
*
* @return array
* The schema definition for the unique keys.
*/
protected function getFieldUniqueKeys($field_name, array $field_schema, array $column_mapping) {
return $this->getFieldSchemaData($field_name, $field_schema, $column_mapping, 'unique keys');
}
/**
* Returns field schema data for the given key.
*
* @param string $field_name
* The name of the field.
* @param array $field_schema
* The schema of the field.
* @param string[] $column_mapping
* A mapping of field column names to database column names.
* @param string $schema_key
* The type of schema data. Either 'indexes' or 'unique keys'.
*
* @return array
* The schema definition for the specified key.
*/
protected function getFieldSchemaData($field_name, array $field_schema, array $column_mapping, $schema_key) {
$data = array();
foreach ($field_schema[$schema_key] as $key => $columns) {
// To avoid clashes with entity-level indexes or unique keys we use
// "{$entity_type_id}_field__" as a prefix instead of just
// "{$entity_type_id}__". We additionally namespace the specifier by the
// field name to avoid clashes when multiple fields of the same type are
// added to an entity type.
$entity_type_id = $this->entityType->id();
$real_key = "{$entity_type_id}_field__{$field_name}__{$key}";
foreach ($columns as $column) {
// Allow for indexes and unique keys to specified as an array of column
// name and length.
if (is_array($column)) {
list($column_name, $length) = $column;
$data[$real_key][] = array($column_mapping[$column_name], $length);
}
else {
$data[$real_key][] = $column_mapping[$column];
}
}
}
return $data;
}
/**
* Returns field foreign keys.
*
* @param string $field_name
* The name of the field.
* @param array $field_schema
* The schema of the field.
* @param string[] $column_mapping
* A mapping of field column names to database column names.
*
* @return array
* The schema definition for the foreign keys.
*/
protected function getFieldForeignKeys($field_name, array $field_schema, array $column_mapping) {
$foreign_keys = array();
foreach ($field_schema['foreign keys'] as $specifier => $specification) {
// To avoid clashes with entity-level foreign keys we use
// "{$entity_type_id}_field__" as a prefix instead of just
// "{$entity_type_id}__". We additionally namespace the specifier by the
// field name to avoid clashes when multiple fields of the same type are
// added to an entity type.
$entity_type_id = $this->entityType->id();
$real_specifier = "{$entity_type_id}_field__{$field_name}__{$specifier}";
$foreign_keys[$real_specifier]['table'] = $specification['table'];
foreach ($specification['columns'] as $column => $referenced) {
$foreign_keys[$real_specifier]['columns'][$column_mapping[$column]] = $referenced;
}
}
return $foreign_keys;
}
/**
* Returns the schema for the 'default_langcode' metadata field.
*
* @param array $schema
* The table schema to add the field schema to, passed by reference.
*
* @return array
* A schema field array for the 'default_langcode' metadata field.
*/
protected function addDefaultLangcodeSchema(&$schema) {
$schema['fields']['default_langcode'] = array(
'description' => 'Boolean indicating whether field values are in the default entity language.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 1,
);
}
/**
* Initializes common information for a base table.
*
* @return array
* A partial schema array for the base table.
*/
protected function initializeBaseTable() {
$entity_type_id = $this->entityType->id();
$schema = array(
'description' => "The base table for $entity_type_id entities.",
'primary key' => array($this->entityType->getKey('id')),
'indexes' => array(),
'foreign keys' => array(),
);
if ($this->entityType->hasKey('uuid')) {
$uuid_key = $this->entityType->getKey('uuid');
$schema['unique keys'] = array(
$this->getEntityIndexName($uuid_key) => array($uuid_key),
);
}
if ($this->entityType->hasKey('revision')) {
$revision_key = $this->entityType->getKey('revision');
$key_name = $this->getEntityIndexName($revision_key);
$schema['unique keys'][$key_name] = array($revision_key);
$schema['foreign keys'][$entity_type_id . '__revision'] = array(
'table' => $this->storage->getRevisionTable(),
'columns' => array($revision_key => $revision_key),
);
}
return $schema;
}
/**
* Initializes common information for a revision table.
*
* @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');
$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(
'table' => $this->storage->getBaseTable(),
'columns' => array($id_key => $id_key),
),
),
);
$schema['indexes'][$this->getEntityIndexName($id_key)] = array($id_key);
return $schema;
}
/**
* Initializes common information for a data table.
*
* @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');
$schema = array(
'description' => "The data table for $entity_type_id entities.",
// @todo Use the language entity key when https://drupal.org/node/2143729
// is in.
'primary key' => array($id_key, 'langcode'),
'indexes' => array(),
'foreign keys' => array(
$entity_type_id => array(
'table' => $this->storage->getBaseTable(),
'columns' => array($id_key => $id_key),
),
),
);
if ($this->entityType->hasKey('revision')) {
$key = $this->entityType->getKey('revision');
$schema['indexes'][$this->getEntityIndexName($key)] = array($key);
}
return $schema;
}
/**
* Initializes common information for a revision data table.
*
* @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');
$schema = array(
'description' => "The revision data table for $entity_type_id entities.",
// @todo Use the language entity key when https://drupal.org/node/2143729
// is in.
'primary key' => array($revision_key, 'langcode'),
'indexes' => array(),
'foreign keys' => array(
$entity_type_id => array(
'table' => $this->storage->getBaseTable(),
'columns' => array($id_key => $id_key),
),
$entity_type_id . '__revision' => array(
'table' => $this->storage->getRevisionTable(),
'columns' => array($revision_key => $revision_key),
)
),