Skip to content

[5.x]: fields/auto-merge fails with a duplicate-key error when merging relation fields that share a target #19519

Description

@jpwdesigns

What happened?

Description

fields/auto-merge fails with an integrity constraint violation when the two relation fields being merged share any (sourceId, sourceSiteId, targetId) combination.

craft\base\Field::afterMergeFrom() repoints the outgoing field's relations with an unguarded UPDATE:

// vendor/craftcms/cms/src/base/Field.php:933
Db::update(DbTable::RELATIONS, ['fieldId' => $this->id], ['fieldId' => $outgoingField->id]);

The relations table has a unique index on (fieldId, sourceId, sourceSiteId, targetId) (Install.php:943). If the same element relates the same target through both fields — e.g. an entry whose Hero Image and SEO Image point at the same asset — the UPDATE tries to create a duplicate and the migration aborts.

In my case, merging two Assets fields used across 12 entry types, 849 of 6,062 rows collided.

This isn't the same as #15869 (relation values being conflated), and the workaround recommended there does not prevent it. I accepted auto-merge's offer to run resave/all --with-fields=…, and it completed successfully immediately before the merge — roughly 21,000 elements_sites rows were rewritten. The merge then failed anyway. Resaving populates the content JSON, but it leaves the relations rows untouched, so the UPDATE still collides.

Partial-failure state

The failure leaves the install inconsistent, because the merge mutates the database before the content migration runs and $projectConfig->flush() is never reached:

State after failure
Outgoing field soft-deleted (dateDeleted set)
Its relation rows still on the old fieldId — never repointed
Field layouts (DB) already repointed to the persisting field
Project config (YAML + stored) still contains the outgoing field
Content migration not recorded as applied

project-config/diff reports no pending changes, because the YAML and the stored project config agree — the divergence is between project config and the fields / fieldlayouts tables, which that command doesn't compare. So the inconsistency is invisible to the usual check.

Re-running the generated migration fails identically, since the colliding rows are still present.

Steps to reproduce

  1. Create two Assets fields, fieldA and fieldB, with identical settings.
  2. Add both to the same entry type.
  3. On at least one entry, select the same asset in both fields, and save.
  4. Run php craft fields/auto-merge and merge fieldA into fieldB. Accepting the
    resave/all --with-fields=… prompt makes no difference.

Expected behavior

The merge either skips or de-duplicates the conflicting rows — the outgoing row is redundant once both layout elements resolve to the persisting field — and completes. Failing that, auto-merge detects the collision up front and refuses before mutating anything.

Actual behavior

 → Updating usages for heroImage … ✓ done
 → Removing heroImage … ✓ done
 → Generating content migration for heroImage … ✓ done
→ Running content migration for heroImage …
Exception: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'217-314772-1-207012' for key 'relations.relations_fieldId_sourceId_sourceSiteId_targetId_unq_idx'
The SQL being executed was: UPDATE `relations` SET `fieldId`=217, `dateUpdated`='…' WHERE `fieldId`=1
#3 vendor/craftcms/cms/src/helpers/Db.php(1144): yii\db\Command->execute()
#4 vendor/craftcms/cms/src/base/Field.php(933): craft\helpers\Db::update()
#5 vendor/craftcms/cms/src/migrations/BaseFieldMergeMigration.php(75): craft\base\Field->afterMergeFrom()
#6 vendor/craftcms/cms/src/db/Migration.php(50): craft\migrations\BaseFieldMergeMigration->safeUp()

Craft CMS version

5.10.13.2 (Pro), schema 5.10.0.0

PHP version

8.3.30

Operating system and version

Linux (DDEV), MySQL 8.0.40

Database type and version

MySQL 8.0.40

Image driver and version

No response

Installed plugins and versions

Not relevant — reproducible with core Assets fields.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions