Problem/Motivation

Updating to 8.0.x-dev this morning I ran into a database update failure from system module.

Cannot change the definition of field comment.langcode: field doesn't exist.  
Failed: Cannot change the definition of field comment.langcode: field doesn't exist.

 
This is multisite installation. Strangely it occurred on only one of the sites. As you can see below I do not have comment module enabled (nor is it enabled on any of the other sites in the installation).

I didn't capture the original failure, which occurred alongside several other successful updates. But this is the output from when I re-ran it:

$ drush updb
The following updates are pending:
system module :
  8007 -   Set langcode fields to be ASCII-only.
Do you wish to run all pending updates? (y/n): y
Cannot change the definition of field comment.langcode: field doesn't exist.                                                                   [error]
Performing system_update_8007                                                                                                                     [ok]
Failed: Cannot change the definition of field comment.langcode: field doesn't exist.                                                           [error]
Cache rebuild complete.                                                                                                                           [ok]
Finished performing updates.                                                                                                                      [ok]
$ drush pm-list --status=enabled --type=module
 Package      Name                                   Version
 Core         Block (block)                          8.0.0-dev
 Core         Breakpoint (breakpoint)                8.0.0-dev
 Core         CKEditor (ckeditor)                    8.0.0-dev
 Core         Color (color)                          8.0.0-dev
 Core         Configuration Manager (config)         8.0.0-dev
 Core         Contextual Links (contextual)          8.0.0-dev
 Core         Custom Block (block_content)           8.0.0-dev
 Core         Custom Menu Links (menu_link_content)  8.0.0-dev
 Core         Database Logging (dblog)               8.0.0-dev
 Core         Field (field)                          8.0.0-dev
 Core         Field UI (field_ui)                    8.0.0-dev
 Core         Filter (filter)                        8.0.0-dev
 Core         Help (help)                            8.0.0-dev
 Core         History (history)                      8.0.0-dev
 Core         Internal Page Cache (page_cache)       8.0.0-dev
 Core         Menu UI (menu_ui)                      8.0.0-dev
 Core         Node (node)                            8.0.0-dev
 Core         Path (path)                            8.0.0-dev
 Core         Quick Edit (quickedit)                 8.0.0-dev
 Core         RDF (rdf)                              8.0.0-dev
 Core         Search (search)                        8.0.0-dev
 Core         Shortcut (shortcut)                    8.0.0-dev
 Core         System (system)                        8.0.0-dev
 Core         Taxonomy (taxonomy)                    8.0.0-dev
 Core         Text Editor (editor)                   8.0.0-dev
 Core         Toolbar (toolbar)                      8.0.0-dev
 Core         Tour (tour)                            8.0.0-dev
 Core         Update Manager (update)                8.0.0-dev
 Core         User (user)                            8.0.0-dev
 Core         Views (views)                          8.0.0-dev
 Core         Views UI (views_ui)                    8.0.0-dev
 Development  Devel (devel)                          8.x-1.x-dev
 Field types  Datetime (datetime)                    8.0.0-dev
 Field types  Entity Reference (entity_reference)    8.0.0-dev
 Field types  File (file)                            8.0.0-dev
 Field types  Image (image)                          8.0.0-dev
 Field types  Link (link)                            8.0.0-dev
 Field types  Options (options)                      8.0.0-dev
 Field types  Text (text)                            8.0.0-dev
$ drush pmi system --fields=schema_version
 Schema version   :  8006

 
I bypassed the failure by manually updating the module schema version.

Proposed resolution

* Add an update path to truncate unneeded entry from the key value (system_update_8008)
* Ensure in system_update_8007that it skips entity types which aren't in there
* Write an update path

Remaining tasks

CommentFileSizeAuthor
#65 interdiff.2573667.61.65.txt1.15 KBYesCT
#65 entity_uninstallation-2573667-65.patch26.94 KBYesCT
#61 entity-field_schema_data_uninstall-2573667-61.patch27 KBplach
#61 entity-field_schema_data_uninstall-2573667-61.interdiff.txt2.46 KBplach
#58 entity-field_schema_data_uninstall-2573667-58.patch25.85 KBplach
#58 entity-field_schema_data_uninstall-2573667-58.interdiff.txt22.17 KBplach
#51 comment_uninstall_does-2573667-51.patch3.84 KBplach
#51 comment_uninstall_does-2573667-51.interdiff.txt1.76 KBplach
#45 interdiff.2573667.44.45.txt2.28 KBYesCT
#45 comment_uninstall_does-2573667-45.patch3.68 KBYesCT
#44 interdiff.2573667.36.44.txt2.94 KBYesCT
#44 comment_uninstall_does-2573667-44.patch2.85 KBYesCT
#36 interdiff.2573667.33.36.txt2.67 KBk4v
#36 comment_uninstall_does-2573667-36.patch2.78 KBk4v
#33 comment_uninstall_does-2573667-32.patch2.66 KBk4v
#33 interdiff.2573667.28.32.txt2.09 KBk4v
#30 comment_uninstall_does-2573667-28.patch2.83 KBk4v
#30 interdiff.2573667.27.28.txt765 bytesk4v
#27 interdiff.2573667.21.27.txt2.06 KBYesCT
#27 comment_uninstall_does-2573667-27-test-only.patch1.7 KBYesCT
#26 interdiff.2573667.13.21.txt2.53 KBk4v
#23 comment_uninstall_does-2573667-21.patch1.44 KBk4v
#23 comment_uninstall_does-2573667-21-test-only.patch1.44 KBk4v
#13 comment_uninstall_does-2573667-13.patch2.23 KBk4v
#9 comment_uninstall_does-2573667-9.patch2.03 KBk4v
#8 comment_uninstall_does-2573667-8-test-only.patch1.1 KBk4v
#2 Screen Shot 2015-09-24 at 02.27.03.png308.47 KBdawehner
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

othermachines created an issue. See original summary.

dawehner’s picture

You can certainly reproduce that by install beta 13, uninstall comment and update to HEAD/beta15

The underlying problem is that uninstalls doesn't cleanup those state entries ...

stefan.r’s picture

Title: Update failed (system module - 8007) "Cannot change the definition of field comment.langcode: field doesn't exist." » Comment uninstall does not clean up schema in state properly
Priority: Normal » Major

This sounds major and possibly critical

catch’s picture

Priority: Major » Critical
Issue tags: +D8 upgrade path

If I'm reading #2 correctly it sounds like uninstalling an entity-providing module leaves the entity/field definitions in state - so the system thinks there are tables there which are not?

Bumping to critical for now since this leaves sites in an unrecoverable state and uninstalling a module then updating a core version is fairly basic.

If this is not a generic problem and only something wrong with the beta13-15 update path then it could go back to major though.

k4v’s picture

Assigned: Unassigned » k4v
Issue tags: +Barcelona2015
dawehner’s picture

If I'm reading #2 correctly it sounds like uninstalling an entity-providing module leaves the entity/field definitions in state - so the system thinks there are tables there which are not?

Exactly

k4v’s picture

k4v’s picture

First the test, that should come out red.

k4v’s picture

This could be green..... please.

Berdir’s picture

Status: Active » Needs review
k4v’s picture

Berdir’s picture

+++ b/core/modules/system/system.module
@@ -1424,3 +1424,21 @@ function system_path_insert() {
+
+/**
+ * Implements hook_modues_uninstalled().
+ */
+function system_modules_uninstalled($modules) {
+  // Clean the storage schema of entities that are gone because their modules

We don't need to implement a hook for this, just put the logic in ModuleInstaller::uninstall().

k4v’s picture

The last submitted patch, 8: comment_uninstall_does-2573667-8-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 13: comment_uninstall_does-2573667-13.patch, failed testing.

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
    @@ -461,6 +461,19 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
    +    $key_value_store = \Drupal::keyValue('entity.storage_schema.sql');
    

    Do we need to take care of entity.definitions.installed as well?

  2. +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
    @@ -461,6 +461,19 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
    +    foreach ($schema as $item_name => $item) {
    

    It feels like a better name for $item would be nice!

  3. +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
    @@ -461,6 +461,19 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
    +      $entity_item = substr($item_name, 0, strpos($item_name, '.'));
    

    We should name it $entity_type_id

  4. +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
    @@ -461,6 +461,19 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
    +      if(!in_array($entity_item, $entities)) {
    
    +++ b/core/modules/comment/src/Tests/CommentUninstallTest.php
    @@ -83,6 +83,21 @@ function testCommentUninstallWithoutField() {
    +      if($entity_item == 'comment') {
    

    Nitpick: code style

  5. +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
    index ad98d68..948e68d 100644
    --- a/core/modules/comment/src/Tests/CommentUninstallTest.php
    
    --- a/core/modules/comment/src/Tests/CommentUninstallTest.php
    +++ b/core/modules/comment/src/Tests/CommentUninstallTest.php
    

    In an ideal world: \Drupal\system\Tests\Entity\EntitySchemaTest would be extended.

    In an ideal world we would also have an update path test.

YesCT’s picture

  1. +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
    @@ -461,6 +461,19 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
    +    // Clean the storage schema of entities that are gone because their modules
    +    // were uninstalled.
    

    I think we can more simply word the comment.

    Delete entities from the storage schema if they are from modules that are not installed.

    Hm...

    We are getting all the entity definitions, and then deleting items from the store if they are not in that list.

    More general than only the case of a module being uninstalled. Oh, but maybe this is in uninstall code... Yep. helps to apply the patch and look at it in context. ;)

    Actually, I read this a few more times, and leaving the comment as is is probably fine.

  2. +++ b/core/modules/comment/src/Tests/CommentUninstallTest.php
    @@ -83,6 +83,21 @@ function testCommentUninstallWithoutField() {
    +    $this->assertEqual($item_count, 0, 'After uninstallation the schema' .
    +      'still contains fields for the the comment entity.');
    +  }
     }
    

    why is this string split into two strings and concatenated?

    maybe to fit on 80 char line? it can just be all one line.

    On thinking some more, the string probably would be better as a comment before the assert. "After uninstalling, ensure there are no comment entity fields in the schema." Then, just let the assert give the default message.

  3. also we want to keep the empty like that was before the closing of the class. https://www.drupal.org/node/608152#indenting
YesCT’s picture

The fail in #8 was nice, just what was expected for the tests only patch.
The fail was:

fail: [Other] Line 101 of core/modules/comment/src/Tests/CommentUninstallTest.php:
After uninstallation the schemastill contains fields for the the comment entity.
Value 19 is equal to value 0.

Interesting, that we got both the custom message and the default X is equal to Y.

(not we should still not concatenate the message though)

k4v’s picture

After talking to @dawehner, I try to move the test code to EntiySchemaTest

xjm’s picture

The last submitted patch, 8: comment_uninstall_does-2573667-8-test-only.patch, failed testing.

The last submitted patch, 13: comment_uninstall_does-2573667-13.patch, failed testing.

k4v’s picture

We moved the test code to EntitySchemaTest.

After feedback from yched and plach we will try to move the fix to SqlContentEntityStorageSchema->onEntityTypeDelete().

dawehner’s picture

Status: Needs work » Needs review
+    $entities = \Drupal::entityManager()->getDefinitions();
+    foreach ($entities as $name => $definition) {
+      if ($definition->getProvider() == 'entity_test') {
+        $this->installEntitySchema($name);
+      };
+    }

Nice!

Now we just need to merge everything together properly

YesCT’s picture

k4v and I are working together.

we figured out how to run the test on the command line! yay
php scripts/run-tests.sh --sqlite /tmp/db2.sqlite --dburl sqlite://localhost//tmp/db2.sqlite --class "\Drupal\system\Tests\Entity\EntitySchemaTest::testCleanUpStorageDefinition"

https://www.drupal.org/node/645286 helped us figure that out (with help from Sutharsan and @dawehner)

--

great. I'm going to do a few clean ups on the test.

let's let the testbot run on it in the mean time. setting to needs review.

k4v’s picture

oops, forgot the interdiff.

YesCT’s picture

some standards and comment improvements on the test.

feedback on the test welcome. :) we are moving onto working on the fix.

The last submitted patch, 23: comment_uninstall_does-2573667-21-test-only.patch, failed testing.

The last submitted patch, 23: comment_uninstall_does-2573667-21.patch, failed testing.

k4v’s picture

This fixes a typo in the last patches.

dawehner’s picture

+++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
@@ -139,4 +139,38 @@ public function testModifyingTranslatableColumnSchema() {
+    // Find all the entity types provided by the entity_test module and install
+    // the schema for them.
+    $entities = \Drupal::entityManager()->getDefinitions();
+    foreach ($entities as $name => $definition) {
+      if ($definition->getProvider() == 'entity_test') {
+        $this->installEntitySchema($name);
+      };
+    }
...
+    // Count the storage definitions that begin with entity_test.
+    foreach (array_keys($schema) as $storage_definition_name) {
+      $entity_item = substr($storage_definition_name, 0, strpos($storage_definition_name, '.'));
+      if ($entity_item == 'entity_test') {
+        $item_count++;
+      }
+    }

What about recording all the entity types above and check all of them after the uninstall?

The last submitted patch, 27: comment_uninstall_does-2573667-27-test-only.patch, failed testing.

k4v’s picture

So finally we moved the fix to SqlContentEntityStorageSchema->onEntityTypeDelete().

YesCT’s picture

Status: Needs review » Needs work

oh... regarding #31
I think I see. The concern is that the entity_test module is providing storage definitions that start with entity_test. and also other patterns.
and then we are only checking that the ones that match exactly the modulename. are being deleted.

we need to find a list of the ones entity_test provides and then make sure all of those were deleted.

The last submitted patch, 33: comment_uninstall_does-2573667-32.patch, failed testing.

k4v’s picture

Assigned: k4v » Unassigned
Status: Needs work » Needs review
FileSize
2.78 KB
2.67 KB

We think we addressed all the concerns.

Before we we only deleting storage definitions that began with the module name. Now we delete storage definitions that begin with entity type ids that are provided by the module.

Also we count the right stuff in the test now and corrected the assertion message.

¡¡¡¡¡¡¡¡¡¡¡YAY!!!!!!!!!!

As far as we know we're done and are looking forward for thorough reviews.

dawehner’s picture

Overall this looks kinda clean, just some non optional variable names.

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -377,6 +377,17 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
    +      if($entity_type_id == $entity_type->id()) {
    

    Some space after the if would be nice

  2. +++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
    @@ -139,4 +139,42 @@ public function testModifyingTranslatableColumnSchema() {
    +    foreach ($entities as $name => $definition) {
    +      if ($definition->getProvider() == 'entity_test') {
    +        $this->installEntitySchema($name);
    +        $entity_type_ids[] = $name;
    

    We could rename $name to $entity_type_id and things would be just way clearer.

  3. +++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
    @@ -139,4 +139,42 @@ public function testModifyingTranslatableColumnSchema() {
    +      $entity_item = substr($storage_definition_name, 0, strpos($storage_definition_name, '.'));
    +      if (in_array($entity_item, $entity_type_ids)) {
    

    If we rename $entity_item to $entity_type_id

  4. +++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
    @@ -139,4 +139,42 @@ public function testModifyingTranslatableColumnSchema() {
    +    $this->assertEqual($item_count, 0, 'After uninstalling entity_test module the schema still contains fields from entities provided by this module.');
    

    The message should be written for the case in which its properly working

+++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
@@ -139,4 +139,42 @@ public function testModifyingTranslatableColumnSchema() {
+    $item_count = 0;

Let's rename $item_count to $containing_entity_type_ids

The last submitted patch, 23: comment_uninstall_does-2573667-21-test-only.patch, failed testing.

The last submitted patch, 23: comment_uninstall_does-2573667-21.patch, failed testing.

The last submitted patch, 27: comment_uninstall_does-2573667-27-test-only.patch, failed testing.

The last submitted patch, 33: comment_uninstall_does-2573667-32.patch, failed testing.

jhedstrom’s picture

This looks great.

One possible suggestion to add while incorporating the feedback above:

+++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
@@ -139,4 +139,42 @@ public function testModifyingTranslatableColumnSchema() {
+    // Uninstall the entity_test module.
+    $this->container->get('module_installer')->uninstall(array('entity_test'));
+
+    // Get a list of all the entities in the schema.
+    $key_value_store = \Drupal::keyValue('entity.storage_schema.sql');
+    $schema = $key_value_store->getAll();
+
+    $item_count = 0;

For completeness sake (even though I'm sure this is tested elsewhere), I think it would be good to ensure these storage schemas are present prior to disabling the module, so we're absolutely sure of this test. A simple count while installing schemas, then an assertion of that count should suffice.

YesCT’s picture

Status: Needs review » Needs work

@dawehner Thank you for reviewing this again. Sorry about 1. You mentioned it before, and I thought I checked it was done... it *is* now!

ok. I am on it.

YesCT’s picture

Status: Needs work » Needs review
FileSize
2.85 KB
2.94 KB

just addressing #37

I will do #42 next.

YesCT’s picture

@jhedstrom thank you for reviewing this.

addressing #42. makes sure there are some storage definitions provided by the entity_test module, before we try and make sure there are none after uninstalling the module.

back to needing comprehensive review.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -377,6 +377,17 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+      $entity_type_id = substr($storage_definition_name, 0, strpos($storage_definition_name, '.'));

+++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
@@ -139,4 +139,59 @@ public function testModifyingTranslatableColumnSchema() {
+      $entity_type_id = substr($storage_definition_name, 0, strpos($storage_definition_name, '.'));

list($entity_type_id, ) = explode('.', $storage_definition_name, 2); would be a bit easier to read?

k4v’s picture

Flight is delayed, I'm @airport

One thing that just came to my mind: This patch does not fix the original problem upgrading from Beta 13 to HEAD. Should there be code to work around this bug to make upgraders happy?

k4v’s picture

I think about putting the cleanup routine in the update hook. Feedback?

plach’s picture

Assigned: Unassigned » plach

Working on a small improvement

plach’s picture

@k4v:

Yes, feel free to write a db update function. It will need to detect all data that's not belonging to an installed entity type returned by the entity manager. We'll need also a db update test to check that this works properly.

plach’s picture

This should be slightly cleaner

plach’s picture

@k4v:

Please file a follow-up for the update stuff, I discussed this with @dawehner and @YesCT and we agreed that part is not critical. We should be done here now, thanks!

dawehner’s picture

@k4v
We discussed this here again. We need an update path, as otherwise things are broken.

dawehner’s picture

@k4v
When do you have boarding? We can work on the update here pretty easy.

plach’s picture

I'll work on the test.

YesCT’s picture

change in #51 makes a lot of sense. thanks @plach

stefan.r’s picture

Patch looks great! Just for completeness can we post a test-only patch?

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -367,9 +367,14 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+      // If we have a field having dedicated storage we need to drop it,

"have...having" sounds a bit odd, maybe just "have...with"?

plach’s picture

Here's the test for the upcoming test function, this is now expected to fail.

Status: Needs review » Needs work

The last submitted patch, 58: entity-field_schema_data_uninstall-2573667-58.patch, failed testing.

The last submitted patch, 58: entity-field_schema_data_uninstall-2573667-58.patch, failed testing.

plach’s picture

And here is the update function, improved also test coverage.

plach’s picture

Title: Comment uninstall does not clean up schema in state properly » Entity uninstallation does not clean up field schema data in state properly
dawehner’s picture

Beside this I think this is ready to go.

+++ b/core/modules/system/system.install
@@ -1648,5 +1648,20 @@ function system_update_8007() {
+    list($entity_type_id, ,) = explode('.', $key);

I'd use explode(, , 2)

+++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
@@ -139,4 +139,59 @@ public function testModifyingTranslatableColumnSchema() {
+      $entity_type_id = substr($storage_definition_name, 0, strpos($storage_definition_name, '.'));
...
+      $entity_type_id = substr($storage_definition_name, 0, strpos($storage_definition_name, '.'));

+++ b/core/modules/system/system.install
@@ -1593,10 +1593,15 @@ function _system_update_create_block($name, $theme_name, array $values) {
+    list($entity_type_id, , ) = explode('.', $item_name);

@@ -1643,5 +1648,20 @@ function system_update_8007() {
+    list($entity_type_id, ,) = explode('.', $key);

Let's use the explode(, , 2) all over the place.

YesCT’s picture

Status: Needs review » Needs work

I will work on that now.

YesCT’s picture

Status: Needs work » Needs review
FileSize
26.94 KB
1.15 KB

since I just want the first thing, I dont need the ,2.

dawehner’s picture

Issue summary: View changes

Thank you YescCT, RTBC conce its green

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Its green now, thank you

Wim Leers’s picture

Silly things that can be fixed on commit:

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -367,9 +367,14 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
    +      // If we have a field having dedicated storage we need to drop it,
    

    Nit: "have … having" is strange.

  2. +++ b/core/modules/system/src/Tests/Entity/EntitySchemaTest.php
    @@ -139,4 +139,59 @@ public function testModifyingTranslatableColumnSchema() {
    +    // Get a list of all the entities in the schema.
    

    s/entities/entity types/

    I could be wrong.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Nice work everyone. Committed 3287afb and pushed to 8.0.x. Thanks!

alexpott’s picture

I didn't address #68 - can be cleanup in a followup.

  • alexpott committed 3287afb on 8.0.x
    Issue #2573667 by k4v, YesCT, plach, dawehner, othermachines: Entity...

Status: Fixed » Closed (fixed)

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