Problem/Motivation

An entity query with a condition on a revision metadata key / field leads to e.g.

Drupal\Core\Entity\Query\QueryException: 'revision_created' not found

The problem relies in \Drupal\Core\Entity\Query\Sql\Tables::addField() where when calling $table = $this->ensureEntityTable($index_prefix, $sql_column, $type, $langcode, $base_table, $entity_id_field, $entity_tables); we are not providing the entity revision table when not querying "all revisions".

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

hchonov created an issue. See original summary.

hchonov’s picture

hchonov’s picture

The last submitted patch, 2: 2986887-2-test-only.patch, failed testing. View results

hchonov’s picture

Issue tags: +Entity query
tstoeckler’s picture

Great find, also nice test! Looks pretty great to me, just one question:

+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
@@ -187,15 +187,20 @@ public function addField($field, $type, $langcode) {
+          $revision_table = $entity_type->getRevisionTable();

Should we not check that the field storage is revisionable and maybe also that the field name is a revision metadata key?

hchonov’s picture

Should we not check that the field storage is revisionable and maybe also that the field name is a revision metadata key?

I don't think that this is necessary, but if we want to be explicit, then it works for me :).

tstoeckler’s picture

Thanks, yeah that looks good to me. Will give one of the other entity maintainers the chance to take a look at this as well, but for me this is good to go.

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me as well. This patch shows how badly \Drupal\Core\Entity\Query\Sql\Tables needs to be rewritten after we finish all the issues from #2960147: Finalize the entity storage :/

hchonov’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
3.65 KB
819 bytes

I've just realized that the $field parameter of \Drupal\Core\Entity\Query\Sql\Tables::addField is not always simply the name of the field, but it might be [field name].[property name], so if we have a field with multiple properties as revision metadata key e.g. entity_reference_revisions, then the current code won't work.
Therefore we should retrieve the field name from the field storage definition Instead of using the $field parameter.

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

#10 makes perfect sense.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Just thinking but might this be better to change:

        if ($all_revisions && $field_storage && $field_storage->isRevisionable()) {
          $data_table = $entity_type->getRevisionDataTable();
          $entity_base_table = $entity_type->getRevisionTable();
        }
        else {
          $data_table = $entity_type->getDataTable();
          $entity_base_table = $entity_type->getBaseTable();
        }

to

          if ($field_storage && $field_storage->isRevisionable()) {
          $data_table = $entity_type->getRevisionDataTable();
          if ($all_revisions) {
            $entity_base_table = $entity_type->getRevisionTable();
          }
          else {
            $entity_base_table = $entity_type->getBaseTable();
          }
        }
        else {
          $data_table = $entity_type->getDataTable();
          $entity_base_table = $entity_type->getBaseTable();
        }

That way we'll get one less join. OTOH this will lead to always using the revision table even when not needed so that's probably wrong. How about:

+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
@@ -191,11 +192,18 @@ public function addField($field, $type, $langcode) {
+            $revision_table = $entity_type->getRevisionTable();
...
+        if ($revision_table) {
+          $entity_tables[$revision_table] = $this->getTableMapping($revision_table, $entity_type_id);
+        }

Change this to assign $data_table instead. The revision table contains all the data from the non revision data table right? And then we won't have to join across the three tables and have the later if.

hchonov’s picture

The revision table contains all the data from the non revision data table right?

@alexpott, if I understand you correctly then you are asking if the tables are identical and that the revision table only has one more column for the revision ID? If this is the question, then the answer is no.
We might have revision table and revision data table, which are different. If the entity type is both translatable and revisionable then there will be a revision and revision data tables, in which case the revision table will contain all the revision related information only - e.g. revision metadata keys, ID, revision ID and langcode, the revision data table will contain only the revisionable fields without the revision metadata fields. Additionally there will be a data table, which will contain the revisionable fields of the current default entity revision together with the non-revisionable fields. Note that non-revisionable fields aren't placed in any revision table, but only in the base or data table.

See \Drupal\Core\Entity\Sql\SqlContentEntityStorage::getTableMapping() for more information.

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

I would really prefer not to try and optimize \Drupal\Core\Entity\Query\Sql\Tables::addField() at this point, it needs to be completely rewritten after #2960147: Finalize the entity storage.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 10: 2986887-10.patch, failed testing. View results

tstoeckler’s picture

Status: Needs work » Reviewed & tested by the community

Random failure.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

This patch causes Drupal\KernelTests\Core\Entity\EntityQueryTest to fail.

tstoeckler’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

@tstoeckler it's not a legacy test though. Legacy tests will be removed in D9 and this shouldn't be. We need a non legacy test.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
1.46 KB
2 KB
3.64 KB

Re #19: See the comment in EntityTestMulWithRevisionLog for the reasoning:

This entity type does not define revision_metadata_keys on purpose to test the BC layer.

In other words, all tests that use that entity type are "inherently" legacy, because they implicitly test that BC layer. That is very unfortunate, I absolutely agree. But it seems that was a conscious decision in that issue so it seems kind of out of scope of this issue to revert or change that decision.

Luckily, though, the test can actually be written to use entity_test_revlog (i.e. the non-translatable "sibling") to avoid this problem entirely. So here's an adapted patch including a test-only one to prove that it correctly fails like this, as well.

The last submitted patch, 20: 2986887-20--tests-only.patch, failed testing. View results

amateescu’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Entity query

Very nice! #19 has been addressed wonderfully, so back to RTBC :)

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 78da9a1dca to 8.7.x and fcac0e25f8 to 8.6.x. Thanks!

  • alexpott committed 78da9a1 on 8.7.x
    Issue #2986887 by hchonov, tstoeckler, amateescu, alexpott: Impossible...

  • alexpott committed fcac0e2 on 8.6.x
    Issue #2986887 by hchonov, tstoeckler, amateescu, alexpott: Impossible...

Status: Fixed » Closed (fixed)

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

Berdir’s picture

Yay, one bug down.

Looks like querying by revision id field is still broken though: #2766135: EntityQuery with condition on the revision field leads to wrong results