Problem/Motivation
There is no way to query entities for the latest revision. For instance, consider this revision status for a node:
- v1: draft
- v2: published
- v3: draft
With the currentRevision()/allRevisions() methods on entity queries, I can either query v2, or all revisions, but I can't query only v3, which in many cases is the one that is of interest.
Reported by @joachim
Proposed resolution
Add a latestRevision() method to \Drupal\Core\Entity\Query\QueryInterface which restricts the result set to the latest revisions.
Remaining tasks
Review.
User interface changes
Nope.
API changes
API addition: A new latestRevision() method is added to \Drupal\Core\Entity\Query\QueryInterface.
Data model changes
Nope.
Comments
Comment #2
hchonovIn which use case do you want to query only the latest draft revision? What if you have two draft revisions after the default one - do you still want to query only the last one?
Comment #4
amateescu commentedFor example when you are building a list of moderated entities (without Views) and you need to show the latest revisions instead of the default ones.
Of course :)
You can see why this is needed in the interdiff from #2902187-35: Provide a way for users to moderate content (hint:
ModeratedNodeListBuilder::load()from that interdiff).Comment #5
amateescu commentedJust like in #2865579: Rewrite the 'Latest revision' views filter and remove the revision_tracker table, using the query suggested by @joachim in #2784201-9: Track the latest_revision and latest_revision_translation_affected ID in the entity data table for revisionable entity types works nicely for entity queries too :)
This is how a simple query without any conditions looks like:
Conditions work just fine as well, as can be seen in the tests added by the patch.
What doesn't work, however, is querying for the latest revision of a *relationship*, i.e. the value of a field from the latest revision of a referenced entity. Consider this scenario:
- The node entity type has an entity reference field to media entities
- There is a media item with revision 1 (name: 'published', default revision) and revision 2 (name: 'draft', pending revision)
- A node is referencing that media item
The following query will not return any results:
But this one will return the correct result:
This is the query generated in this case:
And this is the problematic part:
We are joining the base table for media instead of the revision table, so the condition for the second join (
ON media_field_revision.vid = media.vid) will use the revision ID of the default revision.But
QueryInterface::allRevisions()behaves in the same way so I don't think we should be concerned by this case.Comment #6
amateescu commentedOops, forgot a debug statement in there :)
Comment #8
timmillwoodCan't see any reason not to RTBC.
Comment #10
amateescu commentedThat was a random fail, back to RTBC.
Comment #11
wim leersSorry, I have two questions:
I think some documentation what "latest revision" means compared to "current revision" would be helpful?
Isn't this a BC break?
Comment #12
amateescu commentedThanks for the review :)
#11.1: I don't think the entity query interface is the best place to document the difference between the default (a.k.a. current) revision and the latest revision, a documentation page on d.o would be much more helpful. But I tried my best and write a short paragraph for it.
#11.2: Nope, we are allowed to add methods to interfaces in minor releases, see https://www.drupal.org/core/d8-bc-policy#interfaces
Comment #13
wim leersI forgot this was allowed.
Comment #14
xjmLooks like a reasonable addition.
This looks borderline out-of-scope-ish, though I see it was added in #11 to clarify the difference between this and latest revision; should we also deprecate
currentRevision()and adddefaultRevision()as a more explicit name? Could be a followup and we could stick a @todo on the method here.Comment #15
catchI'm not sure this explains clearly enough what's going on, generally when trying to explain this, I avoid the word 'published' as much as possible, since the default revision can be unpublished (status = 0) too.
Something like this instead maybe:
"The latest revision is the most recent revision of an entity. This will be either the default revision, or a draft revision if one exists newer than the default."
Comment #16
dinesh18 commentedImplemented as per #15. Here is an updated patch and interdiff
Comment #17
amateescu commentedThanks @Dinesh18 :) The comment needed to be wrapped at 80 cols and since we're not mentioning the current revision anymore we can use undo the out-of-scope change mentioned by @xjm in #14.
The feedback has been addressed so back to RTBC.
Comment #18
timmillwood+1 to RTBC
Comment #19
plachDidn't we settle on the "pending revision" terminology instead of draft?
Comment #20
amateescu commentedYep, we did :P
Comment #23
catchAhh yes. That reads so much better with pending revision too.
Committed/pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!
Comment #24
plachAwesome, can we add a change notice please?
Comment #25
amateescu commented@plach, sure thing, here it is: https://www.drupal.org/node/2918184
Comment #26
plachThanks!
Comment #27
xjmThis adds a method to an interface, so I don't think it should have been backported as-is. We could make a backportable version by adding the method to the base class but not to the interface yet.
I'll wait to check with catch, but I think it should be reverted from 8.4.x for now.
Comment #28
amateescu commentedInstead of reverting, this can be committed to 8.4.x if needed.
Comment #30
xjmThanks @amateescu; I committed and pushed the followup to 8.4.x.
Comment #32
2phaI had a problem using `latestRevision()` today.
I am trying to get nodes that reference a specific nid.
my query is this:
The field_release (entity reference) value has changed between the current revision and the latest revision.
It seems that the condition is looking on the current revision rather than the latest revision.
Is this expected behavior?
Comment #33
mpotter commentedIn case other people find this via Google... This change caused an issue on a client site. On this client they had migrated from D7 to D8. They ran node migrations first, then later ran node revision migrations. This had the side effect where the published node revision id is smaller than the previous revision ids (because they were migrated later).
After upgrading to 8.5.x, when the client Edits a node, the form is loaded with the previous revision data (the highest revision id) rather than the current published data.
This matches the intent of the original issue, so makes sense. So Drupal core is probably doing the right thing here. Just be aware that this change can have consequences on existing sites where migrations were not done correctly.