Problem/Motivation
Entity Queries with a condition on the revision field will lead to the wrong results when searching through all revisions, as the revision field defined in ContentEntityBase::baseFieldDefinitions() is not flagged as revisionable and therefore Drupal\Core\Entity\Query\Sql\Tables::addField() will create the query for the revision field for the data table instead for the data revision table.
As an example the following entity query
\Drupal::entityQuery("node")->condition("vid", 2)->allRevisions()->execute();
results in the following SQL:
SELECT base_table.vid AS vid, base_table.nid AS nid
FROM node_revision base_table
INNER JOIN node_field_data node_field_data ON node_field_data.nid = base_table.nid
WHERE (node_field_data.vid = '2');Proposed resolution
Fix Drupal\Core\Entity\Query\Sql\Tables::addField() and ensure that the revision ID field is taken from the revision tables if all revisions are being queried.
Remaining tasks
Review.
User interface changes
Nope.
API changes
Nope.
Data model changes
Nope.
| Comment | File | Size | Author |
|---|---|---|---|
| #42 | 2766135-42.patch | 2.8 KB | amateescu |
| #42 | 2766135-42-test-only.patch | 960 bytes | amateescu |
Comments
Comment #3
hchonovThis is at least how I think the patch should look like, but there will be exceptions a lot of exceptions now. At least locally I get the following message during the tests :
Drupal\KernelTests\Core\Entity\EntityQueryTest::testEntityQuery Array to string conversion /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php:219 /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php:107 /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Database/Schema.php:596 /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php:267 /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php:1356 /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php:1452 /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php:1357 /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Entity/EntityTypeListener.php:71 /Users/hchonov/workspace/devd8/drupal/core/lib/Drupal/Core/Entity/EntityManager.php:384 /Users/hchonov/workspace/devd8/drupal/core/tests/Drupal/KernelTests/KernelTestBase.php:832 /Users/hchonov/workspace/devd8/drupal/core/tests/Drupal/KernelTests/Core/Entity/EntityQueryTest.php:63Comment #5
hchonovThe previous problems and exceptions were caused by SqlContentEntityStorage::getTableMapping where after making the revision field revisionable it being added twice to the table mapping and later SqlContentEntityStorageSchema::getEntitySchema will break the settings of the field when it occurs twice, because we use there array_merge_recursive. I've added a check that the revision field is revisionable and now it will not be added twice to the table mapping.
I've also removed the revision fields definitions from the test entities as it should be provided now from ContentEntityBase::baseFieldDefinitions.
Comment #6
hchonovI've deleted a whitespace only.
Comment #7
hchonovI think that if this gets in, there might be custom/contrib module entities which define the revision field itself and do not retrieve it from ContentEntityBase::baseFieldDefinitions. Would be a Change Record sufficient for this change to inform the others about the change? Are we targeting 8.2.x or 8.1.x?
Comment #10
hchonovSo so and now I've added the update.
Comment #12
jeroen.b commentedI think this is quite a big bug. This also did work before (Drupal 8.0.3).
I attached the patch that I'm currently using to solve this. (I also set ->setRevisionable(TRUE) in baseFieldDefinitions on the revision field on custom entities).
Comment #14
jeroen.b commentedThis should apply. Note that the patch is not complete and only works for custom entities, only use it if you need a quick fix.
Comment #16
timmillwoodI know it makes sense for the 'revision' field to be revisionable, but does it *need* to be?
Could we not just add some conditions into the query to make sure it queries the right table?
Comment #17
hchonov@timmillwood in this case you have to ask for the revision key and then do specific stuff only for it ... I prefer a general solution and the general solution is that the revision field is revisionable...
Comment #18
hchonov@jeroen.b what you've posted here is already included in my patch.
Comment #19
andypostComment #20
jeroen.b commented@hchonov I know. Your patch is more complete. My patch should not be committed to core.
My patch is only for people who need a quick fix for custom entities who don't want to use your patch yet as it might not work correctly as it does some other stuff to existing entities too.
@timmillwood, why shouldn't it be marked "revisionable"? I think that's actually what that property is supposed to be used for.
Comment #21
jmuzz commentedWe should try to follow best practices as much as possible. It looks to me like the property should be marked revisionable. It's probably the more likely solution to get committed to core.
Still needs work as the full patch hasn't passed tests yet.
Comment #22
jmuzz commented@hchonov Why does the patch make a change to $fields['langcode'] ? If it isn't needed to solve the problem in the summary it could be moved to a separate issue to simplify this one.
Comment #23
alexpottComment #25
chipway commentedPatch didn't apply. Rerolled after solving conflict on system.install.
Comment #26
hass commentedComment #31
hchonovComment #32
trevorbradley commentedI just hit this myself, trying to pull paragraphs data, which heavily rely on older revisions (particularly when they're cloned).
Just a quick request - is there actually an alternate to QueryInterface's condition method that would actually filter on $revision_id? A replacement for:
$query->allRevisions()->condition('type','my_paragraph_type')->condition('id',12345)->condition('revision_id',12346);That properly works for filtering on non-current revision_ids?
As previously discussed here, this is generating the query:
SELECT base_table.revision_id AS revision_id, base_table.id AS id FROM paragraphs_item_revision base_table INNER JOIN paragraphs_item_field_data paragraphs_item_field_data ON paragraphs_item_field_data.id = base_table.id WHERE (paragraphs_item_field_data.type = 'my_paragraph_type') AND (paragraphs_item_field_data.id = '12345') AND (paragraphs_item_field_data.revision_id = '12346');Except paragraphs_item_field_data only contains the most recent revision_id. If I could query against the paragraphs_item_revision table instead, the query works, but condition doesn't take table names.
My quick hack is to not filter on revision_id at all, but rather to use $query->allRevisions()->condition('type','my_paragraph_type')->condition('id',12345);, gathering ALL the $revision_ids as [ $revision_id => $id] pairs, then filter on the data manually after the query is complete. Very hacky, and it may scale very poorly but it does work.
I was wondering if there was something else temporary, but more elegant, that didn't involve dumping entityQuery outright.
Comment #33
nwoodland commentedMy org has encountered this same issue. Like TrevorBradley, we've implemented a workaround wherein we have a helper function that does the additional Paragraph revision filtering right after the query runs, but it would be great to have this resolved (or even to have a better workaround, if anyone knows of one). Thanks for the work on this so far everyone!
Comment #35
neclimdulI was struggling with default_content bugs and revision failures as part of #2698425: Do not re-import existing entities and finally and finally started digging into the entity system and fell into this. Oh man that hurts.
Here's a reroll of #25 working around a bunch of code moving to DefaultTableMapping and moving the update hook.
One other interdiff, I removed the logic exception addition in EntityFieldManager. Even with the update hook, I needed a container rebuild but the rebuild failed building the field definitions. Maybe there's a way to keep that but it seemed like a super painful update. Maybe a deprecation notice would be better? I don't know....
Lets see how tests go. Hopefully just the field changes from the previous patch.
Comment #37
neclimdulThat failed pretty terribly. the revision field is completely removed from entity definitions. I assume I botched rerolling but maybe there's been some fundamental change that breaks this approach. I was dodging quite a few commits trying to resolve where changes where suppose to go and I'm not clear on how the fix works so 50/50 on where the blame lies. If someone who understands the approach wants to give some pointers I can take another stab at fixing this.
Comment #39
gordon commentedRerolled again as it was not applying.
Comment #40
dhirendra.mishra commented@gordon. Make sure to move status needs review after applying patch.
Comment #42
amateescu commentedMaking the revision ID field revisionable seems a bit risky at this point, and, since it's a "special" field (like the ID, UUID and bundle fields), I don't see why we shouldn't hardcode it in entity query itself.
Comment #44
hchonovThis is fair enough and I agree.
However, we should still have a follow-up to make the revision ID field revisionable at some point and then refactor the entity query code.
Comment #45
amateescu commentedHere we go: #3125912: Consider making the revision ID field revisionable
Comment #46
hchonovNice, thank you. So we just now need someone to RTBC, however from my point of view we are ready here!
+1 on RTBC.
Comment #47
daffie commentedThe code looks good.
The documentation is in order.
The test tests what is should test.
The bug from the IS will be fixed, small little fix.
For me it is RTBC.
Comment #54
catchCommitted/pushed to 9.1.x and backported through to 8.9.x, thanks!
Comment #55
joseph.olstadThanks!