Skip to content

fix: preserve MATCH FULL on foreign keys - #662

Merged
tianzhou merged 1 commit into
pgplex:mainfrom
spicysharkco:fix/fk-match-full
Oct 27, 2026
Merged

tianzhou merged 1 commit into
pgplex:mainfrom
spicysharkco:fix/fk-match-full

Conversation

@spicysharkco

Copy link
Copy Markdown
Contributor

A table has CONSTRAINT c_fk FOREIGN KEY (x, y) REFERENCES p (a, b) MATCH FULL. pgschema dump prints it without MATCH FULL, so a restore accepts rows with one of x, y NULL, which the original rejects. pgschema plan with a desired file that adds the MATCH FULL foreign key gives ADD CONSTRAINT c_fk FOREIGN KEY (x, y) REFERENCES p (a, b) NOT VALID, also without it. If the database already has a plain foreign key named c_fk, the plan is empty, so apply never adds MATCH FULL. An inline constraint in CREATE TABLE loses it the same way. Every supported Postgres version has MATCH FULL; I ran this on 18.

Cause: the inspector never reads pg_constraint.confmatchtype. The GetConstraintsForSchema select list (queries.sql#L1397-L1412) has the delete and update rules but not the match type, so ir.Constraint has no field for it and the three foreign key writers print nothing: constraint.go#L55, table.go#L2228 and the online rewrite at rewrite.go#L314. constraintsEqual (constraint.go#L189) does not compare it, so the two keys look equal. It is the same gap #589 had for the ON DELETE SET NULL (col) list, fixed in #590.

Fix: the query returns confmatchtype = 'f' as match_full and the inspector stores it in Constraint.MatchFull. Each writer prints MATCH FULL right after the referenced column list, where the grammar puts it. constraintsEqual compares it, so a change drops and re-adds the constraint, as for ON DELETE. MATCH SIMPLE is the default and MATCH PARTIAL is not implemented by Postgres. queries.sql.go is the output of sqlc generate (v1.30.0): rerunning it changes nothing. GetConstraints gets the same column, as in #590, so the two queries stay alike.

Tests: as in #590, I added the cases to existing directories. create_table/add_fk gets a new MATCH FULL key on an existing table (online rewrite) and a plain key that becomes MATCH FULL. create_table/add_table_composite_keys gets the inline form. The plan files were regenerated with --generate.

PGSCHEMA_TEST_FILTER="create_table/add_" go test -count=1 -v ./internal/diff -run TestDiffFromFiles
PGSCHEMA_TEST_FILTER="create_table/add_" go test -count=1 -v ./cmd -run '^TestPlanAndApply$'

With the fix reverted and the tests kept, each command fails 2 of 29 subtests (add_fk, add_table_composite_keys). With the fix both pass 29 of 29. Reverting one hunk at a time fails a test for each: the inspector line and the query (both cases), constraintsEqual (add_fk), the inline writer (add_table_composite_keys), the REFERENCES writer (add_fk, diff test) and the rewrite (add_fk, plan test). go test -count=1 ./ir/... ./internal/diff ./internal/plan and the five dump tests with foreign keys pass, on the default embedded Postgres 18 only. gofmt -l, go vet on those packages and git diff --check are clean.

Overlap checked: searched open and closed issues and PRs for "MATCH FULL", confmatchtype, MATCH SIMPLE, foreign key match and listed the open PRs. None mentions the match type. #645, #660 and #622 edit other functions in table.go and rewrite.go (and #645 the top of ir.Constraint); git merge-tree against each of those heads merges cleanly with this change. #637 already conflicts with main in ir/inspector.go, in the same three places with or without this change.

The inspector did not read pg_constraint.confmatchtype, so dump left MATCH
FULL off a foreign key, plan left it off a new foreign key, and a plan from
a simple foreign key to a MATCH FULL one reported no changes.

Read it into Constraint.MatchFull and write it in the three foreign key
writers: the inline and ALTER constraint writer, the REFERENCES clause
writer, and the online NOT VALID rewrite. Compare it in constraintsEqual so
a change recreates the constraint. GetConstraints selects the same column,
so the two queries stay alike.
@greptile-apps

greptile-apps Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

Preserves MATCH FULL from PostgreSQL inspection through schema comparison and SQL output.

  • Plans replace foreign keys when their MATCH FULL setting changes.
  • Generated foreign-key SQL keeps the MATCH FULL rule.

No actionable issues found. Tests were not run during this review.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[PostgreSQL confmatchtype] --> B[Constraint.MatchFull]
  B --> C[Compare constraints]
  C --> D[Drop and recreate changed key]
  B --> E[Inline or standalone SQL]
  D --> F[Online add and validate]
  E --> G[MATCH FULL preserved]
  F --> G
Loading

Reviews (1) · Last reviewed commit: "fix: preserve MATCH FULL on foreign keys" · Reviewed by Greptile

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the changes preserving foreign-key MATCH FULL semantics.

No open issues 🎉

1 file not reviewed
  • ir/queries/queries.sql.go: Generated file
What changed in this PR

Preserves foreign-key MATCH FULL semantics throughout pgschema’s inspection, dump, and migration workflow, and detects changes between default matching and MATCH FULL.

Changes:

  • Reads the match type from PostgreSQL catalogs into Constraint.MatchFull.
  • Emits MATCH FULL in standalone, inline, and online foreign-key DDL and compares it when detecting changes.
  • Extends regression fixtures for new constraints, constraint replacement, and inline table creation.
File Description
testdata/​diff/​create_table/​add_table_composite_keys/​plan.txt Updates readable inline-constraint plan.
testdata/​diff/​create_table/​add_table_composite_keys/​plan.sql Preserves inline MATCH FULL in plan SQL.
testdata/​diff/​create_table/​add_table_composite_keys/​plan.json Updates serialized inline-constraint plan.
testdata/​diff/​create_table/​add_table_composite_keys/​new.sql Adds inline MATCH FULL coverage.
testdata/​diff/​create_table/​add_table_composite_keys/​diff.sql Updates expected table-creation DDL.
testdata/​diff/​create_table/​add_fk/​plan.txt Shows constraint replacement and online addition.
testdata/​diff/​create_table/​add_fk/​plan.sql Updates expected online migration SQL.
testdata/​diff/​create_table/​add_fk/​plan.json Updates plan steps and source fingerprint.
testdata/​diff/​create_table/​add_fk/​old.sql Adds baseline tables and a default-match constraint.
testdata/​diff/​create_table/​add_fk/​new.sql Adds desired MATCH FULL constraints.
testdata/​diff/​create_table/​add_fk/​diff.sql Records expected constraint addition and replacement.
ir/​queries/​queries.sql.go Adds generated match-type fields and scanning.
ir/​queries/​queries.sql Reads confmatchtype in both constraint queries.
ir/​ir.go Adds Constraint.MatchFull.
ir/​inspector.go Populates the match-type field during inspection.
internal/​plan/​rewrite.go Preserves MATCH FULL during online rewriting.
internal/​diff/​table.go Emits MATCH FULL in inline foreign keys.
internal/​diff/​constraint.go Emits and compares MATCH FULL for constraints.

Review effort: Balanced

@tianzhou tianzhou left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Thanks for the contribution.

@tianzhou
tianzhou merged commit e3672a9 into pgplex:main Oct 27, 2026
2 checks passed
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.

3 participants