Summary / Motivation

Steps to reproduce:

Given I have an entity. In early revisions of the entity, I had data in a field. In the live version, that field is empty.
When I go to field UI and edit that field, even to make a harmless change, Field API decides that field has no data, and erases the history.

Proposed resolution

Let field_has_data use the revisioning data instead of the "current" data.

This works, because there can be data in field_revission tables AND no data in the field_data tables, but not the other way round.

If we have data in field_data, we always have the current data duplicated in the field_revision table.

Therefore following patch fixes the issue:

diff --git a/modules/field/field.module b/modules/field/field.module
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -948,6 +948,7 @@ function field_get_items($entity_type, $entity, $field_name, $langcode = NULL) {
 function field_has_data($field) {
   $query = new EntityFieldQuery();
   return (bool) $query
+    ->age(FIELD_LOAD_REVISION)
     ->fieldCondition($field)
     ->range(0, 1)
     ->count()
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Fabianx’s picture

Issue tags: +Novice

Tagging Novice to create a proper patch

Fabianx’s picture

Issue tags: +Needs tests

The novice part includes creating tests - though that is more intermediate novice:

What needs to happen as part of the test:

* Create an entity with a text field that is populated
* Create revisions of this entity with different contents of this text field
* Create a new revision of this entity where the text field is empty
* Run field_update_field() on the field base.
* Ensure the database still has the revision data.

joshi.rohit100’s picture

Status: Active » Needs review
Issue tags: -Needs tests
FileSize
457 bytes

Please review the submitted patch.

dcam’s picture

Priority: Major » Critical
Issue tags: +Needs tests

Bumping to critical due to data loss.

This issue doesn't seem to affect D8. I used the same test procedure outlined in the "Steps to reproduce" in D8 and D7. I was only able to confirm it for D7.

Since this situation probably doesn't have any test coverage it may have to be added for D8 as well as D7.

dcam’s picture

Status: Needs review » Needs work
nlisgo’s picture

I am going to attempt to write some tests for this. I'll give a progress update in the next couple of days.

nlisgo’s picture

Assigned: Unassigned » nlisgo
nlisgo’s picture

I have a patch that I will upload shortly. I will upload a test-only patch and then a patch with the fix.

I know the default behaviour of Drupal is to store a duplicate entry of the current field data in the revisions table. But when writing tests I would expect field_has_data to return TRUE if there is a current value for the field but no entries in the revision table.

I will supply a 3rd patch with a fix that addresses this issue.

This would mean that the sites that seek to by-pass the revisions table can still expect the field_has_data function to return TRUE as is the behaviour when they enable and configure https://www.drupal.org/project/field_sql_norevisions.

nlisgo’s picture

Status: Needs work » Needs review
FileSize
5.3 KB
5.75 KB

This solution covers the fix provided in comment 3. But I have written a test that is expected to return FALSE if data is only found in the field table but not in the revision table and that simply doesn't feel right.

I will upload the next set of patches in another comment.

nlisgo’s picture

This patch extends the fix to check for values in the revision table first and if none are found then it will look in the field table. The same number of tests are run but the final test is now expected to be TRUE instead of FALSE.

Please review.

Fabianx’s picture

I know the default behaviour of Drupal is to store a duplicate entry of the current field data in the revisions table. But when writing tests I would expect field_has_data to return TRUE if there is a current value for the field but no entries in the revision table.

Oh, and that is a very good catch.

Because IIRC D8 now even supports that case to not even create the revision tables - maybe could also check the table definition? (but needs to be different from D7 to D8)

Not yet reviewed the patch, though.

The last submitted patch, 9: 2278583-field-has-data-test-only-9.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 10: 2278583-field-has-data-test-only-10.patch, failed testing.

Fabianx’s picture

Status: Needs work » Reviewed & tested by the community

RTBC, looks wonderful to me.

@David Rothstein might want us to port the test coverage to D8 first though.

nlisgo’s picture

Version: 7.x-dev » 8.0.x-dev
Assigned: nlisgo » Unassigned
Status: Reviewed & tested by the community » Needs work

We are going to need a D8 test for FieldStorageConfig::hasData() that covers this behaviour.

  • Create an entity with a field.
  • Assign a value to the field.
  • Save the entity.
  • Check that the storage has data.
  • Create a revision of the entity that unsets the value for the field.
  • Save the entity.
  • Check that the storage has data.

This test is not necessarily expected to fail as it was for D7 before the introduction of the patch but we need to protect against regression. The bug has not been reported in D8.

In D8 there are tests for the FieldStorageConfig::hasData() in Drupal\field\Tests\FieldDataCountTest::testEntityCountAndHasData() but they do not cover the behaviour described in this issue. This seems the natural place to put the new tests.

I am setting to 'Unassigned' and 'Needs work' because I lack experience with D8 but would be happy to review a fix.

Fabianx’s picture

nlisgo’s picture

Status: Needs work » Needs review
FileSize
2.82 KB

This is a test only patch for D8. I am creating a revision of an entity with a field value then creating a new revision of the entity with no value for that field and hasData() returns FALSE when they expected behaviour may need to be TRUE. However, if this is not an issue in D8 t may be because the data is not deleted when a field is updated (field_update_field in D7) based upon the return value of hasData().

Status: Needs review » Needs work

The last submitted patch, 17: field_has_data_looks_at-test-only-2278583-17.patch, failed testing.

Berdir’s picture

+++ b/core/modules/field/src/Tests/FieldDataCountTest.php
@@ -97,6 +104,38 @@ public function testEntityCountAndHasData() {
+    unset($entity->{$this->fieldTestData->field_name_2});

Can we set it to an empty array instead? unsetting fields is doing weird stuff atm internally.

Looks like the data isn't lost in 8.x, but it probably still makes sense to keep the behavior of hasData() in sync between 8.x and 7.x and check the revision table here as well? Possibly dynamically based on whether the entity type is revisionable, there's still the hope that we can avoid creating field revision tables for non-revisionable entity types in 8.x

nlisgo’s picture

FileSize
2.82 KB

Implemented the amend suggested in #19.

nlisgo’s picture

Status: Needs work » Needs review
nlisgo’s picture

FileSize
2.82 KB

Ignore patch in #20, this is the same but I have renamed the file to indicate that it is test-only.

The last submitted patch, 20: field_has_data_looks_at-2278583-20.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 22: field_has_data_looks_at-test-only-2278583-22.patch, failed testing.

nlisgo’s picture

The reason that we can not recreate this is D8 is because an entry is created in the field table even if a value was not supplied. If you follow the steps to recreate in D8 and then delete the entries in the field table and ensure there are still entries in the field revision table then updating the field will case the revision data to be deleted and the hasData method to return FALSE.

I'm sure that is a bug. I'm just trying to determine whether it is a known issue already or I should open a new issue.

nlisgo’s picture

@swentel, I think you're spot on. The good news about this task is I believe it can progress without awaiting resolutions on those issues.

We need to provide a patch for D8 to return TRUE for FieldStorageConfig::hasData() if there are entries in database for revisions or current field data.

Fabianx’s picture

@nlisgo: That means we could still reproduce it by deleting values from the DB manually?

Nice that we have that test :) - It would make the other issues fail.

Lets continue with same patch as in D7 here and then move it finally back to there :).

Thanks for all the progress!

nlisgo’s picture

@Fabianx: that is correct. I confirm that you can reproduce this issue in D8 if you delete the entries in the field table manually. Even though there are entries in the field revision table FieldStorageConfig::hasData() will return FALSE. If you update the field storage settings when there are no entries in the field table then all of the data in the field revision table will be deleted.

When the issues referenced in #26 are resolved this bug will be very reproducible.

Berdir’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
3.9 KB

I am a bit confused..

The test is failing right now in 8.x,which kind of proofs that the same problem exists? Without manually deleting anything? I did run the test manually and debugged in and the field data table was empty as expected, so that is why the query returns FALSE, otherwise it would not?

The attached change fixes the test and seems to be the right thing to do here?

nlisgo’s picture

@Berdir, would you be able to post up an interdiff?

Or maybe I am misunderstanding you. Did you have to change the test at all?

Berdir’s picture

Not worth it, the only difference is the fix, the test is identical to what you posted. That's why I didn't bother with an interdiff or test-only patch.

nlisgo’s picture

Thanks, I just misread your message. I will review this tomorrow. Looks good on first glance. Thanks a ton for your help on this.

Fabianx’s picture

The fix looks great, RTBC + 1. Final RTBC can com from nlisgo tomorrow.

swentel’s picture

+1 indeed

nlisgo’s picture

Status: Needs review » Reviewed & tested by the community

RTBC + 1

I am going to move to RTBC for the D8 patch.

I think this is ready. I've read and reviewed the code change by the patch in #30. FieldStorageConfig::hasData() now returns the value we expect. In future this code may be revisited for optimisation would make that improvement non-critical and future development would be helped by these tests in place.

What has become apparent is that not only was FieldStorageConfig::hasData() not returning the expected value but neither was SqlContentEntityStorage::countFieldData().

The patch to the countFieldData() does not break any other existing tests on that method. I have no idea what direction we will be going in with allowing revisions to be disabled for content. Is this a setting that will only be permitted on a content type level or will we be able to decide that a specific entity will not store revisions. If we ever move in that direction and we have entities that use a single field, some with only entries in the data tables and some with only entries in the revision table,s then this code would need to be refactored. But that's ok :)

I would like to resupply the D7 patch before it's committed because of a typo in an annotation. Shall I wait for the D8 patch to be committed first?

alexpott’s picture

Version: 8.0.x-dev » 7.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Committed 03662aa and pushed to 8.0.x. Thanks!

  • alexpott committed 03662aa on 8.0.x
    Issue #2278583 by nlisgo, Berdir, joshi.rohit100 | Fabianx: Fixed...
nlisgo’s picture

Status: Patch (to be ported) » Needs review
FileSize
3.24 KB
901 bytes

Just changes to the annotation of the patch supplied in #10.

Berdir’s picture

Note that my testing in 8.x showed that we already create the revision tables only conditionally, and already implements it dynamically based on the revisionable flag of the host entity type. Would not even work otherwise. So it doesn't need any follow-up or further improvements.

Fabianx’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs backport to D7

RTBC for D7 as well :) - Nice!

I think our only choice for D7 is to check both tables as done here.

Looks great to me!

David_Rothstein’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: +7.33 release notes

Committed to 7.x - thanks!

The Drupal 7 tests might be improved a bit by having them actually use the API (or user interface) to get into a realistic situation in which the revision table has data but the field table doesn't, rather than inserting into the field tables directly? But it works fine as is, and that could certainly be a followup if desired.

  • David_Rothstein committed e7b2772 on 7.x
    Issue #2278583 by nlisgo, Berdir, joshi.rohit100 | Fabianx: Fixed...

Status: Fixed » Closed (fixed)

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