Unverified Commit 31f278d2 authored by alexpott's avatar alexpott

Issue #3039991 by amateescu, lpeabody, bgreco, plach: Base field purging is...

Issue #3039991 by amateescu, lpeabody, bgreco, plach: Base field purging is not handling translatable fields correctly

(cherry picked from commit d97a2dc0)
parent 4272c218
...@@ -835,8 +835,19 @@ protected function getSelectQueryForFieldStorageDeletion($table_name, array $sha ...@@ -835,8 +835,19 @@ protected function getSelectQueryForFieldStorageDeletion($table_name, array $sha
// Add the bundle column. // Add the bundle column.
if ($bundle = $this->entityType->getKey('bundle')) { if ($bundle = $this->entityType->getKey('bundle')) {
if ($base_table) { // The bundle field is not stored in the revision table, so we need to
$select->join($base_table, 'base_table', "entity_table.{$this->entityType->getKey('id')} = %alias.{$this->entityType->getKey('id')}"); // join the data (or base) table and retrieve it from there.
if ($base_table && $base_table !== $table_name) {
$join_condition = "entity_table.{$this->entityType->getKey('id')} = %alias.{$this->entityType->getKey('id')}";
// If the entity type is translatable, we also need to add the langcode
// to the join, otherwise we'll get duplicate rows for each language.
if ($this->entityType->isTranslatable()) {
$langcode = $this->entityType->getKey('langcode');
$join_condition .= " AND entity_table.{$langcode} = %alias.{$langcode}";
}
$select->join($base_table, 'base_table', $join_condition);
$select->addField('base_table', $bundle, 'bundle'); $select->addField('base_table', $bundle, 'bundle');
} }
else { else {
......
...@@ -227,14 +227,18 @@ protected function updateEntityTypeToRevisionableAndTranslatable($perform_update ...@@ -227,14 +227,18 @@ protected function updateEntityTypeToRevisionableAndTranslatable($perform_update
* @param bool $is_revisionable * @param bool $is_revisionable
* (optional) If the base field should be revisionable or not. Defaults to * (optional) If the base field should be revisionable or not. Defaults to
* FALSE. * FALSE.
* @param bool $set_label * @param bool $set_label
* (optional) If the base field should have a label or not. Defaults to * (optional) If the base field should have a label or not. Defaults to
* TRUE. * TRUE.
* @param bool $is_translatable
* (optional) If the base field should be translatable or not. Defaults to
* FALSE.
*/ */
protected function addBaseField($type = 'string', $entity_type_id = 'entity_test_update', $is_revisionable = FALSE, $set_label = TRUE) { protected function addBaseField($type = 'string', $entity_type_id = 'entity_test_update', $is_revisionable = FALSE, $set_label = TRUE, $is_translatable = FALSE) {
$definitions['new_base_field'] = BaseFieldDefinition::create($type) $definitions['new_base_field'] = BaseFieldDefinition::create($type)
->setName('new_base_field') ->setName('new_base_field')
->setRevisionable($is_revisionable); ->setRevisionable($is_revisionable)
->setTranslatable($is_translatable);
if ($set_label) { if ($set_label) {
$definitions['new_base_field']->setLabel(t('A new base field')); $definitions['new_base_field']->setLabel(t('A new base field'));
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\entity_test\FieldStorageDefinition; use Drupal\entity_test\FieldStorageDefinition;
use Drupal\entity_test_update\Entity\EntityTestUpdate; use Drupal\entity_test_update\Entity\EntityTestUpdate;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait; use Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait;
/** /**
...@@ -56,7 +57,7 @@ class EntityDefinitionUpdateTest extends EntityKernelTestBase { ...@@ -56,7 +57,7 @@ class EntityDefinitionUpdateTest extends EntityKernelTestBase {
* *
* @var array * @var array
*/ */
protected static $modules = ['entity_test_update']; protected static $modules = ['entity_test_update', 'language'];
/** /**
* {@inheritdoc} * {@inheritdoc}
...@@ -504,18 +505,22 @@ public function testBundleFieldCreateDeleteWithExistingEntities() { ...@@ -504,18 +505,22 @@ public function testBundleFieldCreateDeleteWithExistingEntities() {
* *
* @dataProvider baseFieldDeleteWithExistingDataTestCases * @dataProvider baseFieldDeleteWithExistingDataTestCases
*/ */
public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_entity_revision, $base_field_revisionable) { public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_entity_revision, $base_field_revisionable, $create_entity_translation) {
// Enable an additional language.
ConfigurableLanguage::createFromLangcode('ro')->save();
/** @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage */ /** @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage */
$storage = $this->entityTypeManager->getStorage($entity_type_id); $storage = $this->entityTypeManager->getStorage($entity_type_id);
$schema_handler = $this->database->schema(); $schema_handler = $this->database->schema();
// Create an entity without the base field, to ensure NULL values are not // Create an entity without the base field, to ensure NULL values are not
// added to the dedicated table storage to be purged. // added to the dedicated table storage to be purged.
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $storage->create(); $entity = $storage->create();
$entity->save(); $entity->save();
// Add the base field and run the update. // Add the base field and run the update.
$this->addBaseField('string', $entity_type_id, $base_field_revisionable); $this->addBaseField('string', $entity_type_id, $base_field_revisionable, TRUE, $create_entity_translation);
$this->applyEntityUpdates(); $this->applyEntityUpdates();
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
...@@ -526,10 +531,22 @@ public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_ent ...@@ -526,10 +531,22 @@ public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_ent
$entity = $storage->create(['new_base_field' => 'foo']); $entity = $storage->create(['new_base_field' => 'foo']);
$entity->save(); $entity->save();
if ($create_entity_translation) {
$translation = $entity->addTranslation('ro', ['new_base_field' => 'foo-ro']);
$translation->save();
}
if ($create_entity_revision) { if ($create_entity_revision) {
$entity->setNewRevision(TRUE); $entity->setNewRevision(TRUE);
$entity->isDefaultRevision(FALSE);
$entity->new_base_field = 'bar'; $entity->new_base_field = 'bar';
$entity->save(); $entity->save();
if ($create_entity_translation) {
$translation = $entity->getTranslation('ro');
$translation->new_base_field = 'bar-ro';
$translation->save();
}
} }
// Remove the base field and apply updates. // Remove the base field and apply updates.
...@@ -544,65 +561,81 @@ public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_ent ...@@ -544,65 +561,81 @@ public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_ent
$dedicated_deleted_table_name = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE); $dedicated_deleted_table_name = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE);
$this->assertTrue($schema_handler->tableExists($dedicated_deleted_table_name), 'A dedicated table was created for the deleted new_base_field.'); $this->assertTrue($schema_handler->tableExists($dedicated_deleted_table_name), 'A dedicated table was created for the deleted new_base_field.');
$expected[] = [
'bundle' => $entity->bundle(),
'deleted' => '1',
'entity_id' => '2',
'revision_id' => '2',
'langcode' => 'en',
'delta' => '0',
'new_base_field_value' => 'foo',
];
if ($create_entity_translation) {
$expected[] = [
'bundle' => $entity->bundle(),
'deleted' => '1',
'entity_id' => '2',
'revision_id' => '2',
'langcode' => 'ro',
'delta' => '0',
'new_base_field_value' => 'foo-ro',
];
}
// Check that the deleted field's data is preserved in the dedicated // Check that the deleted field's data is preserved in the dedicated
// 'deleted' table. // 'deleted' table.
$result = $this->database->select($dedicated_deleted_table_name, 't') $result = $this->database->select($dedicated_deleted_table_name, 't')
->fields('t') ->fields('t')
->orderBy('revision_id', 'ASC')
->orderBy('langcode', 'ASC')
->execute() ->execute()
->fetchAll(); ->fetchAll(\PDO::FETCH_ASSOC);
$this->assertCount(1, $result); $this->assertCount(count($expected), $result);
$expected = [
'bundle' => $entity->bundle(),
'deleted' => '1',
'entity_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,
];
// Use assertEquals and not assertSame here to prevent that a different // Use assertEquals and not assertSame here to prevent that a different
// sequence of the columns in the table will affect the check. // sequence of the columns in the table will affect the check.
$this->assertEquals($expected, (array) $result[0]); $this->assertEquals($expected, $result);
if ($create_entity_revision) { if ($create_entity_revision) {
$dedicated_deleted_revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE); $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.'); $this->assertTrue($schema_handler->tableExists($dedicated_deleted_revision_table_name), 'A dedicated revision table was created for the deleted new_base_field.');
if ($base_field_revisionable) {
$expected[] = [
'bundle' => $entity->bundle(),
'deleted' => '1',
'entity_id' => '2',
'revision_id' => '3',
'langcode' => 'en',
'delta' => '0',
'new_base_field_value' => 'bar',
];
if ($create_entity_translation) {
$expected[] = [
'bundle' => $entity->bundle(),
'deleted' => '1',
'entity_id' => '2',
'revision_id' => '3',
'langcode' => 'ro',
'delta' => '0',
'new_base_field_value' => 'bar-ro',
];
}
}
$result = $this->database->select($dedicated_deleted_revision_table_name, 't') $result = $this->database->select($dedicated_deleted_revision_table_name, 't')
->fields('t') ->fields('t')
->orderBy('revision_id', 'DESC') ->orderBy('revision_id', 'ASC')
->orderBy('langcode', 'ASC')
->execute() ->execute()
->fetchAll(); ->fetchAll(\PDO::FETCH_ASSOC);
// Only one row will be created for non-revisionable base fields. $this->assertCount(count($expected), $result);
$this->assertCount($base_field_revisionable ? 2 : 1, $result);
// Use assertEquals and not assertSame here to prevent that a different // Use assertEquals and not assertSame here to prevent that a different
// sequence of the columns in the table will affect the check. // sequence of the columns in the table will affect the check.
$this->assertEquals([ $this->assertEquals($expected, $result);
'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) {
// Use assertEquals and not assertSame here to prevent that a different
// sequence of the columns in the table will affect the check.
$this->assertEquals([
'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. // Check that the field storage definition is marked for purging.
...@@ -626,45 +659,65 @@ public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_ent ...@@ -626,45 +659,65 @@ public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_ent
*/ */
public function baseFieldDeleteWithExistingDataTestCases() { public function baseFieldDeleteWithExistingDataTestCases() {
return [ return [
'Non-revisionable entity type' => [ 'Non-revisionable, non-translatable entity type' => [
'entity_test_update', 'entity_test_update',
FALSE, FALSE,
FALSE, FALSE,
FALSE,
], ],
'Non-revisionable custom data table' => [ 'Non-revisionable, non-translatable custom data table' => [
'entity_test_mul', 'entity_test_mul',
FALSE, FALSE,
FALSE, FALSE,
FALSE,
], ],
'Non-revisionable entity type, revisionable base field' => [ 'Non-revisionable, non-translatable entity type, revisionable base field' => [
'entity_test_update', 'entity_test_update',
FALSE, FALSE,
TRUE, TRUE,
FALSE,
], ],
'Non-revisionable custom data table, revisionable base field' => [ 'Non-revisionable, non-translatable custom data table, revisionable base field' => [
'entity_test_mul', 'entity_test_mul',
FALSE, FALSE,
TRUE, TRUE,
FALSE,
], ],
'Revisionable entity type, non revisionable base field' => [ 'Revisionable, translatable entity type, non revisionable and non-translatable base field' => [
'entity_test_mulrev', 'entity_test_mulrev',
TRUE, TRUE,
FALSE, FALSE,
FALSE,
], ],
'Revisionable entity type, revisionable base field' => [ 'Revisionable, translatable entity type, revisionable and non-translatable base field' => [
'entity_test_mulrev', 'entity_test_mulrev',
TRUE, TRUE,
TRUE, TRUE,
FALSE,
], ],
'Non-translatable revisionable entity type, revisionable base field' => [ 'Revisionable and non-translatable entity type, revisionable and non-translatable base field' => [
'entity_test_rev', 'entity_test_rev',
TRUE, TRUE,
TRUE, TRUE,
FALSE,
], ],
'Non-translatable revisionable entity type, non-revisionable base field' => [ 'Revisionable and non-translatable entity type, non-revisionable and non-translatable base field' => [
'entity_test_rev', 'entity_test_rev',
TRUE, TRUE,
FALSE, FALSE,
FALSE,
],
'Revisionable and translatable entity type, non-revisionable and translatable base field' => [
'entity_test_mulrev',
TRUE,
FALSE,
TRUE,
],
'Revisionable and translatable entity type, revisionable and translatable base field' => [
'entity_test_mulrev',
TRUE,
TRUE,
TRUE,
], ],
]; ];
} }
......
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