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 therevision_data_tableand 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
RevisionVersionHistoryTesttest is using: https://git.drupalcode.org/project/drupal/-/commit/4d6098cd4564f7d52f4d4... It was previously usingEntityTestRevwhich does not define arevision_data_table, but now it usesEntityTestMulRevwhich does define arevision_data_table.. !So... it is not clear to me if
revision_data_tableis 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 mentionrevision_data_table. FWIW our custom entity type that does not include this has been working fine for many years.
Steps to reproduce
- Create a custom entity type that is translatable and revisionable but does not set
revision_data_table. - Create an entity of that type.
- Visit the revisions overview for that entity.
- 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
Comment #2
m.stentaI 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_tabledeclared). If you declarerevision_data_tableand 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_affectedcolumn in the entity'srevision_table... not in itsrevision_data_table.For example, using the Log entity module for testing (without
revision_data_tabledeclared in thelogentity type definition), if I put a breakpoint on line 376 ofTables.php(in theTables::ensureEntityTable()method, where this exception is thrown), it is looking for arevision_translation_affectedcolumn in thelog_revisiontable.When I add
revision_data_table: 'log_field_revision',to thelogentity type definition and rebuild caches, I would expect thatTables::ensureEntityTable()would instead look for therevision_translation_affectedcolumn in thelog_field_revisiontable (which does exist). However, for some reasonEntityType::getRevisionDataTable()is still returningNULL.https://git.drupalcode.org/project/drupal/-/blob/main/core/lib/Drupal/Co...
Does the
revision_data_tableproperty of entity type definitions get cached somewhere else, which is not rebuilt viadrush cr?Comment #3
m.stentaI dug into this and discovered that entity type definitions are cached in the
key_valuedatabase table in theentity.definitions.installedcollection. Thelogentity definition (for example) has a key name oflog.entity_type.I found that I can update the cached definition to add the
revision_data_tablelike so:Comment #4
m.stentaI just ran into another issue related to the entity definition
key_valuecaching.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
storagehandler class. I had recently refactored the entity type and removed the need for a custom storage handler, and in doing so I removedstoragefrom thehandlersof the#ContentEntityType[...]class attribute, so it would fall back to the defaultSqlContentEntityStorage. Everything worked as expected, and I did not write an update hook to update the cachedkey_valueentity 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
storagefrom thehandlers. So it was cached withSqlContentEntityStorageas 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.Comment #5
m.stentaI 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 .
Looks like I've got my work cut out for me... 😅😭