Problem/Motivation

Currently, only a few entity keys are automatically marked as NOT NULL, which is a problem because it gives the impression that entity keys are somehow special and leads to bugs like #2824851: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior.

Also, developers have to know about this limitation of entity API and override \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::getSharedTableFieldSchema() in order to mark fields as NOT NULL.

An additional (major) problem found is that when we are updating the storage definition of an identifier field (e.g. 'id' or 'revision_id'), the primary keys are not updated properly in the SQL schema.

This patch also uncovered a another bug:

- The entity storage schema stores incorrect field schema data for the identifier fields: both the 'id' and 'revision_id' fields are of type int in their base tables, when they should be serial instead

Proposed resolution

Since we can now mark field storages as required (#2390495: Support marking field storage definitions as required) we can also add the NOT NULL database constraint automatically. This will also improve the performance of indexes that are using those required fields.

Pass the new 'primary key' definition when re-adding the identifier field in \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::createSharedTableSchema().

Fix the field storage schema data for the identifier fields for all entity types.

Remaining tasks

Review.

User interface changes

Nope.

API changes

The 'not_null' parameter of SqlContentEntityStorageSchema::addSharedTableFieldIndex() will become deprecated.
Instead, you should set \Drupal\Core\Field\BaseFieldDefinition::setStorageRequired() on the respective field.

Data model changes

Nope.

CommentFileSizeAuthor
#238 2841291-238-8.9.x.patch52.38 KBdhirendra.mishra
#238 interdiff_234-238.txt1.52 KBdhirendra.mishra
#234 interdiff-231-234-9.0.x.txt1.79 KBgnunes
#234 interdiff-231-234-8.9.x.txt1.79 KBgnunes
#234 2841291-234-9.0.x.patch52.46 KBgnunes
#234 2841291-234-8.9.x.patch52.49 KBgnunes
#231 interdiff-229-231-review.txt3.56 KBgease
#231 2841291-231-review.txt52.44 KBgease
#231 2841291-231-combined.patch62.36 KBgease
#229 interdiff-225-229.txt2.07 KBgease
#229 2841291-229.patch53.34 KBgease
#225 2841291-225.patch54.4 KBgease
#220 2841291-test-with-patch-220.patch58.59 KBgease
#220 2841291-test-only-220.patch4.55 KBgease
#217 2841291-215-8.7.patch54.47 KBgease
#216 2841291-215-8.7.patch54.06 KBgease
#215 interdiff_214_215.txt1.43 KBgease
#215 2841291-215.patch54.42 KBgease
#214 interdiff_206_214.txt7.05 KBgease
#214 2841291-214.patch52.85 KBgease
#207 interdiff_201_206.txt2.76 KBgease
#207 2841291-206.patch50.06 KBgease
#206 interdiff_201_206.txt2.32 KBgease
#206 2841291-206.patch49.62 KBgease
#202 2841291-201.patch48.5 KBGaëlG
#201 interdiff_197-201.txt15.49 KBGaëlG
#197 2841291-197.patch48.97 KBtstoeckler
#197 2841291-194-197-interdiff.txt36.23 KBtstoeckler
#194 2841291-194.patch39.13 KBtstoeckler
#192 2841291-192.patch39.16 KBtstoeckler
#192 2841291-191-192-interdiff.txt29.31 KBtstoeckler
#191 2841291-191--full.patch46.57 KBtstoeckler
#191 2841291-188-191--interdiff.txt1.61 KBtstoeckler
#188 2841291-188--full.patch46.59 KBtstoeckler
#188 2841291-186-188-interdiff.txt3.86 KBtstoeckler
#186 2841291-186--full.patch47.29 KBtstoeckler
#185 2841291-185--full.patch70.97 KBtstoeckler
#185 2841291-182-185-interdiff.txt3.22 KBtstoeckler
#183 2841291-180-182-interdiff.txt7.86 KBtstoeckler
#182 2841291-182.patch70.02 KBtstoeckler
#182 2841291-180-182-interdiff.txt867 byteststoeckler
#180 2841291-180--full.patch69.2 KBtstoeckler
#178 2841291-178.patch68.67 KBtstoeckler
#178 2841291-174-178-interdiff.txt17.71 KBtstoeckler
#174 2841291-174--full.patch63.88 KBtstoeckler
#172 2841291-172--full.patch2.79 KBtstoeckler
#170 2841291-170--full.patch63.88 KBtstoeckler
#170 2841291-170--for-review.txt58.77 KBtstoeckler
#165 2841291-165--for-testbot.patch90.04 KBtstoeckler
#158 2841291-155-158--for-robots.patch74.85 KBtstoeckler
#158 2841291-155-158--for-humans.patch59.93 KBtstoeckler
#158 2841291-155-158--interdiff.txt6.45 KBtstoeckler
#155 2841291-155--for-robots.patch70.42 KBtstoeckler
#155 2841291-155--for-humans.patch54.98 KBtstoeckler
#155 2841291-153-155-interdiff.txt691 byteststoeckler
#153 2841291-153--for-robots.patch71.09 KBtstoeckler
#153 2841291-153--for-humans.patch55.66 KBtstoeckler
#153 2841291-151-153-interdiff.txt4.62 KBtstoeckler
#151 2841291-151--for-robots.patch70.69 KBtstoeckler
#151 2841291-151--for-humans.patch51.5 KBtstoeckler
#145 2841291-145.patch46.03 KBtstoeckler
#136 2841291-136.patch67.31 KBtstoeckler
#136 2841291-134-136-interdiff.txt4.41 KBtstoeckler
#134 2841291-134.patch69.36 KBtstoeckler
#134 2841291-132-134-interdiff.txt10.48 KBtstoeckler
#132 2841291-132.patch75.36 KBtstoeckler
#132 2841291-131-132-interdiff.txt25.97 KBtstoeckler
#131 2841291-131.patch83.36 KBtstoeckler
#131 2841291-128-131-interdiff.txt3.56 KBtstoeckler
#128 2841291-128.patch80.94 KBtstoeckler
#128 2841291-125-128-interdiff.txt9.3 KBtstoeckler
#125 2841291-125.patch77.31 KBtstoeckler
#125 2841291-123-125-interdiff.txt36.62 KBtstoeckler
#123 2841291-123.patch50.99 KBtstoeckler
#123 2841291-120-123-interdiff.txt1.32 KBtstoeckler
#120 2841291-120.patch49.67 KBtstoeckler
#120 2841291-117-120-interdiff.txt6.73 KBtstoeckler
#117 2841291-117.patch50.74 KBkfritsche
#112 2841291-112.patch49.34 KBtstoeckler
#112 2841291-107-112--interdiff-2.txt2.33 KBtstoeckler
#112 2841291-107-112--interdiff-1.txt2.47 KBtstoeckler
#107 2841291-107.patch47.74 KBWim Leers
#105 2841291-104--on-top-of-2346019-76.patch47.74 KBtstoeckler
#104 2841291-104.patch47.73 KBtstoeckler
#93 interdiff.txt3.2 KBamateescu
#93 2841291-93.patch47.42 KBamateescu
#90 interdiff.txt1.42 KBamateescu
#90 2841291-90.patch47.43 KBamateescu
#83 2841291-83.patch48.03 KBamateescu
#79 2841291-79-combined.patch55.65 KBamateescu
#79 2841291-79.patch48.03 KBamateescu
#77 2841291-77.patch48.01 KBamateescu
#69 2841291-68.patch52.82 KBamateescu
#64 2841291-64.patch52.82 KBjofitz
#61 2841291-61.patch52.81 KBamateescu
#58 interdiff.txt2.36 KBamateescu
#58 2841291-58.patch53.08 KBamateescu
#57 2841291-57.patch53.11 KBtameeshb
#57 interdiff-52-57.txt4.47 KBtameeshb
#52 2841291-52.patch52.99 KBamateescu
#51 2841291-51.patch62.27 KBamateescu
#49 interdiff.txt5.16 KBamateescu
#49 2841291-49.patch53.15 KBamateescu
#45 interdiff.txt1.92 KBamateescu
#45 2841291-45.patch52.79 KBamateescu
#40 interdiff.txt1.62 KBamateescu
#40 2841291-40.patch53.89 KBamateescu
#37 interdiff.txt37.96 KBamateescu
#37 2841291-37.patch53.89 KBamateescu
#34 interdiff.txt1.25 KBamateescu
#34 2841291-34.patch19.88 KBamateescu
#31 interdiff.txt2.45 KBamateescu
#31 2841291-31.patch19.9 KBamateescu
#30 interdiff-2.txt1.78 KBamateescu
#30 interdiff.txt785 bytesamateescu
#30 2841291-30.patch19.6 KBamateescu
#29 interdiff.txt3.24 KBamateescu
#29 2841291-29.patch18.93 KBamateescu
#28 interdiff.txt8.57 KBamateescu
#28 2841291-28.patch18.41 KBamateescu
#21 2841291-21-8.3.x.patch16.51 KBamateescu
#20 2841291-20.patch16.5 KBamateescu
#20 2841291-20-test-only.patch8.28 KBamateescu
#14 interdiff.txt2.12 KBamateescu
#14 2841291-14.patch10.62 KBamateescu
#11 interdiff.txt2.72 KBamateescu
#11 2841291-11.patch9.68 KBamateescu
#8 2841291-8.patch9.45 KBamateescu
#7 2841291-7.patch9.47 KBamateescu
#5 interdiff.txt2.82 KBamateescu
#5 2841291-5.patch9.45 KBamateescu
#2 2841291.patch6.94 KBamateescu
#2 2841291-test-only.patch2.4 KBamateescu

Issue fork drupal-2841291

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

amateescu created an issue. See original summary.

amateescu’s picture

Fingers crossed :)

The last submitted patch, 2: 2841291-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 2: 2841291.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
FileSize
9.45 KB
2.82 KB

And this is where things get ugly: we need to update the last installed schema definition of the 'id' field for all entity types, but \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::hasColumnChanges() stands in our way.

Here's one possible solution but I'm open to better ideas :)

Status: Needs review » Needs work

The last submitted patch, 5: 2841291-5.patch, failed testing.

amateescu’s picture

Version: 8.2.x-dev » 8.3.x-dev
Status: Needs work » Needs review
FileSize
9.47 KB

Re-rolled for 8.2.x.

amateescu’s picture

The last submitted patch, 7: 2841291-7.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 8: 2841291-8.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
FileSize
9.68 KB
2.72 KB

Of course, 'revision_id' has the same problem in the revision table.

amateescu’s picture

Side note: this patch doesn't apply cleanly to the 8.3.x branch at the moment because of #2842480: system_update_8203() is named incorrectly.

Status: Needs review » Needs work

The last submitted patch, 11: 2841291-11.patch, failed testing.

amateescu’s picture

Status: Needs review » Needs work

The last submitted patch, 14: 2841291-14.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
Berdir’s picture

But some additional test coverage for the Schema.php change and also the update function would be great.

Didn't review too closely yet, need to actually apply the patch to better understand those changed/removed method calls.

You mentioned that this is fixing multiple related problems, I'm OK with keeping it together but we should mention all the problems and fixes in the issue summary.

tstoeckler’s picture

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -999,9 +999,7 @@ protected function addTableDefaults(&$schema) {
-  protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) {
-    $this->processIdentifierSchema($schema, $entity_type->getKey('id'));
-  }
+  protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) { }

@@ -1631,6 +1637,15 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
+    if ($field_name == $this->entityType->getKey('id') && $table_name == $this->storage->getBaseTable()) {
+      $this->processIdentifierSchema($schema, $this->entityType->getKey('id'));
+    }

Can you elaborate why this is necessary?

If it is, it seems the process*Table() methods are pointless and should be removed? Or should we just move them further down in getSchema()?

Berdir’s picture

Status: Needs review » Needs work
amateescu’s picture

Version: 8.3.x-dev » 8.2.x-dev
Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update
FileSize
8.28 KB
16.5 KB

Added test coverage for all the bugs that were found in this issue and also added them to the issue summary.

@tstoeckler, that change is needed because the process*Table() methods are only called when processing the full entity schema (i.e. what's stored in \Drupal::keyValue('entity.storage_schema.sql')->get('node.entity_schema_data')), but we also need to process the identifiers when processing the per-field storage schema (i.e. what's stored in \Drupal::keyValue('entity.storage_schema.sql')->get('node.field_schema_data.nid')).

Edit: The test-only patch is also the interdiff ;)

amateescu’s picture

And here's the patch for 8.3.x which just solves a context change in system.install.

The last submitted patch, 20: 2841291-20-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 21: 2841291-21-8.3.x.patch, failed testing.

tstoeckler’s picture

Re #20: Ahh thanks. That makes sense, but that also means that process*Table() are fairly pointless and arguably problematic, right? Not your fault though, of course.

amateescu’s picture

Status: Needs work » Needs review

Hmm, I wouldn't say pointless, there might be use-cases for processing the shared tables as a whole. And also I don't think we can remove them now, even if they are protected, because some subclasses of SqlContentEntityStorageSchema might rely on them..

The test fail for #21 was a random one, so back to NR.

tstoeckler’s picture

Yeah, true.

Looked through the changes in SqlContentEntityStorageSchema and those look good to me, but didn't review the entire patch and I can't RTBC the DB-layer changes anyway.

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1157,10 +1153,20 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor
    +              // Check if the field is an auto-incremented one, in which case it
    +              // is part of the table's primary key so we need to specify it
    +              // when adding the field.
    +              if ($specifier['type'] == 'serial') {
    +                $entity_schema = $this->getEntitySchema($this->entityType);
    +                $keys_new = ['primary key' => $entity_schema[$table_name]['primary key']];
    +              }
    

    I'm still wondering whether this is the actually the case. It is some kind of limitation which is not strictly needed in SQL itself. Maybe its some limitation we accept for entity API?

  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1650,6 +1656,15 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
    +    if ($field_name == $this->entityType->getKey('id') && $table_name == $this->storage->getBaseTable()) {
    ...
    +    if ($field_name == $this->entityType->getKey('revision') && $table_name == $this->storage->getRevisionTable()) {
    
    @@ -1977,6 +1992,15 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
    +            if ($definition_spec['type'] == 'serial' && $stored_spec['type'] == 'int') {
    +              return FALSE;
    +            }
                 return TRUE;
    

    Let's use strict equal: ===. Note: We could do (!(... conditionn)

  3. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1977,6 +1992,15 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
    +            // Prior to Drupal 8.2.6, the schema for the 'id' and 'revision'
    

    I guess this would be a 8.3.x only change now ...

  4. +++ b/core/modules/system/src/Tests/Update/EntityUpdateTest.php
    @@ -0,0 +1,49 @@
    +  public function testSystemUpdate8203() {
    

    Looking at the test I'm wondering, don't we need some test that actually saving/loading those entities continues to work? Changing schema is tricky conceptually, isn't it?

  5. +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
    @@ -397,7 +397,7 @@ public function testGetSchemaRevisionable() {
    -    $this->storage->expects($this->exactly(2))
    +    $this->storage->expects($this->exactly(6))
    
    @@ -604,7 +604,7 @@ public function testGetSchemaRevisionableTranslatable() {
    -    $this->storage->expects($this->exactly(3))
    +    $this->storage->expects($this->exactly(11))
    

    In case you are bored, change it to atLeastOnce(), as you change this line already. Exacting on 11 calls is just ridicilous :)

amateescu’s picture

Thanks for the review!

#27.1 and .4: These were *very* helpful in uncovering the fact that we were indeed changing the schema incorrectly, because DBTNG did not support updating composite primary keys. Now fixed and tested in the new patch.

#27.2, 3 and 5: Fixed :)

amateescu’s picture

Fixed PostgreSQL and SQLite. This should be ready for another review now :)

amateescu’s picture

I think this needs to be tested on a filled database as well. And since it will probably not be committed to 8.2.x, changed the update function to be 8.3.x only.

amateescu’s picture

Fixed and updated some docs and also explained the third (!) DBTNG bug that's being fixed here.

timmillwood’s picture

+++ b/core/modules/system/src/Tests/Update/EntityUpdateTest.php
@@ -0,0 +1,62 @@
+   * Tests system_update_8203().
...
+  public function testSystemUpdate8203() {

This should be 8302

Apart from this nit pick it looks good.

Berdir’s picture

Status: Needs review » Needs work
+++ b/core/modules/system/system.install
@@ -1826,5 +1827,31 @@ function system_update_8301() {
 /**
+ * Correct the last installed storage schema for every entity type's ID and
+ * revision fields.
+ */
+function system_update_8302() {

In case we care about keeping this < 80 characters, we can easily replace "for every entity type's ID and.." with a simple "for all ID and .."

Other than that and the nitpick above, this looks good to me as well. Great work on all those fixes and the test coverage.

amateescu’s picture

Status: Needs work » Needs review
FileSize
19.88 KB
1.25 KB

Done!

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

Lets do this! :)

amateescu’s picture

Status: Reviewed & tested by the community » Needs review

I just found that this issue can not be separated from #2850686: Handle NOT NULL correctly at the storage level for required fields, because the update function introduced here will blow up once the code changes from that issue are applied. It's weird that the tests didn't fail over there for the combined patch, so I queued a few more to see what's up.

In the meantime, let's wait a bit with this one.

amateescu’s picture

Issue summary: View changes
FileSize
53.89 KB
37.96 KB

The test fail for php 7 from #2850686-2: Handle NOT NULL correctly at the storage level for required fields confirmed my suspicion above, so we have to bring that patch in the scope of this issue.

The reason is that the update function from the current patch here relies on the code from HEAD which makes entity keys NOT NULL in the storage, but the other patch changes that part of the code and only marks required fields as NOT NULL.

Here's the combined patch from #2850686-5: Handle NOT NULL correctly at the storage level for required fields, and the interdiff to #34. I also merged the other issue's summary into this one.

amateescu’s picture

Issue tags: +Performance

Forgot to bring the tag as well.

amateescu’s picture

Title: The primary key is not correctly re-created when updating the storage definition of an identifier field » Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
amateescu’s picture

hchonov’s picture

2841291-40.patch system_update_8302 is failing when running on our system for a custom entity type with the Exception:

Exception thrown while performing a schema update. Cannot change the definition of field custom_entity_type_field_data.text: field doesn't exist.

This is a required text_long base field and in the field_data table there is no column text, but the columns text__format and text__value. I guess there is some logic error trying to retrieve the column text not considering that there is no such column but split into two columns.

cspitzlay’s picture

The exception reported in #41 could be solved by reordering our update-hooks so the status field is made part of the entity keys first (like it is done for the comment module via system_update_dependencies()).

amateescu’s picture

@hchonov, @cspitzlay, do you think it's worth trying to make system_update_8302() run last at all times? In that case, maybe it should be a post_update hook instead.

catch’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1977,6 +1977,15 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
               if ($definition_spec != $stored_spec) {
    +            // Prior to Drupal 8.3.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.
    +            // @see system_update_8302()
    +            // @see https://www.drupal.org/node/2841291
    +            if ($definition_spec['type'] == 'serial' && $stored_spec['type'] == 'int') {
    +              return FALSE;
    +            }
    

    Can this be removed once the update has run? If so should it have a @todo/followup to remove the logic in 8.5.x?

  2. +++ b/core/modules/aggregator/src/Tests/FeedParserTest.php
    index 782ac6f..4c2891c 100644
    --- a/core/modules/aggregator/tests/src/Kernel/AggregatorTitleTest.php
    
    --- a/core/modules/aggregator/tests/src/Kernel/AggregatorTitleTest.php
    +++ b/core/modules/aggregator/tests/src/Kernel/AggregatorTitleTest.php
    
    +++ b/core/modules/aggregator/tests/src/Kernel/AggregatorTitleTest.php
    +++ b/core/modules/aggregator/tests/src/Kernel/AggregatorTitleTest.php
    @@ -51,6 +51,7 @@ public function testStringFormatter() {
    
    @@ -51,6 +51,7 @@ public function testStringFormatter() {
         $aggregator_feed = Feed::create([
           'title' => 'testing title',
           'url' => 'http://www.example.com',
    +      'refresh' => 900,
         ]);
         $aggregator_feed->save();
     
    

    Should we set a default of 900 somewhere instead of requiring this to be passed in? Seems like it could break any programmatic creation of feeds that's not setting refresh. Or is this something that should never happen outside tests?

  3. +++ b/core/modules/node/node.install
    +++ b/core/modules/node/node.install
    @@ -189,7 +189,9 @@ function node_update_8002() {
    
    @@ -189,7 +189,9 @@ function node_update_8002() {
       // Regenerate entity type indexes, this should drop "node__default_langcode".
       $manager->updateEntityType($manager->getEntityType('node'));
       // Regenerate "langcode" indexes, this should drop "node_field__langcode".
    -  $manager->updateFieldStorageDefinition($manager->getFieldStorageDefinition('langcode', 'node'));
    +  $field_definition = $manager->getFieldStorageDefinition('langcode', 'node');
    +  $field_definition->setStorageRequired(TRUE);
    +  $manager->updateFieldStorageDefinition($field_definition);
     }
    

    Why editing the existing update? This should never run again.

  4. +++ b/core/modules/system/system.install
    @@ -1088,6 +1090,18 @@ function system_schema() {
     /**
    + * Implements hook_update_dependencies().
    + */
    +function system_update_dependencies() {
    +  // system_update_8302() has to run after the 'status' field was made an entity
    +  // key for the Comment entity type.
    +  $dependencies['system'][8302] = array(
    +    'comment' => 8301,
    +  );
    +  return $dependencies;
    +}
    +
    +/**
    

    Comment should ensure it runs before I think? That's also an easier example for contrib to copy if it needs to. Also updates need renumbering to 84**

amateescu’s picture

Thanks for taking a look!

#44.1: That depends whether we want to allow people to upgrade directly from 2 or more prior minor versions, e.g. from 8.3.0 straight to 8.5.0, skipping 8.4.0.

#44.2. Technically this shouldn't happen if the user validates the entity before saving, as that will trigger the 'required' validation error for the refresh base field, but not everyone does that :/ We could set a default value of '900' for that field if we want to babysit code that doesn't do any validation before saving.

#44.3: Similar to 1), if we want to allow people to update from 8.0.0 to 8.4.0 directly, we need to change the existing update function. Since I haven't seen any real obstacles so far that would prevent us from supporting updates over multiple minor versions, I think we should try to keep it that way. But maybe this needs a bigger policy discussion.

#44.4: Good point, fixed :)

Status: Needs review » Needs work

The last submitted patch, 45: 2841291-45.patch, failed testing.

cspitzlay’s picture

Status: Needs work » Needs review

Regarding #43: I will talk to tstoeckler about this next week and
we will get back to you on that.

catch’s picture

#44.1 - hmm OK but should we add a note to remove in 9.x, possibly trigger_error('...', E_USER_DEPRECATED) in that code path?

So #44.3 is purely a bugfix to the existing update, yeah that's fine to keep. We don't explicitly support going from 8.0.x to 8.3.x, but we don't prevent people trying to either.

#44.2 my guess is that would be the default case for custom code that's creating feeds (install profiles and similar things), so we should probably babysit - but we could also deprecate the babysitting and trigger_error() with E_USER_DEPRECATED maybe?

amateescu’s picture

should we add a note to remove in 9.x, possibly trigger_error('...', E_USER_DEPRECATED) in that code path?

Added a note to remove that code in 9.0.x. I think trigger_error() should only be used when developers actually have a way to fix/update their code in order to not trigger the error anymore, but that's not the case here.

Also added a default value for the 'refresh' field of aggregator feeds, I agree that it makes sense :)

tstoeckler’s picture

Status: Needs review » Needs work

Re #43: Since the update doesn't actually modify any database schema or such I think it would be safe to turn it into a post-update instead and, thus, would also make the update path safer for people with custom entity types. Marking needs work for that.

amateescu’s picture

Status: Needs work » Needs review
FileSize
62.27 KB

Re #50: I started to do that locally but then I realized that hook_post_update_NAME() does not have a dependency resolution system like hook_update_N(), and it's very likely that converting the current update function to a post_update one will conflict with post update functions that are converting entity types to be revisionable, because this one needs to run first.

So, I'm sorry to say but #43/#50 is not really possible, this needs to remain a hook_update_N() implementation :/

In the meantime, here's a reroll.

amateescu’s picture

Oops, the patch from #51 contains the work started for the post_update change, this should be better.

The last submitted patch, 51: 2841291-51.patch, failed testing.

timmillwood’s picture

Status: Needs review » Reviewed & tested by the community

I can't see any issues with this.

jibran’s picture

Status: Reviewed & tested by the community » Needs work

NW for the renaming of node_update_8301.

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1977,6 +1977,16 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
    +            // @see system_update_8302()
    

    8401

  2. +++ b/core/modules/aggregator/aggregator.install
    @@ -58,3 +58,22 @@ function aggregator_update_8200() {
    +function aggregator_update_8400() {
    

    Do we need a separate test for this?

  3. +++ b/core/modules/node/node.install
    @@ -253,5 +255,18 @@ function node_update_8301() {
    + * Update the 'uid' field in order to make it required at the storage level.
    

    Do we need a test for this update hook?

  4. +++ b/core/modules/node/node.install
    @@ -253,5 +255,18 @@ function node_update_8301() {
    +function node_update_8302() {
    

    Shouldn't it be 8401 and in 8.4.x updates group?

  5. +++ b/core/modules/system/src/Tests/Update/EntityUpdateTest.php
    @@ -0,0 +1,62 @@
    +   * Tests system_update_8302().
    ...
    +  public function testSystemUpdate8302() {
    

    8401 now.

  6. +++ b/core/modules/system/system.install
    @@ -1812,7 +1814,7 @@ function system_update_8202() {
    + * @addtogroup updates-8.3.x
    

    s/8.3.x/8.4.x

jibran’s picture

It is a major bug. Is it going to be backported to 8.3.x? If yes then update hooks name should 83xx.

tameeshb’s picture

Status: Needs work » Needs review
FileSize
4.47 KB
53.11 KB

Made changes as recommended in #55
Please review! :)

amateescu’s picture

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -1982,7 +1982,7 @@
interdiff impossible; taking evasive action
reverted:

I don't understand this interdiff at all.. it changes the same file (node.install) twice?

Anyway, I made the changes for #55 myself:
#55.1, 4, 5, 6: Fixed.
#55.2: I don't think so, it's a very simple change.
#55.3: Nope, not doing this update would make all our update tests fail so it's already tested implicitly many times over.

The interdiff attached is for #52.

--

It is a major bug. Is it going to be backported to 8.3.x? If yes then update hooks name should 83xx.

This issue is similar to #2248983: Define the revision metadata base fields in the entity annotation in order for the storage to create them only in the revision table in terms of "big changes" to the entity storage API, so I don't think it should be committed to 8.3.x at this point.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

tstoeckler’s picture

Status: Reviewed & tested by the community » Needs review

I'm not quite content with #51 TBH. I need to play around with this and our live site that this broke a bit more, though, before I can write a proper response. So setting back for now. If I don't post anything by Monday feel free to re-RTBC.

timmillwood’s picture

Status: Needs review » Reviewed & tested by the community

Re-RTBC'ing as @tstoeckler suggested in #60.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 61: 2841291-61.patch, failed testing.

jofitz’s picture

Status: Needs work » Needs review
FileSize
52.82 KB

Re-rolled.

amateescu’s picture

@Jo Fitzgerald, why do you think this patch needed a re-roll? The one from #61 applies without any error to 8.4.x.

Also, the patch in #64 has a different file size than #61. What else was changed in there?

jofitz’s picture

Sorry, I think that was my mistake. I've been switching between 8.3.x and 8.4.x too much and suspect I rolled that patch against the wrong version. Please ignore it.

Status: Needs review » Needs work

The last submitted patch, 64: 2841291-64.patch, failed testing.

jofitz’s picture

Status: Needs work » Reviewed & tested by the community

Re-setting to RTBC - #63 appears to have been a testbot glitch (and then I further confused things with an incorrect re-roll in #64).

amateescu’s picture

Re-uploading the correct patch so it's the last visible one...

catch’s picture

that converting the current update function to a post_update one will conflict with post update functions that are converting entity types to be revisionable, because this one needs to run first.

Just to say I don't think we'd ever convert entities to revisionable in a post-update? Am I missing something?

amateescu’s picture

@catch, we do actually in #2721313: Upgrade path between revisionable / non-revisionable entities :) The "schema converter" class introduced there is meant to be run in a post-update hook so it can update the definitions and copy the data in a single place.

tstoeckler’s picture

So our local errors were actually caused by a separate bug, so I don't want to hold this up anymore.

I haven't reviewed #2721313: Upgrade path between revisionable / non-revisionable entities yet, but my intuition is the same as @catch in #70 that making entities revisionable in a post-update is a bad idea. So I still don't really buy #51 TBH, but I don't feel that strongly about it.

However, I don't see a change notice for this. A lot of (custom and contrib) modules will want to add status fields to their entities just like comment and they will all potentially hit the same update ordering issue as Comment module, so we should add a change notice recommending hook_update_dependencies().

tstoeckler’s picture

amateescu’s picture

@tstoeckler, that's fair so I wrote a change record, hope it's detailed enough :) https://www.drupal.org/node/2858195

tstoeckler’s picture

Wow, that's awesome, thanks a lot!

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Triaged core major

Discussed with @catch, @cilefen, @cottser, @laurii, and @xjm. We agreed that this is a major issue as the entity API is complex enough without requiring developers to know that they need override specific parts to support NOT NULL fields.

This overlaps with other issues for example #2615496: A serial/primary key field can not be added to an existing table for some databases. The issue summary reads like a list of issues that can and should be addressed separately. Is there any reason why they are not?

amateescu’s picture

The issue summary reads like a list of issues that can and should be addressed separately. Is there any reason why they are not?

Mostly because I'm currently working with a dependency chain of 6 issues (#2820848: Make BlockContent entities publishable) and it's getting really really hard to keep all of them clean and updated :) But one more won't kill me so I moved the DBTNG parts over to #2615496: A serial/primary key field can not be added to an existing table for some databases.

I also had an attempt to further split out this patch in two but it was unsuccessful, see #36/#37.

No interdiff because I only removed the parts that were posted in #2615496-23: A serial/primary key field can not be added to an existing table for some databases.

amateescu’s picture

Title: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
Status: Needs review » Postponed

Obviously, this will fail without the DBTNG fixes :)

amateescu’s picture

lokapujya’s picture

In mysql, the field type is still INT, not SERIAL. Right?

amateescu’s picture

Yes, 'serial' is just a DBTNG specification for an auto incremented INT.

daffie’s picture

Title: [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
amateescu’s picture

Nice! Re-uploading the patch from #79 which was RTBC before.

Note that I still think this is not backportable to 8.3.x due to the amount of changes in the entity storage. Contrib and custom code will need a few months to update their tests for required fields.

jibran’s picture

This was RTBC in #76 after that @amateescu removed #2615496: A serial/primary key field can not be added to an existing table for some databases code and just the rerolled the patch.

hchonov’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
    @@ -1207,22 +1207,26 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
         if ($entity_type->hasKey('revision')) {
           $fields[$entity_type->getKey('revision')] = BaseFieldDefinition::create('integer')
             ->setLabel(new TranslatableMarkup('Revision ID'))
             ->setReadOnly(TRUE)
    +        ->setStorageRequired(TRUE)
             ->setSetting('unsigned', TRUE);
    
    +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1607,35 +1604,29 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
    +    $field_storage_is_required = $storage_definition->isStorageRequired();
    ...
    +    if ($table_name == $this->storage->getBaseTable() && $field_name == $this->entityType->getKey('revision')) {
    +      $field_storage_is_required = FALSE;
         }
    

    Why do we mark the revision field in CEB::baseFieldDefinitions as storage required and then pretend it is not storage required in SqlContentEntityStorageSchema ::getSharedTableFieldSchema?

  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1607,35 +1604,29 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
    -      $schema['fields'][$schema_field_name]['not null'] = in_array($field_name, $not_null_keys);
    +      $schema['fields'][$schema_field_name]['not null'] = $field_storage_is_required && $properties[$field_column_name]->isRequired();
    

    I am not really sure that I understand this correctly but what will happen if the field storage is required and we have two properties - one required and one not? Then in the first loop we set NOT NULL to TRUE and then in the second to FALSE? I guess we should first check if not already set to TRUE, right?

hchonov’s picture

+++ b/core/modules/comment/comment.install
@@ -114,6 +114,17 @@ function comment_schema() {
+function comment_update_dependencies() {
+  // comment_update_8301() needs to run before system_update_8401().
+  $dependencies['system'][8401] = array(
+    'comment' => 8301,
+  );
+  return $dependencies;

Probably out of scope of this issue, but why is it even possible that some update hook 84** will be executed before an update hook 83**? Is this a desired/expected behavior?

Berdir’s picture

83xx and 84xx is just a convention, the only thing that matters is that updates for a specific module are run in order. And the update system doesn't ensure a specific order across multiple modules, that's when you need that hook.

amateescu’s picture

Re #85:

1. The comment from SqlContentEntityStorageSchema ::getSharedTableFieldSchema() just above the code you quoted describes in detail why we need to make the revision field nullable on the base table :)

2. Each required column of a field will get NOT NULL => TRUE if the field storage is required, the foreach loop doesn't influence the previous or the next column.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
  1. The additional rigour introduced by this patch is really nice. It is going to be a big win for data integrity.
  2. Really sorry this review has taken so long to be posted.
  3. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php
    @@ -117,4 +118,12 @@ public function onChange($property_name, $notify = TRUE) {
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
    +    $values['value'] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
    +    return $values;
    +  }
    

    How come we have to do this in this issue?

  4. +++ b/core/modules/comment/comment.install
    @@ -114,6 +114,17 @@ function comment_schema() {
     /**
    + * Implements hook_update_dependencies().
    + */
    +function comment_update_dependencies() {
    +  // comment_update_8301() needs to run before system_update_8401().
    +  $dependencies['system'][8401] = array(
    +    'comment' => 8301,
    +  );
    +  return $dependencies;
    +}
    

    Scary. How much contrib / custom going to run into this?

  5. +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
    @@ -59,14 +59,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +      // @todo Make this field required in https://www.drupal.org/node/2817835.
    ...
    +      // @todo Make this field required in https://www.drupal.org/node/2817835.
    

    The current rtbc patch is not going to be able to do this. Why do we have to make this change here?

  6. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
    @@ -213,11 +213,10 @@ protected function processStubRow(Row $row) {
    -          $values[] = $default_value;
    +          $values = reset($default_value);
    

    This looks weird. Why did we have to make this change here?

  7. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
    @@ -213,11 +213,10 @@ protected function processStubRow(Row $row) {
    -          $field_type = $field_definition->getType();
    

    Must be an unnecessary change. Looks out-of-scope.

  8. +++ b/core/modules/node/node.install
    @@ -189,7 +189,9 @@ function node_update_8002() {
    -  $manager->updateFieldStorageDefinition($manager->getFieldStorageDefinition('langcode', 'node'));
    +  $field_definition = $manager->getFieldStorageDefinition('langcode', 'node');
    +  $field_definition->setStorageRequired(TRUE);
    +  $manager->updateFieldStorageDefinition($field_definition);
    

    What about sites that have already had this update run on them? Don't we need to ensure langcode is required on them? Yep this is covered by system_update_8401(). How come we're changing this update and not using hook_update_dependencies()?

  9. +++ b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php
    @@ -135,14 +135,12 @@ protected function modifyBaseField() {
       /**
    -   * Promotes a field to an entity key.
    +   * Makes a base field required.
        */
    -  protected function makeBaseFieldEntityKey() {
    -    $entity_type = clone $this->entityManager->getDefinition('entity_test_update');
    -    $entity_keys = $entity_type->getKeys();
    -    $entity_keys['new_base_field'] = 'new_base_field';
    -    $entity_type->set('entity_keys', $entity_keys);
    -    $this->state->set('entity_test_update.entity_type', $entity_type);
    +  protected function makeBaseFieldRequired() {
    +    $definitions = \Drupal::state()->get('entity_test_update.additional_base_field_definitions', []);
    +    $definitions['new_base_field']->setRequired(TRUE);
    +    $this->state->set('entity_test_update.additional_base_field_definitions', $definitions);
       }
    

    Not entirely sure that we should be replacing this method here. An entity key is not quite the same as a required field. Also this is a trait - contrib / custom tests could be using it and the breakage seems unnecessary.

  10. +++ b/core/modules/system/system.install
    @@ -1927,3 +1929,47 @@ function system_update_8400(&$sandbox) {
    +            $original_entity_type->getKey('langcode'),
    

    So here is where the node langcode will get updated - still strange to go and "fix" the old update. I'm really concerned about contrib and custom entity updates needing changes or hook_update_dependencies() as a result of this change. Do we think it is worth not making the fix so generic. Ie. having entities opt in to the new behaviour and able to upgrade at there own pace. I.e. an update per entity type rather than this catch all? OTOH it is great that the change record tries to tell people what to do.

amateescu’s picture

Thanks for getting to this one, Alex :)

3. This was necessary in a previous iteration of the patch but I have the feeling that it might not be needed anymore. Let's try it out.

4. Will reply at the end :)

5. Because *a lot* of CM test will have to be updated, which would make this patch huge. I will open a followup to investigate and fix them.

6. That's because the logic there is simply broken and not tested. With this patch, we're providing some indirect test coverage. Do you want a followup issue for adding some dedicated test coverage as well?

7. Yeah, it was just an unused variable that I removed. Anyway, reverted for now.

8. It's true that this could be fixed by using hook_update_dependencies() but in this particular case, sites that already ran that update function won't be impacted in any way. The change only affects sites upgrading from 8.0.0 to 8.4.0 directly, and I personally don't think that warrants the use of hook_update_dependencies().

9. That method name change should be looked at in the context of its (single) usage in core, which has the following comment:

-    // Promote the base field to an entity key. This will trigger the addition
-    // of a NOT NULL constraint.
-    $this->makeBaseFieldEntityKey();

The purpose of the old method was to trigger the addition of a NOT NULL constraint, and, since we're changing entity keys to not be marked as NOT NULL automatically, contrib or custom code tests that might be using that method won't be testing what they thought they would be testing anymore. That's why I think they need to be explicitly aware of this change and update their code/tests.

10. and 4.: If we make the fix "optional" and let each entity type update on their own, I think some contrib and custom entity types will never do it, which will have a big impact on their future support for basically everything worked on by the Workflow Initiative:

- they will not be able to update their 'id' or 'revision_id' base fields (i.e. what this patch fixes generically)
- they will not be able to use the new "initial values" addition for base fields (#2346019: Handle initial values when creating a new field storage definition)
- they will not be able to be converted to be revisionable and publishable
- they will not be able to participate in any current or future developments in the WI "area": Content Moderation, Trash, Workspaces

Given all that, I personally think we should go ahead with the generic fix approach and help with any problems encountered by contrib and custom code on a case by case basis. This is also why the current issue, while being a major bug, should not go into 8.3.x but straight to 8.4.x instead, so people have at least a few months in advance to spot any potential problems.

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

Putting back to RTBC to get committer feedback.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs followup

Re #90
5. yep a followup would be great.
6. yep a followup would be great too.
8. I'm not sure I agree changing an update should be thing of last resort - hook_update_dependencies() is for this exact case
9. Well the problem here is that writing a test that works against 8.3.x and 8.4.x for contrib will be hard - I think we should prioritise easy test coverage. Why not deprecate the method and make it call the new method?
10. Good points - I'd like catch's thoughts on this one too - will ping him.

amateescu’s picture

Assigned: Unassigned » catch
Status: Needs work » Needs review
FileSize
47.42 KB
3.2 KB

@alexpott #92:

5. here it is: #2871036: [PP-1] Fix Content Moderation and Workspace tests and mark the 'workflow', 'moderation_state' and 'workspace' base fields as required again

6. and another one :) #2871026: Add test coverage for \Drupal\migrate\Plugin\migrate\destination\EntityContentBase::processStubRow()

8. actually, this particular problem with the 'langcode' field was fixed by the extra test coverage you requested in #2615496-26: A serial/primary key field can not be added to an existing table for some databases, which led to fixing even more subtle bugs in DBTNG! So changing the existing update function is no longer necessary.

9. sure thing, we can keep the old method and mark it as deprecated :)

Marking as NR for @catch.

catch’s picture

I'd prefer per-entity updates here. It means when contrib is incompatible with workflow it'll have to be patched/updated separately, but it makes the upgrade path much more predictable.

We've had several issues with the 8.2.x to 8.3.x upgrade path (and especially 8.1.x to 8.3.x) - only one of these was a core bug, but several interactions with contrib updates and a drush bug that took hours/days to track down. So the less variables in the upgrade path the better.

amateescu’s picture

Issue tags: -Needs followup

The problem with per-entity updates is that it's not only about splitting the update function in X (the number of content entity types in core) but it requires much more work for both core and contrib/custom code and it also comes with additional complexity to Entity API in general:

- we will need a flag in the entity definition which will be used to determine if an entity type should have its entity keys marked as NOT NULL (the current behavior in HEAD) or all of its required fields (the behavior proposed by this patch)
- the entity storage will have to maintain both branches of the code that deals with adding NOT NULL to a column, and switch between the two behaviors based on the flag mentioned above
- each potential contrib/custom entity storage class will have to take care of this separation as well
- many other places will need to know about this flag and allow or disallow some things based on it. A few of them I already mentioned in #90, for example: disallow setting an initial value for a base field, which in turn means disallow adding a 'published' base field, which then means that an entity type can not be converted to publishable, etc. Note that these are just a few examples that I have fresh in my mind because of the immediate work planned for WI.

And all this added complexity for an update function that simply updates the 'id', 'revision_id', 'uuid', 'langcode' and the 'status' base fields. I'm sorry but I don't think it's worth it...

amateescu’s picture

OTOH, it is true that contrib and custom modules will need at least some test updates to take care of fields that are marked as required but not populated during tests, but, to answer #89.10, it's not their existing update functions that will need to be changed.

I'm really on the fence with this one. Can we get some more opinions from other entity maintainers?

And just to be clear.. I'm not opposed to implementing all the things mentioned in #95, I just want us to be sure that the additional complexity is worth it :)

dixon_’s picture

This issue was discussed during the Hard Problems meeting at DrupalCon in Baltimore. See notes and decisions here: https://docs.google.com/document/d/1-5B-Gndhklns20NZpGQul10525rFWuYB2NbT...

tstoeckler’s picture

+++ b/core/modules/node/node.install
@@ -246,3 +246,16 @@ function node_update_8301() {
+/**
+ * Update the 'uid' field in order to make it required at the storage level.
+ */
+function node_update_8400() {
+  // The SQL content entity schema no longer uses entity keys to mark their
+  // corresponding fields as NOT NULL in the database, so we need to update the
+  // 'uid' field manually.
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  $field_storage_definition = $definition_update_manager->getFieldStorageDefinition('uid', 'node');
+  $field_storage_definition->setStorageRequired(TRUE);
+  $definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
+}

+++ b/core/modules/node/src/Entity/Node.php
@@ -385,6 +385,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+      ->setStorageRequired(TRUE)

I just hit this locally on our project as well.

I think this should be part of the change notice, that if you have an entity type that declares a uid entity key, you need to decide whether you want to make it storage required and write an update path (either way).

The other entity keys (id, uuid, etc.) are handled automatically for everyone calling ContentEntityBase::baseFieldDefinitions() so I can see how we don't need a change notice for that (although might still be nice to mention it) and by system_update_8401(), but for this one, every contrib/custom module with an entity type with a uid key needs to explicitly handle this with an update hook.

Thoughts?

timmillwood’s picture

Status: Needs review » Needs work

Switching to "needs work" based on discussions in #97 and points from #98.

tstoeckler’s picture

Just as another data point, I hit a problem simliar to #98 with the contributed Media Entity module, since we're already running with this patch. That particular module won't be a problem obviously, because it's now in core, but I can see other modules, that have been in development for a long time during the D8 cycle that have not gotten around to do parent::baseFieldDefinitions() in their own baseFieldDefinitions() method. So I now feel a bit more strongly that a section should be added to the change notice for entity types that don't use the generic base field defintions (for whichever reasons).

tstoeckler’s picture

Just re-read the change notice and found

only base field definitions that are marked as 'required' with setStorageRequired() or setRequired() will be marked as NOT NULL automatically

I was not aware of isStorageRequired() falling back to isRequired() for BaseFieldDefinitions. That lead me to think this whole thing through again. And since I was already pretty deep down the rabbit hole I went ahead and restructured and expanded the change notice a bit so that - at least to me as a potential contrib/custom module developer - it is more actionable. @amateescu: It would be awesome if you could have a look at it and revert anything that is not correct, or further improve it in any way.

However, I do have some more thoughts on the actual patch after reading through it now. At least 2. is a "needs work" to me, unless of course I'm missing something.

  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
    @@ -1222,22 +1222,26 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +        ->setStorageRequired(TRUE)
    ...
    +        ->setStorageRequired(TRUE);
    ...
    +        ->setStorageRequired(TRUE)
    ...
    +        ->setStorageRequired(TRUE)
    

    Any specific reason for marking them "just" storage required vs. required?

    Since I was not clear about this the change notice is also a bit vague about this, at the moment, by the way.

  2. I am not seeing the bundle field being marked as (storage) required, that should definitely happen, though, right?Ahh,
    the bundle field is being marked as required already in HEAD. Missed that, sorry.
  3. +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
    @@ -216,7 +216,8 @@ protected function buildBaseFieldDefinitions($entity_type_id) {
    +          ->setDefaultValue(TRUE)
    +          ->setRequired(TRUE);
    

    If the others are storage required (but not required), it seems this should too?

  4. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php
    @@ -2,6 +2,7 @@
    +use Drupal\Core\Field\FieldDefinitionInterface;
    

    Not used.

  5. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
    @@ -213,7 +213,7 @@ protected function processStubRow(Row $row) {
    -          $values[] = $default_value;
    +          $values = reset($default_value);
    

    Sorry if this was asked before already, but is this in scope of the issue? I don't see any explanation nor test coverage, so it's hard to tell.

Edit: 2 is in fact bogus, updated for that. So would be nice to get a response to the above points but seems pretty close to RTBC then to me.

amateescu’s picture

@tstoeckler, the updates to the CR look very good and useful, thanks for taking the time to do that!

Unfortunately, the outcome of the discussion that @dixon_ mentioned in #97 did not make it back into the issue, so here it is: this change was deemed to risky at this point in the 8.x cycle so, if we want to go through with it, we will need to provide a BC layer by making the new NOT NULL behavior opt-in (through an entity type definition key or something).

That does not invalidate the current patch/CR, it's just that it will need a lot more work to get it done :/

Re #98:

It's not only about having the 'uid' field in the entity keys, every field that is specified as an entity key and not marked as required (or storage required) yet will need to be handled in an update function.

Re #101:
1. The difference between storage required and required is that a field that is marked as required also makes its widget required in an entity form, while storage required has no influence over the UI at all.

3. That's true, it can be set to storage required but, since this field is not editable through the UI, I simply chose to mark it as required.

5. That was brought up before, see #90.6 :)

tstoeckler’s picture

Re #102:

Hmm... that is kind of unfortunate. But fairly understandable given the extent of the change notice and the fact that missing something can lead to fatals that are possibly unrecoverable. Are there more specific plans, though? A flag on the entity type seems sensible to me and I could try to cook something up in that direction but not if anything concrete was already discussed.

It's not only about having the 'uid' field in the entity keys, every field that is specified as an entity key and not marked as required (or storage required) yet will need to be handled in an update function.

Yes, exactly. I tried to make that clear in the change notice. I do think there is value in explicitly mentioning all examples from core, though, as a lot of people both in contrib and custom copy-paste.

1. The difference between storage required and required is that a field that is marked as required also makes its widget required in an entity form, while storage required has no influence over the UI at all.

3. That's true, it can be set to storage required but, since this field is not editable through the UI, I simply chose to mark it as required.

So these two together don't really make sense to me. None of the other entity keys except for language are editable in the UI either, but we set those to storage required. Thinking about this some more I actually think the latter makes sense because we do care explicitly about the storage, not the UI. So I think it would make sense to make the default langcode storage required, as well.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
47.73 KB

Here's a re-roll first. Still waiting for more info before going any further with this, but just want to point out that I could make some time for this if we can agree on a direction.

tstoeckler’s picture

OK, so this currently conflicts with #2346019: Handle initial values when creating a new field storage definition so here's a patch on top of the latest one (#76) from there, since I needed that for our project and it seems that one is going to land a bit sooner than this.

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

Wim Leers’s picture

Wim Leers’s picture

Assigned: catch » Unassigned
Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -2058,6 +2061,16 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
    +            if ($definition_spec['type'] == 'serial' && $stored_spec['type'] == 'int') {
    

    Do we want ===?

  2. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php
    @@ -2,6 +2,7 @@
    +use Drupal\Core\Field\FieldDefinitionInterface;
    

    Unused.

  3. +++ b/core/modules/comment/tests/src/Kernel/Views/CommentLinksTest.php
    @@ -26,14 +33,26 @@ class CommentLinksTest extends CommentViewsKernelTestBase {
    +      'entity_id' => $host->id(),
    
    +++ b/core/modules/menu_link_content/tests/src/Functional/LinksTest.php
    @@ -125,6 +125,7 @@ public function testCreateLink() {
    +      'title' => 'Link test',
    
    +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
    @@ -213,7 +213,7 @@ protected function processStubRow(Row $row) {
    -          $values[] = $default_value;
    +          $values = reset($default_value);
    

    Why are changes like these necessary? How come these tests used to work fine? Are these examples of areas where code used to be not strict enough?

    If so, does that mean this increased strictness may cause BC problems for sites updating from 8.3 to 8.4?

  4. +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
    @@ -60,14 +60,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +      // @todo Make this field required in https://www.drupal.org/node/2871036.
    +      //->setRequired(TRUE)
    ...
    +      // @todo Make this field required in https://www.drupal.org/node/2871036.
    +      //->setRequired(TRUE)
    

    Oh, interesting! I explicitly marked that issue as postponed on this issue, and added it to the related issues.

  5. +++ b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php
    @@ -156,13 +156,27 @@ protected function modifyBaseField() {
    +   * NOT NULL. Starting with Drupal 8.4.0, only required fields are marked as
    

    "only required fields", this says. Shouldn't it say "only storage-required fields"?

  6. +++ b/core/modules/system/src/Tests/Update/EntityUpdateFilledTest.php
    @@ -0,0 +1,23 @@
    +class EntityUpdateFilledTest extends EntityUpdateTest {
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected function setDatabaseDumpFiles() {
    +    $this->databaseDumpFiles = [
    +      __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
    +    ];
    +  }
    +
    +}
    

    This is an interesting update path test. If it's clear to Entity API maintainers, then ignore me. But it looks kinda mysterious/confusing to me :)

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

    I don't think clearing static caches explicitly here is necessary?

  8. Finally: does this mean that we should make certain entity keys no longer be entity keys? I.e. if they were made an entity key solely for the NOT NULL behavior.

Unassigning catch, AFAICT it's no longer blocked on him.

Wim Leers’s picture

tstoeckler’s picture

Assigned: Unassigned » tstoeckler

Working on the test failures.

tstoeckler’s picture

I tried to fix the tests, and did find out problem with the current patch (see *--interdiff-1.txt).

However, actually resolving the failures would have required me to adapt the field definitions in EntityTestUpdate. Currently neither id nor langcode are marked as required, and so SQL complains when creating the data table which has (id, langcode) as the primary key, but neither of those are NOT NULL. But updating those field definitions is wrong because they are explicitly copied from the state of 8.0.0-rc1. So this fortifies what we kind of already new (and documented in the change notice): entity types written before this change are broken after this change. I guess this is where the discussion in Baltimore already arrived at regardless of this specific test and I think the only solution to this is what #102 already alludes to: we need to make the new behavior opt-in and provide a BC-layer. Thus, not proceeding with any more test "fixes" and instead waiting for a confirmation by ideally @amateescu that proceeding with an opt-in entity type annotation entry is the way to go.

I did find one strange issue where SqlContentEntityStorageSchemaConverterTest::testMakeRevisionableErrorHandling (which tests that the schema converter doesn't do anything if the update fails) did slightly alter the stored schema of the entity type even though it should not have. Didn't dive into that too deeply, though, because of the aforementioned issue.

Re #109: Thanks for the review, some commends (and see *--interdiff-2.txt):

  1. Yes, fixed
  2. Fixed
  3. See #90.6 (I asked the same thing in #101.5. Unfortunately,
    I think there is not much we can do here about this that would be in scope.) But no, this should not cause BC problems.
  4. Thanks!
  5. so either setRequired() or setStorageRequired() are sufficient to get NOT NULL. I adapted the text to say "only required (or storage-required) fields", I hope that works for you.
  6. So this extends EntityUpdateTest and, thus, re-runs its test. It just uses a different database dump as a starting point. That is how many update tests work.
  7. I tend to agree that this should not be necessary (or should receive a comment if it is), but the test is failing at the moment, anyway, so not touching that.
  8. I suppose we could remove e.g. the uid key from nodes, but it doesn't really hurt to have entity keys lying around even if they are not used, so I would prefer not to discuss that here
Wim Leers’s picture

Thanks for addressing #109, your answers to my points make sense.

And more importantly: hopefully we can continue here again soon, this is all tricky stuff clearly, where we need to be very careful to not break BC. Kudos to all of you for fixing these foundational issues — that's very hard!

amateescu’s picture

Sorry for replying so late here, I'll take a look later today at what would be best to do for this issue :)

amateescu’s picture

Issue tags: -blocker

I looked into the test failure and @tstoeckler is spot on with his investigation in #112. My testing also revealed that we should depend on #2860654: Field storage CRUD operations must use the last installed entity type and field storage definitions if we want to be able to update field storage definitions at the same when changing an entity type from non-revisionable to revisionable.

So, to answer the question about what needs to happen here to move forward, I think the only good solution is to have an opt-in entity type definition key which defaults to keeping the current behavior, then have individual update functions for each core entity type that we want to upgrade to the new behavior. Something that should be discussed is what to do when we install a new entity type (e.g. something that's not yet stored in the last installed entity type definitions), do we default to the new behavior for them?

I will be away for about a week so I won't be able to review patches, but I'll try to comment whenever my input is needed :)

Version: 8.4.x-dev » 8.5.x-dev

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

kfritsche’s picture

Status: Needs work » Needs review
FileSize
50.74 KB

Just a re-roll, comments from #115 still open.

Status: Needs review » Needs work

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

tstoeckler’s picture

Trying to get back to this, first off a self-review. I will provide an updated patch with the fixes. I think, even though we have now agreed that we can't do the update wholesale, I think it makes sense to get the patch green first (including the update!), so that we now it works for all core entity types. We can then split that off into separate update functions and optionally tackle those in dedicated follow-up issues.

  1. +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
    @@ -216,7 +216,8 @@ protected function buildBaseFieldDefinitions($entity_type_id) {
    +          ->setRequired(TRUE);
    

    Above @amateescu mentioned that it's fairly arbitrary whether to mark this "just" required or storage required. Since IMO this is more similar to ID/revision ID/etc. than e.g. 'published', I would prefer storage required here, for consistency.

  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1689,28 +1693,33 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
    +    // @todo Revisit once we have support for 'initial' in
    +    //   https://www.drupal.org/node/2346019.
    

    Ahem, this has since been committed, so I'm not sure this is still going to fly.

  3. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1689,28 +1693,33 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
    -    // Label and the 'revision_translation_affected' fields are not necessarily
    -    // required.
    +    // Label fields are not necessarily required.
         unset($not_null_keys['label'], $not_null_keys['revision_translation_affected']);
    +    // properties of required fields.
    

    This doesn't read well. Borked re-roll?

  4. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1689,28 +1693,33 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
    +    if ($table_name == $this->storage->getBaseTable() && $field_name == $this->entityType->getKey('revision')) {
    

    Strict equality would be nice here, especially for the second one. I don't even want to think about what happens if someone names their field '0' and doesn't provide a revision key...

  5. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -2060,6 +2078,16 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
    +            // Prior to Drupal 8.4.0, the last installed schema for the 'id' and
    
    +++ b/core/modules/aggregator/aggregator.install
    @@ -40,3 +40,13 @@ function aggregator_update_8200() {
    +function aggregator_update_8400() {
    
    +++ b/core/modules/comment/comment.install
    @@ -114,6 +114,17 @@ function comment_schema() {
    +  $dependencies['system'][8401] = array(
    
    +++ b/core/modules/node/node.install
    @@ -255,3 +255,16 @@ function node_update_8400() {
    +function node_update_8401() {
    
    +++ b/core/modules/system/system.install
    @@ -2042,3 +2043,47 @@ function system_update_8403() {
    +function system_update_8404() {
    

    All these need to be bumped for 8.5 now

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
6.73 KB
49.67 KB

Here we go.

Status: Needs review » Needs work

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

tstoeckler’s picture

Opened #2923915: Stubbing content entities with required fields with a default value is broken for the migration problem that came up a couple times here. I don't think it would fly to just include this here without any test coverage. So I guess this is now postponed on that one, but that is fairly irrelevant at this point as we first need a green patch and then do the refactoring to make the behavior change opt-in as discussed above. Hopefully that one will have landed, by the time this is finished.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
1.32 KB
50.99 KB

OK, this adds an update path dependency for block_content (and re-introduces the one for comment which I mistakenly deleted before). This should fix at least some of the fails.

Status: Needs review » Needs work

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

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
36.62 KB
77.31 KB

Fixed a couple of the tests but wrangled quite a bit with SqlContentEntityStorageSchemaConverterTest, as that revealed a few issues with the actual (i.e. non-test) code. It's green now but in the course of getting there I made some changes that are more invasive that I have liked. I'm going to keep them for now, until this is green, and then consider whether we can revert some of them, split them off into a separate issue or whether we actually have to keep them as is. In any case, my objective here is a green patch first, to validate that the code actually works correctly in all cases.

Since I did change a couple thing uploading this to see what else I now broke.

Status: Needs review » Needs work

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

Wim Leers’s picture

Title: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field

Conveying #122 in the issue title. (Doesn't mean we can't continue the NW/NR patch updating cycle.)

More importantly: impressive progress! 😵👏

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
9.3 KB
80.94 KB

This fixes all the remaining tests. Since I did change some runtime code I may have broken a few new ones, though, so not opening the champaign quite yet ;-)

Also, even if this is green, this is not "final-review" ready, see the earlier comments for the broader plan. But as mentioned there I want this to be green to make sure we don't break anything before refactoring the patch.

Re #127, yes, thanks for that. Since you bring it up both of the following issues have patches including tests so could use a review while we sort things out here:

The first one is not a hard blocker for this one (thus, PP-1 is correct) because we don't have the use-case that fixes in core, but it will trip up a lot of contrib modules and people with custom entities once they start working NOT NULL constraints per this issue. So would be nice to get that out of the way.

tstoeckler’s picture

Oh, and this is now related to #2619328: A required boolean field behaves differently depending on the widget because making the status field properly required breaks the ability to use a checkbox widget for it per that bug, but that is what we do now in core.

Status: Needs review » Needs work

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

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
3.56 KB
83.36 KB

OK, this time I'm legitimately crossing my fingers...

tstoeckler’s picture

Title: [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-2] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
FileSize
25.97 KB
75.36 KB

OK, so I was able to revert most of the interdiff in #125 while keeping SqlContentEntityStorageSchemaConverterTest green. Let's see what else breaks...

I still think the field schema changes in #125 make sense (i.e. having all indexes and keys related to a field part of the field schema) but it would be awesome to be able to this in a separate issue.

In the meantime I extracted two issues from this:

I even tagged the first on as "API-first Initiative" so hoping we can quickly make some progress there. Note that that issue includes test coverage and upgrade path test coverage, so should be pretty ready. The same goes for the issues linked in #128 by the way, still hoping for a review there...

Since the latter issue is not in itself a bug and solely exists to bring down the patch size here, only increasing the "PP-" count by one, not two. If this gets to RTBC without the second one going in before, I will just mark that as duplicate.

tstoeckler’s picture

Self-review of the patch in the meantime.

  1. +++ b/core/modules/comment/comment.install
    @@ -114,6 +114,17 @@ function comment_schema() {
    +  // comment_update_8301() needs to run before system_update_8401().
    +  $dependencies['system'][8401] = [
    

    This should be 8501 by now.

  2. +++ b/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php
    @@ -219,7 +224,24 @@ public function testMakeRevisionableErrorHandling() {
    +      // Ignore the fact that the field schema now contains defaults for DX.
    +      foreach ($new_field_schema_data[$storage_definition->getName()] as $table_name => $field_table_schema) {
    +        foreach (['unique keys', 'indexes', 'foreign keys'] as $key) {
    +          if (isset($field_table_schema[$key]) && ($field_table_schema[$key] === [])) {
    +            $original_field_schema_data[$storage_definition->getName()][$table_name] += [
    +              $key => [],
    +            ];
    +          }
    +        }
    +      }
    

    Oops, meant to remove this. Should be obsolete.

tstoeckler’s picture

FileSize
10.48 KB
69.36 KB

I was able to revert the "serial -> int" as well, with only a small workaround, so will move that to #2928906: The field schema incorrectly stores serial fields as int which I have opened for that and the keys/indexes issue discussed above.

As far as I can tell, all the changes in the patch are now strictly required and related to the NOT NULL behavior of entity fields (except for the issues that this is postponed on, whose patches are included here. Sorry for not providing a for-review patch, by the way...) So assuming this is green the next step is to actually make this behavior opt-in per #115 and related. Note that I probably won't have time to work on this in the next few days, unfortunately...

Status: Needs review » Needs work

The last submitted patch, 134: 2841291-134.patch, failed testing. View results

tstoeckler’s picture

ContactStorageTest exposed a failure when creating the ID field, as at that time the ID key is not set in the last installed entity type (as previously there was no ID key). I never liked the idea that contact messages do not have an ID field out of the box, but for now I implemented a workaround that allows to handle this case "properly".

EntityUpdate(|Filled)Test were related to the field schema changes, which I reverted above, so they now rightly fail. Removing them here and will include them in the next roll of #2928906: The field schema incorrectly stores serial fields as int.

Status: Needs review » Needs work

The last submitted patch, 136: 2841291-136.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
Wim Leers’s picture

Great progress, @tstoeckler! 👏

I think this primarily needs reviews from Entity/Field/Typed Data experts.

tstoeckler’s picture

Status: Needs review » Needs work

Actually, this still needs work per #115 as we decided against making this behavior change automatically for all entity types but instead have some sort of opt-in mechanism. So - while it doesn't hurt for someone to look at this - it's not yet RTBC-able. Marking accordingly.

tstoeckler’s picture

tstoeckler’s picture

Status: Needs review » Needs work
larowlan’s picture

Title: [PP-2] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
larowlan’s picture

Title: [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
tstoeckler’s picture

Title: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
Status: Needs work » Needs review
FileSize
46.03 KB

Wow, thanks for the quick reviews and commits!!! That's awesome. Note that this is still postponed on #2923915: Stubbing content entities with required fields with a default value is broken (that is the weird-looking hunk in the EntityContentBase migration destination) so hopefully we can get that in soon, as well. Also #2858184: Adding NOT NULL to base fields with multiple columns is broken - while not a blocker - is closely related and still looking for a review.

In the meantime, here's a reroll of #136.

Note that in addition to not having super much time for this in the next days I won't pick this one up again before I get at least #2928906: The field schema incorrectly stores serial fields as int and maybe also #2929120: The stored field schema does not contain field-level primary keys, unique keys and indexes in better shape since I extracted @amateescu's fixes to that issue but borked up the tests somehow, which is a bit unfair.

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

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

tstoeckler’s picture

Title: [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-2] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field

Now also extracted #2938951: The primary key is not correctly re-created when updating the storage definition of an identifier field into its own issue, as that is both required here and in #2928906: The field schema incorrectly stores serial fields as int. I only recently discovered that that was in fact the original scope of this issue. It seems that at the time it was necessary to expand the scope here, but - unless I'm missing something - that is no longer the case, as is proven by the passing test over there. Therefore I think it makes sense to get that in separately with dedicated test coverage. As noted multiple times already, this one will still need to be reworked anyway to make entity types opt-in to the new NOT NULL behavior.

Interestingly I don't need the very ugly "drop primary keys" hunk that I included here to make the patch over there pass. So once that patch is in I will revisit here whether that is in fact still needed or not.

Marking PP-2 now.

To recap:
This is now blocked on:

And while we're at it here's another shameless plug for the closely-related (yet not blocking) #2858184: Adding NOT NULL to base fields with multiple columns is broken which is looking for reviews.

tstoeckler’s picture

Status: Needs review » Needs work

Also marking needs work for now, as it still needs to be reworked. It's important to now that this passes tests, however.

Wim Leers’s picture

@tstoeckler: I looked at all those patches, but … they're in areas that I don't feel qualified to review. I did want to leave a comment to:

  • let you know I'm cheering for you from the sidelines! 👊🍻
  • suggest that you ping Entity/Field/Typed Data API maintainers in IRC who would be able to provide good reviews :)
tstoeckler’s picture

Title: [PP-2] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
tstoeckler’s picture

Status: Needs work » Needs review
FileSize
51.5 KB
70.69 KB

OK, next round. This needed a re-roll. And since #2938951: The primary key is not correctly re-created when updating the storage definition of an identifier field is green, I've now properly split the patch into a review patch and a full one which contains the one from over there.

Since I had to redo a bunch of things by hand anyway I also changed a couple things. Most notably I removed everywhere were we were manually setting 'not null' in the entity schema to properly set the fields to storage required. I ran all tests that are touched here locally - including a couple update tests - and they are green, so let's see...

BTW, opened #2973299: Add the label of the entity type to the error message when there are outstanding entity changes in UpdatePathTestBase when debugging the update tests.

Status: Needs review » Needs work

The last submitted patch, 151: 2841291-151--for-robots.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
4.62 KB
55.66 KB
71.09 KB

So ContentModerationState and Workspace are doing the same trick that Node is doing: They add their uid field as an entity key to get a NOT NULL constraint. So we need to update that accordingly for them as well. For content_moderation I added an update path for that, as far as I know that's not required for workspace yet.

Workspace brings in another interesting case, though. The target field is a required string field, but the default live workspace is created with '' as the value. Because an empty string is considered an empty value, however, and the entity system wants to avoid saving empty values, this ends up as NULL in the database. When this patch now adds a NOT NULL constraint to the field since it is required things explode. In theory, I think it would work to make the field required and explicitly not storage required, but I'm not sure if that's semantically sensible. In the UI a required string field also prevents you from entering an empty string, but of course in the UI there is no way to distinguish between an empty string and NULL. Not yet sure what to do about this, for now I just commented out the setRequired() call like the rest of the fields. In any case, this strengthens the case that we cannot "just do this" for all entity types, as here we would actually have a NULL value in a column that we would try to add a NOT NULL constraint to.

I based this 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, but while the tests over there pass this makes the spurious primary key problem that I "fixed" above re-appear. So for now just skipping all update tests because they will all fail and will make the test output very hard to parse. So this patch should hopefully be "green", but in this particular case that doesn't mean it passes all tests ;-) I will have to figure out why this patch is messing up the nice primary key re-creation that we fixed over in that issue. Not today, though...

amateescu’s picture

Don't worry about the 'target' field on the workspace entity type, we'll remove that weirdness in #2958752: Refactor workspace content replication plugin system to three services and won't try to store an empty value there :)

tstoeckler’s picture

OK, so in trying to figure this out today I could - as I think I had before at some point - pinpoint the issue to MySQL not being able to drop a column that is part of a composite primary key. This is the case for the ID and langcode fields in the data table and the revision ID and langcode fields in the revision data table. I remember finding something on StackOverflow or similar about this before, but I couldn't find anything this time around.

However, I was wondering why I was seeing this issue since we now have test coverage of all those code paths over in #2938951: The primary key is not correctly re-created when updating the storage definition of an identifier field which is green. So I ran those tests locally and saw the same failure. Even without the patch here, with just the one from over there, I was seeing failures in exactly the cases described above:

  • passed: testPrimaryKeyUpdate with data set "entity_test:id"
  • passed: testPrimaryKeyUpdate with data set "entity_test_rev:id"
  • passed: testPrimaryKeyUpdate with data set "entity_test_rev:revision_id"
  • failed: testPrimaryKeyUpdate with data set "entity_test_mul:id"
  • failed: testPrimaryKeyUpdate with data set "entity_test_mul:langcode"
  • failed: testPrimaryKeyUpdate with data set "entity_test_mulrev:id"
  • failed: testPrimaryKeyUpdate with data set "entity_test_mulrev:revision_id"
  • failed: testPrimaryKeyUpdate with data set "entity_test_mulrev:langcode"

So since those evidently pass on Drupal.org, I suspect that this might be something on my environment. I'm pretty sure I saw this on Drupal.org as well at some point - as I don't just go on running update path tests on my machine for fun ;-) - but we'll see.

So just re-enabling the update path tests for now to see what happens.

The last submitted patch, 155: 2841291-155--for-humans.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 155: 2841291-155--for-robots.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Here's a work-in-progress patch, not green, yet, though.

The fixes entail:

  1. Reinstating a workaround for #2928906: The field schema incorrectly stores serial fields as int that I inadvertantly dropped above
  2. Fixing EntityDefinitionTestTrait::updateEntityTypeToRevisionable() to account for entity types that are not translatable but have langcode entity key
  3. A minor fix to EntityDefinitionTestTrait::makeBaseFieldRequired() to not unnecessarily use \Drupal
  4. Adapt SqlContentEntityStorageSchemaTest::testOnFieldStorageDefinitionUpdateShared() from #2938951: The primary key is not correctly re-created when updating the storage definition of an identifier field to account for the different NOT NULL logic from here. This also required to mark both properties of ShapeItem required which I hope doesn't break any other test.

The first one in the list above was causing a lot of the update path test failures but fixing that reveals another update path problem which I have not found a solution for:

system_update_8004() calls EntityDefinitionUpdateManager::updateEntityType() with the dynamic entity type from EntityTypeManager::getDefinition(). This means that at this point every revisionable entity type will have a revision_default field installed because EntityFieldManager::buildBaseFieldDefinitions() will add it. However, at this point the revision_default field is not (yet) a revision metadata key because system_update_8501() has not run yet. Therefore DefaultTableMapping::create() will add it to the list of fields to add to the entity data table, which then happily succeeds. Regardless of any other updates it is never actually removed from that table subsequently, but it no longer gets added to the data table in the table mapping after system_update_8501() has run. When actually saving an entity after having run all the updates, SqlContentEntityStorage::mapToStorageRecord() will not add an entry for the revision_default field in the record to save to the data table.

What this issue changes is that the field gets added with a NOT NULL constraint in the database including when it's initially added to the data table because it is marked storage required. Because of this, saving an entity now throws an SQL exception because there's a required field for which we are not providing a value.

I see a number of problems here:

  1. system_update_8004() using the runtime entity type definition. This is a clear bug, but it's not clear whether and if so how we can fix this, as the update will already have run on most systems
  2. EntityFieldManager::buildBaseFieldDefinitions() adding the field even if there isn't an accompanying revision metadata key. More than adding a simple check this would require EntityFieldManager relying on the passed in entity type to build the field definitions, although the fact that that's not the case when ::onEntityTypeUpdate() is called seems like a clear bug to me
  3. The fact that we do not remove fields from the data table when we make them a revision metadata key and the field already exists
  4. Regardless of how we actually fix all the above we will need an update path to remove the wrongly created revision metadata fields in the data table from existing entity types

In other words: Where I thought I was beginning to make out some clarity in all the fog, the craziness has really just begun.

The last submitted patch, 158: 2841291-155-158--for-humans.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 158: 2841291-155-158--for-robots.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Title: [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-3] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
Related issues: +#2976034: system_update_8004() accidentally installs the 'revision_default' field if run on a 8.5 codebase, +#2976035: Entity type CRUD operations must use the last installed entity type and field storage definitions
Boobaa’s picture

Bumping this to critical per the related [#2951279/41]. IDK which one to mark as a dupe, tho.

tstoeckler’s picture

Actually @Boobaa I think that one is a separate issue. Here I had a revision_default column too many, i.e. one leftover in the wrong table, not a missing one.

tacituseu’s picture

@Boobaa: they're separate issues, all I meant is that the mechanism that was described in #158 might also be responsible for #2951279: After 8.5.0 upgrade: Unknown column revision.revision_default .

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
90.04 KB

Here's an up-to-date testbot patch with the latest code from all the dependent issues. No change in the "for-review" portion of the patch. So should still have the same amount of fails.

Status: Needs review » Needs work

The last submitted patch, 165: 2841291-165--for-testbot.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Title: [PP-3] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-2] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field

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-2] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
tstoeckler’s picture

Here's a rebase, and one including the latest patch (#10) from {#2976040]). The EntityOwnerTrait was a pretty big conflict, I hope I did everything correctly, but I wouldn't be surprised if there's some fallout from that. Let's see.

Status: Needs review » Needs work

The last submitted patch, 170: 2841291-170--full.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
2.79 KB

🙈 both this and #2976040: default_revision can be left around in data table due to broken entity type CRUD add a system update... ...not uploading a new "for-review" for that...

Status: Needs review » Needs work

The last submitted patch, 172: 2841291-172--full.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
63.88 KB

Well that was the interdiff, not the patch...

Status: Needs review » Needs work

The last submitted patch, 174: 2841291-174--full.patch, failed testing. View results

mpp’s picture

Issue summary: View changes

Updated issue description with the API change ahead.

tstoeckler’s picture

Assigned: Unassigned » tstoeckler
Issue summary: View changes

Thanks @mpp! The setStorageRequired() method is already pre-existing, however. It just doesn't enforce the required-ness on the DB level yet.

Also assigning to me for now. I have started to look at some of the test failures, so it doesn't make sense for anyone else to pick this up at the moment.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
17.71 KB
68.67 KB

Let's see if this is green.

If so, I will extract a bunch of the "optional" changes, i.e. the marking required of various entity fields that are not entity keys to a follow-up issue, to re-reduce this issue's scope. Then, as a second step, we can (finally!) change this patch to not update all entity types automatically but make it opt-in in some way. That will get rid of all the update-hook-dependency madness currently in the patch.

Status: Needs review » Needs work

The last submitted patch, 178: 2841291-178.patch, failed testing. View results

tstoeckler’s picture

Status: Needs review » Needs work

The last submitted patch, 180: 2841291-180--full.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
867 bytes
70.02 KB

Bah, the update issue really is gnarly, hopefully this one will be green.

tstoeckler’s picture

Sorry, here's the correct interdiff.

Status: Needs review » Needs work

The last submitted patch, 182: 2841291-182.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
3.22 KB
70.97 KB

Alright, making the Content Moderation State fields required again so they don't fail the update path tests. I ran all Content Moderation tests locally, so not sure if anything else will break due to that, if we're lucky, this may be fine. Also fixing the convertToPublishableTest()

tstoeckler’s picture

FileSize
47.29 KB

Extracting some stuff to #3003586: [PP-4] Use setStorageRequired() instead of overriding the storage schema to mark fields as NOT NULL in the database to reduce scope / patch size here. Hopefully this is still green in which case we can finally tackle the update problem.

Status: Needs review » Needs work

The last submitted patch, 186: 2841291-186--full.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
3.86 KB
46.59 KB

Ahh apparently I had some update dependencies mixed up which was hidden by the added update functions that are now gone. Also took the opportunity to clean up some minor stuff.

tstoeckler’s picture

Status: Needs review » Needs work

The last submitted patch, 188: 2841291-188--full.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
1.61 KB
46.57 KB

Sigh...

tstoeckler’s picture

OK, here's a first stab at making the implementation opt-in. This allows to remove all the update stuff. I ran all of the tests that are/were touched here and pass with this. That also includes some update tests which makes me reasonably confident that this isn't too terrible. We'll see.

Status: Needs review » Needs work

The last submitted patch, 192: 2841291-192.patch, failed testing. View results

tstoeckler’s picture

Status: Needs review » Needs work

The last submitted patch, 194: 2841291-194.patch, failed testing. View results

tstoeckler’s picture

OK, so I spent a lot of time with this during the Global Contribution Weekend and, unfortunately, I am now convinced that the approach in #192 / #194 will not fly. So I am now taking a step back to give some in-depth reasoning on what that approach was, why it doesn't work and what I think we should do instead.

Issue summary

To re-cap, here are the basic facts of this issue:

  1. To fix this issue, we need to change the schema generation for entity types. Currently only the entity keys are marked NOT NULL in the database, and we want to make this configurable depending on the "storage required" information of the field. Each field that is storage required, will get a NOT NULL in the appropriate columns.
  2. Because this change requires changing field definitions of all entity types, including contrib and custom ones, it is too risky to automatically provide an update path for the schema as we cannot know what state those entity types are in on any given site
  3. In addition to possible unexpected breakage per 2., there is also the problem that the schema change will cause some columns to be NOT NULL which previously weren't. Any given site may actually have NULL values, in those columns, however, so we cannot perform an upgrade path for that reason, either.

Previous approach

So while we want to allow entity types to somehow upgrade their schema and opt-in to the new schema generation mechanism, it is clear that we also need to keep supporting the old way. That is why #192 implemented a flag inside of SqlContentEntityStorageSchema depending on which the generated schema would follow either the old mechanism or the new one. My thought was that we could avoid having module maintainers be aware of this subtlety so I attached the following logic to the flag:

  1. If the flag is not set, use the legacy schema generation mechanism

    This ensures backwards-compatibility for any pre-existing entity types for existing sites. Each entity type would then have needed to add an update function where it sets that flag on the entity type and subsequently calls EntityDefinitionUpdateManager::updateEntityType() which would then upgrade the schema. From then on the flag would be set and the new schema would be used. I had to fight with #2554235: Make the content entity storage and entity query use the last installed definitions instead of the ones living in code, because with this we need to make sure that when calling EntityTypeManager::getDefinition() the flag will be set. That was annoying, but solvable.
  2. For entity types being newly installed, set the flag automatically

    This ensures that newly installed entity types benefit from the new schema generation mechanism without having anything to worry about.

Because with this solution the flag would have been completely internal to the storage schema API, I decided in the patches above to add a new storage_schema_settings array, which contained this flag so as to support further possible settings in the future to support
further improvements to the schema.

The problem

I did not realize, though, that 3. from the first list is in conflict with 2. in the second list. If a newly installed Drupal site with the patch above would install the Custom Blocks module, the info column would get a NOT NULL constraint. Before this patch, that constraint was not there, though, so there might be code in custom modules, or, for example, default content that relies on the ability to insert NULL into that column. This is just one example, but it's already enough to prove that the logic above is not feasible.

New approach

So the only way to move forward here, I think, is to force module authors to explicitly opt-in to the new schema generation as part of the entity type annotation. That way not only previously installed entity types, but also newly installed (but previously coded) entity types will still use the legacy schema generation until the module author explicitly changes it in the annotation and provides an update path accordingly.

We then need to document that module authors creating new entity types are encouraged to opt-in to the new-style schema generation from the get-go. This is not ideal, but, as far as I can tell, unavoidable.

To avoid module authors having to remember or copy-paste verbose and somewhat obscure strings as are used in the last two patches above, I think it would make sense to add an opaque "storage_schema_version" key to the entity type annotation. The current (implied/implicit) storage schema version would be 1, and this patch would raise the "preferred" storage schema version to 2. That would still provide a mechanism for further improvements in the future but be less tedious to maintain for module authors than a bunch of separate flags/settings.

I will attempt to adapt the patch for this now, but wanted to provide some background information to give others a chance to provide some input here.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
36.23 KB
48.97 KB

OK, here's a first stab at the solution proposed in #196. I ran a bunch of tests locally and it looked alright. This is, however, inherently much less invasive, so passing tests are not necessarily great validation of the patch. I set the schema version on all of the test entity types as those by definition do not have any BC-constraints.

tstoeckler’s picture

So nice, this doesn't break anything. I am currently working on updating #3003586: [PP-4] Use setStorageRequired() instead of overriding the storage schema to mark fields as NOT NULL in the database to actually upgrade all core entity types to storage schema version 2 using this patch. That one will be the real validation that this is feasible. Also we probably want some more tests here before this is ready.

Just leaving a note here for now that I forgot to update content translation's entity_test_translatable_UI_skip and entity_test_translatable_no_skip in the last patch, just so I don't forget.

tstoeckler’s picture

I forgot to update content translation's entity_test_translatable_UI_skip and entity_test_translatable_no_skip in the last patch

And also the following:

migrate_string_id_entity_test
no_language_entity_test
entity_test_revlog
entity_test_mul_revlog

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.

GaëlG’s picture

FileSize
15.49 KB

Here's a reroll against 8.8.x. It also applies on 8.7.x and 8.7.1.

I add some conflicts. Most of them where merged automagically, but on one commit, some expectations in tests changed both in patch #197 and meanwhile in core. It was count expectations, like initially the code was $this->storage->expects($this->exactly(2)), the patch changed it to exactly(1) and a commit in core changed it to atLeastOnce(). In these cases, I kept the less strict expectation that matched both cases (atLeastOnce()).

GaëlG’s picture

Status: Needs review » Needs work

The last submitted patch, 202: 2841291-201.patch, failed testing. View results

gease’s picture

catch’s picture

Issue tags: +Drupal 8 upgrade path
gease’s picture

gease’s picture

FileSize
50.06 KB
2.76 KB

Minor update of comments, compared with the previous patch.

gease’s picture

The patch from #207 fixes two issues with tests, observed in #201.
First one was due to deprecation of entity manager in favour of entity type manager and entity field manager and introduction of entity type bundle info service.
In the second one, because of the cached entity type the entity type manager used to return the old definition containing the langcode entity key and based on that the field for the langcode was still returned. This was the case without the patch and with the previous version. However without the patch the field was defined as "DEFAULT NULL" in the DB schema, because it wasn't an entity key anymore. However with the patch we are setting the field as "storage required" and therefore when present the field is always "NOT NULL". When we clear the cached entity type from the entity type manager, then the langcode field will not be returned at all, which is actually the desired behavior.

gease’s picture

Title: [PP-1] Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field » Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field
Status: Needs work » Needs review
tstoeckler’s picture

Thanks @GaëlG and @gease, nice work! Still needs work for #199 and also needs a change record, but most importantly, the new approach needs a review from another entity API maintainer before we can proceed. Tagging this accordingly. After looking at this again after quite some time I still think the approach is sound, but I also authored it so just me saying that doesn't really count.

hchonov’s picture

In ContentEntityBase::baseFieldDefinitions() the patch sets storage required on the entity key fields without checking if the entity storage schema is at least version 2. Wouldn't that create an inconsistency between the last installed field storage schema and the live definitions, which will result into the entity definition update manager listing unapplied updates?

hchonov’s picture

Status: Needs review » Needs work

I've just checked and drush updbst does not return any pending schema updates.

However the following returns FALSE and shows the inconsistency I have in mind:

/** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repo */
  $entity_last_installed_schema_repo = \Drupal::service('entity.last_installed_schema.repository');
  $last_installed_title_definition = $entity_last_installed_schema_repo->getLastInstalledFieldStorageDefinitions('node')['nid'];

  /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */
  $field_manager = \Drupal::service('entity_field.manager');
  $field_manager->clearCachedFieldDefinitions();
  $live_title_definition = $field_manager->getBaseFieldDefinitions('node')['nid'];

  var_dump($last_installed_title_definition->isStorageRequired() == $live_title_definition->isStorageRequired());

The question is whether we need an update for this? I would assume that we either need an update for that or we have to check the entity storage schema version in ContentEntityBase::baseFieldDefinitions() before defining the entity keys as storage required.

The updates in #3003586: [PP-4] Use setStorageRequired() instead of overriding the storage schema to mark fields as NOT NULL in the database already take care of updating the last installed storage definition when updating the entity storage schema for core entities to level 2.

----

Based on that I would say that we do not need an update here, but instead should set the entity keys with storage required in ContentEntityBase::baseFieldDefinitions() only if the entity storage schema is >= 2.

Setting needs work for this.

hchonov’s picture

I finally managed to do a FULL review :). Well, what should I say ... amazing work! I really like the new approach and how the patch looks like.

I have some notes, but I consider them minor stuff, which however should be addressed and when this is done I will RTBC it.

I apologise if some of my questions have been answered already in previous comments, but it is not easy to go through all the 200+ comments.

  1. +++ b/core/lib/Drupal/Core/Entity/EntityPublishedTrait.php
    @@ -37,7 +37,13 @@ public static function publishedBaseFieldDefinitions(EntityTypeInterface $entity
    +        // The published field cannot be made required, as that makes it
    +        // impossible to create unpublished entities using the user interface in
    +        // case the checkbox widget is used.
    +        // @todo Change this to setRequired() once
    +        //   https://www.drupal.org/project/drupal/issues/2619328 is fixed.
    +        ->setStorageRequired(TRUE),
    
    +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -38,6 +38,9 @@ public static function ownerBaseFieldDefinitions(EntityTypeInterface $entity_typ
    +        // This cannot be marked required, because we support submitting an
    +        // empty textfield which will set the owner to the anonymous user.
    +        ->setStorageRequired(TRUE)
    

    For all other entity keys we don't set them as required. Why are we then adding the todo for the published entity key here?

    Similar to that - why is there the note on the owner entity key that it cannot be required? A side note - the anonymous user is 0 and not NULL and therefore I think that required will be fine, however according to the points above I think this is out of scope.

  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1573,6 +1567,14 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor
    +              // Serial fields are incorrectly stored as 'int' in the field
    +              // schema.
    +              // @todo Remove this in
    +              //   https://www.drupal.org/project/drupal/issues/2928906
    +              if (($specifier['type'] === 'int') && ($entity_schema[$table_name]['fields'][$name]['type'] === 'serial')) {
    +                $specifier['type'] = 'serial';
    +              }
    

    Why is this with the current patch needed?

  3. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -2071,31 +2073,37 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
    +    $schema_version = $this->entityType->get('storage_schema_version') ?: 1;
    +    if ($schema_version >= 2) {
    

    Let's not add the notion that the version is not there and remove the ternary operator. NULL will be converted to 0, which is fine for the check.

  4. +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
    @@ -47,6 +47,7 @@
    + *   storage_schema_version = 2,
    

    So we'll not be testing any test entities with the legacy storage schema even if knowing that most installations might keep using it until D9?

    I would suggest that we include a legacy entity type with storage_schema_version=1.

  5. +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ShapeItem.php
    @@ -31,10 +31,12 @@ public static function defaultStorageSettings() {
    +      ->setRequired(TRUE);
    ...
    +      ->setRequired(TRUE);
    

    Why are making the properties of the test shape item required?

  6. +++ b/core/modules/system/tests/modules/entity_test_update/entity_test_update.module
    @@ -29,6 +29,18 @@ function entity_test_update_entity_base_field_info(EntityTypeInterface $entity_t
    +/**
    + * Implements hook_entity_base_field_info_alter().
    + */
    +function entity_test_update_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
    +  if ($entity_type->id() === 'entity_test_update') {
    +    $field_names = \Drupal::state()->get('entity_test_update.required_field_storage_definitions', []);
    +    foreach ($field_names as $field_name) {
    +      $fields[$field_name]->setStorageRequired(TRUE);
    +    }
    +  }
    +}
    

    I don't find any results when I search for the string "required_field_storage_definitions" whether in the patch nor in Drupal 8.8.x. Could you explain why this hook implementation is needed?

  7. +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
    @@ -451,6 +467,138 @@ public function testOnEntityTypeCreate() {
    +    $this->fieldDefinitions['id']->expects($this->any())
    +      ->method('isStorageRequired')
    +      ->will($this->returnValue(TRUE));
    

    I think that we should add the same test and expecting it to return FALSE to the upper test testOnEntityTypeCreateVersion1() See my previous comment about this, where I suggest to set storage required only if the storage schema is >=2 - #212.

gease’s picture

According to suggestion from #212 added checks for storage schema version in calls to ::baseFieldDefinitions().

gease’s picture

Fixed tests with some missing EntityType::get('storage_schema_version') mocks.

gease’s picture

EntityFieldManagerTest::testGetBaseFieldDefinitionsTranslatableEntityTypeLangcode() for 8.7 seems to be shorter than for 8.8 so one hunk of the above patch didn't apply automatically. Re-rolled for this purpose for 8.7.x branch.

gease’s picture

FileSize
54.47 KB
gease’s picture

Status: Needs work » Needs review
hchonov’s picture

It looks like that @amateescu was also in favour of a solution where the new NOT NULL behaviour should be OPT IN for BC reasons. From #102:

Unfortunately, the outcome of the discussion that @dixon_ mentioned in #97 did not make it back into the issue, so here it is: this change was deemed to risky at this point in the 8.x cycle so, if we want to go through with it, we will need to provide a BC layer by making the new NOT NULL behavior opt-in (through an entity type definition key or something).

-----

From #101:

I was not aware of isStorageRequired() falling back to isRequired() for BaseFieldDefinitions.

I was not aware of that as well. But that means, that an update to the storage schema v2 would mean, that all required base fields will now get the new NOT NULL constraint. This is a problem, because base field field overrides for different bundles might be affected by updating the storage schema to v2, as then if a base field is required for one bundle, but not for the other it will still be storage required and therefore it will be impossible to save entities for the second bundle, where the field isn't required, because the storage schema will already be defined with the NOT NULL constraint.

In order to solve that problem we cannot allow for BaseFieldDefinitions::isStorageRequired() to fallback to isRequired() for entity storage schema >= 2 anymore. This is a behaviour change, but not BC break, because the new behaviour is OPT IN and will remain like this until Drupal 9 even for new installations and entities. This change is needed because even if we call both ->setRequired(FALSE) and ->setStorageRequired(FALSE) on the overridden base field, it still will already be created in the database with the NOT NULL constraint and the only possible solution to prevent that would be to implement hook_entity_base_field_info_alter() and call ->setStorageRequired(FALSE) for that field - however this not a solution for existing entity types, because the hook implementation should be present before the entity type has been installed, which would prevent custom and contrib modules from overriding required base fields for specific bundles.

We need a test for this. I suggest adding a new required base field on EntityTestWithBundle, then adding two bundles and implementing hook_entity_bundle_field_info() in the entity_test module, cloning the required base field there and returning the cloned object with ->setRequired(FALSE) for the second bundle only. Then create two entities with a missing value for the field - one for each bundle, and save them. This test should pass on HEAD and fail with the current version of the patch.

gease’s picture

Implemented a test as suggested in the last paragraph of #219. The assumption was correct, it passes without the patch and fails with the patch, except that calling ->setRequired(FALSE) in hook_entity_bundle_field_info() doesn't seem to have any effect.

The last submitted patch, 220: 2841291-test-only-220.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 220: 2841291-test-with-patch-220.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.

amateescu’s picture

Priority: Critical » Major

This issue was mistakenly bumped to critical in #162 (see the next few comments), but hasn't been brought back to major after that.

gease’s picture

A reroll from #215. Also removed one erroneous 'schema_version' from annotation of config entity.

hchonov’s picture

Status: Needs work » Needs review
joseph.olstad’s picture

this patch also will apply to 8.7.10 (with fuzz)

hchonov’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
    @@ -238,10 +241,12 @@ protected function buildBaseFieldDefinitions($entity_type_id) {
    -        ->setStorageRequired(TRUE)
    ...
    +      if ($entity_type->get('storage_schema_version') >= 2) {
    +        $base_field_definitions[$field_name]->setStorageRequired(TRUE);
    +      }
    

    We need the check only for new additions, not for existing ones. The change here is wrong as it will affect existing installations with the old schema, but it shouldn't as they have been already installed with the storage required flag set to TRUE.

  2. +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBundle.php
    @@ -34,7 +34,7 @@
    - *   }
    + *   },
    

    non-related changes should not be part of the patch.

gease’s picture

Per comment #228 removed condition where setStorageRequired was introduced before this patch, and wrapped for convertToRevisionable(), where, instead, setStorageRequired is introduced with this patch.

hchonov’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
    @@ -1355,6 +1366,9 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
         if ($entity_type->hasKey('bundle')) {
           if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
    

    The bundle field needs to be updated with ::setStorageRequired(TRUE) as well.

  2. +++ b/core/lib/Drupal/Core/Entity/EntityPublishedTrait.php
    @@ -32,13 +32,20 @@ public static function publishedBaseFieldDefinitions(EntityTypeInterface $entity
    +      // The published field cannot be made required, as that makes it
    +      // impossible to create unpublished entities using the user interface in
    +      // case the checkbox widget is used.
    +      // @todo Change this to setRequired() once
    +      //   https://www.drupal.org/project/drupal/issues/2619328 is fixed.
    

    This comment is unnecessary here as we are dealing with the storage only and not with the form level requirement. Instead it should be taken care of in the referenced issue.

  3. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -1580,6 +1574,14 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor
    +              // Serial fields are incorrectly stored as 'int' in the field
    +              // schema.
    +              // @todo Remove this in
    +              //   https://www.drupal.org/project/drupal/issues/2928906
    +              if (($specifier['type'] === 'int') && ($entity_schema[$table_name]['fields'][$name]['type'] === 'serial')) {
    +                $specifier['type'] = 'serial';
    +              }
    +
    

    This has been resolved in #2928906: The field schema incorrectly stores serial fields as int which is RTBC. Let's remove it from here and provide two files - one "--combined.patch" for testing and one "--review.txt" for the changes of the current issue only. The "--combined.patch" should be build on top of the referenced issue.

  4. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -32,14 +32,17 @@ public static function ownerBaseFieldDefinitions(EntityTypeInterface $entity_typ
    +    $field_definitions = BaseFieldDefinition::create('entity_reference')
    ...
    +      $field_definitions->setStorageRequired(TRUE);
    ...
    +    return [$entity_type->getKey('owner') => $field_definitions];
    

    $field_definitions => $field_definition

gease’s picture

gease’s picture

Status: Needs work » Needs review
gnunes’s picture

I've re-rolled the review patch from #231 (2841291-231-review.txt), because it didn't contain the merges from the merged patch as requested in #230, for both 9.0 and 8.9. I added the interdiffs too.
It needs review please.

tstoeckler’s picture

Assigned: tstoeckler » Unassigned

Just noticed that this is still assigned to me. Will try to get to review this in the next couple of days but will but for now rectifying the assignment status to match the status quo.

hchonov’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -32,14 +32,17 @@ public static function ownerBaseFieldDefinitions(EntityTypeInterface $entity_typ
    +      // This cannot be marked required, because we support submitting an
    +      // empty textfield which will set the owner to the anonymous user.
    

    This comment does not have anything to do with the current issue and therefore is unnecessary and out of scope. I think it was based on a previous assumption, that setRequired(TRUE) leads to ::isStorageRequired()=TRUE, which however turned out to be wrong and the behaviour for that will be removed in #3083131: BaseFieldDefinition::isStorageRequired wrongly falls back to ::isRequired.

  2. +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
    @@ -361,13 +369,18 @@ public function testSetTableMapping() {
    +  public function testOnEntityTypeCreateVersion1() {
    
    @@ -451,6 +467,138 @@ public function testOnEntityTypeCreate() {
    +  public function testOnEntityTypeCreateVersion2() {
    

    Let us be more explicit in the naming of the tests methods and name them e.g. "testOnEntityTypeCreateWithStorageSchemaVersionN".

I've found only this nitpicks. Beside them I consider this RTBC already. Feel free to set to RTBC directly with uploading the next patch for those nitpicks in order to save on comments count :).

dhirendra.mishra’s picture

Assigned: Unassigned » dhirendra.mishra
Issue tags: +vb13febContribution
dhirendra.mishra’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -Needs reroll +Needs issue summary update, +Needs followup, +Needs change record

I think this issue needs quite a bit of metadata work.

  • The issue summary doesn't seem to fully explain the solution chosen
  • The change record seems very out-of-date. The change record needs to explain what happens when set this and how to update an entity type from 1 to 2.
  • What's the plan for all core's real entity types - I think we need a follow-up meta to organise this
  • Existing core documentation needs checking to see we've updated all the right places.
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -281,6 +281,13 @@ class EntityType extends PluginDefinition implements EntityTypeInterface {
+  /**
+   * The storage schema version.
+   *
+   * @var int
+   */
+  protected $storage_schema_version = 1;

Needs more documentation about what this is.

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

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Murz made their first commit to this issue’s fork.

Murz’s picture

I re-rolled the patch from #238 to 9.3.x branch as GitLab MR with some removals:

  • Deleted patch of SqlContentEntityStorageSchemaConverter.php patch because of deprecating https://www.drupal.org/project/drupal/issues/3004642 and removing in 9.x
  • Deleted patch for removed test core/modules/system/tests/modules/entity_test/src/Entity/EntityTestLabelCallback.php

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.