Problem/Motivation

Spinning this off as a follow-up to #2722307: Move translation based conditions into database query on revisions overview page, which changed the logic that is used in the default revision overview controller.

If a custom entity type is defined without setting revision_data_table in the entity type definition, the following error is thrown when you visit the revision overview route for an entity of that type:

Uncaught PHP Exception Drupal\\Core\\Entity\\Query\\QueryException: "'revision_translation_affected' not found" at /opt/drupal/web/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php line 376

Notably, simple adding revision_data_table to the entity type definition does not fix the error, so there must be something else that is required (perhaps in an update hook for the entity type module?)...

Originally reported by @paul121 in this comment: https://www.drupal.org/project/drupal/issues/2722307#comment-16523985

This change broke the Version History "Revisions" page for our custom entity types that do not include a revision_data_table. The revision page works after defining the revision_data_table and doing a fresh install, but not after simply doing a cache rebuild. See error details below.

Interestingly, there was a change to the entity type the RevisionVersionHistoryTest test is using: https://git.drupalcode.org/project/drupal/-/commit/4d6098cd4564f7d52f4d4... It was previously using EntityTestRev which does not define a revision_data_table, but now it uses EntityTestMulRev which does define a revision_data_table .. !

So... it is not clear to me if revision_data_table is a required attribute for revisionable content entity types?? If it is not required, then I think this issue introduced a breaking change.

Unfortunately, I cannot find good documentation about what is required with revision_data_table. This doc page Making an entity revisionable does not mention revision_data_table. FWIW our custom entity type that does not include this has been working fine for many years.

Steps to reproduce

  1. Create a custom entity type that is translatable and revisionable but does not set revision_data_table.
  2. Create an entity of that type.
  3. Visit the revisions overview for that entity.
  4. Observe a white screen w/ the dreaded message: "The website encountered an unexpected error. Try again later."

Note: we discovered this with the Log module, which provides a log entity type without defining revision_data_table, so that can be used to replicate the issue.

Proposed resolution

The revision_data_table entity type property is not documented in Making an entity revisionable, which implies that it is not required. If it is in fact required, then that means [#] inadvertently introduced a breaking change.

We should determine if it is possible to fix the error in core code without requiring revision_data_table. If so, let's do that!

Otherwise, we should update the Making an entity revisionable documentation to make it clear that revision_data_table is required. And we should document how module maintainers can properly update their entity definitions (for example: if an update hook is required, because simply adding revision_data_table does not fix the error).

Remaining tasks

TBD

User interface changes

None.

Introduced terminology

None.

API changes

None.

Data model changes

None.

Release notes snippet

None.

Comments

m.stenta created an issue. See original summary.

m.stenta’s picture

Uncaught PHP Exception Drupal\\Core\\Entity\\Query\\QueryException: "'revision_translation_affected' not found" at /opt/drupal/web/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php line 376

I haven't full wrapped my head around all the logic involved, but wanted to note something I discovered while investigating this...

This error occurs when you try to visit the Revisions tab on an existing installation (where the entity type has already been installed without revision_data_table declared). If you declare revision_data_table and reinstall the entity type from scratch, then everything works as expected.

When the error occurs it is because it's looking for the revision_translation_affected column in the entity's revision_table... not in its revision_data_table.

For example, using the Log entity module for testing (without revision_data_table declared in the log entity type definition), if I put a breakpoint on line 376 of Tables.php (in the Tables::ensureEntityTable() method, where this exception is thrown), it is looking for a revision_translation_affected column in the log_revision table.

When I add revision_data_table: 'log_field_revision', to the log entity type definition and rebuild caches, I would expect that Tables::ensureEntityTable() would instead look for the revision_translation_affected column in the log_field_revision table (which does exist). However, for some reason EntityType::getRevisionDataTable() is still returning NULL.

https://git.drupalcode.org/project/drupal/-/blob/main/core/lib/Drupal/Co...

Does the revision_data_table property of entity type definitions get cached somewhere else, which is not rebuilt via drush cr?

m.stenta’s picture

Does the revision_data_table property of entity type definitions get cached somewhere else, which is not rebuild via drush cr?

I dug into this and discovered that entity type definitions are cached in the key_value database table in the entity.definitions.installed collection. The log entity definition (for example) has a key name of log.entity_type.

I found that I can update the cached definition to add the revision_data_table like so:

$manager = \Drupal::service('entity.definition_update_manager');
$entity_type = $manager->getEntityType('log');
$entity_type->set('revision_data_table', 'log_field_revision');
$manager->updateEntityType($entity_type);
m.stenta’s picture

I dug into this and discovered that entity type definitions are cached in the key_value database table in the entity.definitions.installed collection.

I just ran into another issue related to the entity definition key_value caching.

Running \Drupal::service('entity.definition_update_manager')->updateEntityType($entity_type); in an update hook (see my previous comment's example) threw this error in production (but not locally):

Error: Class name must be a valid object or a string in Drupal\Core\Entity\EntityTypeManager->createHandlerInstance() (line 283 of /opt/drupal/web/core/lib/Drupal/Core/Entity/EntityTypeManager.php) #0 /opt/drupal/web/core/lib/Drupal/Core/Entity/EntityTypeListener.php(112): Drupal\Core\Entity\EntityTypeManager->createHandlerInstance()

This caused the update hook to fail for one particular entity type I was updating. I investigated, and found that the cached entity type had an outdated storage handler class. I had recently refactored the entity type and removed the need for a custom storage handler, and in doing so I removed storage from the handlers of the #ContentEntityType[...] class attribute, so it would fall back to the default SqlContentEntityStorage. Everything worked as expected, and I did not write an update hook to update the cached key_value entity definition. I didn't know I needed to!

So this issue was sitting there waiting to surprise me ever since. It only presented itself when running \Drupal::service('entity.definition_update_manager')->updateEntityType($entity_type).

I didn't encounter it locally because my local development database was created after the change that removed storage from the handlers. So it was cached with SqlContentEntityStorage as the storage handler class.

Now I wonder what other discrepancies exist in my cached entity definitions...

Is this behavior documented somewhere? Is it best practice to always write update hook whenever #ContentEntityType[...] class attributes are changed? It feels like this shouldn't be necessary, since in theory Drupal core can just rebuild the cached version from the class attribute - that's essentially what the update hook(s) would do.

m.stenta’s picture

I did some more digging to understand this... and now I realized this "works as designed", and has for quite some time... :-/

Relevant change record w/ guidance from Drupal 8.7: https://www.drupal.org/node/3034742

And the original issue: #2976035: Entity type CRUD operations must use the last installed entity type and field storage definitions .

Now I wonder what other discrepancies exist in my cached entity definitions...

Looks like I've got my work cut out for me... 😅😭

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.