Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/diff/constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func generateConstraintSQL(constraint *ir.Constraint, targetSchema string, quali
ir.QuoteIdentifier(constraint.Name),
strings.Join(cols, ", "),
qualifiedRefTable, strings.Join(refCols, ", "))
if constraint.MatchFull {
stmt += " MATCH FULL"
}
// Only add ON UPDATE/DELETE if they are not the default "NO ACTION"
if constraint.UpdateRule != "" && constraint.UpdateRule != "NO ACTION" {
stmt += fmt.Sprintf(" ON UPDATE %s", constraint.UpdateRule)
Expand Down Expand Up @@ -189,6 +192,9 @@ func constraintsEqual(old, new *ir.Constraint) bool {
if old.UpdateRule != new.UpdateRule {
return false
}
if old.MatchFull != new.MatchFull {
return false
}
if old.Deferrable != new.Deferrable {
return false
}
Expand Down
4 changes: 4 additions & 0 deletions internal/diff/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -2225,6 +2225,10 @@ func generateForeignKeyClauseMode(constraint *ir.Constraint, targetSchema string
}
}

if constraint.MatchFull {
clause += " MATCH FULL"
}

// Add referential actions
if constraint.UpdateRule != "" && constraint.UpdateRule != "NO ACTION" {
clause += fmt.Sprintf(" ON UPDATE %s", constraint.UpdateRule)
Expand Down
4 changes: 4 additions & 0 deletions internal/plan/rewrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ func generateForeignKeyRewrite(constraint *ir.Constraint) []RewriteStep {
refTableName,
joinStrings(refColumnNames, ", "))

if constraint.MatchFull {
fkClause += " MATCH FULL"
}

// Add ON UPDATE/DELETE clauses if specified (in correct order)
if constraint.UpdateRule != "" && constraint.UpdateRule != "NO ACTION" {
fkClause += fmt.Sprintf(" ON UPDATE %s", constraint.UpdateRule)
Expand Down
1 change: 1 addition & 0 deletions ir/inspector.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ func (i *Inspector) buildConstraints(ctx context.Context, schema *IR, targetSche
if updateRule := i.safeInterfaceToString(constraint.UpdateRule); updateRule != "" && updateRule != "<nil>" {
c.UpdateRule = updateRule
}
c.MatchFull = constraint.MatchFull.Bool
}

// Handle check constraints
Expand Down
1 change: 1 addition & 0 deletions ir/ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ type Constraint struct {
DeleteRule string `json:"delete_rule,omitempty"`
DeleteSetColumns []string `json:"delete_set_columns,omitempty"` // PG15+: column list of ON DELETE SET NULL/SET DEFAULT (confdelsetcols)
UpdateRule string `json:"update_rule,omitempty"`
MatchFull bool `json:"match_full,omitempty"` // FOREIGN KEY ... MATCH FULL (confmatchtype = 'f')
Deferrable bool `json:"deferrable,omitempty"`
InitiallyDeferred bool `json:"initially_deferred,omitempty"`
IsValid bool `json:"is_valid,omitempty"`
Expand Down
4 changes: 4 additions & 0 deletions ir/queries/queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ SELECT
c.convalidated AS is_valid,
COALESCE((to_jsonb(c) ->> 'conperiod')::boolean, false) AS is_period,
c.connoinherit AS no_inherit,
-- MATCH FULL (pg_constraint.confmatchtype = 'f'); only foreign keys can set it.
(c.confmatchtype = 'f') AS match_full,
-- ON DELETE SET NULL/SET DEFAULT (column list), PG15+ (pg_constraint.confdelsetcols).
-- Rendered as a JSON array of column names so it survives the per-column row fan-out
-- and stays a single scalar on PG14 (where the attribute does not exist). Issue #589.
Expand Down Expand Up @@ -1418,6 +1420,8 @@ SELECT
-- pg_index.indnullsnotdistinct is PG15+. Use to_jsonb so the column reference
-- doesn't fail to plan on PG14 (where the attribute does not exist on pg_index).
COALESCE((to_jsonb(i) ->> 'indnullsnotdistinct')::boolean, false) AS nulls_not_distinct,
-- MATCH FULL (pg_constraint.confmatchtype = 'f'); only foreign keys can set it.
(c.confmatchtype = 'f') AS match_full,
-- ON DELETE SET NULL/SET DEFAULT (column list), PG15+ (pg_constraint.confdelsetcols).
-- Rendered as a JSON array of column names so it survives the per-column row fan-out
-- and stays a single scalar on PG14 (where the attribute does not exist). Issue #589.
Expand Down
8 changes: 8 additions & 0 deletions ir/queries/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions testdata/diff/create_table/add_fk/diff.sql
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ ADD CONSTRAINT products_category_code_fkey FOREIGN KEY (category_code) REFERENCE
ALTER TABLE projects
ADD CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id);

ALTER TABLE returns DROP CONSTRAINT returns_region_id_warehouse_id_fkey;

ALTER TABLE returns
ADD CONSTRAINT returns_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES warehouses (region_id, warehouse_id) MATCH FULL;

ALTER TABLE shipments
ADD CONSTRAINT shipments_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES warehouses (region_id, warehouse_id) MATCH FULL;

ALTER TABLE tasks
ADD CONSTRAINT tasks_org_id_owner_member_id_fkey FOREIGN KEY (org_id, owner_member_id) REFERENCES members (org_id, id) ON DELETE SET DEFAULT (owner_member_id);

Expand Down
25 changes: 25 additions & 0 deletions testdata/diff/create_table/add_fk/new.sql
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,31 @@ CREATE TABLE public.notes (
CONSTRAINT notes_org_id_author_member_id_fkey FOREIGN KEY (org_id, author_member_id) REFERENCES public.members(org_id, id) ON DELETE SET NULL (author_member_id)
);

-- FK with MATCH FULL case: added on a new constraint
CREATE TABLE public.warehouses (
region_id integer NOT NULL,
warehouse_id integer NOT NULL,
CONSTRAINT warehouses_pkey PRIMARY KEY (region_id, warehouse_id)
);

CREATE TABLE public.shipments (
id integer NOT NULL,
region_id integer,
warehouse_id integer,
CONSTRAINT shipments_pkey PRIMARY KEY (id),
CONSTRAINT shipments_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES public.warehouses(region_id, warehouse_id) MATCH FULL
);

-- Existing FK that is simple: the desired state adds MATCH FULL, so the
-- constraint must be recreated (the match type must be compared).
CREATE TABLE public.returns (
id integer NOT NULL,
region_id integer,
warehouse_id integer,
CONSTRAINT returns_pkey PRIMARY KEY (id),
CONSTRAINT returns_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES public.warehouses(region_id, warehouse_id) MATCH FULL
);

-- Temporal FK case (PG18+)
CREATE TABLE public.price_history (
product_id integer NOT NULL,
Expand Down
24 changes: 24 additions & 0 deletions testdata/diff/create_table/add_fk/old.sql
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,30 @@ CREATE TABLE public.notes (
CONSTRAINT notes_org_id_author_member_id_fkey FOREIGN KEY (org_id, author_member_id) REFERENCES public.members(org_id, id) ON DELETE SET NULL
);

-- FK with MATCH FULL case: added on a new constraint
CREATE TABLE public.warehouses (
region_id integer NOT NULL,
warehouse_id integer NOT NULL,
CONSTRAINT warehouses_pkey PRIMARY KEY (region_id, warehouse_id)
);

CREATE TABLE public.shipments (
id integer NOT NULL,
region_id integer,
warehouse_id integer,
CONSTRAINT shipments_pkey PRIMARY KEY (id)
);

-- Existing FK that is simple: the desired state adds MATCH FULL, so the
-- constraint must be recreated (the match type must be compared).
CREATE TABLE public.returns (
id integer NOT NULL,
region_id integer,
warehouse_id integer,
CONSTRAINT returns_pkey PRIMARY KEY (id),
CONSTRAINT returns_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES public.warehouses(region_id, warehouse_id)
);

-- Temporal FK case (PG18+)
CREATE TABLE public.price_history (
product_id integer NOT NULL,
Expand Down
48 changes: 47 additions & 1 deletion testdata/diff/create_table/add_fk/plan.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"pgschema_version": "1.13.3",
"created_at": "1970-01-01T00:00:00Z",
"source_fingerprint": {
"hash": "d362a1d307cacbda3523c26227836ff883ea2839ece7b16eec4eb53c5d6959d6"
"hash": "24701480f5a3206f0530e8cce038066c4b550ffcd8c097e683d15e12b2441726"
},
"groups": [
{
Expand Down Expand Up @@ -232,6 +232,52 @@
}
]
},
{
"steps": [
{
"sql": "ALTER TABLE returns DROP CONSTRAINT returns_region_id_warehouse_id_fkey;",
"type": "table.constraint",
"operation": "drop",
"path": "public.returns.returns_region_id_warehouse_id_fkey"
},
{
"sql": "ALTER TABLE returns\nADD CONSTRAINT returns_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES warehouses (region_id, warehouse_id) MATCH FULL NOT VALID;",
"type": "table.constraint",
"operation": "create",
"path": "public.returns.returns_region_id_warehouse_id_fkey"
}
]
},
{
"steps": [
{
"sql": "ALTER TABLE returns VALIDATE CONSTRAINT returns_region_id_warehouse_id_fkey;",
"type": "table.constraint",
"operation": "create",
"path": "public.returns.returns_region_id_warehouse_id_fkey"
}
]
},
{
"steps": [
{
"sql": "ALTER TABLE shipments\nADD CONSTRAINT shipments_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES warehouses (region_id, warehouse_id) MATCH FULL NOT VALID;",
"type": "table.constraint",
"operation": "create",
"path": "public.shipments.shipments_region_id_warehouse_id_fkey"
}
]
},
{
"steps": [
{
"sql": "ALTER TABLE shipments VALIDATE CONSTRAINT shipments_region_id_warehouse_id_fkey;",
"type": "table.constraint",
"operation": "create",
"path": "public.shipments.shipments_region_id_warehouse_id_fkey"
}
]
},
{
"steps": [
{
Expand Down
12 changes: 12 additions & 0 deletions testdata/diff/create_table/add_fk/plan.sql
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ ADD CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) RE

ALTER TABLE projects VALIDATE CONSTRAINT projects_tenant_id_org_id_fkey;

ALTER TABLE returns DROP CONSTRAINT returns_region_id_warehouse_id_fkey;

ALTER TABLE returns
ADD CONSTRAINT returns_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES warehouses (region_id, warehouse_id) MATCH FULL NOT VALID;

ALTER TABLE returns VALIDATE CONSTRAINT returns_region_id_warehouse_id_fkey;

ALTER TABLE shipments
ADD CONSTRAINT shipments_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES warehouses (region_id, warehouse_id) MATCH FULL NOT VALID;

ALTER TABLE shipments VALIDATE CONSTRAINT shipments_region_id_warehouse_id_fkey;

ALTER TABLE tasks
ADD CONSTRAINT tasks_org_id_owner_member_id_fkey FOREIGN KEY (org_id, owner_member_id) REFERENCES members (org_id, id) ON DELETE SET DEFAULT (owner_member_id) NOT VALID;

Expand Down
35 changes: 28 additions & 7 deletions testdata/diff/create_table/add_fk/plan.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Plan: 12 to modify.
Plan: 14 to modify.

Summary by type:
tables: 12 to modify
tables: 14 to modify

Tables:
~ audit_log
Expand All @@ -25,6 +25,11 @@ Tables:
+ products_category_code_fkey (constraint)
~ projects
+ projects_tenant_id_org_id_fkey (constraint)
~ returns
- returns_region_id_warehouse_id_fkey (constraint)
+ returns_region_id_warehouse_id_fkey (constraint)
~ shipments
+ shipments_region_id_warehouse_id_fkey (constraint)
~ tasks
+ tasks_org_id_owner_member_id_fkey (constraint)
~ teams
Expand Down Expand Up @@ -115,22 +120,38 @@ ADD CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) RE
ALTER TABLE projects VALIDATE CONSTRAINT projects_tenant_id_org_id_fkey;

-- Transaction Group #23
ALTER TABLE returns DROP CONSTRAINT returns_region_id_warehouse_id_fkey;

ALTER TABLE returns
ADD CONSTRAINT returns_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES warehouses (region_id, warehouse_id) MATCH FULL NOT VALID;

-- Transaction Group #24
ALTER TABLE returns VALIDATE CONSTRAINT returns_region_id_warehouse_id_fkey;

-- Transaction Group #25
ALTER TABLE shipments
ADD CONSTRAINT shipments_region_id_warehouse_id_fkey FOREIGN KEY (region_id, warehouse_id) REFERENCES warehouses (region_id, warehouse_id) MATCH FULL NOT VALID;

-- Transaction Group #26
ALTER TABLE shipments VALIDATE CONSTRAINT shipments_region_id_warehouse_id_fkey;

-- Transaction Group #27
ALTER TABLE tasks
ADD CONSTRAINT tasks_org_id_owner_member_id_fkey FOREIGN KEY (org_id, owner_member_id) REFERENCES members (org_id, id) ON DELETE SET DEFAULT (owner_member_id) NOT VALID;

-- Transaction Group #24
-- Transaction Group #28
ALTER TABLE tasks VALIDATE CONSTRAINT tasks_org_id_owner_member_id_fkey;

-- Transaction Group #25
-- Transaction Group #29
ALTER TABLE teams
ADD CONSTRAINT teams_manager_id_fkey FOREIGN KEY (manager_id) REFERENCES managers (id) ON DELETE SET NULL NOT VALID;

-- Transaction Group #26
-- Transaction Group #30
ALTER TABLE teams VALIDATE CONSTRAINT teams_manager_id_fkey;

-- Transaction Group #27
-- Transaction Group #31
ALTER TABLE user_profiles
ADD CONSTRAINT user_profiles_user_id_fkey FOREIGN KEY (user_id) REFERENCES users (id) DEFERRABLE INITIALLY DEFERRED NOT VALID;

-- Transaction Group #28
-- Transaction Group #32
ALTER TABLE user_profiles VALIDATE CONSTRAINT user_profiles_user_id_fkey;
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ CREATE TABLE IF NOT EXISTS projects (
description text,
CONSTRAINT projects_pkey PRIMARY KEY (tenant_id, org_id, project_id),
CONSTRAINT projects_project_name_tenant_id_project_id_key UNIQUE (project_name, tenant_id, project_id),
CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id)
CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id) MATCH FULL
);
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ CREATE TABLE public.projects (
project_code text NOT NULL,
description text,
PRIMARY KEY (tenant_id, org_id, project_id),
FOREIGN KEY (tenant_id, org_id) REFERENCES public.organizations(tenant_id, org_id),
FOREIGN KEY (tenant_id, org_id) REFERENCES public.organizations(tenant_id, org_id) MATCH FULL,
UNIQUE (project_name, tenant_id, project_id)
);
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"path": "public.organizations"
},
{
"sql": "CREATE TABLE IF NOT EXISTS projects (\n tenant_id integer,\n org_id integer,\n project_id integer,\n project_name text NOT NULL,\n project_code text NOT NULL,\n description text,\n CONSTRAINT projects_pkey PRIMARY KEY (tenant_id, org_id, project_id),\n CONSTRAINT projects_project_name_tenant_id_project_id_key UNIQUE (project_name, tenant_id, project_id),\n CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id)\n);",
"sql": "CREATE TABLE IF NOT EXISTS projects (\n tenant_id integer,\n org_id integer,\n project_id integer,\n project_name text NOT NULL,\n project_code text NOT NULL,\n description text,\n CONSTRAINT projects_pkey PRIMARY KEY (tenant_id, org_id, project_id),\n CONSTRAINT projects_project_name_tenant_id_project_id_key UNIQUE (project_name, tenant_id, project_id),\n CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id) MATCH FULL\n);",
"type": "table",
"operation": "create",
"path": "public.projects"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ CREATE TABLE IF NOT EXISTS projects (
description text,
CONSTRAINT projects_pkey PRIMARY KEY (tenant_id, org_id, project_id),
CONSTRAINT projects_project_name_tenant_id_project_id_key UNIQUE (project_name, tenant_id, project_id),
CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id)
CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id) MATCH FULL
);
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ CREATE TABLE IF NOT EXISTS projects (
description text,
CONSTRAINT projects_pkey PRIMARY KEY (tenant_id, org_id, project_id),
CONSTRAINT projects_project_name_tenant_id_project_id_key UNIQUE (project_name, tenant_id, project_id),
CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id)
CONSTRAINT projects_tenant_id_org_id_fkey FOREIGN KEY (tenant_id, org_id) REFERENCES organizations (tenant_id, org_id) MATCH FULL
);
Loading