fix schema.c: filter out internal partition FK clones when listing FK constraints#41
Merged
Merged
Conversation
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
force-pushed
the
fix/fk-partition-clone-filter
branch
from
July 16, 2026 15:15
c3c9294 to
8e0eec9
Compare
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Filter internal partition FK clones from FK constraint listing
Problem
FK_BASE_WHEREinschema.cfiltered FK constraints oncontype = 'f'with noconparentidguard, solistSourceFKConstraintsSQLreturned Postgres's internalper-partition FK clones (
conparentid != 0) alongside the real top-levelconstraints. On a partitioned schema this caused two failures:
clone's
pg_get_constraintdef()rendersREFERENCES <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.
constraint already existserrors. Clones on a partitionedreferencing table are recreated automatically via
CASCADEwhen the parentFK is added, so explicitly listing them collided.
Fix
One line:
AND c.conparentid = 0inFK_BASE_WHERE(shared by all 5 filtervariants), 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_' " \