Problem/Motivation
In #2746541: Migrate D6 and D7 node revision translations to D8, the content_entity source plugin was changed, adding the revision ID as a source key if the source entity type is revisionable. This makes the migration mapping table inconsistent between Drupal 8.8 and later versions. This is a serious problem for sites that use the content_entity source plugin in recurring migrations.
This manifests in two ways:
- Existing migrations throw "Column not found: 1054 Unknown column 'sourceid3' in 'field list'" for the migrate_map_* table
- If that table is fixed (or does not exist in the first place), the migration breaks, as new revisions create new target objects, when they should update the target object.
Proposed resolution
Add the new, optional, boolean configuration key 'add_revision_id', defaulting to TRUE.
If 'add_revision_id' is not specified, or if it is TRUE, then include the revision ID if the source entity type supports revisions. This is the same behavior as Drupal 8.9, 9.0, and 9.1.
If 'add_revision_id' is FALSE, then do not include the revision ID. This is the same behavior as Drupal 8.8 and earlier.
Remaining tasks
Update the patch following comments #38-42.
User interface changes
None.
API changes
API addition: a new. optional configuration key for the content_entity source plugin.
Data model changes
None.
Release notes snippet
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #46 | 3184650-46.patch | 16.08 KB | quietone |
| #46 | interdiff-31-46.txt | 6.3 KB | quietone |
| #46 | 3184650-46-fail.patch | 14.05 KB | quietone |
| #31 | 3184650-31.patch | 16.38 KB | quietone |
| #31 | interdiff-27-31.txt | 759 bytes | quietone |
Issue fork drupal-3184650
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3184650-migrate-source-content-entity-minimal-bc
compare
- 3184650-migrate-source-content-entity-include-revisions
changes, plain diff MR !63
- 3184650-TEST-ONLY-migrate-source-content-entity
changes, plain diff MR !108
- 3184650-TEST-PLUS_FIX-migrate-source-content-entity
changes, plain diff MR !118
Comments
Comment #2
geek-merlinOK pushed a first shot on this. As this depends on #3184627: D8.9 breaks D8-to-foo entity migrations and needs rebase then, postponing.
Comment #4
geek-merlinSorry for the commit noise, that was unrelated.
Comment #5
geek-merlinOK merged #3184627: D8.9 breaks D8-to-foo entity migrations into here.
Comment #9
geek-merlin(note that the 77 commits are from a rebase)
@benjifisher
OK i prepared everything for review.
See #3187361: Make DrupalCI generate and run test-only patches for the 2 MRs:
Comment #10
rutiolmaThe fix provided works on D8.9 and it fixes my migrations after updating core from D8.8 to D8.9
Comment #11
benjifisher@geek-merlin, thanks for working on this issue. And thanks to @rutiolma for testing: that means that I can just do a review.
I left several comments on the MR. The most important is that we have to make the default behavior consistent with the existing behavior of Drupal 9. It is unfortunate that we have an unintended BC break between D8 and D9, but the way to fix a BC break between major versions is not to introduce a BC break between minor versions.
I am also adding a tag for a change record. That is the right place to give an extended explanation of the new option, usage examples, and instructions for updating from Drupal 8.
Comment #12
geek-merlin@benjifisher
Happy to work on this.
> I left several comments on the MR.
I don't see anything in !63. ???
Comment #13
geek-merlinComment #14
benjifisherOops, I forgot to click the "submit review" button.
Done.
Comment #15
geek-merlin@benjifisher: Can you comment on the direction here before i go in code-mode? Everything else i'm thankfully buying.
Comment #16
benjifisherI would like to keep the scope of this issue focused and keep the changes as simple as possible. If we do not do that, then there is a good chance that we get this issue to RTBC but then a core commiter will set it back to NW for just that reason.
As part of narrowing the scope, we should change the issue title and update the proposed resolution. I was thinking of that in my first review, but I will make it official by adding the issue tag for that.
Let's make it so that nothing changes from this fix unless the developer adds the new, optional parameter.
We can fix both of these problems with the new, optional parameter.
That may be true, but I have not thought about it enough to decide. If it is true, then let's discuss it in a separate issue.
It might be a good idea to add a third option in the suggested follow-up issue. Let's use a string-valued option instead of an integer-valued one with a glossary. In this issue, the new option has two possible values, and we can add a third option in the follow-up issue. So something like this: the new option is called
revisions, and this issue provides two options:add key(the default, for the current D9 behavior) andnone(for the D8 behavior). Later, we can add a third option,all. Maybe you can come up with better names.Comment #17
geek-merlin@benjifisher: Yes i agree to split the issue in a bugfix and a task. This is what i had in mind when i opened 2 issues in the beginning. (Yes in d.o metrics the shortest path between 2 code states is often not one commit ;-) But can you please cross-check, that your current reasoning will stay stronger than your reasoning when you wanted the issues merged in #3184627-9: D8.9 breaks D8-to-foo entity migrations?
Setting to NR for that confirmation, but i will proceed with confidence to code-mode like so:
- Make a 3-valued configuration parameter (and deprecate the BC-only code path) so we both agree on what the code should be like *after* the second issue
- Once we agree, remove everything that goes in the 2nd issue, only leaving the bugfix (then we can revert that partial revert in the other issue)
Comment #18
benjifisher@geek-merlin:
Maybe I did not understand exactly what you wanted in each of the two issues, but I have been consistent in this: I do not want to fix a BC break between D8 and D9 by introducing a BC break between D9.0 and D9.2. The initial patch on the other issue changed behavior without providing an option. I thought the difference between the issues was that this one made the behavior configurable.
To be clear: Drupal 8.9 is only receiving security fixes, not bug fixes. At this point, it is too late to fix the BC break between 8.8 and 8.9. This issue currently targets 9.2. If we keep the scope narrow, then it is likely to be back-ported to 9.1.
Short answer: yes, I confirm what I said in #16.
Comment #19
geek-merlinHope it's finally green now. Now the paperwork.
Comment #20
geek-merlinIf tests are green, this is now done functionality-wise, including the followup, which part i then split off in the last commit.
I thought a bit how to add the BC configuration the least insane way. A 3-valued string config option makes me a lot of pain DX-wise, because after the BC option will be gone, we'll be left with include_translations (bool), but include_revisions (string). So i added a separate bool config option revisions_bc_mode. But this feels insane, too, so leaving NW.
I see 2 saner ways:
1) Have a bool/string valued "include_revisions" config option with values like TRUE/FALSE/'d9bc'.
2) Have a bool valued "include_revisions" config option, that activates BC mode when unset.
Gut feeling tells me i'll try how 2) looks.
Comment #21
geek-merlinComment #22
benjifisherThe quickest way to get this issue fixed is to narrow the scope and to keep the changes as simple as possible.
Adding a deprecation notice does not meet those criteria. Neither does adding two options instead of one.
We discussed the plans in several comments on this issue before you went into code-mode. If you did not like the idea of string-valued
revisionsoption with three possible values, why did you not continue the discussion first?I do not like "d9bc" as one of the options. It describes the history of the project rather than what the option does.
Drupal 8.9.12 was released yesterday, and it seems that I was wrong in #18:
Some bug fixes, not just security issues, are still being applied to the 8.9.x branch. I still think I was right when I said that the best chance for getting the fix back-ported is to keep the scope narrow.
Comment #23
quietone commentedI've been keeping an eye on this and finally sat down to read it thoroughly. It took a while and I wish the IS was updated. What I see is that the solution is doing more than it needs to and differs from the suggestions in the reviews by benjifisher. the new dataprovider for the tests is nice and adds test coverage.
I think there is value in seeing what the solution suggested in the reviews looks like. With that in mind I have made a patch, there is no interdiff because this is a different approach.
Comment #24
quietone commentedSetting NR to start the tests.
Comment #25
benjifisher@quietone, thanks for implementing the suggestions from my earlier reviews. I think this approach is sound, and we just need to do a little cleanup.
Nit: add some commas to these comments, and rewrap as needed.
I think we need commas before “which adds”, after “If ‘none’”, and after “This is the default”. In fact, I would add “, then” after “If ‘none’”.
I do not think the comment is useful:
I like the decision to test
!== 'none'rather than=== 'add key':As I said at the end of #16, we plan to add a third possibility for this option in a follow-up issue. If we do that, then we will not have to change this line.
This is not the typical data provider:
I think the one-line summary should be something like “Data provider for several test methods.” and we should add
@seecomments for each test method.I do not think it is a requirement, but we normally define the data provider after the related test method.
A test-only patch would show us how well
http_build_query($configuration)works as a key.I think we should test
!== 'none'rather than=== 'add key'for the same reason as (3):This applies to several (all?) of the test methods:
I think that
@group legacyis left over from the earlier patches on this issue and should be removed. If I am wrong about that, then I still think we should remove the comment, “Legacy group is needed …”.I am a little worried that this change is out of scope. Do we need it? Again, this applies to several or all of the test methods.
I see that we need to add
$configurationwhen defining$migration, but do we need to change how we get$user_source? Even if it is out of scope, it does seem like an improvement.If we decide to keep that change, then we should remove the
$sourcePluginManagerproperty and the line insetUp()that sets it.Same snippet: break the line before
->createStubMigration().Class properties are always
camelCase. Other variables can be eithercamelCaseorsnake_case, but should be consistent within one file:We could remove the last usage and put the comparison inside the
if(...), but it makes sense to use the same pattern in both test methods.Same snippet: do we want to compare
=== 'add key'instead of!== 'none'?This is a little surprising:
The existing test defined
$configurationand used it when defining$media_sourcebut not$migration. That makes me wonder whether we could use the same migration in all the test methods and define it insetUp(), and go back to the original definition using$this->sourcePluginManager. That makes me lean more towards deferring this simplification to a follow-up issue if it is out of scope.Comment #26
quietone commentedYes, I put very little effort into cleanup.
#25, the first half
1,2 Fixed.
3. I was hoping you would like that!
4. Fixed. That is much better.
5. Moved the provider to after testTermSource
6. Agreed. Fail patch will be provided when 9-14 are fixed.
7. Fixed
8. Fixed
13. Fixed
9, 10, 11, 12, 14. todo
Edit: 13 was fixed as well
Comment #27
quietone commentedAnd now for the rest of #25.
25
9. It is needed because $this->migrationDefinition adds the configuration to the source plugin before creating the migration. If this is not done then the rewind results in a
Undefined index: revision_id. Somewhere in rewind the source plugin is rebuilt and the 'source' needs to have the configuration. If not, the default configuration is used, which sets revisions to 'add key'. I'm not sure why the original test did a create instance for the source plugin, getting from the migration makes more sense to me.10. $this->sourcePluginManager removed
11. Fixed
12. Fixed
14. I don't think the migration can be in setup, they are derived and need to have the unique entity type in the ids.
Comment #28
geek-merlinOK after the last comment i was quite pissed off and needed some time (but ok its just hard times for anyone).
I do not want to be part of any fight. But i totally care for the migrate ecosystem for years now so there is some sentiment in it.
As i already said, i warn against option c) to make the current behavior a configuration option to support long-term. Why? Because it violates the basic contract of the IDMap, which is to map one source object to one target object. We have some invariants, that, up to now, all migrations fulfill. For instance, monotonicity: A target object is only deleted or orphaned, if a source object is deleted.
By making this bug permanent (and i showed deprecating it later is hard, if feasible), we invite hard to estimate future burdens. We do not know what exactly they are, but just for illustration:
- If we ever have a test that auto-tests monotonicyty (as above) for all migrations, we must create a special case just for this migration option
- If there are - in core or contrib - code parts that assume monotonicity (as above), which is until now totally reasonable, they must create a special case for this in the future
- There may be other repercussions, and breaking a basic contract can not be over-estimated in technical debt.
Please everyone, consider this thoroughly before going that path. And let me suggest to consult Moshe on this too.
Comment #29
geek-merlinComment #30
benjifisher@geek-merlin:
I am trying to control scope on this issue. I want a simple change to fix the problem that migrations that used to work are now broken. We can have follow-up issues to do more, so I do not think anything we do on this issue makes the bug permanent.
I am familiar with the mathematical meaning of "monotonic", and this is not what it means. Either the word has additional meaning(s) or you should use a different term.
@quietone:
I do not see a test-only patch (#25.6).
I still see several uses of
@group legacy(#25.8).Follow-up to #25.9: I agree that using
getSourcePlugin()has advantages: it is more like "real life". The disadvantage is that it is less direct, so it is less clear exactly what we are testing. But if the tests fail somewhere in therewind(), then that makes it an easy decision. Thanks for explaining that.All the other points from #25 have been addressed. Thanks for that! NW for (1) and (2).
Comment #31
quietone commentedAnd the fixes for #30.
30.1 I think I intended to come back and make a fail patch after #27 passed tests, and I just forgot.
30.2 Removed 'legacy'.
Comment #33
benjifisherComment #34
benjifisherI forget why I set this issue to NW the last time I looked at it. Maybe I did not notice that the failure was from the test-only patch. Maybe I meant to say it was for an IS update.
I looked at the failures from the test-only patch. There are 5 tests using the same data provider:
That is because User and File entities are not revisionable; the other three types are. +1
All three failures are similar: they fail for the two test cases with
revisions=none. On the one hand, this suggests that the tests are redundant. On the other hand, the point of automated tests is to catch mistakes that we have not yet made, so I think it is good to use the data provider for all entity types.As I said in #25.6,
It works pretty well! For example,
In #30.2, I said there were "several uses of
@group legacy", but I think there was just one, and it is removed in #31. Thanks for that.The code looks good to me. I will update that CR and the IS.
Comment #35
benjifisherI have updated the change record, the issue summary, and the title of this issue.
It occurs to me that this issue is now reviewed but not tested. It would be good to get confirmation from people who have run into this bug that the solution actually works! I hope that @geek-merlin and/or @rutiolma can confirm.
I looked again at the sample migration cited in #3184627-8: D8.9 breaks D8-to-foo entity migrations. The odd thing is that this migration uses
plugin: content_entity:user. That comment also refers to revisions of User entities.Since User entities are not revisionable, I would expect the patches on this issue to have no effect when the source entity type is User: see my comments on the test results in #34. Perhaps there is an additional module that makes User entities support revisions? I see the User Revision module, but that is only for Drupal 7.
Conclusion: I am leaving the status at NW, and I am adding the "Needs manual testing" issue tag. If we can get some manual testing (and the patch works), then the issue will be RTBC.
Comment #36
benjifisherSorry, I meant to mark this issue NR in my last comment.
I am doing the manual testing now. If I do not comment within 24 hours, feel free to reassign this issue.
Comment #37
benjifisherManual testing
I installed Drupal 8.8.9 with the Umami demo profile, so I had some content for the test.
I added a plain text field,
field_copy_of_title, to the Article content type.I added the Migrate Plus and Migrate Tools modules and Drush, so that I could run migrations from the CLI.
I added a custom module with a simple migration:
I enabled the custom module, along with Migrate Plus, Migrate Tools, and Migrate Drupal.
I ran the migration. It populated the new field as expected.
I backed up the database.
I checked out the HEAD of 9.2.x. I re-installed the contrib modules and Drush with Composer.
I ran database updates.
I ran the migration again (
drush migrate:import migrate_test). I got an error message similar to the one described in #3184627: D8.9 breaks D8-to-foo entity migrations.I applied the patch from #31.
I dropped the database and imported the backup.
I ran database updates.
I ran the migration again. I got the error again.
I added
revisions: noneto the source configuration and cleared the plugin cache.I reset the test migration to Idle (
drush migrate:reset-status migrate_test).I ran the migration. No error. Success!
Just for good measure, I added another Article node, leaving the extra field empty, and ran the migration yet again. It populated the test field as expected.
This issue is now tested as well as reviewed.
Comment #38
alexpottThis name feels quite confusing. Is it possible to change this to
add_revision_idand have the values as TRUE or FALSE? I think this was a string because we consider a tri-state solution earlier but thats no longer planned.This comment feels very confusing. Because regardless of the value of the 'revisions' setting the default revision will be returned.
assertCount should be used.
As this is a major bug it is a candidate for backport to 8.9.x - if the current patch applies it'd be worth triggering a test run.
Comment #39
benjifisher#38.1: I still want the flexibility to add a third option. If @geek-merlin is right, then we should deprecate, and eventually remove, the current default option.
Comment #40
alexpott@benjifisher deprecating the configuration option does not require a third state as far as I can see.
Comment #41
benjifisher@alexpott:
I have more time now. Let me be more explicit.
There could be as many as four options regarding revisions:
vidas a source keyThe second row is there just for completeness: if we are going to return multiple revisions, then we need a source ID to distinguish them.
@geek-merlin says that the third row is equally useless. That may be true. I have not thought about whether there might be a use case for this (the current behavior).
The fourth row is why I want to use a string-valued option, not a boolean. It seems like the simplest way to let us add Yes/All in a later issue. We could have two boolean options, but then we would have to say which combinations are allowed. If we decide that only No/Default and Yes/All are reasonable, then we could have a single boolean option, but that would mean dropping the possibility of the current behavior.
As far as I am concerned, the 3-way option is not currently planned as part of this issue since I want to narrow the scope to fixing migrations that worked with earlier versions of Drupal.
Regarding #38.2, what do you think of this explanation?
The first two sentences are the current text. I would not object to switching it around:
I tested locally: the patch applies cleanly to the 8.9.x branch, and the ContentEntityTest test class passes. I will ask the testbot to confirm.
Edit: I see you already exercised the testbot.
Comment #42
alexpott@benjifisher thanks for #41. It doesn't really change my thoughts from #38. Meaningful configuration keys and values are helpful. So for the table in #41 - I think we can have two configuration keys -
add_revision_idandall_revisionsboth of which are boolean. And if theadd_revision_idis FALSE andall_revisionsis TRUE then we can throw a runtime exception.+1 to the documentation improvements suggested in #41 (after they are updated for the earlier part of this comment).
Comment #43
benjifisherFor this issue, we just need the
add_revision_idoption. I am updating the issue summary.@alexpott, I gave two suggestions in #41. Both add to the existing text, and the second also rearranges the existing text. Do you have a preference?
Comment #44
benjifisherI have been putting off deciding whether the current behavior (include revision key, return only the default revision) is ever useful. @geek-merlin argues that this behavior is fundamentally broken.
If so, then we will want to change the default and remove the include/default option (in follow-up issue(s)). If we have two boolean options, as agreed in the last two comments, then we will be in the odd position of using two options to choose between two behaviors and throwing runtime exceptions in two of four cases.
I am assigning this issue to myself until I find time to decide whether @geek-merlin is right. If I agree, then I will push back against the approach with a boolean option in this issue and a second boolean option in a follow-up issue.
Comment #45
benjifisherI do not have a complete, realistic use case, but I think the current behavior (include revision key as a source ID, fetch just the current revision) could be useful.
Suppose I want to use a migration as an alternative to
hook_entity_presave(). Every time a new entity (node, for example) is created, or an existing entity is updated, I want to do some processing. For the update case, I have configured the entity to always create a new revision.The current (Drupal 8.9, 9.x) behavior of the ContentEntity source plugin supports this requirement. We should continue to support it.
So let's go with the boolean option
add_revision_idas discussed in #42, #43. In #43, I already update the issue summary, so no one has to hunt through the comments. We may decide, in a follow-up issue, to add another boolean option ,all_revisions.@geek-merlin, do you think I am missing anything? You wrote on MR !63,
(That comment was copied to this issue, but not assigned a comment number here, making hard to refer to it.)
Comment #46
quietone commented#38
1. The key is now a boolean
add_revision_id2. Comment changed. Since there was no agreement on which of two suggestion in #41, I created another possibility.
3. Fixed.
These changes seem to warrant a new fail patch, so that is added. I think this is now up to date.
Comment #48
benjifisher@quietone, thanks for the update! This patch addresses all the points in #38, and I like your version of the comment better than the variants I proposed in #41.
Back to RTBC.
Comment #49
alexpottCommitted and pushed 75efbd32eb to 9.2.x and e8001d02ef to 9.1.x. Thanks!
Backporting to 9.1.x because behaviour is not changed by default and you can now use this to get out of the problem if you're in it.
Comment #52
benjifisher@alexpott:
In #38, you suggested that this patch might be applied to 8.9. Is that still an option?
Comment #53
quietone commentedThe change record needs to be published. I updated it a few days ago. Can some check it and hopefully publish it?
Comment #54
benjifisherDrupal 9.1.5 was release a few days ago, and it includes this fix. I updated the CR and published it.
The patch already passes tests on 8.9.x, so I am changing the status to RTBC for 8.9.x.
Comment #55
alexpottDiscussed with @catch we agreed to put this in 8.9.x.
Comment #57
benjifisherThanks, @alexpott!
I update the CR, adding
Drupal 8.9
Issue #3184650 was fixed for the 8.9.x branch. The new
add_revision_idshould be available in the next bugfix release after 8.9.13.I did not change the metadata (introduced in 9.1.x/9.1.5).