Problem/Motivation
It's possible for the revision ID in {node} and the one in {node_revision to be out of sync.
All reports on this issue point to this happening due to migrations, although since migrate uses the entity API, it's posted against that.
Proposed resolution
Throw an exception if either of the following happens:
1. The entity is saved with a different ID to the one it was loaded with
2. The entity is saved with a different revision ID to the one it was loaded with, if it's not saving a new revision.
This only stops the bad data from being saved, it doesn't fix any migration issues that might lead to attempting to save that bad data, but it should help to flush them out - we already have issues open for incremental migrations and ID preservation which are possible culprits.
Remaining tasks
Better/shorter steps to reproduce.
User interface changes
API changes
Data model changes
Original report:
Hello Views crew. I just did an upgrade from D6 to D8 using drupal update module.
After doing the update I used the UI to delete all the content types the client doesn't require anymore.
When I go to /admin/content
I get the following PHP error:
[24-Dec-2015 13:25:36 America/Denver] Recoverable fatal error: Argument 1 passed to Drupal\views\Plugin\views\field\EntityOperations::getEntityTranslation() must implement interface Drupal\Core\Entity\EntityInterface, null given, called in [path.to.my.site]/core/modules/views/src/Plugin/views/field/EntityOperations.php on line 116 and defined in [path.to.my.site]/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php on line 74
So it looks like Drupal8 is using a View to list the content on this page. All the content should just be in English there is no translation going on on the old D6 site. I looked at the source code on the lines given but I am still getting up to speed on D8 architecture - anyone have any insight into the issue? Why would this sort of error happen on the basic admin/content page?
Thank you.
Comment | File | Size | Author |
---|---|---|---|
#49 | entity-revision-save-2640496-49-interdiff.txt | 2.95 KB | Berdir |
#49 | entity-revision-save-2640496-49.patch | 3.69 KB | Berdir |
#43 | entity-revision-save-2640496-43-full-interdiff.txt | 822 bytes | Berdir |
#43 | entity-revision-save-2640496-43-full.patch | 3.68 KB | Berdir |
#39 | entity-revision-save-2640496-39-full.patch | 3.68 KB | Berdir |
|
Comments
Comment #2
LendudeNot sure if this a migration or views issue so putting it in the migration component with VDC tag to get both sets of eyes on it.
Comment #3
ghyjek CreditAttribution: ghyjek commentedHi, I'm facing the same problem right now. Some nodes are not deletable by bulk delete. Can't get to content because of the same error. It also happened before on some content pages where any of that broken nodes were printed. And yes, that's content migrated from d6 -> d7 -> d8.
Comment #4
quietone CreditAttribution: quietone as a volunteer commentedComment #5
vgutekunst CreditAttribution: vgutekunst commentedSame problem here. After migrate from D7 to D8.1
Comment #6
cilefen CreditAttribution: cilefen commentedComment #7
cilefen CreditAttribution: cilefen commentedComment #8
vgutekunst CreditAttribution: vgutekunst commentedhint: for a fast solution made some changes at the view:content which gives the /admin/content overview out. I added a content type filter and excluded some of my content types, then it works. For me my migrated content type "basic page" was broken, i dont know why so far but when i exclude them the view shows the /admin/content page.
Comment #9
cilefen CreditAttribution: cilefen commentedRelated, or duplicate: #2540876: WSOD with listing content after removing language
Comment #10
albertski CreditAttribution: albertski at Xeno Media, Inc. commentedWe ran into this error and it was related to revisions. The version ids did not match in the node and node_revisions table.
Example node: 13223
vid = 15431 in node_revision table
vid = 27810 in node
I believe it may have to do that our d7 site revisions were changing after migration. Then our d8 site was changing and then we migrated again.
Comment #12
pcho CreditAttribution: pcho commentedIf you're still diagnosing this issue, I will echo #10. We had this same exact problem after performing updates to content from one environment to another, and discovered that the vid wasn't getting updated to the latest version on the node_revisions table.
One node in the admin/content list can break the entire page. Make sure all nodes' vid lines up.
Comment #13
catchComment #14
BerdirI've also seen this caused by migrate, but migrate is just using the entity API? The API shouldn't allow you to get to that point I think, so it is probably a bug/to be fixed in the entity storage.
Comment #15
catchOK let's move this to the entity system. However since migrate might be the only way to trigger this in core, leaving the migrate critical tag. Wild guess would be that it's to do with the revision migration.
Comment #16
BerdirYeah, pretty sure it happens when trying to save a node with an explicitly provided revision ID but the node already exists with a different revision. We had this when we migrated nodes (default revision only) and kept the nid and revision_id while users also created new content on the site, so sometimes the nodes that migrate created already existed as manually created content, with a different revision. In some cases, even with a different node type, which resulted in additional fun behavior ;)
Comment #17
mikeryan@Berdir: #2748609: [meta] Preserving auto-increment IDs on migration is fragile
Comment #18
catchThis is a data-integrity issue, so should be critical whether it turns out to be migrate or not.
Comment #19
catchComment #20
mikeryanQuestion - has anyone seen this problem in a scenario other than the following (i.e., is #2748609: [meta] Preserving auto-increment IDs on migration is fragile the only known trigger)?
Comment #21
BerdirOk, this is pretty evil. Took me a while to make it fail and I'm not sure if it's something that needs to be fixed in entity storage or migrate.
To get to this point, what you have to do is load an existing entity, tell it that this is not a new revision and the default revision (the default value of those two flags when loading the default revision) but change the revision ID to a different value.
I'm not sure if that is a valid operation? Do we allow changing the revision ID of an existing revision? We don't support re-saving an entity with another ID either (no idea what would happen...)
This partially fixes it, but it currently leaves orphaned records with the old revision_id in the revision data table. I'm using the original revision id when updating the revisions table, then we match the old record and change its revision ID.
Note that there are various scenarios where saving fails, like trying to save as a new revision with a revision ID that already exists, both in another entity and in the existing one. Migrate currently doesn't protect against that in any way.
Comment #24
ycle CreditAttribution: ycle commentedive just ran into the same thing, i guess. im working on a custom d6>d8 migration.
although was able to build a custom view displaying my content - only the standard admin view didnt work. my view, though, didnt show anything as the content type of the nodes. titles were shown, but no type - although in the database itself, i could see types.
now, since i needed to move forward, i manually deleted all the rows in node and node_revision - but the error persists.
so now, this is not cool, haha. any hint how to recover my site without having to have a fresh installation?
Comment #25
ycle CreditAttribution: ycle commentedok, solved it myself. im sorry for this offtopic stuff in a thread about a feature. just in case anyone else stops by: ive just deleted any row in any table representing any form of node content. hence, revisions, fields etc. this did it.
Comment #26
mikeryanycle: Can you confirm whether your problem was triggered by the known scenario in #2748609: [meta] Preserving auto-increment IDs on migration is fragile? I.e., are your migrations attempting to preserve IDs (e.g., your node migrations have a
nid: nid
mapping), and did you manually create any content on the site before the last time your ran migration?Comment #27
catchDiscussed this with @xjm, @alexpott, @webchick, @effulgentsia and @cilifen.
We agreed this is critical.
Just the capability to mess things up with the API might not be critical on its own, but since migrate is doing it in core that tips it over the edge. We generally treat data-integrity and security issues in experimental modules as critical anyway (and this isn't reversible by uninstalling migrate).
Comment #28
ycle CreditAttribution: ycle commented@mikeryan i do not have nid: nid mappings in my migrations, but im actually working on a migration into a system with no existing entities for certain of the migrated types. means i have some taxonomy stuff, but the problem i faced was definitely related to nodes and/or paragraphs (paragraphs project) - while not having any preexisting nodes nor paragraphs.
although, good point, i did manually add one paragraph to one of the nodes in between two migration runs. after that, i performed a rollback, but could then not import again.
unfortunately i dont remember well enough for insisting that this caused the problem.
but i do remember that my drush migrate-... actions did not show any errors.
Comment #29
catchForgot to add the tag.
I think we should detect this case, but rather than trying to fix it throw an exception explaining the problem. That will prevent data getting into this state, and at worst direct people here after some googling.
@ycle what you described sounds like exactly what ought to trigger this bug - editing content on a site in between migrations.
Comment #30
heddnre #28: I'm not sure what you are doing with paragraphs, but you might try taking a look at #2809793: EntityReference migrate destination. Currently, none of the core destinations support the composite relationship required by paragraphs via its dependency on ERR.
Comment #31
BerdirHere's a new version that throws an exception instead. Lets see how many fails we have.
I started fixing those fails but noticed a problem. I don't fully understand \Drupal\KernelTests\Core\Entity\ContentEntityChangedTest::testRevisionChanged(), it is pretty strange in that it clones an entity and then mixes saving the old translation and the new one. But since those objects are no longer connected, I *think* that test is weird but while my fix makes at least partial sense, it's not the actual problem.-
What's actually the problem is that both the previous code and this new exception fail when updating a revision other than the default. Because we load the original, which is the default revision and compare against that. No idea how we didn't find that stuff earlier, but that basically means that #2809227: Store revision id in originalRevisionId property to use after revision id is updated is a blocker for this. \Drupal\KernelTests\Core\Entity\EntityRevisionTranslationTest::testTranslationValuesWhenSavingForwardRevisions() shows that perfectly.
Comment #34
xjmShould we postpone on #2809227: Store revision id in originalRevisionId property to use after revision id is updated then?
Comment #35
BerdirStill postponed but the other issue is RTBC and wanted to verify that it solves our problem here as well.
Adding a combined patch, a separate patch for reviews and a test-only.patch
Also further simplified the test to the required minimum and added a similar check for preventing an ID change as well. That's actually tricky because if the ID changes it means we possibly fail to load ->original unless we have an ID that is another entity and then we might load something that's completely bogus, so included both checks.
test-only will fail obviously right now without the other patch committed but we can re-test when that is in.
Comment #38
BerdirSee #2809227-119: Store revision id in originalRevisionId property to use after revision id is updated for an explanation of those test fails. This is a relatively simple workaround but we might want to do something else there. Also, ContentEntityChangedTest is kinda weird.
Comment #39
BerdirRebased and removed the condition again for not having a loaded revision, as we now ensure it directly above. Lets see if this works. Also 8.3.x only for now, as it has only been committed there.
Comment #41
BerdirComment #43
BerdirForgot to update one renamed method.
Comment #44
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI don't see where this is used below, so do we really need to assign it here?
The first part of this comment doesn't make too much sense :)
Comment #45
BerdirWe don't use it, but preSave() must return the ID :)
Will fix the comment.
Comment #46
catchCode looks good, nits on the comments:
'Do not allow changing the revision ID' reads easier.
s/Since this/Since.
'with a different revision ID' might be more explicit?
Same here 'with a different entity ID'.
Then presumably people running migrations are going to start hitting exceptions, but I guess that's better than mis-matched revision IDs and we have open issues about incremental migrations anyway.
Comment #47
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented#45, what I meant is that I don't see why we're changing the existing
return parent::doPreSave($entity);
line if we're not using$id
in the new if blocks.Comment #48
BerdirBecause the code needs to run *after* the parent, as that's where ->original is loaded. Maybe we can use ->getOriginalId() but nothing so far uses that for content entities afaik and I'm not sure if it is 1005 correct.
Comment #49
Berdiryeah, doing it afterwards is definitely required because Entity::getOriginalId() has this:
Meaning, we can't use it. Not sure why the methods exists there in the first place and not just on config entities. Oh well ;)
Fixed the comments. The problem with the test method descriptions is that entity/revision ID means we're over 80 characters, leaving that out was my attempt at keeping it below that.
Comment #50
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedCool, it just seemed weird to me but the explanation makes sense :)
Comment #52
BerdirComment #54
catchComment #55
alexpottSo whilst this does not fix the issue it will prevent entities with mismatching ids and revision IDs from being saved and therefore prevent data integrity issues. I think that committing this might give us more error reports but at least then we'll be able to fix this before breaking someones site.
I was going to commit this but then I noticed that the issue summary update that @catch requested has never been done and it certainly is a little sparse. Let's get that done and then get this in.
Comment #56
catchUpdated the issue summary - only a couple of sentences but I think that's probably OK? Bump back if not.
Comment #57
alexpottCommitted cc3f37c and pushed to 8.3.x. Thanks!
Comment #59
BerdirAs discussed in #2809227-149: Store revision id in originalRevisionId property to use after revision id is updated we were not sure if we should commit this to 8.2 as well, to quote alexpott in IRC: "It's against the BC policy to introduce the new exceptions. But the new exceptions are better than corrupt data."
Re-opening and postponing this on that other issue for now and re-opening that.
Comment #60
BerdirOk, we decided to not commit that dependency, which means we also can't get this in. Back to fixed for 8.3.
Comment #64
xjmComment #65
gngn CreditAttribution: gngn at Computer Manufaktur GmbH commentedI also encountered this migrating from D6 -> D8.
I tried to roolback the migration but ended up with two entries in node and node_revision.
Reading #25 I checked which tables belonged to my content type and deleted everything with the nid / entity_id found in node/ node_revision from:
(I had no rows in node__body)
That helped.