diff --git a/internal/diff/diff.go b/internal/diff/diff.go index b6cea7cd..a1dea859 100644 --- a/internal/diff/diff.go +++ b/internal/diff/diff.go @@ -1064,9 +1064,6 @@ func (d *ddlDiff) generateCreateSQL(targetSchema string, collector *diffCollecto // Create tables WITHOUT function dependencies first (functions may reference these) deferredPolicies1, deferredConstraints1 := generateCreateTablesSQL(tablesWithoutFunctionDeps, targetSchema, collector, existingTables, shouldDeferPolicy) - // Add deferred foreign key constraints from first batch - generateDeferredConstraintsSQL(deferredConstraints1, targetSchema, collector) - // Create functions (functions may depend on tables created above) generateCreateFunctionsSQL(d.addedFunctions, targetSchema, collector) @@ -1076,8 +1073,10 @@ func (d *ddlDiff) generateCreateSQL(targetSchema string, collector *diffCollecto // Create tables WITH function dependencies (now that functions exist) deferredPolicies2, deferredConstraints2 := generateCreateTablesSQL(tablesWithFunctionDeps, targetSchema, collector, existingTables, shouldDeferPolicy) - // Add deferred foreign key constraints from second batch - generateDeferredConstraintsSQL(deferredConstraints2, targetSchema, collector) + // Add deferred foreign key constraints from BOTH batches AFTER all tables are created + // This ensures FK references to tables in the second batch (function-dependent tables) work correctly + allDeferredConstraints := append(deferredConstraints1, deferredConstraints2...) + generateDeferredConstraintsSQL(allDeferredConstraints, targetSchema, collector) // Merge deferred policies from both batches allDeferredPolicies := append(deferredPolicies1, deferredPolicies2...) diff --git a/testdata/diff/dependency/table_fk_to_generated_column/diff.sql b/testdata/diff/dependency/table_fk_to_generated_column/diff.sql new file mode 100644 index 00000000..ab07d9d7 --- /dev/null +++ b/testdata/diff/dependency/table_fk_to_generated_column/diff.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS articlesource ( + id integer, + article_id integer NOT NULL, + source_url text, + CONSTRAINT articlesource_pkey PRIMARY KEY (id) +); + +CREATE OR REPLACE FUNCTION calc_priority() +RETURNS integer +LANGUAGE sql +IMMUTABLE +AS $$SELECT 1 +$$; + +CREATE TABLE IF NOT EXISTS article ( + id integer, + title text NOT NULL, + priority integer GENERATED ALWAYS AS (public.calc_priority()) STORED, + CONSTRAINT article_pkey PRIMARY KEY (id) +); + +ALTER TABLE articlesource +ADD CONSTRAINT articlesource_article_id_fkey FOREIGN KEY (article_id) REFERENCES article (id); diff --git a/testdata/diff/dependency/table_fk_to_generated_column/new.sql b/testdata/diff/dependency/table_fk_to_generated_column/new.sql new file mode 100644 index 00000000..d2a1c151 --- /dev/null +++ b/testdata/diff/dependency/table_fk_to_generated_column/new.sql @@ -0,0 +1,20 @@ +-- Function used in generated column +CREATE FUNCTION public.calc_priority() RETURNS integer + LANGUAGE sql + IMMUTABLE + AS $$SELECT 1$$; + +-- Table with generated column using the function +CREATE TABLE public.article ( + id integer PRIMARY KEY, + title text NOT NULL, + priority integer GENERATED ALWAYS AS (calc_priority()) STORED +); + +-- Table with FK referencing article +CREATE TABLE public.articlesource ( + id integer PRIMARY KEY, + article_id integer NOT NULL, + source_url text, + CONSTRAINT articlesource_article_id_fkey FOREIGN KEY (article_id) REFERENCES public.article(id) +); diff --git a/testdata/diff/dependency/table_fk_to_generated_column/old.sql b/testdata/diff/dependency/table_fk_to_generated_column/old.sql new file mode 100644 index 00000000..16479dab --- /dev/null +++ b/testdata/diff/dependency/table_fk_to_generated_column/old.sql @@ -0,0 +1 @@ +-- Empty schema (no objects) diff --git a/testdata/diff/dependency/table_fk_to_generated_column/plan.json b/testdata/diff/dependency/table_fk_to_generated_column/plan.json new file mode 100644 index 00000000..84612f1f --- /dev/null +++ b/testdata/diff/dependency/table_fk_to_generated_column/plan.json @@ -0,0 +1,38 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.5.1", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "965b1131737c955e24c7f827c55bd78e4cb49a75adfd04229e0ba297376f5085" + }, + "groups": [ + { + "steps": [ + { + "sql": "CREATE TABLE IF NOT EXISTS articlesource (\n id integer,\n article_id integer NOT NULL,\n source_url text,\n CONSTRAINT articlesource_pkey PRIMARY KEY (id)\n);", + "type": "table", + "operation": "create", + "path": "public.articlesource" + }, + { + "sql": "CREATE OR REPLACE FUNCTION calc_priority()\nRETURNS integer\nLANGUAGE sql\nIMMUTABLE\nAS $$SELECT 1\n$$;", + "type": "function", + "operation": "create", + "path": "public.calc_priority" + }, + { + "sql": "CREATE TABLE IF NOT EXISTS article (\n id integer,\n title text NOT NULL,\n priority integer GENERATED ALWAYS AS (public.calc_priority()) STORED,\n CONSTRAINT article_pkey PRIMARY KEY (id)\n);", + "type": "table", + "operation": "create", + "path": "public.article" + }, + { + "sql": "ALTER TABLE articlesource\nADD CONSTRAINT articlesource_article_id_fkey FOREIGN KEY (article_id) REFERENCES article (id);", + "type": "table.constraint", + "operation": "create", + "path": "public.articlesource.articlesource_article_id_fkey" + } + ] + } + ] +} diff --git a/testdata/diff/dependency/table_fk_to_generated_column/plan.sql b/testdata/diff/dependency/table_fk_to_generated_column/plan.sql new file mode 100644 index 00000000..ab07d9d7 --- /dev/null +++ b/testdata/diff/dependency/table_fk_to_generated_column/plan.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS articlesource ( + id integer, + article_id integer NOT NULL, + source_url text, + CONSTRAINT articlesource_pkey PRIMARY KEY (id) +); + +CREATE OR REPLACE FUNCTION calc_priority() +RETURNS integer +LANGUAGE sql +IMMUTABLE +AS $$SELECT 1 +$$; + +CREATE TABLE IF NOT EXISTS article ( + id integer, + title text NOT NULL, + priority integer GENERATED ALWAYS AS (public.calc_priority()) STORED, + CONSTRAINT article_pkey PRIMARY KEY (id) +); + +ALTER TABLE articlesource +ADD CONSTRAINT articlesource_article_id_fkey FOREIGN KEY (article_id) REFERENCES article (id); diff --git a/testdata/diff/dependency/table_fk_to_generated_column/plan.txt b/testdata/diff/dependency/table_fk_to_generated_column/plan.txt new file mode 100644 index 00000000..b93f5d0d --- /dev/null +++ b/testdata/diff/dependency/table_fk_to_generated_column/plan.txt @@ -0,0 +1,40 @@ +Plan: 3 to add. + +Summary by type: + functions: 1 to add + tables: 2 to add + +Functions: + + calc_priority + +Tables: + + article + + articlesource + + articlesource_article_id_fkey (constraint) + +DDL to be executed: +-------------------------------------------------- + +CREATE TABLE IF NOT EXISTS articlesource ( + id integer, + article_id integer NOT NULL, + source_url text, + CONSTRAINT articlesource_pkey PRIMARY KEY (id) +); + +CREATE OR REPLACE FUNCTION calc_priority() +RETURNS integer +LANGUAGE sql +IMMUTABLE +AS $$SELECT 1 +$$; + +CREATE TABLE IF NOT EXISTS article ( + id integer, + title text NOT NULL, + priority integer GENERATED ALWAYS AS (public.calc_priority()) STORED, + CONSTRAINT article_pkey PRIMARY KEY (id) +); + +ALTER TABLE articlesource +ADD CONSTRAINT articlesource_article_id_fkey FOREIGN KEY (article_id) REFERENCES article (id); diff --git a/testdata/dump/tenant/pgschema.sql b/testdata/dump/tenant/pgschema.sql index 3f9283fd..3e5973bd 100644 --- a/testdata/dump/tenant/pgschema.sql +++ b/testdata/dump/tenant/pgschema.sql @@ -67,13 +67,6 @@ CREATE TABLE IF NOT EXISTS posts ( CONSTRAINT posts_pkey PRIMARY KEY (id) ); --- --- Name: posts_author_id_fkey; Type: CONSTRAINT; Schema: -; Owner: - --- - -ALTER TABLE posts -ADD CONSTRAINT posts_author_id_fkey FOREIGN KEY (author_id) REFERENCES users (id); - -- -- Name: auth_uid(); Type: FUNCTION; Schema: -; Owner: - -- @@ -198,6 +191,13 @@ CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); ALTER TABLE users ENABLE ROW LEVEL SECURITY; +-- +-- Name: posts_author_id_fkey; Type: CONSTRAINT; Schema: -; Owner: - +-- + +ALTER TABLE posts +ADD CONSTRAINT posts_author_id_fkey FOREIGN KEY (author_id) REFERENCES users (id); + -- -- Name: users_isolation; Type: POLICY; Schema: -; Owner: - --