Problem/Motivation

Follow-up to #2282119: Make the Entity Field API handle field purging. Base fields can be stored in different entity tables based on a custom data_table set in the entity definition or if the base field is translatable or not. Currently when copying base fields from shared table storage to dedicated table storage for purging, the wrong tables are used in some scenarios.

Proposed resolution

Add additional test coverage for different combinations of entity and field definitions and fix these.

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#47 Drupal_core___Drupal_org.png81.92 KBplach
#43 interdiff-43.txt1.02 KBamateescu
#43 2923567-43.patch15.96 KBamateescu
#40 2923567-40.patch17.16 KBSam152
#40 2923567-interdiff.txt3.36 KBSam152
#35 2923567-35.patch15.94 KBSam152
#31 2923567-31.patch15.8 KBSam152
#31 2923567-31_TEST_ONLY.patch12.65 KBSam152
#31 interdiff.txt4.15 KBSam152
#27 2923567-27.patch14.45 KBSam152
#27 2923567-27_TEST_ONLY.patch11.53 KBSam152
#27 interdiff.txt4.46 KBSam152
#24 2923567-23_TEST_ONLY.patch11.36 KBSam152
#23 2923567-23.patch13.8 KBSam152
#23 interdiff.txt6.82 KBSam152
#21 2923567-20.patch10.43 KBSam152
#21 interdiff.txt5.3 KBSam152
#13 2923567-13.patch8.27 KBSam152
#13 interdiff_.txt521 bytesSam152
#12 2923567-12.patch8.49 KBSam152
#12 2923567-12_TEST_ONLY.patch6.8 KBSam152
#12 interdiff.txt1.48 KBSam152
#8 2923567-8.patch7.02 KBSam152
#8 2923567-8_TEST_ONLY.patch5.32 KBSam152
#2 2923567-2.patch1.69 KBSam152
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Sam152 created an issue. See original summary.

Sam152’s picture

Status: Active » Needs review
FileSize
1.69 KB

Seeing if this passes the existing tests.

amateescu’s picture

That makes perfect sense, want to add a test for it?

Sam152’s picture

Status: Needs review » Needs work

Thanks for reviewing. I'll have a look at the test for this now.

amateescu’s picture

Having looked a bit more into it, how come $base_table = $is_translatable ? $this->storage->getDataTable() : $this->storage->getBaseTable(); is not returning 'user_field_data' in your case? The user entity is translatable and correctly defines a data table in its annotation.

amateescu’s picture

Also, the issue summary says "based on if the entity is revisionable or not" but the patch is about being translatable or not..

Sam152’s picture

Issue summary: View changes

Good catch, updating.

Sam152’s picture

Status: Needs work » Needs review
FileSize
5.32 KB
7.02 KB

Adding a test for this too.

Sam152’s picture

Title: Incorrect field table used when purging base fields on certain types of entities » Incorrect table used when purging base fields for entity types with custom data tables

The last submitted patch, 8: 2923567-8_TEST_ONLY.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 8: 2923567-8.patch, failed testing. View results

Sam152’s picture

Status: Needs work » Needs review
FileSize
1.48 KB
6.8 KB
8.49 KB

Fixing the views test.

Sam152’s picture

Removing unused use.

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

Sam152’s picture

I missed the comment in #5, $is_translatable was coming up FALSE because I believe the field was not translatable.

Sam152’s picture

Issue summary: View changes

The code snippet in the issue summary is actually incorrect. Must have pulled that from somewhere else. Updating.

amateescu’s picture

That's weird, because if the field was not translatable, why is it stored in the data table? In any case, we need to also check the hunk below the one changed by this patch where we do the same if for the field revision table. And probably add some dedicated test coverage for that as well.

Sam152’s picture

I'm not sure how the field is assigned to the tables, however the test case fails in the same fashion as I encountered. The entity_test_mul table is used however I believe the base field ends up in entity_test_mul_property_data.

Some good additional coverage might be additional entity revisions in testBaseFieldDeleteWithExistingData or would you suggest a new test method?

amateescu’s picture

I think adding a few revisions in testBaseFieldDeleteWithExistingData is fine :)

Sam152’s picture

Title: Incorrect table used when purging base fields for entity types with custom data tables » Select from correct entity tables for additional entity/field definitions when moving data to dedicated table storage from shared table storage for the purposes of purging
Issue summary: View changes

Adding the revisions instantly exposed a bug in #13, so great catch to check this. To fix this part I think it makes sense to add the equivalent method to getFieldTableName for revisions.

I think it also allows us to add coverage for another scenario which I also think has issues, a non-revisionable base field on a revisionable entity type. That's based on the following part of the patch:

      if ($this->entityType->isRevisionable()) {
        $dedicated_revision_table_name = $table_mapping->getDedicatedRevisionTableName($deleted_storage_definition, TRUE);
        ...
      }
...
        if (isset($dedicated_revision_table_name)) {
          $revision_table = $is_translatable ? $this->storage->getRevisionDataTable() : $this->storage->getRevisionTable();

In this case I don't think the field appears in either getRevisionDataTable or getRevisionTable because it's not revisionable, but the condition is based on the entity type.

I haven't had much luck debugging these and it's getting a bit late but I think it makes sense to include these test cases in the scope of this issue and rejigging the title and IS. Feel free to adjust for brevity, the title got a bit wordy.

Sam152’s picture

Helps to upload the patch files...

Status: Needs review » Needs work

The last submitted patch, 21: 2923567-20.patch, failed testing. View results

Sam152’s picture

Status: Needs work » Needs review
FileSize
6.82 KB
13.8 KB

I was able to get the test cases I introduced to pass with this approach.

Sam152’s picture

And a test only patch for #23.

Status: Needs review » Needs work

The last submitted patch, 24: 2923567-23_TEST_ONLY.patch, failed testing. View results

Sam152’s picture

Status: Needs work » Needs review
Sam152’s picture

amateescu’s picture

Status: Needs review » Needs work

This is getting better and better :) I found just a few more issues:

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
    @@ -517,17 +518,15 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
    +          $revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionDataTable() : $this->storage->getDataTable();
    

    This still doesn't look quite right. What happens when the entity type is revisionable but not translatable? It shouldn't have a data table in that case..

  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
    @@ -436,13 +444,49 @@ public function testBaseFieldDeleteWithExistingData() {
    +        'revision_id' => $entity->getRevisionId(),
    ...
    +          'revision_id' => '2',
    

    How come the revision id is 1 for the first assertion? Didn't we just created and saved a new revision above?

  3. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
    @@ -453,6 +497,48 @@ public function testBaseFieldDeleteWithExistingData() {
    +      $this->assertFalse($schema_handler->tableExists($dedicated_deleted_revision_table_name), 'A dedicated table was created for the deleted new_base_field.');
    

    We should mention in the assertion message that this is about the dedicated revision table.

    And I'm not sure the message is quite right, we're asserting that the table does not exist anymore. The assertion above has the same problem and should probably be changed as well.

Sam152’s picture

1. Is there a test entity type that meets this criteria we can use?
2. We create an additional entity in #2923572: Account for NULL base field values in shared table storage when copying to dedicated table storage for field purging., so the revision IDs in these test cases are 3 and 2.
3. Good point.

amateescu’s picture

1. Yep, entity_test_rev ;)
2. Maybe it deserves a comment then because it definitely tripped me at first.

Sam152’s picture

Status: Needs work » Needs review
FileSize
4.15 KB
12.65 KB
15.8 KB

1. Both new test cases indeed failed!
2. Hard coding both revision IDs seems clearer to me.
3. Fixed.

I experimented with a few ways of representing the list tables. I'm not sure my matrix implementation makes it any more understandable :D

$tables = [
  [$this->storage->getBaseTable(), $this->storage->getRevisionTable()],
  [$this->storage->getDataTable(), $this->storage->getRevisionDataTable()],
];
$revision_table = $tables[$this->entityType->isTranslatable()][$storage_definition->isRevisionable()];

The last submitted patch, 31: 2923567-31_TEST_ONLY.patch, failed testing. View results

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

I think the code that we have in the patch is just fine, being more explicit is better here :)

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 31: 2923567-31.patch, failed testing. View results

Sam152’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
15.94 KB

Rerolling.

plach’s picture

Status: Reviewed & tested by the community » Needs review

This looks great, I have only one doubt (and the inevitable nitpick):

  1. +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
    @@ -445,6 +453,42 @@ public function testBaseFieldDeleteWithExistingData() {
    +      $dedicated_deleted_revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
    +      $this->assertTrue($schema_handler->tableExists($dedicated_deleted_revision_table_name), 'A dedicated revision table was created for the deleted new_base_field.');
    

    Why are we creating a dedicated deleted field revision table even if the field is non-revisionable?

  2. +++ b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php
    @@ -167,9 +172,12 @@ protected function makeBaseFieldEntityKey() {
    +   *   (optional) The entity type ID the base field should be attached to.
    

    Missing default description :)

Sam152’s picture

Had a look into this. I can confirm for the Revisionable entity type, non revisionable base field test case, a deleted revision and data and revision table exist, with a single row for both. Not sure if that is intentional or was just easier to implement by design. Maybe @amateescu can help out here.

amateescu’s picture

Sorry, I'm afk for a few days so I won't be able to look at the code, but it may very well be a mistake from the initial patch that we could correct here :)

Sam152’s picture

Dug into this a little bit deeper. In SqlContentEntityStorageSchema::getDedicatedTableSchema, the code which checks if a revision table is created for some FieldStorageDefinitionInterface is:

    if ($entity_type->isRevisionable()) {
      ...
      $dedicated_table_schema += [$table_mapping->getDedicatedRevisionTableName($storage_definition) => $revision_schema];
    }

So, I think the answer is that currently if a field requires dedicated table storage and is not revisionable but the entity type it is attached to is, field revision tables are created. This could be so that in ::updateDedicatedTableSchema, table layouts don't have to be changed if a field is changed from revisionable to non-revisionable and back again?

Perhaps we could detect in getDedicatedTableSchema if the schema was being created for a previously shared table field which had been deleted, and was thus unlikely to change from non-revisionable to revisionable. In that sense we'd have less tables but also more complexity, at which point I'd ask, is it worth it? Perhaps worth further discussion in a follow up?

Sam152’s picture

Here is an attempt at this. Do we purge field data for all revisions of a field or only the default?
I had a read through SqlContentEntityStorage::readFieldItemsToPurge, but am a little out of my depth.

Not confident this is the right approach to take or even why this works.

amateescu’s picture

@Sam152, thanks for digging into this!

I think #39 is the proper answer for #36.1 (dedicated revision tables are created even for non-revisionable fields if the entity type itself is revisionable), and changing that is not in the scope of this issue.

So we just need a small update to the patch in #35 for #36.2 :)

plach’s picture

Agreed, I just wanted to make sure it wasn't a bug in the test code, #36 makes sense to me as well, and given that at the time I was working on #2497737: Entity type revisionability is not taken into account when switching field revisionability, it's probably the right answer:

Currently entity type revisionability is not taken into account when determining whether marking fields as revisionable should trigger db updates. In fact, if the entity is not revisionable switching field revisionability shouldn't affect the final schema, at least for the default SQL storage.

Let's just fix the nitpick and get this in :)

amateescu’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
15.96 KB
1.02 KB

This simple change fixes the nitpick, so back to RTBC! Interdiff is to #35.

Sam152’s picture

Thanks @plach and @amateescu! Looks good to me.

  • plach committed bdba294 on 8.5.x
    Issue #2923567 by Sam152, amateescu: Select from correct entity tables...
plach’s picture

Status: Reviewed & tested by the community » Fixed

Committed bdba294 and pushed to 8.5.x. Thanks!

plach’s picture

FileSize
81.92 KB

Oops :)

Sam152’s picture

Congrats on the first commit @plach! 🎉

mondrake’s picture

plach’s picture

Ouch :(

Status: Fixed » Closed (fixed)

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