From f93e7c08dd64ab2c2abe5621db17a349cd002701 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Thu, 2 Jul 2026 19:49:17 -0700 Subject: [PATCH 1/3] fix: skip NoInherit comparison for non-CHECK constraints to fix partition convergence (#495) connoinherit is only meaningful for CHECK constraints (NO INHERIT modifier). PostgreSQL sets it inconsistently for PK/UNIQUE on partition children: standalone constraint gets connoinherit=true while PARTITION OF gets false. This caused constraintsEqual to return false, producing perpetual no-op DROP+re-ADD on every plan for partitioned tables. Co-Authored-By: Claude Opus 4.6 --- internal/diff/constraint.go | 7 +++++- .../diff.sql | 0 .../new.sql | 8 +++++++ .../old.sql | 22 +++++++++++++++++++ .../plan.json | 9 ++++++++ .../plan.sql | 0 .../plan.txt | 1 + .../diff.sql | 0 .../new.sql | 8 +++++++ .../old.sql | 19 ++++++++++++++++ .../plan.json | 9 ++++++++ .../plan.sql | 0 .../plan.txt | 1 + 13 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 testdata/diff/create_table/issue_495_partition_constraint_convergence/diff.sql create mode 100644 testdata/diff/create_table/issue_495_partition_constraint_convergence/new.sql create mode 100644 testdata/diff/create_table/issue_495_partition_constraint_convergence/old.sql create mode 100644 testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.json create mode 100644 testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.sql create mode 100644 testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.txt create mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/diff.sql create mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/new.sql create mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/old.sql create mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json create mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/plan.sql create mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/plan.txt diff --git a/internal/diff/constraint.go b/internal/diff/constraint.go index 1f3de2b7..2897e409 100644 --- a/internal/diff/constraint.go +++ b/internal/diff/constraint.go @@ -166,7 +166,12 @@ func constraintsEqual(old, new *ir.Constraint) bool { if old.CheckClause != new.CheckClause { return false } - if old.NoInherit != new.NoInherit { + // NoInherit (connoinherit) is only meaningful for CHECK constraints where + // it controls the NO INHERIT modifier. For PK/UNIQUE/FK/EXCLUDE constraints, + // PostgreSQL sets connoinherit inconsistently (e.g. true for a standalone + // constraint vs false for one created via PARTITION OF), so comparing it + // would cause false diffs on partition children (#495). + if old.Type == ir.ConstraintTypeCheck && old.NoInherit != new.NoInherit { return false } if old.ExclusionDefinition != new.ExclusionDefinition { diff --git a/testdata/diff/create_table/issue_495_partition_constraint_convergence/diff.sql b/testdata/diff/create_table/issue_495_partition_constraint_convergence/diff.sql new file mode 100644 index 00000000..e69de29b diff --git a/testdata/diff/create_table/issue_495_partition_constraint_convergence/new.sql b/testdata/diff/create_table/issue_495_partition_constraint_convergence/new.sql new file mode 100644 index 00000000..56f2a4f9 --- /dev/null +++ b/testdata/diff/create_table/issue_495_partition_constraint_convergence/new.sql @@ -0,0 +1,8 @@ +CREATE TABLE public.p ( + band_id integer NOT NULL, + geom_id integer NOT NULL, + CONSTRAINT p_band_id_geom_id_key UNIQUE (band_id, geom_id) +) PARTITION BY LIST (band_id); + +CREATE TABLE public.p_12 PARTITION OF public.p FOR VALUES IN (12); +CREATE TABLE public.p_13 PARTITION OF public.p FOR VALUES IN (13); diff --git a/testdata/diff/create_table/issue_495_partition_constraint_convergence/old.sql b/testdata/diff/create_table/issue_495_partition_constraint_convergence/old.sql new file mode 100644 index 00000000..66b880f4 --- /dev/null +++ b/testdata/diff/create_table/issue_495_partition_constraint_convergence/old.sql @@ -0,0 +1,22 @@ +-- This represents what the target database looks like after pgschema apply: +-- partition children created as standalone tables with explicit constraints, +-- then attached to the parent table. +CREATE TABLE public.p ( + band_id integer NOT NULL, + geom_id integer NOT NULL, + CONSTRAINT p_band_id_geom_id_key UNIQUE (band_id, geom_id) +) PARTITION BY LIST (band_id); + +CREATE TABLE public.p_12 ( + band_id integer NOT NULL, + geom_id integer NOT NULL, + CONSTRAINT p_12_band_id_geom_id_key UNIQUE (band_id, geom_id) +); +ALTER TABLE public.p ATTACH PARTITION public.p_12 FOR VALUES IN (12); + +CREATE TABLE public.p_13 ( + band_id integer NOT NULL, + geom_id integer NOT NULL, + CONSTRAINT p_13_band_id_geom_id_key UNIQUE (band_id, geom_id) +); +ALTER TABLE public.p ATTACH PARTITION public.p_13 FOR VALUES IN (13); diff --git a/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.json b/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.json new file mode 100644 index 00000000..404855fa --- /dev/null +++ b/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.json @@ -0,0 +1,9 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.11.1", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "" + }, + "groups": null +} diff --git a/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.sql b/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.sql new file mode 100644 index 00000000..e69de29b diff --git a/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.txt b/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.txt new file mode 100644 index 00000000..241994af --- /dev/null +++ b/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.txt @@ -0,0 +1 @@ +No changes detected. diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/diff.sql b/testdata/diff/create_table/issue_495_partition_pk_convergence/diff.sql new file mode 100644 index 00000000..e69de29b diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/new.sql b/testdata/diff/create_table/issue_495_partition_pk_convergence/new.sql new file mode 100644 index 00000000..03ec0e16 --- /dev/null +++ b/testdata/diff/create_table/issue_495_partition_pk_convergence/new.sql @@ -0,0 +1,8 @@ +CREATE TABLE public.p ( + band_id integer NOT NULL, + geom_id integer NOT NULL, + CONSTRAINT p_pkey PRIMARY KEY (band_id, geom_id) +) PARTITION BY LIST (band_id); + +CREATE TABLE public.p_12 PARTITION OF public.p FOR VALUES IN (12); +CREATE TABLE public.p_13 PARTITION OF public.p FOR VALUES IN (13); diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/old.sql b/testdata/diff/create_table/issue_495_partition_pk_convergence/old.sql new file mode 100644 index 00000000..4f3e27cd --- /dev/null +++ b/testdata/diff/create_table/issue_495_partition_pk_convergence/old.sql @@ -0,0 +1,19 @@ +CREATE TABLE public.p ( + band_id integer NOT NULL, + geom_id integer NOT NULL, + CONSTRAINT p_pkey PRIMARY KEY (band_id, geom_id) +) PARTITION BY LIST (band_id); + +CREATE TABLE public.p_12 ( + band_id integer NOT NULL, + geom_id integer NOT NULL, + CONSTRAINT p_12_pkey PRIMARY KEY (band_id, geom_id) +); +ALTER TABLE public.p ATTACH PARTITION public.p_12 FOR VALUES IN (12); + +CREATE TABLE public.p_13 ( + band_id integer NOT NULL, + geom_id integer NOT NULL, + CONSTRAINT p_13_pkey PRIMARY KEY (band_id, geom_id) +); +ALTER TABLE public.p ATTACH PARTITION public.p_13 FOR VALUES IN (13); diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json new file mode 100644 index 00000000..404855fa --- /dev/null +++ b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json @@ -0,0 +1,9 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.11.1", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "" + }, + "groups": null +} diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.sql b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.sql new file mode 100644 index 00000000..e69de29b diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.txt b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.txt new file mode 100644 index 00000000..241994af --- /dev/null +++ b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.txt @@ -0,0 +1 @@ +No changes detected. From b7f44ab412cca294dc05c7edcf77cb99df86fee6 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Thu, 2 Jul 2026 19:57:13 -0700 Subject: [PATCH 2/3] fix: correct source_fingerprint hashes in test plan.json files Co-Authored-By: Claude Opus 4.6 --- .../issue_495_partition_constraint_convergence/plan.json | 2 +- .../create_table/issue_495_partition_pk_convergence/plan.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.json b/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.json index 404855fa..e67dc6bd 100644 --- a/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.json +++ b/testdata/diff/create_table/issue_495_partition_constraint_convergence/plan.json @@ -3,7 +3,7 @@ "pgschema_version": "1.11.1", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "" + "hash": "b52a7d3e192793cd8af86dbf6b990056fa88b14303a29a10bb55048423a811c0" }, "groups": null } diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json index 404855fa..62050da4 100644 --- a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json +++ b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json @@ -3,7 +3,7 @@ "pgschema_version": "1.11.1", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "" + "hash": "8a0a43aaa15c76ab7e2a6a21db2b1bdfb93c912c794af7b307afbbb96340f2b8" }, "groups": null } From 88fda5d4d707f4a3ebb9b207686f86a1fe02c808 Mon Sep 17 00:00:00 2001 From: tianzhou Date: Thu, 2 Jul 2026 20:16:25 -0700 Subject: [PATCH 3/3] test: remove redundant PK convergence test case The UNIQUE test already covers the root cause (NoInherit comparison); a separate PK case exercises the same code path with no additional value. Co-Authored-By: Claude Opus 4.6 --- .../diff.sql | 0 .../new.sql | 8 -------- .../old.sql | 19 ------------------- .../plan.json | 9 --------- .../plan.sql | 0 .../plan.txt | 1 - 6 files changed, 37 deletions(-) delete mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/diff.sql delete mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/new.sql delete mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/old.sql delete mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json delete mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/plan.sql delete mode 100644 testdata/diff/create_table/issue_495_partition_pk_convergence/plan.txt diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/diff.sql b/testdata/diff/create_table/issue_495_partition_pk_convergence/diff.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/new.sql b/testdata/diff/create_table/issue_495_partition_pk_convergence/new.sql deleted file mode 100644 index 03ec0e16..00000000 --- a/testdata/diff/create_table/issue_495_partition_pk_convergence/new.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE public.p ( - band_id integer NOT NULL, - geom_id integer NOT NULL, - CONSTRAINT p_pkey PRIMARY KEY (band_id, geom_id) -) PARTITION BY LIST (band_id); - -CREATE TABLE public.p_12 PARTITION OF public.p FOR VALUES IN (12); -CREATE TABLE public.p_13 PARTITION OF public.p FOR VALUES IN (13); diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/old.sql b/testdata/diff/create_table/issue_495_partition_pk_convergence/old.sql deleted file mode 100644 index 4f3e27cd..00000000 --- a/testdata/diff/create_table/issue_495_partition_pk_convergence/old.sql +++ /dev/null @@ -1,19 +0,0 @@ -CREATE TABLE public.p ( - band_id integer NOT NULL, - geom_id integer NOT NULL, - CONSTRAINT p_pkey PRIMARY KEY (band_id, geom_id) -) PARTITION BY LIST (band_id); - -CREATE TABLE public.p_12 ( - band_id integer NOT NULL, - geom_id integer NOT NULL, - CONSTRAINT p_12_pkey PRIMARY KEY (band_id, geom_id) -); -ALTER TABLE public.p ATTACH PARTITION public.p_12 FOR VALUES IN (12); - -CREATE TABLE public.p_13 ( - band_id integer NOT NULL, - geom_id integer NOT NULL, - CONSTRAINT p_13_pkey PRIMARY KEY (band_id, geom_id) -); -ALTER TABLE public.p ATTACH PARTITION public.p_13 FOR VALUES IN (13); diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json deleted file mode 100644 index 62050da4..00000000 --- a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": "1.0.0", - "pgschema_version": "1.11.1", - "created_at": "1970-01-01T00:00:00Z", - "source_fingerprint": { - "hash": "8a0a43aaa15c76ab7e2a6a21db2b1bdfb93c912c794af7b307afbbb96340f2b8" - }, - "groups": null -} diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.sql b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.txt b/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.txt deleted file mode 100644 index 241994af..00000000 --- a/testdata/diff/create_table/issue_495_partition_pk_convergence/plan.txt +++ /dev/null @@ -1 +0,0 @@ -No changes detected.