Problem/Motivation
Drupal 6 allowed duplicate paths in the database and it was possible for those values to sneak into the database. This doesn't currently cause a fatal error because #1314214: MySQL driver does not support full UTF-8 (emojis, asian symbols, mathematical symbols) removed the unique constraint from the mysql database but as noted in the IS the constraint still conceptually exists.
We have a Drupal-level unique constraint on the File URI field instead of a database-level unique constraint, until we fix #2492171: Provide options to sanitize filenames (transliterate, lowercase, replace whitespace, etc) (at which point we can put back the database constraint).
Additionally, we need to enforce this uniqueness for other sources that could target file entities.
Proposed resolution
Update destination to update the existing entity.
Remaining tasks
Review.
User interface changes
None.
API changes
None.
Data model changes
None.
Original Report:
In Drupal 6 there was no constraint on the file path being unique but in d7 and d8 there is. When migrating to d8 we don't handle the case where there are duplicates and it throws warnings.
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry [error]
'public://pictures/picture-101947.jpg' for key
'file_field__uri': INSERT INTO {file_managed} (uuid,
langcode, uid, filename, uri, filemime, filesize, status, created,
changed) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1,
:db_insert_placeholder_2, :db_insert_placeholder_3,
:db_insert_placeholder_4, :db_insert_placeholder_5,
:db_insert_placeholder_6, :db_insert_placeholder_7,
:db_insert_placeholder_8, :db_insert_placeholder_9);#2030501: [meta] Ensure that Drupal 6 sites have a functional upgrade path to either Drupal 7 or 8 before Drupal 6 loses security support is a similar issue for but this has to take a different approach because migrations vs upgrades.
The attached patch catches duplicates during conversion to an entity and uses the first match for the migration instead of trying to write a duplicate.
| Comment | File | Size | Author |
|---|---|---|---|
| #41 | interdiff-2504815-34-41.txt | 843 bytes | phenaproxima |
| #41 | 2504815-41.patch | 9.62 KB | phenaproxima |
| #34 | d6_to_d8_migration-2504815-34.patch | 8.67 KB | neclimdul |
| #34 | mergediff.txt | 2.06 KB | neclimdul |
Comments
Comment #1
neclimdulNR for testbot but this is probably NW because it doesn't have tests, I'm just not sure where the tests belong.
Comment #2
catchMarked #2504553: Ensure that Drupal 6 duplicate file names are correctly migrated as duplicate.
Comment #4
neclimdulwoops
Comment #8
neclimdulbah, failed at making a patch, made 2 interdiffs.
Comment #9
neclimdulreroll, fix conflict with #2505521: Clean-up un-need test classes in migrate_drupal
Comment #11
neclimdulFixed.
Comment #13
neclimdulpro work there...
Comment #16
fabianx commentedLooks great to me, but needs tests AFAICS.
Comment #17
phenaproximaThis patch includes a test, as well as a completely refactored MigrateFileTest and a unit test of the file_uri process plugin.
Comment #19
phenaproximaRestored MigrateDumpAlterInterface to MigrateFileTest -- turns out it's important and prevents test failures due to concurrency.
Comment #20
mikeryanThis will need a reroll when #2534012: Move module-specific migration support into the file module lands.
Comment #21
neclimdulre-roll.
Comment #23
neclimdulyeah... merge couldn't catch that. sorry was in a hurry and didn't run unit tests like normal.
Comment #24
phenaproximaFileUriTest should be moved into the File module's test suite and be in the file @group.
Comment #25
neclimdulno problem.
Comment #26
benjy commentedCan you explain the ltrim? Isn't the point that the base path has been removed which would have also included the slash?
Similarly, i find this interesting that the unit test isn't setting the temporary_path properties relative to the values being passed in?
Comment #27
quietone commentedTried to reproduce the error without success. How can I do that? I created a duplicate file path and that was migrated to D8 just fine.
Comment #28
neclimdulYeah, I don't see any errors either now. We've been running this patch so long I'm not sure how this got resolved. We'll do some validation checks to make sure its working I guess.
Comment #29
mikeryanIf no one can reproduce this within the next week or two, let's close it.
Comment #30
neclimdulWell, lets do this. We've got additional test coverage should trigger the failures. Lets see what happens with just the tests.
Comment #33
neclimdulSo I sorted out why the duplicate constraint went away. #1314214: MySQL driver does not support full UTF-8 (emojis, asian symbols, mathematical symbols) removed the unique constraint. That probably doesn't make sense but the CR has some information why. This is regression though and I'm not sure how to handle it... We don't want to be writing duplicate files so the previous patch may actually still be the fix we just may have a different failure on our hands.
edit: fix link to CR
Comment #34
neclimdulre #26 ask phena and he said he was just being defensive. I don't see a problem with it and the new one just makes that path consistent with the other code bath.
Re-roll of #25 after #2554321: Clean up Migrate's test suite. Just some minor chunk conflicts in the test. @see mergediff.
To clarify why we still need to merge the file entities rather then duplicating the entries despite the database currently supporting duplicate paths, the is this note from the utf-8 that explains it:
Comment #35
neclimdulClarifying the current state of things in the IS. The constraint violation doesn't exist, but we still need to enforce the constraint.
Comment #36
mikeryanAll righty, ready to go I think...
Comment #39
phenaproximaBULLCRAP. This is just PIFR being mean, and I've re-queued the patch for testing by DrupalCI to prove it. Restoring RTBC.
Comment #40
phenaproximaPostgreSQL is not liking this patch.
Comment #41
phenaproximaOoooooooh! PostgreSQL, as it is wont to do, found a genuine (if edge-casey) problem. It's fixed now in the File source, but we'll need to address it in all sources.
Consider two file entities in the source database, with fids of 5 and 7, and imagine that they have the same timestamp. If the source plugin selects them and orders only by timestamp, they will appear in a non-deterministic order that can vary by database -- which is why PostgreSQL revealed this issue but MySQL and SQLite did not.
The solution is that sources which order on a high-water property need to also order on a unique field (fid, nid, uid, etc.) to force the order to be deterministic. This should be fixed for all sources, but let's do that in a follow-up issue. It will only be a problem when there's a high-water property collision, which is relatively unlikely anyway -- although the likelihood of it increases with the size of your data set.Comment #42
neclimdulGood job.
Comment #43
webchickYay, lovin' all the new test coverage here, as well as postgreSQL is the culprit for fleshing out a real bug. ;)
Committed and pushed to 8.0.x. Thanks!
Comment #46
webchickGrowl.
Comment #47
benjy commentedWhy does the order need to be deterministic? The only way I can see that is if we have two files with the same id and the same uri in the dumps? And that is surely invalid anyway?
Comment #48
neclimdulSort of, unfortunately d6 allowed conceptually invalid data. Logically resolving that, and enforcing core's soft constraint is the point of this issue.
Comment #49
benjy commentedCan you expand on that?
So, if it's some niche little problem to files, it doesn't need applying to all sources as was said in #41?
Comment #50
mikeryanI think the point is that the secondary sort isn't logically necessary - what does it matter whether it's deterministic or not? My suspicion is that the real problem is the tests are assuming a specific order, and the secondary sort is forcing the order to what the tests want? If so, I would suggest that it's the tests that need fixing.
Comment #51
neclimdulSo first, re: #49, it is an edge case. D6 had no constraints and essentially no API for managed files. The API was, write to the files table and it will be there. This allowed edge cases where duplicates could be written and not just by custom code, but by the core upload module. These values where not "correct" in d6 either but they where valid and didn't really break because things just resolved and kept working.
As to whether this applies to other sources, no sources come to mind that have this sort of weirdness. D7 had a unique constraint, and most other tables have consistent "uniqueness" so the order won't have to be this strict.
re: #50 you are correct that the tests are enforcing the order but I don't think that is an assumption but an assertion. We could just as easily have fixed the test so it naturally sorted on timestamp but this actually is better this way. Since literally anything could be written to the files table (as mentioned earlier) the order of sort of important because the last one is the one we want to migrate in the end. This is inline with #1260938-124: d6 to d7 update fails on file duplicates '#7061 Integrity constraint violation' if it is implemented completely different because we don't want to embed the logic for how to resolve the d6 table in the destination.
Comment #52
phenaproximaY'all have made me see sense with regard to making all sources order deterministically. @neclimdul is correct; the only reason it's important in this case is because the D6 files table is screwy. Paths/URIs are unique identifiers but D6 didn't treat them that way, thus the reason we need to do these acrobatics. But that's unique to files, and only D6 files at that -- virtually every other form of content we migrate from D6 has a single, unique identifier which renders order irrelevant.
So I was mistaken in saying that all sources should enforce a deterministic order. My brain must have been on a beach somewhere. I recant!