Commit 1ab26517 authored by alexpott's avatar alexpott

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) { ...@@ -555,14 +555,18 @@ protected static function invalidateTagsOnDelete(array $entities) {
* Acts on entities of which this entity is a bundle entity type. * Acts on entities of which this entity is a bundle entity type.
*/ */
protected function onUpdateBundleEntity() { 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(); $bundle_of = $this->getEntityType()->getBundleOf();
$entity_manager = \Drupal::entityManager(); if ($bundle_of !== FALSE) {
if ($bundle_of !== FALSE && $entity_manager->hasController($bundle_of, 'view_builder')) { // If this entity is a bundle entity type of another entity type, and we're
$entity_manager->getViewBuilder($bundle_of)->resetCache(); // 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) { ...@@ -76,6 +76,7 @@ public function __construct(EntityTypeInterface $entity_type) {
$this->entityTypeId = $entity_type->id(); $this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type; $this->entityType = $entity_type;
$this->idKey = $this->entityType->getKey('id'); $this->idKey = $this->entityType->getKey('id');
$this->uuidKey = $this->entityType->getKey('uuid');
$this->entityClass = $this->entityType->getClass(); $this->entityClass = $this->entityType->getClass();
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityDatabaseStorage; use Drupal\Core\Entity\ContentEntityDatabaseStorage;
use Drupal\Core\Entity\Query\QueryException; use Drupal\Core\Entity\Query\QueryException;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldConfig;
use Drupal\field\FieldConfigInterface; use Drupal\field\FieldConfigInterface;
...@@ -35,7 +36,6 @@ class Tables implements TablesInterface { ...@@ -35,7 +36,6 @@ class Tables implements TablesInterface {
*/ */
protected $entityTables = array(); protected $entityTables = array();
/** /**
* Field table array, key is table name, value is alias. * Field table array, key is table name, value is alias.
* *
...@@ -45,11 +45,19 @@ class Tables implements TablesInterface { ...@@ -45,11 +45,19 @@ class Tables implements TablesInterface {
*/ */
protected $fieldTables = array(); protected $fieldTables = array();
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManager
*/
protected $entityManager;
/** /**
* @param \Drupal\Core\Database\Query\SelectInterface $sql_query * @param \Drupal\Core\Database\Query\SelectInterface $sql_query
*/ */
public function __construct(SelectInterface $sql_query) { public function __construct(SelectInterface $sql_query) {
$this->sqlQuery = $sql_query; $this->sqlQuery = $sql_query;
$this->entityManager = \Drupal::entityManager();
} }
/** /**
...@@ -57,7 +65,6 @@ public function __construct(SelectInterface $sql_query) { ...@@ -57,7 +65,6 @@ public function __construct(SelectInterface $sql_query) {
*/ */
public function addField($field, $type, $langcode) { public function addField($field, $type, $langcode) {
$entity_type_id = $this->sqlQuery->getMetaData('entity_type'); $entity_type_id = $this->sqlQuery->getMetaData('entity_type');
$entity_manager = \Drupal::entityManager();
$age = $this->sqlQuery->getMetaData('age'); $age = $this->sqlQuery->getMetaData('age');
// This variable ensures grouping works correctly. For example: // This variable ensures grouping works correctly. For example:
// ->condition('tags', 2, '>') // ->condition('tags', 2, '>')
...@@ -73,13 +80,13 @@ public function addField($field, $type, $langcode) { ...@@ -73,13 +80,13 @@ public function addField($field, $type, $langcode) {
// This will contain the definitions of the last specifier seen by the // This will contain the definitions of the last specifier seen by the
// system. // system.
$propertyDefinitions = array(); $propertyDefinitions = array();
$entity_type = $entity_manager->getDefinition($entity_type_id); $entity_type = $this->entityManager->getDefinition($entity_type_id);
$field_storage_definitions = array(); $field_storage_definitions = array();
// @todo Needed for menu links, make this implementation content entity // @todo Needed for menu links, make this implementation content entity
// specific after https://drupal.org/node/2256521. // specific after https://drupal.org/node/2256521.
if ($entity_type instanceof ContentEntityTypeInterface) { 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 ++) { for ($key = 0; $key <= $count; $key ++) {
// If there is revision support and only the current revision is being // If there is revision support and only the current revision is being
...@@ -154,10 +161,10 @@ public function addField($field, $type, $langcode) { ...@@ -154,10 +161,10 @@ public function addField($field, $type, $langcode) {
$entity_tables = array(); $entity_tables = array();
if ($data_table = $entity_type->getDataTable()) { if ($data_table = $entity_type->getDataTable()) {
$this->sqlQuery->addMetaData('simple_query', FALSE); $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_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; $sql_column = $specifier;
$table = $this->ensureEntityTable($index_prefix, $specifier, $type, $langcode, $base_table, $entity_id_field, $entity_tables); $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) { ...@@ -174,8 +181,8 @@ public function addField($field, $type, $langcode) {
if (isset($propertyDefinitions[$relationship_specifier]) && $field->getPropertyDefinition('entity')->getDataType() == 'entity_reference' ) { if (isset($propertyDefinitions[$relationship_specifier]) && $field->getPropertyDefinition('entity')->getDataType() == 'entity_reference' ) {
// If it is, use the entity type. // If it is, use the entity type.
$entity_type_id = $propertyDefinitions[$relationship_specifier]->getTargetDefinition()->getEntityTypeId(); $entity_type_id = $propertyDefinitions[$relationship_specifier]->getTargetDefinition()->getEntityTypeId();
$entity_type = $entity_manager->getDefinition($entity_type_id); $entity_type = $this->entityManager->getDefinition($entity_type_id);
$field_storage_definitions = $entity_manager->getFieldStorageDefinitions($entity_type_id); $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
// Add the new entity base table using the table and sql column. // Add the new entity base table using the table and sql column.
$join_condition= '%alias.' . $entity_type->getKey('id') . " = $table.$sql_column"; $join_condition= '%alias.' . $entity_type->getKey('id') . " = $table.$sql_column";
$base_table = $this->sqlQuery->leftJoin($entity_type->getBaseTable(), NULL, $join_condition); $base_table = $this->sqlQuery->leftJoin($entity_type->getBaseTable(), NULL, $join_condition);
...@@ -199,8 +206,8 @@ public function addField($field, $type, $langcode) { ...@@ -199,8 +206,8 @@ public function addField($field, $type, $langcode) {
* @throws \Drupal\Core\Entity\Query\QueryException * @throws \Drupal\Core\Entity\Query\QueryException
*/ */
protected function ensureEntityTable($index_prefix, $property, $type, $langcode, $base_table, $id_field, $entity_tables) { protected function ensureEntityTable($index_prefix, $property, $type, $langcode, $base_table, $id_field, $entity_tables) {
foreach ($entity_tables as $table => $schema) { foreach ($entity_tables as $table => $mapping) {
if (isset($schema['fields'][$property])) { if (isset($mapping[$property])) {
if (!isset($this->entityTables[$index_prefix . $table])) { if (!isset($this->entityTables[$index_prefix . $table])) {
$this->entityTables[$index_prefix . $table] = $this->addJoin($type, $table, "%alias.$id_field = $base_table.$id_field", $langcode); $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) { ...@@ -241,4 +248,27 @@ protected function addJoin($type, $table, $join_condition, $langcode) {
return $this->sqlQuery->addJoin($type, $table, NULL, $join_condition, $arguments); 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\EntitySchemaHandlerInterface.
*/
namespace Drupal\Core\Entity\Schema;
/**
* Defines an interface for handling the storage schema of entities.
*/
interface EntitySchemaHandlerInterface extends EntitySchemaProviderInterface {
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Schema\EntitySchemaProviderInterface.
*/
namespace Drupal\Core\Entity\Schema;
/**
* Defines a common interface to return the storage schema for entities.
*/
interface EntitySchemaProviderInterface {
/**
* Gets the full schema array for a given entity type.
*
* @return array
* A schema array for the entity type's tables.
*/
public function getSchema();
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Sql\DefaultTableMapping.
*/
namespace Drupal\Core\Entity\Sql;
/**
* Defines a default table mapping class.
*/
class DefaultTableMapping implements TableMappingInterface {
/**
* A list of field storage definitions that are available for this mapping.
*
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
*/
protected $fieldStorageDefinitions = array();
/**
* 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 = array();
/**
* 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 = array();
/**
* 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 = array();
/**
* 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 = array();
/**
* Constructs a DefaultTableMapping.
*
* @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(array $storage_definitions) {
$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] = array();
foreach ($this->getFieldNames($table_name) as $field_name) {
$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));
}
return $this->allColumns[$table_name];
}
/**
* {@inheritdoc}
*/
public function getFieldNames($table_name) {
if (isset($this->fieldNames[$table_name])) {
return $this->fieldNames[$table_name];
}
return array();
}
/**
* {@inheritdoc}
*/
public function getColumnNames($field_name) {
if (!isset($this->columnMapping[$field_name])) {
$column_names = array_keys($this->fieldStorageDefinitions[$field_name]->getColumns());
if (count($column_names) == 1) {
$this->columnMapping[$field_name] = array(reset($column_names) => $field_name);
}
else {
$this->columnMapping[$field_name] = array();
foreach ($column_names as $column_name) {
$this->columnMapping[$field_name][$column_name] = $field_name . '__' . $column_name;
}
}
}
return $this->columnMapping[$field_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 array();
}
/**
* 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;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Sql\SqlEntityStorageInterface.
*/
namespace Drupal\Core\Entity\Sql;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface;
/**
* A common interface for SQL-based storage controllers.
*/
interface SqlEntityStorageInterface extends EntityStorageInterface, EntitySchemaProviderInterface {
/**
* Gets a table mapping for the entity's SQL tables.
*
* @return \Drupal\Core\Entity\Sql\TableMappingInterface
* A table mapping object for the entity's tables.
*/
public function getTableMapping();
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Sql\TableMappingInterface.
*/
namespace Drupal\Core\Entity\Sql;
/**
* Provides a common interface for mapping field columns to SQL tables.
*/
interface TableMappingInterface {
/**
* Returns a list of table names for this mapping.
*
* @return string[]
* An array of table names.
*/
public function getTableNames();
/**
* Returns a list of all database columns for a given table.
*
* @param string $table_name
* The name of the table to return the columns for.
*
* @return string[]
* An array of database column names for this table. Both field columns and
* extra columns are returned.
*/
public function getAllColumns($table_name);
/**
* Returns a list of names of fields stored in the specified table.
*
* @param string $table_name
* The name of the table to return the field names for.
*
* @return string[]
* An array of field names for the given table.
*/
public function getFieldNames($table_name);
/**
* Returns a mapping of field columns to database columns for a given field.
*
* @param string $field_name
* The name of the entity field to return the column mapping for.
*
* @return string[]
* The keys of this array are the keys of the array returned by
* FieldStorageDefinitionInterface::getColumns() while the respective values
* are the names of the database columns for this table mapping.
*/
public function getColumnNames($field_name);
/**
* Returns a list of extra database columns, which store denormalized data.
*
* These database columns do not belong to any entity fields. Any normalized
* data that is stored should be associated with an entity field.
*
* @param string $table_name
* The name of the table to return the columns for.
*
* @return string[]
* An array of column names for the given table.
*/
public function getExtraColumns($table_name);
}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
use Drupal\Component\Serialization\Yaml; use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
...@@ -796,6 +797,22 @@ public function install(array $module_list, $enable_dependencies = TRUE) { ...@@ -796,6 +797,22 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
} }
drupal_set_installed_schema_version($module, $version); drupal_set_installed_schema_version($module, $version);
// Install any entity schemas belonging to the module.
$entity_manager = \Drupal::entityManager();
$schema = \Drupal::database()->schema();
foreach ($entity_manager->getDefinitions() as $entity_type) {
if ($entity_type->getProvider() == $module) {
$storage = $entity_manager->getStorage($entity_type->id());
if ($storage instanceof EntitySchemaProviderInterface) {
foreach ($storage->getSchema() as $table_name => $table_schema) {
if (!$schema->tableExists($table_name)) {
$schema->createTable($table_name, $table_schema);
}
}
}
}
}
// Record the fact that it was installed.