Commit bdba2942 authored by plach's avatar plach

Issue #2923567 by Sam152, amateescu: Select from correct entity tables for...

Issue #2923567 by Sam152, amateescu: Select from correct entity tables for additional entity/field definitions when moving data to dedicated table storage from shared table storage for the purposes of purging
parent e4c874e1
......@@ -469,6 +469,7 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
// Retrieve a table mapping which contains the deleted field still.
$storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id());
$table_mapping = $this->storage->getTableMapping($storage_definitions);
$field_table_name = $table_mapping->getFieldTableName($storage_definition->getName());
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
// Move the table to a unique name while the table contents are being
......@@ -519,17 +520,20 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
}
try {
// Copy the data from the base table.
$is_translatable = $this->entityType->isTranslatable() && $storage_definition->isTranslatable();
$base_table = $is_translatable ? $this->storage->getDataTable() : $this->storage->getBaseTable();
$this->database->insert($dedicated_table_name)
->from($this->getSelectQueryForFieldStorageDeletion($base_table, $shared_table_field_columns, $dedicated_table_field_columns))
->from($this->getSelectQueryForFieldStorageDeletion($field_table_name, $shared_table_field_columns, $dedicated_table_field_columns))
->execute();
// Copy the data from the revision table.
if (isset($dedicated_revision_table_name)) {
$revision_table = $is_translatable ? $this->storage->getRevisionDataTable() : $this->storage->getRevisionTable();
if ($this->entityType->isTranslatable()) {
$revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionDataTable() : $this->storage->getDataTable();
}
else {
$revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionTable() : $this->storage->getBaseTable();
}
$this->database->insert($dedicated_revision_table_name)
->from($this->getSelectQueryForFieldStorageDeletion($revision_table, $shared_table_field_columns, $dedicated_table_field_columns, $base_table))
->from($this->getSelectQueryForFieldStorageDeletion($revision_table, $shared_table_field_columns, $dedicated_table_field_columns, $field_table_name))
->execute();
}
}
......@@ -622,7 +626,7 @@ protected function getSelectQueryForFieldStorageDeletion($table_name, array $sha
$or = $select->orConditionGroup();
foreach ($shared_table_field_columns as $field_column_name => $schema_column_name) {
$select->addField('entity_table', $schema_column_name, $dedicated_table_field_columns[$field_column_name]);
$or->isNotNull($schema_column_name);
$or->isNotNull('entity_table.' . $schema_column_name);
}
$select->condition($or);
......
......@@ -111,12 +111,19 @@ protected function updateEntityTypeToRevisionableAndTranslatable() {
*
* @param string $type
* (optional) The field type for the new field. Defaults to 'string'.
* @param string $entity_type_id
* (optional) The entity type ID the base field should be attached to.
* Defaults to 'entity_test_update'.
* @param bool $is_revisionable
* (optional) If the base field should be revisionable or not. Defaults to
* FALSE.
*/
protected function addBaseField($type = 'string') {
protected function addBaseField($type = 'string', $entity_type_id = 'entity_test_update', $is_revisionable = FALSE) {
$definitions['new_base_field'] = BaseFieldDefinition::create($type)
->setName('new_base_field')
->setRevisionable($is_revisionable)
->setLabel(t('A new base field'));
$this->state->set('entity_test_update.additional_base_field_definitions', $definitions);
$this->state->set($entity_type_id . '.additional_base_field_definitions', $definitions);
}
/**
......@@ -167,9 +174,12 @@ protected function makeBaseFieldEntityKey() {
/**
* Removes the new base field from the 'entity_test_update' entity type.
*
* @param string $entity_type_id
* (optional) The entity type ID the base field should be attached to.
*/
protected function removeBaseField() {
$this->state->delete('entity_test_update.additional_base_field_definitions');
protected function removeBaseField($entity_type_id = 'entity_test_update') {
$this->state->delete($entity_type_id . '.additional_base_field_definitions');
}
/**
......
......@@ -2,6 +2,8 @@
namespace Drupal\entity_test\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Defines the test entity class.
*
......@@ -44,4 +46,11 @@
*/
class EntityTestMul extends EntityTest {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
return parent::baseFieldDefinitions($entity_type) + \Drupal::state()->get($entity_type->id() . '.additional_base_field_definitions', []);
}
}
......@@ -2,6 +2,8 @@
namespace Drupal\entity_test\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Defines the test entity class.
*
......@@ -47,4 +49,11 @@
*/
class EntityTestMulRev extends EntityTestRev {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
return parent::baseFieldDefinitions($entity_type) + \Drupal::state()->get($entity_type->id() . '.additional_base_field_definitions', []);
}
}
......@@ -65,7 +65,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setCardinality(1)
->setReadOnly(TRUE);
return $fields;
return $fields + \Drupal::state()->get($entity_type->id() . '.additional_base_field_definitions', []);
}
}
......@@ -18,6 +18,7 @@
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;
use Drupal\Core\Field\Plugin\Field\FieldType\UuidItem;
use Drupal\Core\State\StateInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\text\Plugin\Field\FieldType\TextLongItem;
use Drupal\entity_test\Entity\EntityTest;
......@@ -25,6 +26,7 @@
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\Tests\UnitTestCase;
use Drupal\views\EntityViewsData;
use Prophecy\Argument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
......@@ -125,10 +127,14 @@ protected function setUp() {
->method('getDefaultFieldSettings')
->willReturn([]);
$state = $this->prophesize(StateInterface::class);
$state->get(Argument::any(), [])->willReturn([]);
$container = new ContainerBuilder();
$container->set('plugin.manager.field.field_type', $field_type_manager);
$container->set('entity.manager', $this->entityManager);
$container->set('typed_data_manager', $typed_data_manager);
$container->set('state', $state->reveal());
\Drupal::setContainer($container);
}
......
......@@ -389,10 +389,12 @@ public function testBundleFieldCreateDeleteWithExistingEntities() {
/**
* Tests deleting a base field when it has existing data.
*
* @dataProvider baseFieldDeleteWithExistingDataTestCases
*/
public function testBaseFieldDeleteWithExistingData() {
public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_entity_revision, $base_field_revisionable) {
/** @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage */
$storage = $this->entityManager->getStorage('entity_test_update');
$storage = $this->entityManager->getStorage($entity_type_id);
$schema_handler = $this->database->schema();
// Create an entity without the base field, to ensure NULL values are not
......@@ -401,23 +403,29 @@ public function testBaseFieldDeleteWithExistingData() {
$entity->save();
// Add the base field and run the update.
$this->addBaseField();
$this->addBaseField('string', $entity_type_id, $base_field_revisionable);
$this->entityDefinitionUpdateManager->applyUpdates();
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $storage->getTableMapping();
$storage_definition = $this->entityManager->getLastInstalledFieldStorageDefinitions('entity_test_update')['new_base_field'];
$storage_definition = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id)['new_base_field'];
// Save an entity with the base field populated.
$entity = $storage->create(['new_base_field' => 'foo']);
$entity->save();
if ($create_entity_revision) {
$entity->setNewRevision(TRUE);
$entity->new_base_field = 'bar';
$entity->save();
}
// Remove the base field and apply updates.
$this->removeBaseField();
$this->removeBaseField($entity_type_id);
$this->entityDefinitionUpdateManager->applyUpdates();
// Check that the base field's column is deleted.
$this->assertFalse($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.');
$this->assertFalse($schema_handler->fieldExists($entity_type_id, 'new_base_field'), 'Column deleted from shared table for new_base_field.');
// Check that a dedicated 'deleted' table was created for the deleted base
// field.
......@@ -436,7 +444,7 @@ public function testBaseFieldDeleteWithExistingData() {
'bundle' => $entity->bundle(),
'deleted' => '1',
'entity_id' => $entity->id(),
'revision_id' => $entity->id(),
'revision_id' => $create_entity_revision ? $entity->getRevisionId() : $entity->id(),
'langcode' => $entity->language()->getId(),
'delta' => '0',
'new_base_field_value' => $entity->new_base_field->value,
......@@ -445,6 +453,42 @@ public function testBaseFieldDeleteWithExistingData() {
// sequence of the columns in the table will affect the check.
$this->assertEquals($expected, (array) $result[0]);
if ($create_entity_revision) {
$dedicated_deleted_revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
$this->assertTrue($schema_handler->tableExists($dedicated_deleted_revision_table_name), 'A dedicated revision table was created for the deleted new_base_field.');
$result = $this->database->select($dedicated_deleted_revision_table_name, 't')
->fields('t')
->orderBy('revision_id', 'DESC')
->execute()
->fetchAll();
// Only one row will be created for non-revisionable base fields.
$this->assertCount($base_field_revisionable ? 2 : 1, $result);
$this->assertSame([
'bundle' => $entity->bundle(),
'deleted' => '1',
'entity_id' => $entity->id(),
'revision_id' => '3',
'langcode' => $entity->language()->getId(),
'delta' => '0',
'new_base_field_value' => 'bar',
], (array) $result[0]);
// Two rows only exist if the base field is revisionable.
if ($base_field_revisionable) {
$this->assertSame([
'bundle' => $entity->bundle(),
'deleted' => '1',
'entity_id' => $entity->id(),
'revision_id' => '2',
'langcode' => $entity->language()->getId(),
'delta' => '0',
'new_base_field_value' => 'foo',
], (array) $result[1]);
}
}
// Check that the field storage definition is marked for purging.
$deleted_storage_definitions = \Drupal::service('entity_field.deleted_fields_repository')->getFieldStorageDefinitions();
$this->assertArrayHasKey($storage_definition->getUniqueStorageIdentifier(), $deleted_storage_definitions, 'The base field is marked for purging.');
......@@ -454,7 +498,59 @@ public function testBaseFieldDeleteWithExistingData() {
field_purge_batch(10);
$deleted_storage_definitions = \Drupal::service('entity_field.deleted_fields_repository')->getFieldStorageDefinitions();
$this->assertEmpty($deleted_storage_definitions, 'The base field has been deleted.');
$this->assertFalse($schema_handler->tableExists($dedicated_deleted_table_name), 'A dedicated table was created for the deleted new_base_field.');
$this->assertFalse($schema_handler->tableExists($dedicated_deleted_table_name), 'A dedicated field table was deleted after new_base_field was purged.');
if (isset($dedicated_deleted_revision_table_name)) {
$this->assertFalse($schema_handler->tableExists($dedicated_deleted_revision_table_name), 'A dedicated field revision table was deleted after new_base_field was purged.');
}
}
/**
* Test cases for ::testBaseFieldDeleteWithExistingData.
*/
public function baseFieldDeleteWithExistingDataTestCases() {
return [
'Non-revisionable entity type' => [
'entity_test_update',
FALSE,
FALSE,
],
'Non-revisionable custom data table' => [
'entity_test_mul',
FALSE,
FALSE,
],
'Non-revisionable entity type, revisionable base field' => [
'entity_test_update',
FALSE,
TRUE,
],
'Non-revisionable custom data table, revisionable base field' => [
'entity_test_mul',
FALSE,
TRUE,
],
'Revisionable entity type, non revisionable base field' => [
'entity_test_mulrev',
TRUE,
FALSE,
],
'Revisionable entity type, revisionable base field' => [
'entity_test_mulrev',
TRUE,
TRUE,
],
'Non-translatable revisionable entity type, revisionable base field' => [
'entity_test_rev',
TRUE,
TRUE,
],
'Non-translatable revisionable entity type, non-revisionable base field' => [
'entity_test_rev',
TRUE,
FALSE,
],
];
}
/**
......
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