Problem/Motivation

This was discovered in #2841291: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field.

The field schema data doesn't accurately store the schema for identifier fields (ID and Revision ID): Fields that are stored as serial in certain tables are actually saved as int in the field schema data for those tables.

Proposed resolution

Move the ::processIdentifierSchema() calls from process*Table() to getSharedTableFieldSchema().

CommentFileSizeAuthor
#69 interdiff-65-69-9.0x.txt914 bytesgnunes
#69 2928906-69-9.0.x.patch15.04 KBgnunes
#69 2928906-69-8.9.x.patch12.2 KBgnunes
#65 interdiff-60-65-9.0.x.txt1.03 KBgnunes
#65 interdiff-60-65-8.9.x.txt1.11 KBgnunes
#65 2928906-65-9.0.x.patch15.03 KBgnunes
#65 2928906-65-8.9.x.patch12.2 KBgnunes
#60 interdiff-50-60-9.0.x.txt7.44 KBgnunes
#60 2928906-60-9.0.x.patch15.04 KBgnunes
#60 2928906-60-8.9.x.patch11.89 KBgnunes
#50 interdiff-50.txt3.86 KBamateescu
#50 2928906-50.patch11.89 KBamateescu
#49 interdiff-46-49.txt1.89 KBhchonov
#49 2928906-49.patch11.81 KBhchonov
#48 exception_backtrace_storage_init_deleted_entity_type.txt5.24 KBhchonov
#48 interdiff-46-48.txt1.89 KBhchonov
#48 2928906-48.patch56.56 KBhchonov
#46 2928906-46.patch11.8 KBhchonov
#46 interdiff-40-46.txt6.59 KBhchonov
#40 interdiff-8.8.x-8.9.x.txt2.3 KBhchonov
#40 2928906-39-commit-to-8.9.x-and-onwards.patch11.69 KBhchonov
#40 2928906-39-commit-to-8.8.x-and-onwards.patch11.69 KBhchonov
#40 interdiff-38-39.txt1.89 KBhchonov
#38 interdiff-37-38.txt1.2 KBhchonov
#38 2928906-38.patch11.6 KBhchonov
#37 interdiff-35-37.txt873 byteshchonov
#37 2928906-37.patch11.76 KBhchonov
#35 interdiff-33-35.txt4.52 KBhchonov
#35 2928906-35.patch11.61 KBhchonov
#33 2928906-33.patch11.82 KBgease
#30 2928906-29.patch11.83 KBgease
#27 2928906-27.patch11.96 KBtstoeckler
#27 2928906-21-27-interdiff.txt7.8 KBtstoeckler
#21 2928906-21--for-testbot.patch41.6 KBtstoeckler
#21 2928906-21--tests-only.patch40.34 KBtstoeckler
#21 2928906-21--for-review.patch11.93 KBtstoeckler
#19 2928906-19.patch14.84 KBtstoeckler
#19 2928906-17-19-interdiff.txt2.22 KBtstoeckler
#17 2928906-17.patch12.61 KBtstoeckler
#17 2928906-15-17-interdiff.txt1.19 KBtstoeckler
#15 2928906-15.patch12.51 KBtstoeckler
#15 2928906-13-15-interdiff.txt4.23 KBtstoeckler
#13 2928906-13.patch9.34 KBtstoeckler
#13 2928906-10-13-interdiff.txt1.71 KBtstoeckler
#10 2928906-10.patch9.28 KBtstoeckler
#4 2928906-4.patch37.31 KBtstoeckler
#4 2928906-2-4-interdiff.txt5.64 KBtstoeckler
#2 2928906-2.patch31.75 KBtstoeckler
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tstoeckler created an issue. See original summary.

tstoeckler’s picture

Issue summary: View changes
Status: Active » Needs review
FileSize
31.75 KB

Here we go, this is mostly extracted from #2841291: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field, but I started looking around and also started cleaning up some of the overridden storage schema handlers. This is work in progress. I also verified that a bunch of the schema-related tests pass, but let's see what the entire testsuite brings...

Status: Needs review » Needs work

The last submitted patch, 2: 2928906-2.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
5.64 KB
37.31 KB

Interesting, so the changes I did to the node and user storage schema require an update path. Makes sense. I'm including those update paths for now, but not sure whether that's in scope for this issue. In any case not proceeding with any further conversions until we have some agreement on the general direction of this.

This also brings in the tests from #2841291: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field that I had incorrectly left out before (and I learned about git add -N because of that, neat! ;-))

amateescu’s picture

Table-level primary keys, unique keys and indexes are not stored as part of the field schema

Are you sure that needs to be handled in this issue? I don't remember having a problem with this when I was working on #2841291: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field.

tstoeckler’s picture

Well, we don't have to solve the two issues in the IS in one issue. I just thought it made sense this both of them affect the stored field schema so that we will need to have some update path for both of them. It's not necessary for #2841291: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field, but it does avoid the necessity of fetching the entity schema e.g. for creating a composite primary key for example for the data table when making an entity type translatable. But if you prefer to tackle the two issues separately, I'm absolutely fine with that; the first one certainly is smaller in scope, in particular because it will not require any changes to the overridden schema handlers.

amateescu’s picture

I think it would make the patch(es) a lot easier to review if we handle them separately :)

Edit: for example, while looking at the latest patch from this issue, it was very hard to determine if a change was needed for the first or the second bug.

tstoeckler’s picture

Sure, no problem. Will move the key/index stuff to #2929120: The stored field schema does not contain field-level primary keys, unique keys and indexes then. Am waiting for a green patch here first, though. Re-titling and updating the issue summary in the meantime.

tstoeckler’s picture

Title: The field schema incorrectly stores serial fields as int and is missing keys and indexes » The field schema incorrectly stores serial fields as int
tstoeckler’s picture

Status: Needs work » Needs review
FileSize
9.28 KB

OK, here we go. This should be what remains after I extracted #2929120-2: The stored field schema does not contain field-level primary keys, unique keys and indexes. Let's see if the split worked out.

Status: Needs review » Needs work

The last submitted patch, 10: 2928906-10.patch, failed testing. View results

mayurjadhav’s picture

Issue tags: +DrupalMumbaiCodeSprint
tstoeckler’s picture

Version: 8.5.x-dev » 8.6.x-dev
Status: Needs work » Needs review
FileSize
1.71 KB
9.34 KB

Updating this for 8.6 and also attempting to fix the update path tests. Unfortunately I'm not able to run those locally.

Status: Needs review » Needs work

The last submitted patch, 13: 2928906-13.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
4.23 KB
12.51 KB

Status: Needs review » Needs work

The last submitted patch, 15: 2928906-15.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
1.19 KB
12.61 KB

We also need to update the revision ID field, of course.

Status: Needs review » Needs work

The last submitted patch, 17: 2928906-17.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
2.22 KB
14.84 KB

Though shalt be green.

tstoeckler’s picture

Title: The field schema incorrectly stores serial fields as int » [PP-1] The field schema incorrectly stores serial fields as int
Status: Needs review » Postponed
tstoeckler’s picture

Title: [PP-1] The field schema incorrectly stores serial fields as int » [PP-3] The field schema incorrectly stores serial fields as int
Status: Postponed » Needs review
FileSize
11.93 KB
40.34 KB
41.6 KB

This is now also PP-3.

Rebased on top of the latest patch in #2938951: The primary key is not correctly re-created when updating the storage definition of an identifier field. Should be green, at least I ran all the tests touched here locally.

The last submitted patch, 21: 2928906-21--tests-only.patch, failed testing. View results

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jibran’s picture

Title: [PP-3] The field schema incorrectly stores serial fields as int » The field schema incorrectly stores serial fields as int
claudiu.cristea’s picture

Version: 8.7.x-dev » 8.6.x-dev
Status: Needs review » Needs work

Few remarks...

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -2237,6 +2238,16 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
    +            // Prior to Drupal 8.5.0, the last installed schema for the 'id' and
    

    Probably 8.7.0?

  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -2237,6 +2238,16 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
    +            // @see system_update_8601()
    
    +++ b/core/modules/system/system.install
    @@ -2144,3 +2144,31 @@ function system_update_8501() {
    +function system_update_8601() {
    
    +++ b/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTranslatableTest.php
    @@ -74,6 +74,11 @@ public function testMakeRevisionableErrorHandling() {
    +    // @see system_update_8601()
    
    +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateTest.php
    @@ -0,0 +1,64 @@
    +   * Tests system_update_8601().
    ...
    +  public function testSystemUpdate8601() {
    

    The other patch was committed only in 8.7.x, so this should be also on 8.7.x: system_update_8701()

  3. +++ b/core/modules/system/system.install
    @@ -2144,3 +2144,31 @@ function system_update_8501() {
    +    if (!$original_entity_type) {
    

    Should we filter out also entity types that are not ContentEntityTypeInterface?

  4. +++ b/core/modules/system/system.install
    @@ -2144,3 +2144,31 @@ function system_update_8501() {
    +      if (!$field_storage_definition) {
    

    Should we filter out non-numeric id/revision fields?

  5. +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateTest.php
    @@ -0,0 +1,64 @@
    +      __DIR__ . '/../../../fixtures/update/drupal-8.bare.standard.php.gz',
    

    We have now drupal-8.4.0.bare.standard.php.gz

  6. +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateTest.php
    @@ -0,0 +1,64 @@
    +    $this->assertEqual('int', $id_schema['node']['fields']['nid']['type']);
    +    $this->assertEqual('int', $id_schema['node_revision']['fields']['nid']['type']);
    +    $this->assertEqual('int', $revision_id_schema['node']['fields']['vid']['type']);
    +    $this->assertEqual('int', $revision_id_schema['node_revision']['fields']['vid']['type']);
    ...
    +    $this->assertEqual('serial', $id_schema['node']['fields']['nid']['type']);
    +    $this->assertEqual('int', $id_schema['node_revision']['fields']['nid']['type']);
    +    $this->assertEqual('int', $revision_id_schema['node']['fields']['vid']['type']);
    +    $this->assertEqual('serial', $revision_id_schema['node_revision']['fields']['vid']['type']);
    ...
    +    $this->assertEqual('Test update', $node->label());
    

    This is a new test so let's not use deprecated assertion methods.

  7. +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateTest.php
    @@ -0,0 +1,64 @@
    +    $node_storage->resetCache();
    

    It should work w/o resetting the cache.

claudiu.cristea’s picture

Version: 8.6.x-dev » 8.7.x-dev

Changed version by mistake.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
7.8 KB
11.96 KB

Thanks for the review @claudiu.cristea, those are all great points! Fixed that and also added the now-required legacy group to the update tests and updated the update hook to use the new EntityDefinitionUpdateManager::getEntityTypes() which allows a slight simplification.

tstoeckler’s picture

I was wondering, by the way, whether it would be nicer to have the update path use the InstalledSchemaRepository directly instead of calling updateFieldStorageDefinition() as that would allow us to get rid of the hack in the runtime check in SqlContentEntityStorageSchema. Not sure what others think about that...

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

gease’s picture

Re-rolled the patch from #27 against 8.8.x

Status: Needs review » Needs work

The last submitted patch, 30: 2928906-29.patch, failed testing. View results

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

gease’s picture

Reroll for 8.9.x, basically just changing the hook_updateN() number.

daffie’s picture

@gease: I would like to see this issue fixed. Could you fix the test failures. I will review.

hchonov’s picture

Status: Needs work » Needs review
FileSize
11.61 KB
4.52 KB

The problem was that during the update to revisionable the storage wasn't using the new table mapping and entity type and therefore the result of $storage->getRevisionTable() was NULL instead of the correct one.

Status: Needs review » Needs work

The last submitted patch, 35: 2928906-35.patch, failed testing. View results

hchonov’s picture

Status: Needs work » Needs review
FileSize
11.76 KB
873 bytes

This ensures that we will not be touching deleted entity types, which was the reason for the last 2 fails.

hchonov’s picture

daffie’s picture

The patch looks good to me. I only have one worry about it:

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -2452,6 +2456,16 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
+            // Prior to Drupal 8.9.0, the last installed schema for the 'id' and
+            // 'revision' fields of all content entity types was storing an
+            // 'int' type instead of 'serial', so we need to explicitly allow
+            // this conversion.
+            // @todo Remove this in 9.0.x.
+            // @see system_update_8901()
+            // @see https://www.drupal.org/project/drupal/issues/2928906
+            if ($definition_spec['type'] === 'serial' && $stored_spec['type'] === 'int') {
+              return FALSE;
+            }

I am worried that there could be multiple differences between the $definition_spec and $stored_spec, which with the above code will return as FALSE. I am not sure if it is possible. What do you think @hchonov?

Update: What are we exactly testing in this test? I do not understand why we have this test.

+++ b/core/modules/system/system.install
@@ -2521,3 +2521,34 @@ function system_update_8805() {
diff --git a/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php b/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php

diff --git a/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php b/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php
new file mode 100644

new file mode 100644
index 0000000000..16bd0bfba9

index 0000000000..16bd0bfba9
--- /dev/null

--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php

+++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php
+++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php
@@ -0,0 +1,24 @@

@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Update;
+
+/**
+ * Tests the upgrade path with existing data for various entity update issues.
+ *
+ * @see https://www.drupal.org/node/2928906
+ *
+ * @group Update
+ * @group legacy
+ */
+class EntityUpdateFilledTest extends EntityUpdateTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../fixtures/update/drupal-8.filled.standard.php.gz',
+    ];
+  }
+
+}
hchonov’s picture

I am worried that there could be multiple differences between the $definition_spec and $stored_spec, which with the above code will return as FALSE. I am not sure if it is possible. What do you think @hchonov?

@daffie, very good catch!

I've updated the code now to ensure that we properly compare the complete arrays by setting the type of the one to the other's value. We could unset the keys as well before the comparison, but I don't think that the one or the other approach is much better.

Update: What are we exactly testing in this test? I do not understand why we have this test.

The test you are asking about - EntityUpdateFilledTest extends from EntityUpdateTest and as such uses its test method \Drupal\Tests\system\Functional\Update\EntityUpdateTest::testSystemUpdate8901(). The only difference is that EntityUpdateTest is testing with a Drupal dump installed at 8.4.0 without any content and EntityUpdateFilledTest is testing with a Drupal dump installed at 8.0 (I think), but filled with content.

---

I am uploading two patches now - one if we decide to commit to 8.8.x and onwards and one if we decide to commit to 8.9.x and onwards. The difference is the update function number only.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

I am happy with this patch, for me it is RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1410,10 +1414,6 @@ protected function addTableDefaults(&$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'));
    -    }
    
    @@ -1425,10 +1425,6 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr
    -    // Process the schema for the 'revision' entity key only if it exists.
    -    if ($entity_type->hasKey('revision')) {
    -      $this->processIdentifierSchema($schema, $entity_type->getKey('revision'));
    -    }
    

    Do we need to change the behaviour here? Just in case some alternate implementations rely on this. Or alternatively why are we emptying the methods and not removing them?

  2. +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php
    @@ -0,0 +1,24 @@
    +  protected function setDatabaseDumpFiles() {
    +    $this->databaseDumpFiles = [
    +      __DIR__ . '/../../../fixtures/update/drupal-8.filled.standard.php.gz',
    +    ];
    +  }
    
    +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateTest.php
    @@ -0,0 +1,63 @@
    +  protected function setDatabaseDumpFiles() {
    +    $this->databaseDumpFiles = [
    +      __DIR__ . '/../../../fixtures/update/drupal-8.4.0.bare.standard.php.gz',
    +    ];
    +  }
    

    Let's use the new 8.8 update files.

  3. +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
    @@ -426,7 +426,7 @@ public function testGetSchemaRevisionable() {
    -    $this->storage->expects($this->exactly(1))
    +    $this->storage->expects($this->atLeastOnce())
    
    @@ -647,7 +647,7 @@ public function testGetSchemaRevisionableTranslatable() {
    -    $this->storage->expects($this->exactly(2))
    +    $this->storage->expects($this->atLeastOnce())
    

    Are we sure we want to lose the exactly here?

daffie’s picture

According to @catch this issue can no longer land in 8.8.0, but it can land in 8.9.0. All mentions in the patch to 8.8 need to be updated.

amateescu’s picture

Status: Needs work » Reviewed & tested by the community
  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -2451,6 +2455,18 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
    +          // Prior to Drupal 8.8.0, the last installed schema for the 'id' and
    +          // 'revision' fields of all content entity types was storing an
    +          // 'int' type instead of 'serial', so we need to explicitly allow
    +          // this conversion and not consider it as a change here.
    +          // @todo Remove this in 9.0.x.
    +          // @see system_update_8806()
    +          // @see https://www.drupal.org/project/drupal/issues/2928906
    +          if ($definition_spec['type'] === 'serial' && $stored_spec['type'] === 'int') {
    +            $stored_spec['type'] = 'serial';
    +          }
    

    I think #28 is the better way to handle this, since the actual database schema is not impacted by this change, we should update only the last installed schema definitions which would allow us to get rid of this hunk.

  2. +++ b/core/modules/system/system.install
    @@ -2521,3 +2521,34 @@ function system_update_8805() {
    +    // Ensure that we are dealing with a non-deleted entity type.
    +    if (!$entity_type || !($entity_type instanceof ContentEntityTypeInterface) || !$entity_type_manager->hasDefinition($entity_type_id)) {
    +      continue;
    +    }
    

    Since Drupal 8.7, it is possible (and required for update repeatability) to update the schema of deleted entity types as well, so this isn't needed anymore.

  3. +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateFilledTest.php
    @@ -0,0 +1,24 @@
    +class EntityUpdateFilledTest extends EntityUpdateTest {
    

    This test class is not needed, we're not doing any database schema changes that could impact existing data.

  4. +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateTest.php
    @@ -0,0 +1,63 @@
    +class EntityUpdateTest extends UpdatePathTestBase {
    

    This test class should be renamed to something less generic because it's only testing the update of the last installed schema definitions.

amateescu’s picture

Status: Reviewed & tested by the community » Needs work
hchonov’s picture

Re #42:
1.

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -1410,10 +1414,6 @@ protected function addTableDefaults(&$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'));
-    }

@@ -1425,10 +1425,6 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr
-    // Process the schema for the 'revision' entity key only if it exists.
-    if ($entity_type->hasKey('revision')) {
-      $this->processIdentifierSchema($schema, $entity_type->getKey('revision'));
-    }

Do we need to change the behaviour here? Just in case some alternate implementations rely on this. Or alternatively why are we emptying the methods and not removing them?

We don't really change the behavior. Now the identifiers are processed right before calling the methods ::processBaseTable() and ::processRevisionTable(). However those are not the only methods for processing tables. We have also ::processDataTable() and ::processRevisionDataTable(). Therefore I think it makes sense to keep them if not for not breaking overrides, but at least for consistency.

2.
Done.

3.

+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
@@ -426,7 +426,7 @@ public function testGetSchemaRevisionable() {
-    $this->storage->expects($this->exactly(1))
+    $this->storage->expects($this->atLeastOnce())

@@ -647,7 +647,7 @@ public function testGetSchemaRevisionableTranslatable() {
-    $this->storage->expects($this->exactly(2))
+    $this->storage->expects($this->atLeastOnce())

Are we sure we want to lose the exactly here?

I don't really have a strong preference here, therefore I've changed it back to an exactly N expectation.

-------

Re #44:

1.
Done.

2.

Since Drupal 8.7, it is possible (and required for update repeatability) to update the schema of deleted entity types as well, so this isn't needed anymore.

This was still required for the tests to pass with the previous update approach. I didn't know how else to fix those fails. Maybe you have some ideas what is going on when using ::updateFieldStorageDefinition()?
See the test fails in #35 that were fixed with this change in #37. I've actually created a dedicated issue for this problem as it occurred already in two different issues during updates - #3092497: Deleted entity types now break update tests.

I thought that with the new approach that will work, but it doesn't as in the update I am initializing the storage now and for that the live entity type is being used, which is deleted in some tests and therefore they fail. This does not allow us to update their field definitions. And I don't actually understand why do we have to update fields for deleted entity types?

3.
Removed.

4.
Renamed EntityUpdateTest to IdentifierFieldSchemaTypeUpdateTest.

---

I've addressed everything but #44.2 as I don't see how this can be solved. @amateescu, if we really have to solve it, could you please elaborate on why this is necessary and maybe provide some hints on how this can be done? Thanks!

amateescu’s picture

The storage handler can be instantiated for deleted entity types like this:

$storage = $entity_type_manager->createHandlerInstance($entity_type->getStorageClass(), $entity_type);
  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -560,6 +560,10 @@ protected function postUpdateEntityTypeSchema(EntityTypeInterface $entity_type,
    +      // Let the storage use the updated entity type and the new table mapping
    +      // before creating the table schema.
    +      $this->storage->setEntityType($entity_type);
    +      $this->storage->setTableMapping($new_table_mapping);
    

    I don't think this change is needed anymore.

  2. +++ b/core/modules/system/system.install
    @@ -2521,3 +2523,60 @@ function system_update_8805() {
    +        $ref_getStorageSchema = new \ReflectionMethod($storage, 'getStorageSchema');
    ...
    +        $ref_processIdentifierSchema = new \ReflectionMethod($storage_schema, 'processIdentifierSchema');
    

    These variable names should be lower snake case.

hchonov’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
      @@ -560,6 +560,10 @@ protected function postUpdateEntityTypeSchema(EntityTypeInterface $entity_type,
      +      // Let the storage use the updated entity type and the new table mapping</li>
      +      // before creating the table schema.</li>
      +      $this->storage->setEntityType($entity_type);</li>
      +      $this->storage->setTableMapping($new_table_mapping);

    I don't think this change is needed anymore.

    It is, without it we cannot properly find the revision table name in \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::getSharedTableFieldSchema() and then the test fail with :

    1) Drupal\Tests\system\Functional\Update\EntityUpdateAddRevisionTranslationAffectedTest::testAddingTheRevisionTranslationAffectedField
    Taxonomy term: The <em class="placeholder">Revision ID</em> field needs to be updated.
    

    .

  2. Updated.

The storage handler can be instantiated for deleted entity types like this:

$storage = $entity_type_manager->createHandlerInstance($entity_type->getStorageClass(), $entity_type);

Which also leads to an exception when executing EntityUpdateAddRevisionTranslationAffectedTest. Backtrace:

  1. EntityTypeManager::createHandlerInstance
  2. EntityTypeManager::createInstance
  3. SqlContentEntityStorage::__construct
  4. SqlContentEntityStorage::initTableLayout
  5. SqlContentEntityStorage::getTableMapping
  6. SqlContentEntityStorage::getCustomTableMapping
  7. DefaultTableMapping::create
  8. ContentEntityType::getRevisionMetadataKeys
  9. ContentEntityType::getBaseFieldDefinitions
  10. EntityFieldManager::buildBaseFieldDefinitions
  11. EntityTypeManager::getDefinition

I've attached the complete backtrace.

If you think that this should work, then could you please try it yourself? I've tried a lot of things and I just cannot make it work and I still don't understand the reasoning behind the requirement for this.

hchonov’s picture

FileSize
11.81 KB
1.89 KB

oups.. I was on the 8.8.x when created the previous patch against 8.9.x ... Here is the proper patch now.

amateescu’s picture

Issue summary: View changes
FileSize
11.89 KB
3.86 KB

Re #48:

1. I looked at the code and I see now why we need those changes. Let's ensure that we set the new field storage definitions as well though.

2. That's because \Drupal\Core\Entity\ContentEntityType::getRevisionMetadataKeys() should use EntityFieldManager::getActiveFieldStorageDefinitions() instead of getBaseFieldDefinitions(), but I'm fine with not handling/fixing that in this issue.

Made a few small adjustments and I think this is ready to go now.

hchonov’s picture

  1. looked at the code and I see now why we need those changes. Let's ensure that we set the new field storage definitions as well though.

    I've missed that one, thanks!

  2. That's because \Drupal\Core\Entity\ContentEntityType::getRevisionMetadataKeys() should use EntityFieldManager::getActiveFieldStorageDefinitions() instead of getBaseFieldDefinitions(), but I'm fine with not handling/fixing that in this issue.

    IIRC that was left out because of the BC layer over there. As soon as it is removed \Drupal\Core\Entity\ContentEntityType::getRevisionMetadataKeys() will not be retrieving any fields anymore.

daffie’s picture

The patch looks good to me. For me just one point left:

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -561,6 +561,12 @@ protected function postUpdateEntityTypeSchema(EntityTypeInterface $entity_type,
@@ -1410,10 +1416,6 @@ protected function addTableDefaults(&$schema) {

@@ -1410,10 +1416,6 @@ protected function addTableDefaults(&$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'));
-    }
   }
 
   /**
@@ -1425,10 +1427,6 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr

@@ -1425,10 +1427,6 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr
    *   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'));
-    }
   }
 
   /**
@@ -2047,6 +2045,7 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st

@@ -2047,6 +2045,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 = [];
@@ -2125,6 +2124,13 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st

@@ -2125,6 +2124,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;
   }
 

@alexpott did not want to move this piece of code. See his comment #42.1. Can we remove these hunks from the patch?

hchonov’s picture

@alexpott did not want to move this piece of code. See his comment #42.1. Can we remove these hunks from the patch?

No, this is the actual fix of the problem.

The ::process*Table methods are called only from within \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::getEntitySchema(), but ::getSharedTableFieldSchema() is called from everywhere where the schema is needed - for example from within \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::createSharedTableSchema() where the field schema data is saved and here the ::process*Table methods are not called. This is the reason for the inconsistency now.

::getSharedTableFieldSchema() is therefore the single only place where we can correctly alter the schema for all use cases.

I've also responded to @alexpotts's comment in #46:

We don't really change the behavior. Now the identifiers are processed right before calling the methods ::processBaseTable() and ::processRevisionTable(). However those are not the only methods for processing tables. We have also ::processDataTable() and ::processRevisionDataTable(). Therefore I think it makes sense to keep them if not for not breaking overrides, but at least for consistency.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@hchonov: Thank you for your explaination.

With the explaination from @hchonov (thanks for that), the patch is for me RTBC.
All points are addresssed. Thanks everybody for working on this issue.

catch’s picture

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -1410,10 +1416,6 @@ protected function addTableDefaults(&$schema) {
    */
   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'));
-    }
   }
 
   /**
@@ -1425,10 +1427,6 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr

@@ -1425,10 +1427,6 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr
    *   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'));
-    }
   }
 
   /**

Given these two methods are empty, why not remove them and the calls to them?

hchonov’s picture

Given these two methods are empty, why not remove them and the calls to them?

@alexpott asked the same question in #42:

Do we need to change the behaviour here? Just in case some alternate implementations rely on this. Or alternatively why are we emptying the methods and not removing them?

which I've answered in #46:

We don't really change the behavior. Now the identifiers are processed right before calling the methods ::processBaseTable() and ::processRevisionTable(). However those are not the only methods for processing tables. We have also ::processDataTable() and ::processRevisionDataTable(). Therefore I think it makes sense to keep them if not for not breaking overrides, but at least for consistency.

catch’s picture

We don't have empty protected methods anywhere else for consistency though. If we want to leave them in 8.x just in case, we could still remove them from Drupal 9.

hchonov’s picture

I am fine with this. I would be fine removing them in Drupal 8 as well if you think we're allowed doing so.

catch’s picture

Status: Reviewed & tested by the community » Needs work

Let's add a 9.x version of the patch without the empty methods. Will also probably need a re-roll for context with the update.

gnunes’s picture

Let's add a 9.x version of the patch without the empty methods. Will also probably need a re-roll for context with the update.

I've re-rolled the patch for Drupal 9. The interdiff contains the removed methods and the resolved conflict in system.install

gnunes’s picture

Status: Needs work » Needs review

Also, it needs to be reviewed, please.

hchonov’s picture

Status: Needs review » Reviewed & tested by the community

@gnunes, thank you.

The rerolls look good, therefore I think it is safe to put it back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. We need a change record for this change to say that the protected methods have been removed from Drupal 9.0.0.
  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1410,10 +1416,6 @@ protected function addTableDefaults(&$schema) {
       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'));
    -    }
       }
    
    @@ -1425,10 +1427,6 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr
       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'));
    -    }
       }
    

    We should document that these are being removed in Drupal 9.0.x - we can't deprecate them because we're still calling them but a comment as to why they are empty would seem to be a good idea. This comment should reference the change record.

  3. +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
    @@ -426,7 +426,7 @@ public function testGetSchemaRevisionable() {
    -    $this->storage->expects($this->exactly(1))
    +    $this->storage->expects($this->exactly(9))
    
    @@ -647,7 +647,7 @@ public function testGetSchemaRevisionableTranslatable() {
    -    $this->storage->expects($this->exactly(2))
    +    $this->storage->expects($this->exactly(30))
    

    I'm not sure that exactly() is testing much and it certainly less meaningful when it is 30! How about changing these to $this->any()

daffie’s picture

Issue tags: +Novice

Added a change record.

For the novice: Removed the 2 methods that the patch makes empty. See the added change record and the comment #63 of @alexpott. Also make the change that @alexpott is requesting in comment #63.3

gnunes’s picture

I've done the changes suggested in comment #63.2 and #63.3.
It needs to be reviewd, please

Status: Needs review » Needs work

The last submitted patch, 65: 2928906-65-9.0.x.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

hchonov’s picture

+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
@@ -426,7 +426,7 @@ public function testGetSchemaRevisionable() {
-    $this->storage->expects($this->exactly(1))
+    $this->storage->expects($this->exactly(9))

@@ -647,7 +647,7 @@ public function testGetSchemaRevisionableTranslatable() {
-    $this->storage->expects($this->exactly(2))
+    $this->storage->expects($this->exactly(30))

I'm not sure that exactly() is testing much and it certainly less meaningful when it is 30! How about changing these to $this->any()

#65 has made the change to any() only in the patch for D9, however we see that for some reason it does not work. However here we are actually adapting the test only to the new count. I don't think as well that we should be using an exact number anywhere in unit tests unless an algorithm is being tested, but however it might be worth to change this at once across all unit tests and concentrate here only on the required changes for the issue. I would therefore prefer to create a dedicated issue for this.

alexpott’s picture

@hchonov so fails in #65 are instructive!

1) Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageSchemaTest::testGetSchemaRevisionable
This test did not perform any assertions

2) Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageSchemaTest::testGetSchemaRevisionableTranslatable
This test did not perform any assertions

So the only assertion we're making is about how times something is called. That's not much of a test...

But this flimsy-ness is not introduced here. So sure let's file a follow-up about fixing this test to actually test something more than counts of function calls.

gnunes’s picture

Status: Needs work » Needs review
hchonov’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -1414,6 +1414,9 @@ protected function addTableDefaults(&$schema) {
+   * This function will be removed in Drupal:9.0.x
+   * @see https://www.drupal.org/node/3111613
    */
   protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) {

@@ -1425,6 +1428,9 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr
+   * This function will be removed in Drupal:9.0.x
+   * @see https://www.drupal.org/node/3111613
    */
   protected function processRevisionTable(ContentEntityTypeInterface $entity_type, array &$schema) {

Adding this comment to the 8.9.x patch as requested in #2928906-63: The field schema incorrectly stores serial fields as int.2 is the only change that occurred since the last RTBC. Therefore I think that it is safe to put the issue back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -Novice

Committed ff16159 and pushed to 9.0.x. Thanks!
Committed 23bb593 and pushed to 8.9.x. Thanks!

diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 1d7cbd9957..5903461c9a 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1208,7 +1208,6 @@ function system_update_last_removed() {
   return 8805;
 }
 
-
 /**
  * Update the stored schema data for entity identifier fields.
  */
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntitySchemaTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntitySchemaTest.php
index 7168ca170b..06ba440839 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntitySchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntitySchemaTest.php
@@ -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.

Fixed coding standards on commit.

diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index 22fd9de5b1..a52f76ab41 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -1410,12 +1410,13 @@ protected function addTableDefaults(&$schema) {
   /**
    * Processes the gathered schema for a base table.
    *
+   * This function will be removed in Drupal 9.0.x.
+   *
    * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
    *   The entity type.
    * @param array $schema
    *   The table schema, passed by reference.
    *
-   * This function will be removed in Drupal:9.0.x
    * @see https://www.drupal.org/node/3111613
    */
   protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) {
@@ -1424,12 +1425,13 @@ protected function processBaseTable(ContentEntityTypeInterface $entity_type, arr
   /**
    * Processes the gathered schema for a base table.
    *
+   * This function will be removed in Drupal 9.0.x.
+   *
    * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
    *   The entity type.
    * @param array $schema
    *   The table schema, passed by reference.
    *
-   * This function will be removed in Drupal:9.0.x
    * @see https://www.drupal.org/node/3111613
    */
   protected function processRevisionTable(ContentEntityTypeInterface $entity_type, array &$schema) {

I made this change on the D8 patch to comply with coding standards but to also keep the removal notice in the correct place.

  • alexpott committed 23bb593 on 8.9.x
    Issue #2928906 by tstoeckler, hchonov, gnunes, gease, amateescu, daffie...

  • alexpott committed ff16159 on 9.0.x
    Issue #2928906 by tstoeckler, hchonov, gnunes, gease, amateescu, daffie...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.