Commit ff161590 authored by alexpott's avatar alexpott

Issue #2928906 by tstoeckler, hchonov, gnunes, gease, amateescu, daffie,...

Issue #2928906 by tstoeckler, hchonov, gnunes, gease, amateescu, daffie, alexpott, catch, claudiu.cristea: The field schema incorrectly stores serial fields as int
parent 38fdab6b
......@@ -1108,9 +1108,6 @@ protected function mapToStorageRecord(ContentEntityInterface $entity, $table_nam
*
* @return bool
* TRUE if the column is serial, FALSE otherwise.
*
* @see \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::processBaseTable()
* @see \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::processRevisionTable()
*/
protected function isColumnSerial($table_name, $schema_name) {
$result = FALSE;
......
......@@ -550,6 +550,12 @@ protected function postUpdateEntityTypeSchema(EntityTypeInterface $entity_type,
$this->fieldStorageDefinitions = $field_storage_definitions;
$this->saveEntitySchemaData($entity_type, $new_entity_schema);
// The storage needs to use the updated definitions and table mapping
// before generating and saving the final field schema data.
$this->storage->setEntityType($entity_type);
$this->storage->setFieldStorageDefinitions($field_storage_definitions);
$this->storage->setTableMapping($new_table_mapping);
// Store the updated field schema for each field storage.
foreach ($field_storage_definitions as $field_storage_definition) {
if ($new_table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
......@@ -918,10 +924,6 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
}
// Process tables after having gathered field information.
$this->processBaseTable($entity_type, $schema[$tables['base_table']]);
if (isset($tables['revision_table'])) {
$this->processRevisionTable($entity_type, $schema[$tables['revision_table']]);
}
if (isset($tables['data_table'])) {
$this->processDataTable($entity_type, $schema[$tables['data_table']]);
}
......@@ -1390,36 +1392,6 @@ protected function addTableDefaults(&$schema) {
];
}
/**
* Processes the gathered schema for a base table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
* @param array $schema
* The table schema, passed by reference.
*/
protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) {
// Process the schema for the 'id' entity key only if it exists.
if ($entity_type->hasKey('id')) {
$this->processIdentifierSchema($schema, $entity_type->getKey('id'));
}
}
/**
* Processes the gathered schema for a base table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
* @param array $schema
* The table schema, passed by reference.
*/
protected function processRevisionTable(ContentEntityTypeInterface $entity_type, array &$schema) {
// Process the schema for the 'revision' entity key only if it exists.
if ($entity_type->hasKey('revision')) {
$this->processIdentifierSchema($schema, $entity_type->getKey('revision'));
}
}
/**
* Processes the gathered schema for a base table.
*
......@@ -2036,6 +2008,7 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
$field_name = $storage_definition->getName();
$base_table = $this->storage->getBaseTable();
$revision_table = $this->storage->getRevisionTable();
// Define the initial values, if any.
$initial_value = $initial_value_from_field = [];
......@@ -2114,6 +2087,13 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
$schema['foreign keys'] = $this->getFieldForeignKeys($field_name, $field_schema, $column_mapping);
}
// Process the 'id' and 'revision' entity keys for the base and revision
// tables.
if (($table_name === $base_table && $field_name === $this->entityType->getKey('id')) ||
($table_name === $revision_table && $field_name === $this->entityType->getKey('revision'))) {
$this->processIdentifierSchema($schema, $field_name);
}
return $schema;
}
......
......@@ -12,6 +12,8 @@
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PrivateStream;
......@@ -1206,6 +1208,62 @@ function system_update_last_removed() {
return 8805;
}
/**
* Update the stored schema data for entity identifier fields.
*/
function system_update_8901() {
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
$entity_type_manager = \Drupal::entityTypeManager();
$installed_storage_schema = \Drupal::keyValue('entity.storage_schema.sql');
foreach ($definition_update_manager->getEntityTypes() as $entity_type_id => $entity_type) {
// Ensure that we are dealing with a non-deleted entity type that uses the
// default SQL storage.
if (!$entity_type_manager->hasDefinition($entity_type_id)) {
continue;
}
$storage = $entity_type_manager->getStorage($entity_type_id);
if (!$storage instanceof SqlContentEntityStorage) {
continue;
}
foreach (['id', 'revision'] as $key) {
if (!$entity_type->hasKey($key)) {
continue;
}
$field_name = $entity_type->getKey($key);
$field_storage_definition = $definition_update_manager->getFieldStorageDefinition($field_name, $entity_type_id);
if (!$field_storage_definition) {
continue;
}
if ($field_storage_definition->getType() !== 'integer') {
continue;
}
// Retrieve the storage schema in order to use its own method for updating
// the identifier schema - ::processIdentifierSchema(). This is needed
// because some storage schemas might not use serial identifiers.
// @see \Drupal\user\UserStorageSchema::processIdentifierSchema()
$ref_get_storage_schema = new \ReflectionMethod($storage, 'getStorageSchema');
$ref_get_storage_schema->setAccessible(TRUE);
$storage_schema = $ref_get_storage_schema->invoke($storage);
if ($storage_schema instanceof SqlContentEntityStorageSchema) {
$field_schema_data = $installed_storage_schema->get($entity_type_id . '.field_schema_data.' . $field_storage_definition->getName(), []);
$table = $key === 'id' ? $entity_type->getBaseTable() : $entity_type->getRevisionTable();
$ref_process_identifier_schema = new \ReflectionMethod($storage_schema, 'processIdentifierSchema');
$ref_process_identifier_schema->setAccessible(TRUE);
$ref_process_identifier_schema->invokeArgs($storage_schema, [&$field_schema_data[$table], $field_name]);
$installed_storage_schema->set($entity_type_id . '.field_schema_data.' . $field_storage_definition->getName(), $field_schema_data);
}
}
}
}
/**
* Ensures that Drupal is updated from a supported version.
*/
......
<?php
namespace Drupal\Tests\system\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests the upgrade path for updating the stored type of identifier fields.
*
* @see https://www.drupal.org/node/2928906
*
* @group Update
* @group legacy
*/
class IdentifierFieldSchemaTypeUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../fixtures/update/drupal-8.8.0.bare.standard.php.gz',
];
}
/**
* Tests system_update_8901().
*/
public function testSystemUpdate8901() {
// The installed field storage schema is wrong before running the update.
$key_value_store = \Drupal::keyValue('entity.storage_schema.sql');
$id_schema = $key_value_store->get('node.field_schema_data.nid', []);
$revision_id_schema = $key_value_store->get('node.field_schema_data.vid', []);
$this->assertEquals('int', $id_schema['node']['fields']['nid']['type']);
$this->assertEquals('int', $id_schema['node_revision']['fields']['nid']['type']);
$this->assertEquals('int', $revision_id_schema['node']['fields']['vid']['type']);
$this->assertEquals('int', $revision_id_schema['node_revision']['fields']['vid']['type']);
$this->runUpdates();
// Now check that the schema has been corrected.
$id_schema = $key_value_store->get('node.field_schema_data.nid', []);
$revision_id_schema = $key_value_store->get('node.field_schema_data.vid', []);
$this->assertEquals('serial', $id_schema['node']['fields']['nid']['type']);
$this->assertEquals('int', $id_schema['node_revision']['fields']['nid']['type']);
$this->assertEquals('int', $revision_id_schema['node']['fields']['vid']['type']);
$this->assertEquals('serial', $revision_id_schema['node_revision']['fields']['vid']['type']);
// Check that creating and loading a node still works as expected.
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$node = $node_storage->create([
'title' => 'Test update',
'type' => 'article',
]);
$node->save();
$node = $node_storage->load($node->id());
$this->assertEquals('Test update', $node->label());
}
}
......@@ -328,7 +328,7 @@ public function testCleanUpStorageDefinition() {
if ($definition->getProvider() == 'entity_test') {
$this->installEntitySchema($entity_type_id);
$entity_type_ids[] = $entity_type_id;
};
}
}
// Get a list of all the entities in the schema.
......@@ -371,4 +371,63 @@ public function testCleanUpStorageDefinition() {
$this->assertEqual($entity_type_id_count, 0, 'After uninstalling entity_test module the schema should not contains fields from entities provided by the module.');
}
/**
* Tests the installed storage schema for identifier fields.
*/
public function testIdentifierSchema() {
$this->installEntitySchema('entity_test_rev');
$key_value_store = \Drupal::keyValue('entity.storage_schema.sql');
$id_schema = $key_value_store->get('entity_test_rev.field_schema_data.id', []);
$revision_id_schema = $key_value_store->get('entity_test_rev.field_schema_data.revision_id', []);
$expected_id_schema = [
'entity_test_rev' => [
'fields' => [
'id' => [
'type' => 'serial',
'unsigned' => TRUE,
'size' => 'normal',
'not null' => TRUE,
],
],
],
'entity_test_rev_revision' => [
'fields' => [
'id' => [
'type' => 'int',
'unsigned' => TRUE,
'size' => 'normal',
'not null' => TRUE,
],
],
],
];
$this->assertEquals($expected_id_schema, $id_schema);
$expected_revision_id_schema = [
'entity_test_rev' => [
'fields' => [
'revision_id' => [
'type' => 'int',
'unsigned' => TRUE,
'size' => 'normal',
'not null' => FALSE,
],
],
],
'entity_test_rev_revision' => [
'fields' => [
'revision_id' => [
'type' => 'serial',
'unsigned' => TRUE,
'size' => 'normal',
'not null' => TRUE,
],
],
],
];
$this->assertEquals($expected_revision_id_schema, $revision_id_schema);
}
}
......@@ -111,7 +111,6 @@ protected function setUp() {
* @covers ::getFieldUniqueKeys
* @covers ::getFieldForeignKeys
* @covers ::getFieldSchemaData
* @covers ::processBaseTable
* @covers ::processIdentifierSchema
*/
public function testGetSchemaBase() {
......@@ -405,7 +404,6 @@ public function testGetSchemaBase() {
* @covers ::initializeRevisionTable
* @covers ::addTableDefaults
* @covers ::getEntityIndexName
* @covers ::processRevisionTable
* @covers ::processIdentifierSchema
*/
public function testGetSchemaRevisionable() {
......@@ -426,7 +424,7 @@ public function testGetSchemaRevisionable() {
->method('getRevisionMetadataKeys')
->will($this->returnValue([]));
$this->storage->expects($this->exactly(1))
$this->storage->expects($this->exactly(9))
->method('getRevisionTable')
->will($this->returnValue('entity_test_revision'));
......@@ -647,7 +645,7 @@ public function testGetSchemaRevisionableTranslatable() {
->method('isTranslatable')
->will($this->returnValue(TRUE));
$this->storage->expects($this->exactly(2))
$this->storage->expects($this->exactly(30))
->method('getRevisionTable')
->will($this->returnValue('entity_test_revision'));
......
......@@ -384,7 +384,6 @@ public function testOnEntityTypeCreate() {
->will($this->returnValueMap([
// SqlContentEntityStorageSchema::initializeBaseTable()
['revision', FALSE],
// SqlContentEntityStorageSchema::processBaseTable()
['id', TRUE],
]));
$this->entityType->expects($this->any())
......@@ -397,7 +396,6 @@ public function testOnEntityTypeCreate() {
['bundle', NULL],
// SqlContentEntityStorageSchema::initializeBaseTable()
['id' => 'id'],
// SqlContentEntityStorageSchema::processBaseTable()
['id' => 'id'],
]));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment