Problem/Motivation

Currently, if an entity is revisionable, you are forced to make all non-ID base fields revisionable. Otherwise values of non-revisioned fields are not attached to loaded entities.

A bonus of this patch would be modifying entity definitions that include "created" properties from revisioned to non-revisioned. As values of created field types should not change between revisions.

Proposed resolution

Detect if any non-revisioned fields are on an entity and join the tables if any exist.

Remaining tasks

User interface changes

none

API changes

Original report by ivanjaros

The \Drupal\Core\Entity\ContentEntityDatabaseStorage::attachPropertyData() method is responsible for loading field data directly form entity's table.

If the entity is revisionable, it loads data from the {ENTITY_field_revision} table. If the entity is not revisionable, it loads data from {ENTITY_field_data} table.

However, not all fields has to be revisioned so there may be more fields in the {ENTITY_field_data} table than in {ENTITY_field_revision} table.

Previousely(before A13 and the [#2259243]) such fields could have existed in the {ENTITY} table. Now, it is not possible anymore. That means that fields that are not revisioned has to be placed in the {ENTITY_field_data} table.

What all of this means is that such fields will never be loaded since attachPropertyData() will load the data from the {ENTITY_field_revision} table.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Anonymous’s picture

Title: \Drupal\Core\Entity\ContentEntityDatabaseStorage::attachPropertyData() ignores unrevisioned fields » ContentEntityDatabaseStorage::attachPropertyData() ignores unrevisioned fields
Anonymous’s picture

Anonymous’s picture

Status: Active » Needs review
FileSize
2.4 KB

This patch does address this the issue by joining the data and revision table and using the data from the revision table for common(ie. revisioned) fields.

Status: Needs review » Needs work

The last submitted patch, 3: attachPropertyData-unrevisione-fields.patch, failed testing.

Anonymous’s picture

bump

plach’s picture

I'd say this is the correct solution. Maybe an optimization could be checking whether we do have non-revisionable fields, otherwise we would skip the join on the data table. Anyway, entity cache would avoid the performance penalty of the additional join most of the time.

plach’s picture

Assigned: Unassigned » Berdir

Let's see what Berdir thinks about #6.

Anonymous’s picture

I tihnk that we should indeed check if the entity has non-revisioned fields and store this information in static cache....but

I was just about to rewrite the patch but then I've noticed that there are these possible scenarios:

  • a) entity is not revisionable at all - we'll use field data table
  • b) entity is revisionable and does not have any revisioned base fields - we'll use field data table
  • c) entity is revisionable and has revisioned base fields
    1. some base fields are revisioned and some are not - we'll use field data table and join it with field revision table
    2. all base fields are revisioned - we'll use field revision table

So that basically means this:


Scenario | field data table | field revision table
--------------------------------------------------------------
a | 1 | 0
b | 1 | 0
c1 | 1 | 1
c2 | 0 | 1

So in order to incorporate this into the code it could look messy(coditions) and unnecessarily long(DX).

So IMHO since we're (almost)always using the data table and joining the revision table only when needed(which takes care of overriding data from the latest revision stored in data table) I think one JOIN is not that harmful....or it's just 2AM and I'm too lazy to think about this :)

Anonymous’s picture

This is still present in the HEAD and it is critical for contrib modules with custom entities.

Anonymous’s picture

This patch works with HEAD.

plach’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 10: Revisionable_Entities_Field_Data_Fix.patch, failed testing.

Anonymous’s picture

Title: ContentEntityDatabaseStorage::attachPropertyData() ignores unrevisioned fields » Unrevisioned base fields are ignored during entity load
dpi’s picture

Status: Needs work » Needs review
FileSize
2.26 KB

Spent half a day chasing this one, what a DX disaster.

Updated. Needs tests.

dpi’s picture

Issue summary: View changes

Status: Needs review » Needs work
dpi’s picture

Trying a new approach.

Instead of always using dataTable. We will always use revisionDataTable, and include dataTable if any non-revisionable fields exist.

dpi’s picture

Status: Needs work » Needs review
dpi’s picture

Status: Needs review » Needs work
dpi’s picture

Fix other tests adding non-revisioned field to schemas and renaming due to pre-existing name.

Status: Needs review » Needs work
plach’s picture

Thanks, the patch is looking good to me! Just a few minor remarks:

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -667,29 +667,36 @@ protected function attachPropertyData(array &$entities) {
    +      $translations = array();
    ...
             $revision_ids = array();
    

    This line is misplaced also in HEAD, but I think the proper place would be right before the foreach below.

  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -667,29 +667,36 @@ protected function attachPropertyData(array &$entities) {
    +        // Are there any fields unique to dataTable, that are not IDs or revisionable?
    

    Can we expand this comment a bit? Also, the comment is not wrapping at column 80.

  3. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -667,29 +667,36 @@ protected function attachPropertyData(array &$entities) {
    +        $data_fields_static = array_diff($table_mapping->getFieldNames($this->dataTable), $table_mapping->getFieldNames($this->revisionDataTable), $table_mapping->getFieldNames($this->baseTable));
    +        if ($data_fields_static) {
    +          $data_fields = array_merge($data_fields, $data_fields_static);
    +
    +          $query->leftJoin($this->dataTable, 'static', "(data.$this->idKey = static.$this->idKey)");
    +          $query->fields('static', $data_fields_static);
    +        }
    +
    

    This 'static' terminology is not very clear to me. Can we use something more self-documenting, like $revision_data_fields and $data_fields? Also, there's a surplus blank line.

  4. +++ b/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php
    @@ -93,6 +96,10 @@ protected function assertRevisions($entity_type) {
    +      // Check 'data_table' values are loaded.
    

    I'd replace "data_table" with "non-revisionable".

  5. +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
    @@ -120,6 +120,11 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +    $fields['nonrevisioned'] = BaseFieldDefinition::create('integer')
    

    What about adding an underscore to improve readability? Also, the field is non revisionable, even a revisionable field could be "non revisioned" :)
    Thus I'd suggest non_revisionable.

  6. +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
    @@ -120,6 +120,11 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +      ->setLabel(t('Foo'))
    

    Can we use a more meaningful label here?

dpi’s picture

Thank you, I've addressed your feedback.

Changed terminology of "non-revisioned" to "persistent".

Aliases are now consistent on which tables are used.

dpi’s picture

Issue summary: View changes

As a separate issue, we should consider changing "created" properties of core entities to non-revisioned fields.

Status: Needs review » Needs work
dpi’s picture

Status: Needs work » Needs review

Apparently file upload order matters for patches.

plach’s picture

Thanks, even more minor nits:

  1. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -665,24 +665,24 @@
    +        // Find revisioned fields that are non-entity ID's
    

    This should say "entity keys" instead of "non-entity ID's". Also, missing trailing dot.

  2. +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
    @@ -665,24 +665,24 @@
    +        // Find fields that are non-revisioned and non ID's. The value of
    +        // data fields are persistent regardless of entity revision.
    

    As above, please use "entity keys". Also, "data" should go in the previous line, it fits within 80 chars.

  3. +++ b/core/modules/serialization/src/Tests/EntitySerializationTest.php
    @@ -59,7 +59,7 @@
    +      'persistent' => 33,
    

    Mmh, I can't really connect persistent to the revisions concept at first sight. What's wrong with non_revisionable?

chx’s picture

Priority: Major » Critical

Load and save such an entity and kaboom. Definitely critical:

Critical
Bugs

Critical bugs include those that:

  • Cause loss of data.
dpi’s picture

Hooray, a promotion!

What's wrong with non_revisionable?

"Non-revisionable fields are persistent through all revisions."

I feel calling non-something doesn't feel right. I really was hoping for an example from elsewhere.

For those looking on, looking for term for an entity property that is non-revisionable, and not an entity key.

Short list: base data, data, constant, persistent, persistent data, static.

Alternatively, give a name that describes the purpose of the field, rather than the function.

dpi’s picture

Scrap the last comment then. I have replaced the vague "persistent" integer field, with a non-revisionable "created" field type.

I have amended the comments

Status: Needs review » Needs work

Berdir’s picture

created makes sense to me. Maybe include that the field is not revisioned in the field definition description to make that clear?

Status: Needs review » Needs work

dpi’s picture

plach’s picture

Looks ready to go to me. @Berdir?

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me as well :)

Berdir’s picture

Assigned: Berdir » Unassigned
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This issue addresses a critical bug and is allowed per https://www.drupal.org/core/beta-changes. Committed cf40fa1 and pushed to 8.0.x. Thanks!

  • alexpott committed cf40fa1 on 8.0.x
    Issue #2300101 by dpi, ivanjaros: Unrevisioned base fields are ignored...
Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

Anonymous’s picture

7 months...finally this made it into the core. Great news.

dpi’s picture

Go easy on the celebrating

plach’s picture

lol

Status: Fixed » Closed (fixed)

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