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) {
* 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\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 @@
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -796,6 +797,22 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
}
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.
$modules_installed[] = $module;
......@@ -888,6 +905,22 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
// Remove all configuration belonging to the module.
\Drupal::service('config.manager')->uninstall('module', $module);
// Remove 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->dropTable($table_name);
}
}
}
}
}
// Remove the schema.
drupal_uninstall_schema($module);
......
......@@ -7,6 +7,7 @@
namespace Drupal\Core\Field\Plugin\Field\FieldType;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldItemBase;
......@@ -114,7 +115,9 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
'target_id' => array(
'description' => 'The ID of the target entity.',
'type' => 'varchar',
'length' => '255',
// If the target entities act as bundles for another entity type,
// their IDs should not exceed the maximum length for bundles.
'length' => $target_type_info->getBundleOf() ? EntityTypeInterface::BUNDLE_MAX_LENGTH : 255,
),
);
}
......
......@@ -41,6 +41,9 @@ public static function defaultInstanceSettings() {
'max' => '',
'prefix' => '',
'suffix' => '',
// Valid size property values include: 'tiny', 'small', 'medium', 'normal'
// and 'big'.
'size' => 'normal',
) + parent::defaultInstanceSettings();
}
......@@ -91,6 +94,9 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
'not null' => FALSE,
// Expose the 'unsigned' setting in the field item schema.
'unsigned' => $field_definition->getSetting('unsigned'),
// Expose the 'size' setting in the field item schema. For instance,
// supply 'big' as a value to produce a 'bigint' type.
'size' => $field_definition->getSetting('size'),
),
),
);
......
......@@ -52,7 +52,8 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
return array(
'columns' => array(
'value' => array(
'type' => 'text',
'type' => 'varchar',
'length' => (int) $field_definition->getSetting('max_length'),
'not null' => TRUE,
),
),
......
......@@ -22,179 +22,3 @@ function aggregator_requirements($phase) {
}
return $requirements;
}
/**
* Implements hook_schema().
*/
function aggregator_schema() {
$schema['aggregator_feed'] = array(
'description' => 'Stores feeds to be parsed by the aggregator.',
'fields' => array(
'fid' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique feed ID.',
),
'uuid' => array(
'description' => 'Unique Key: Universally unique identifier for this entity.',
'type' => 'varchar',
'length' => 128,
'not null' => FALSE,
),
'title' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Title of the feed.',
),
'langcode' => array(
'description' => 'The {language}.langcode of this feed.',
'type' => 'varchar',
'length' => 12,
'not null' => TRUE,
'default' => '',
),
'url' => array(
'type' => 'text',
'not null' => TRUE,
'description' => 'URL to the feed.',
),
'refresh' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'How often to check for new feed items, in seconds.',
),
'checked' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Last time feed was checked for new items, as Unix timestamp.',
),
'queued' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Time when this feed was queued for refresh, 0 if not queued.',
),
'link' => array(
'type' => 'text',
'not null' => TRUE,
'description' => 'The parent website of the feed; comes from the <link> element in the feed.',
),
'description' => array(
'type' => 'text',
'not null' => TRUE,
'size' => 'big',
'description' => "The parent website's description; comes from the <description> element in the feed.",
),
'image' => array(
'type' => 'text',
'not null' => TRUE,
'size' => 'big',
'description' => 'An image representing the feed.',
),
'hash' => array(