Skip to content

fix schema.c: filter out internal partition FK clones when listing FK constraints#41

Merged
teknogeek0 merged 1 commit into
mainfrom
fix/fk-partition-clone-filter
Jul 16, 2026
Merged

fix schema.c: filter out internal partition FK clones when listing FK constraints#41
teknogeek0 merged 1 commit into
mainfrom
fix/fk-partition-clone-filter

Conversation

@DmitriiAn

Copy link
Copy Markdown

Filter internal partition FK clones from FK constraint listing

Problem

FK_BASE_WHERE in schema.c filtered FK constraints on contype = 'f' with no
conparentid guard, so listSourceFKConstraintsSQL returned Postgres's internal
per-partition FK clones (conparentid != 0) alongside the real top-level
constraints. On a partitioned schema this caused two failures:

  • Un-writable target tables. For an FK referencing a partitioned table, each
    clone's pg_get_constraintdef() renders REFERENCES <specific_partition>.
    Recreating all of them produces standalone single-partition FKs; since a key
    lives in exactly one partition, no row can satisfy all of them at once, so the
    first insert — and CDC apply — fails.
  • Spurious constraint already exists errors. Clones on a partitioned
    referencing table are recreated automatically via CASCADE when the parent
    FK is added, so explicitly listing them collided.

Fix

One line: AND c.conparentid = 0 in FK_BASE_WHERE (shared by all 5 filter
variants), so only top-level constraints are listed. This mirrors pg_dump,
which excludes cascade clones the same way. Recreating the parent constraint
re-cascades to all partitions, so no clone is lost.

 #define FK_BASE_WHERE \
 	" WHERE c.contype = 'f' " \
+	"   AND c.conparentid = 0 " \
 	"   AND n.nspname !~ '^pg_' " \

@DmitriiAn
DmitriiAn requested a review from teknogeek0 July 15, 2026 06:36
pgcopydb lists FK constraints straight from pg_constraint and creates them
directly. On a partitioned schema that also returns Postgres's internal
per-partition FK clones (pg_constraint.conparentid <> 0): once the top-level
constraint is created it cascades to every partition, then pgcopydb tries to
create each clone again and hits "constraint already exists" — one ERROR per
clone, so a migration that actually succeeds looks like it failed.

Filter the clones out with c.conparentid = 0; recreating the top-level
constraint re-cascades to all partitions, so nothing is lost (mirrors
pg_dump). It cannot be a static SQL constant, though:

  - conparentid is PG 11+ only; referencing it on a 9.x/10 source aborts the
    clone with "column c.conparentid does not exist". Those sources are still
    supported (follow-9.6) and cannot have partition FK clones anyway.

  - --defer-validate-fks relies on the clones: PG < 18 rejects NOT VALID on a
    partitioned parent, so pgcopydb skips the parent and creates the per-leaf
    clones NOT VALID instead, which is the only enforcement that lands.

So the predicate is injected at runtime in schema_list_fk_constraints only
when the source is PG >= 11 and --defer-validate-fks is off.

Adds a fk-partition-clone test (HASH-partitioned table with FKs to regular
tables) asserting the clone finishes with no "constraint already exists"
errors and correct, validated FKs on the target. Verified locally: full suite
green on PG16/PG18; FK and partition suites green on PG16/17/18.
@teknogeek0
teknogeek0 force-pushed the fix/fk-partition-clone-filter branch from c3c9294 to 8e0eec9 Compare July 16, 2026 15:15
@teknogeek0
teknogeek0 merged commit 4aaa669 into main Jul 16, 2026
93 checks passed
@teknogeek0
teknogeek0 deleted the fix/fk-partition-clone-filter branch July 16, 2026 15:51
@teknogeek0 teknogeek0 mentioned this pull request Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants