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.

Comments

neclimdul’s picture

Status: Active » Needs review

NR for testbot but this is probably NW because it doesn't have tests, I'm just not sure where the tests belong.

catch’s picture

Priority: Normal » Major
Issue tags: +Migrate critical

Status: Needs review » Needs work

The last submitted patch, fix_duplicate_d6_d8_file_migrations.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new806 bytes
new806 bytes

woops

Status: Needs review » Needs work

The last submitted patch, 4: d6_to_d8_migration-2504815-4.patch, failed testing.

The last submitted patch, fix_duplicate_d6_d8_file_migrations.patch, failed testing.

The last submitted patch, 4: d6_to_d8_migration-2504815-4.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new2.13 KB

bah, failed at making a patch, made 2 interdiffs.

neclimdul’s picture

StatusFileSize
new1.13 KB

Status: Needs review » Needs work

The last submitted patch, 9: d6_to_d8_migration-2504815-9.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new806 bytes
new806 bytes

Fixed.

Status: Needs review » Needs work

The last submitted patch, 11: d6_to_d8_migration-2504815-11.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new1.14 KB

pro work there...

The last submitted patch, 9: d6_to_d8_migration-2504815-9.patch, failed testing.

The last submitted patch, 11: d6_to_d8_migration-2504815-11.patch, failed testing.

fabianx’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

Looks great to me, but needs tests AFAICS.

phenaproxima’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new12.39 KB
new10.8 KB

This patch includes a test, as well as a completely refactored MigrateFileTest and a unit test of the file_uri process plugin.

Status: Needs review » Needs work

The last submitted patch, 17: 2504815-17.patch, failed testing.

phenaproxima’s picture

Status: Needs work » Needs review
StatusFileSize
new8.62 KB
new7.39 KB

Restored MigrateDumpAlterInterface to MigrateFileTest -- turns out it's important and prevents test failures due to concurrency.

mikeryan’s picture

neclimdul’s picture

Status: Postponed » Needs review
StatusFileSize
new8.68 KB
new1.5 KB

re-roll.

Status: Needs review » Needs work

The last submitted patch, 21: d6_to_d8_migration-2504815-21.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new914 bytes
new8.66 KB

yeah... merge couldn't catch that. sorry was in a hurry and didn't run unit tests like normal.

phenaproxima’s picture

Status: Needs review » Needs work

FileUriTest should be moved into the File module's test suite and be in the file @group.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new756 bytes
new8.67 KB

no problem.

benjy’s picture

+++ b/core/modules/file/src/Plugin/migrate/process/d6/FileUri.php
@@ -24,20 +24,19 @@ class FileUri extends ProcessPluginBase {
+      return 'temporary://' . ltrim($uri, '/');
...
+    return ($is_public ? 'public' : 'private') . '://' . ltrim($uri, '/');

Can you explain the ltrim? Isn't the point that the base path has been removed which would have also included the slash?

+++ b/core/modules/file/tests/src/Unit/Plugin/migrate/process/d6/FileUriTest.php
@@ -0,0 +1,84 @@
+      '/path/to/private/files/baz.gif',
+      'sites/default/files',

Similarly, i find this interesting that the unit test isn't setting the temporary_path properties relative to the values being passed in?

quietone’s picture

Tried 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.

neclimdul’s picture

Yeah, 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.

mikeryan’s picture

Status: Needs review » Postponed (maintainer needs more info)

If no one can reproduce this within the next week or two, let's close it.

neclimdul’s picture

Status: Postponed (maintainer needs more info) » Needs review
StatusFileSize
new6.26 KB

Well, lets do this. We've got additional test coverage should trigger the failures. Lets see what happens with just the tests.

Status: Needs review » Needs work

The last submitted patch, 30: d6_to_d8_migration-2504815-30.patch, failed testing.

The last submitted patch, 30: d6_to_d8_migration-2504815-30.patch, failed testing.

neclimdul’s picture

So 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

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new2.06 KB
new8.67 KB

re #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:

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).

neclimdul’s picture

Issue summary: View changes

Clarifying the current state of things in the IS. The constraint violation doesn't exist, but we still need to enforce the constraint.

mikeryan’s picture

Status: Needs review » Reviewed & tested by the community

All righty, ready to go I think...

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 34: d6_to_d8_migration-2504815-34.patch, failed testing.

Status: Needs work » Needs review
phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

BULLCRAP. This is just PIFR being mean, and I've re-queued the patch for testing by DrupalCI to prove it. Restoring RTBC.

phenaproxima’s picture

Status: Reviewed & tested by the community » Needs work

PostgreSQL is not liking this patch.

phenaproxima’s picture

Status: Needs work » Needs review
StatusFileSize
new9.62 KB
new843 bytes

Ooooooooh! 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.

neclimdul’s picture

Status: Needs review » Reviewed & tested by the community

Good job.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Yay, 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!

  • webchick committed 89c6949 on 8.0.x
    Issue #2504815 by neclimdul, phenaproxima, mikeryan: d6 to d8 migration...

Status: Fixed » Needs work

The last submitted patch, 41: 2504815-41.patch, failed testing.

webchick’s picture

Status: Needs work » Fixed

Growl.

benjy’s picture

Why 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?

neclimdul’s picture

Sort of, unfortunately d6 allowed conceptually invalid data. Logically resolving that, and enforcing core's soft constraint is the point of this issue.

benjy’s picture

d6 allowed conceptually invalid data

Can 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?

mikeryan’s picture

I 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.

neclimdul’s picture

So 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.

phenaproxima’s picture

Y'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!

Status: Fixed » Closed (fixed)

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