How to query on most recent language specific revision? Entity query latestRevision() is not language aware

Created on 16 October 2019, about 5 years ago
Updated 30 April 2024, 8 months ago

Problem/Motivation

How to query on the most recent language specific revision?

When using entity query latestRevision(), it always returns the single most recent revision ID regardless of language.

When using content_translation, content_moderation and workflows, a site visitor can create revisions for content translations independently of each other. For example, Node 1 has both translation A and translation B. A revision of translation B is in a draft state, call this Revision 42. Content author then makes a revision to translation A, call this revision 43.

In the case of πŸ› Make this work with multilingual Closed: won't fix , lightning_scheduler TransitionManager::getTransitionable() uses the following query logic to find pending revisions to transition. The query logic fails to find the revision 42 with the scheduled transition.

    $ids = $storage->getQuery()
      ->latestRevision()
      ->accessCheck(FALSE)
      ->condition('scheduled_transition_date.value', $now, '<=')
      ->execute();

Proposed resolution

TBD

Remaining tasks

TBD

User interface changes

TBD

API changes

TBD

Data model changes

TBD

Release notes snippet

TBD

Related Issues

See these and/or sidebar
πŸ› Make this work with multilingual Closed: won't fix
#3064267: Node revision issue: langcode for all the revisions is default language β†’
#2784201: Track the latest_revision and latest_revision_translation_affected ID in the entity data table for revisionable entity types β†’
πŸ› Entity queries for latest revisions should return the latest workspace-specific revisions Postponed: needs info
πŸ› Entity queries querying the latest revision very slow with lots of revisions Needs work

πŸ’¬ Support request
Status

Active

Version

11.0 πŸ”₯

Component
EntityΒ  β†’

Last updated about 17 hours ago

Created by

πŸ‡ΊπŸ‡ΈUnited States jasonawant New Orleans, USA

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡¨πŸ‡¦Canada mparker17 UTC-4

    Here's some code that seems to do what my use-case requires, in Drupal 10, running on PostgreSQL, based on my understanding of how the node translation system works (which might be wrong)...

      /**
       * Load the latest revision for a translation.
       *
       * @param int|string $nodeId
       *   The node ID to load.
       * @param string $langcode
       *   The language code to load.
       *
       * @return \Drupal\node\NodeInterface|null
       *   The node revision for the given node in the given language.
       */
      public function loadLatestRevisionTranslation(int|string $nodeId, string $langcode): ?NodeInterface {
        $answer = NULL;
    
        // Select rows from the node_field_revision table, which contains all
        // revisions from all languages (node_revision only contains revisions for
        // the language that the node was created in).
        $query = $this->database->select('node_field_revision', 'nfr');
    
        // Limit the selection to rows that match the given node and language code.
        $query->condition('nfr.nid', $nodeId);
        $query->condition('nfr.langcode', $langcode);
    
        // When you save a revision for a node, revision_translation_affected is set
        // to 1 for the language that you were editing when you clicked "Save". If
        // the node happens to have other translations, all translations get a new
        // revision with the same vid. However, in this case,
        // revision_translation_affected is set to 1 for the language you were
        // editing when you clicked "Save" as before; but the revisions for the
        // other languages get set to NULL.
        // We only care about the revision that the user clicked "Save" on, so we
        // limit the selection to rows where revision_translation_affected is set to
        // 1.
        $query->condition('nfr.revision_translation_affected', 1);
    
        // Tell the query engine to group rows where the nid and langcode match,
        // i.e.: group rows by what we think of as a node-translation. Inside that
        // grouping of rows (a.k.a. window, sliding window, subgroup, etc.), find
        // the maximum revision ID (vid). Because vids are assigned sequentially,
        // this effectively finds the most-recent revision for that translation.
        //
        // Note that if this is the ONLY field that we are selecting, we could just
        // SELECT MAX(vid) here, without the sliding window... however, if we
        // decided to SELECT any other fields (i.e.: other use-cases, future
        // changes, debugging purposes, etc.), we would get different results
        // (unless we are very careful about how we prepare the GROUP BY and ORDER
        // BY clauses). Using a window function allows us to SELECT other fields
        // without affecting the result in this column.
        $query->addExpression('MAX(nfr.vid) OVER (PARTITION BY nfr.nid, nfr.langcode)');
    
        // Limits/ranges are applied at the end: and in this case, we would have one
        // row for each revision created for the given translation, with the same
        // MAX(vid) value in each row, so we only need to return the first row.
        $query->range(0, 1);
    
        // Now execute the query, get the first value in the first column, try to
        // load the node with that revision, and if we are successful, assign the
        // variable that we return.
        try {
          $statement = $query->execute();
          $result = $statement->fetchField(0);
          if (\is_numeric($result)) {
            $candidate = $this->getNodeStorage()->loadRevision($result);
            if ($candidate instanceof NodeInterface) {
              $answer = $candidate->getTranslation($langcode);
            }
          }
        }
        catch (\Throwable $t) {
          // No-op: if an exception occurs, return NULL.
        }
    
        return $answer;
      }
    
Production build 0.71.5 2024