When you have a field with dedicated storage (e.g. a base field with cardinality > 1), DefaultTableMapping::getFieldTableName() will not return the dedicated table that the field actually resides in.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

kevin.dutra created an issue. See original summary.

dawehner’s picture

That sounds indeed like a bug which should be solved.

amateescu’s picture

Component: database system » entity system
Priority: Normal » Major
Status: Active » Needs review
Issue tags: +entity storage
FileSize
1.77 KB
5.94 KB

This blocks #2477899: Multiple valued Base fields won't work in Views and at least two contrib modules have to work around the problem (Dynamic Entity Reference and Entityqueue), so I'm bumping to major.

Here's the relevant portion of the patch from #2477899: Multiple valued Base fields won't work in Views and a unit test for it.

The last submitted patch, 3: 2644088-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 3: 2644088.patch, failed testing.

dawehner’s picture

Looking into the failures for now.

dawehner’s picture

Status: Needs work » Needs review
FileSize
7.61 KB
1.95 KB

Here is a fix for that.

Regarding the actual patch, this looked perfect for me. First I thought you renamed variables for the sake of it, but this was actually required as $definitions is reused later again.

kevin.dutra’s picture

Looks good to my eyes. RTBC+1

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

This looks like a great solution. I remember from working on #2477899: Multiple valued Base fields won't work in Views that the $definitions rename was needed.

jibran’s picture

I think @plach_ should eyeball this other then that +1 for RTBC.

alexpott’s picture

I think this looks good too. I've pinged @plach about this issue in IRC.

As far as I can see the patch looks good - it comes with a test that fails if the fix is not applied and the changes make sense. Also I think this is an obvious bug fix that belongs in 8.0.x.

plach’s picture

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -360,8 +360,8 @@ public function getTableMapping(array $storage_definitions = NULL) {
+        return $table_mapping->requiresDedicatedTableStorage($definition) && $definition->isBaseField();

Sorry, I don't understand this change: why are we excluding configurable fields?

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Changing status given #11

amateescu’s picture

@plach, I'm not sure either, it came from #2477899-11: Multiple valued Base fields won't work in Views - see the last paragraph of that comment.

If we remove that change from the patch and nothing breaks.. we're missing some more test coverage?

Status: Needs review » Needs work

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

kevin.dutra’s picture

@plach, excellent question. It's been too long at this point and I can't remember what reasoning I may have had (if any...I may have just been too focused on base fields).

EDIT: Heck, it may have just been to work around that wall of errors that just popped up. ;) Can't remember if I tried to go down that rabbit hole.

plach’s picture

If we remove that change from the patch and nothing breaks.. we're missing some more test coverage?

I think we do: the previous patch was green and IIRC configurable fields are supposed to be returned.

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

amateescu’s picture

Status: Needs work » Needs review
FileSize
8.27 KB
801 bytes

IIRC configurable fields are supposed to be returned

Well.. in HEAD they are not :) If you try this snippet:

\Drupal::entityManager()->getStorage('node')->getTableMapping()->getFieldTableName('body');

you'll get a nice exception:

Drupal\Core\Entity\Sql\SqlContentEntityStorageException: Table information not available for the 'body' field. in Drupal\Core\Entity\Sql\DefaultTableMapping->getFieldTableName() (line 180 of core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php).

And with the patch, the correct table name is returned (i.e. node__body). Let's see if this small changes fixes all the failures from #14, and I'll work on adding an integration test for the default table mapping because phpunit tests can only take us so far...

kevin.dutra’s picture

This patch adds a small integration test case which (I think) exposes another issue. I expect this to fail.

SqlContentEntityStorage contains a $tableMapping property which is populated on the first call to getTableMapping(). Subsequent calls use this locally cached version rather than recreating it. However, nothing forces this to be rebuilt after a field storage definition is inserted, so the table mapping has stale data for the remainder of the request.

To illustrate further, if the test were to use this code segment, where you use the current definitions to forcibly bypass the locally cached version, it would pass:

$entity_type = 'entity_test_rev';
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $field_storage = \Drupal::entityManager()->getFieldStorageDefinitions($entity_type);
    $table_mapping = \Drupal::entityManager()->getStorage($entity_type)->getTableMapping($field_storage);
    $this->assertEqual($table_mapping->getFieldTableName($this->fieldName), $table_mapping->getDedicatedDataTableName($this->fieldStorage));

But I'm not sure that adjusting the test like that is really the right way to go.

The easy fix would be to simply remove the property and force it to be rebuilt every time. I'm not sure how bad the performance impact would be if that was done. It implements onFieldStorageDefinitionCreate(), but that appears to run prior to the save of the field storage, so that doesn't really help. As far as I can tell, there isn't any sort of post-save equivalent mechanism.

Status: Needs review » Needs work

The last submitted patch, 20: 2644088-20.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
FileSize
5.33 KB
10.33 KB
2.72 KB

After lots of digging around I finally have a firm answer for #12/#17: SqlContentEntityStorage::getTableMapping() is *not* supposed to return configurable fields, because of this line inside that method:

$definitions = $storage_definitions ?: $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);

and \Drupal\Core\Entity\EntityFieldManager::getFieldStorageDefinitions() only returns base field storage definitions: http://cgit.drupalcode.org/drupal/tree/core/lib/Drupal/Core/Entity/Entit...

So I went back to the patch from #7 and added a small integration test class for DefaultTableMapping. Interdiff is to #7.

The last submitted patch, 22: 2644088-22-test-only.patch, failed testing.

dawehner’s picture

+++ b/core/modules/system/src/Tests/Entity/DefaultTableMappingIntegrationTest.php
@@ -0,0 +1,75 @@
+class DefaultTableMappingIntegrationTest extends EntityUnitTestBase {

Please use the new base class: \Drupal\KernelTests\Core\Entity\EntityKernelTestBase

amateescu’s picture

Status: Needs review » Postponed

Please use the new base class: \Drupal\KernelTests\Core\Entity\EntityKernelTestBase

We need #2683391: Backport EntityKernelTestBase to 8.0.x if we want to do that.

dawehner’s picture

Well, do we really need this patch for 8.0.x still?

amateescu’s picture

Why not, 8.1.0 is still a month away :)

dawehner’s picture

Well, you could use one base class for the tests for one version and the old one for 8.0.x

amateescu’s picture

Yes, but it would be nicer to have a single patch for all three branches. Otherwise I wouldn't have bothered with #2683391: Backport EntityKernelTestBase to 8.0.x :)

kevin.dutra’s picture

Status: Postponed » Needs review

The backport for the EntityKernelTestBase went in, so this is unblocked. :)

kevin.dutra’s picture

And while I won't transition it to RTBC myself, based on the explanation in #22, I'll say RTBC +1.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Thank you @amateescu!

jibran’s picture

Assigned: Unassigned » plach

Thank you @amateescu for working on this patch. Assigning it to @plach to review it one final time.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 32: 2644088-32.patch, failed testing.

alexpott’s picture

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

Just a note I'm leaving this RTBC at the moment in case plach is able to re-review before commit.

andypost’s picture

xjm’s picture

Version: 8.0.x-dev » 8.1.x-dev
Issue tags: +rc target, +Needs subsystem maintainer review

Marking for @plach's subsystem maintainer review but leaving at RTBC as @catch said.

Discussed with @Cottser, @effulgentsia, @alexpott and we agreed to look at this as an RC target given its impact and the fact that it is risky for a patch release.

plach’s picture

Assigned: plach » Unassigned
Status: Reviewed & tested by the community » Needs review
Issue tags: -Needs subsystem maintainer review
  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -360,8 +360,8 @@ public function getTableMapping(array $storage_definitions = NULL) {
    +        return $table_mapping->requiresDedicatedTableStorage($definition) && $definition->isBaseField();
    
    After lots of digging around I finally have a firm answer for #12/#17: SqlContentEntityStorage::getTableMapping() is *not* supposed to return configurable fields, because of this line inside that method:

    I'm sorry, but I'm not convinced by this argument: SqlContentEntityStorage::getTableMapping() *is* supposed to return configurable fields, in fact EntityFieldManager::getFieldStorageDefinitions() returns the body storage definition of the related configurable field. Besides of this, if we agree that user__roles should be part of the table mapping (and we do :), I don't see a reason on earth why node__body shouldn't be.

  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -1587,7 +1591,12 @@ public function finalizePurge(FieldStorageDefinitionInterface $storage_definitio
    +    // The storage definition might be different stored to the one in the DB, so
    

    The first sentence sounds a bit weird, but maybe it's just me.

amateescu’s picture

@plach, you're right, I only looked at the code of EntityFieldManager::getFieldStorageDefinitions() and it seemed to return only base fields, but after some manual testing I can confirm that it does return configurable fields as well.

So I added back the interdiffs from #14 and #19 to the latest patch and opened #2705205: Improve test coverage around updating table mapping after a new field storage definition is added for the problem discovered in #20.

plach’s picture

Thanks! And here's a test-only patch

tstoeckler’s picture

Patch looks good to me. I agree it shouldn't land in a patch release.

amateescu’s picture

We also had one in #22 :P

plach’s picture

Status: Needs review » Reviewed & tested by the community

The patch looks great to me now, and tests fail/succeed as expected.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 42: 2644088-42.test.patch, failed testing.

amateescu’s picture

Status: Needs work » Reviewed & tested by the community

:)

  • effulgentsia committed fbbf509 on 8.2.x
    Issue #2644088 by amateescu, kevin.dutra, dawehner, plach:...

  • effulgentsia committed fdca24c on 8.1.x
    Issue #2644088 by amateescu, kevin.dutra, dawehner, plach:...
effulgentsia’s picture

Status: Reviewed & tested by the community » Fixed

Pushed to 8.2.x and 8.1.x. I'm very happy that we waited for #40.1 to be addressed before committing this.

+++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
@@ -147,15 +147,16 @@ public function getFieldTableName($field_name) {
       $table_names = array(
         $storage->getDataTable(),
         $storage->getBaseTable(),
         $storage->getRevisionTable(),
+        $this->getDedicatedDataTableName($storage_definition),
       );

This patch doesn't add getDedicatedRevisionTableName(), which I think is okay, because the function returns the first matching table, and I don't think you can be in a situation where you wouldn't match the data table and match the revision table only. That said, is that logic also true for getRevisionTable(), in which case, should we remove that too? Or else, add a comment explaining why we have one but not the other? Either way, non-release-blocking follow-up material.

plach’s picture

That said, is that logic also true for getRevisionTable(), in which case, should we remove that too? Or else, add a comment explaining why we have one but not the other? Either way, non-release-blocking follow-up material.

The current logic should be fine because we can have fields living only in the revision table, e.g. revision_log, while, as you were pointing out, we can't have a field living in a dedicated revision table while not living in the dedicated data table. A comment explaining this would definitely be helpful :)

Status: Fixed » Closed (fixed)

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