From ade173c9534d01cfe3df27c2704762f1fa1f3fd0 Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Thu, 19 Feb 2026 22:49:47 -0800 Subject: [PATCH] fix: defer functions with view dependencies to after view creation (#300) Functions that reference views in their return type (e.g., RETURNS SETOF view_name) or parameter types were created before the referenced views, causing "type does not exist" errors during apply. Split function creation into two phases: functions without view dependencies are created before views (existing behavior), and functions with view dependencies are created after views. Also fix return type normalization to strip same-schema qualifiers from SETOF types for consistent comparison across different search_path contexts. Co-Authored-By: Claude Opus 4.6 --- internal/diff/diff.go | 133 ++++++++++++++++-- ir/normalize.go | 50 +++++++ .../diff.sql | 25 ++++ .../new.sql | 25 ++++ .../old.sql | 1 + .../plan.json | 38 +++++ .../plan.sql | 25 ++++ .../plan.txt | 45 ++++++ .../issue_300_view_depends_on_view/diff.sql | 36 +++++ .../issue_300_view_depends_on_view/new.sql | 33 +++++ .../issue_300_view_depends_on_view/old.sql | 1 + .../issue_300_view_depends_on_view/plan.json | 44 ++++++ .../issue_300_view_depends_on_view/plan.sql | 36 +++++ .../issue_300_view_depends_on_view/plan.txt | 54 +++++++ 14 files changed, 532 insertions(+), 14 deletions(-) create mode 100644 testdata/diff/dependency/issue_300_function_table_composite_type/diff.sql create mode 100644 testdata/diff/dependency/issue_300_function_table_composite_type/new.sql create mode 100644 testdata/diff/dependency/issue_300_function_table_composite_type/old.sql create mode 100644 testdata/diff/dependency/issue_300_function_table_composite_type/plan.json create mode 100644 testdata/diff/dependency/issue_300_function_table_composite_type/plan.sql create mode 100644 testdata/diff/dependency/issue_300_function_table_composite_type/plan.txt create mode 100644 testdata/diff/dependency/issue_300_view_depends_on_view/diff.sql create mode 100644 testdata/diff/dependency/issue_300_view_depends_on_view/new.sql create mode 100644 testdata/diff/dependency/issue_300_view_depends_on_view/old.sql create mode 100644 testdata/diff/dependency/issue_300_view_depends_on_view/plan.json create mode 100644 testdata/diff/dependency/issue_300_view_depends_on_view/plan.sql create mode 100644 testdata/diff/dependency/issue_300_view_depends_on_view/plan.txt diff --git a/internal/diff/diff.go b/internal/diff/diff.go index 3e25077e..aad14c69 100644 --- a/internal/diff/diff.go +++ b/internal/diff/diff.go @@ -1541,8 +1541,26 @@ func (d *ddlDiff) generateCreateSQL(targetSchema string, collector *diffCollecto // Create tables WITHOUT function/domain dependencies first (functions may reference these) deferredPolicies1, deferredConstraints1 := generateCreateTablesSQL(tablesWithoutDeps, targetSchema, collector, existingTables, shouldDeferPolicy) - // Create functions (functions may depend on tables created above) - generateCreateFunctionsSQL(d.addedFunctions, targetSchema, collector) + // Build view lookup - needed for detecting functions that depend on views + newViewLookup := buildViewLookup(d.addedViews) + + // Separate functions into those with/without view dependencies + // Functions that reference views in their return type or parameters must be created after views + functionsWithoutViewDeps := d.addedFunctions + var functionsWithViewDeps []*ir.Function + if len(newViewLookup) > 0 { + functionsWithoutViewDeps = nil + for _, fn := range d.addedFunctions { + if functionReferencesNewView(fn, newViewLookup) { + functionsWithViewDeps = append(functionsWithViewDeps, fn) + } else { + functionsWithoutViewDeps = append(functionsWithoutViewDeps, fn) + } + } + } + + // Create functions WITHOUT view dependencies (functions may depend on tables created above) + generateCreateFunctionsSQL(functionsWithoutViewDeps, targetSchema, collector) // Create domains WITH function dependencies (now that functions exist) // These domains have CHECK constraints that reference functions @@ -1572,6 +1590,10 @@ func (d *ddlDiff) generateCreateSQL(targetSchema string, collector *diffCollecto // Create views generateCreateViewsSQL(d.addedViews, targetSchema, collector) + // Create functions WITH view dependencies (now that views exist) + // These functions reference views in their return type or parameter types (issue #300) + generateCreateFunctionsSQL(functionsWithViewDeps, targetSchema, collector) + // Revoke default grants on new tables that the user explicitly didn't include // This must happen AFTER tables are created but BEFORE explicit grants // See https://github.com/pgplex/pgschema/issues/253 @@ -1828,30 +1850,113 @@ func columnExistsInTables(tables map[string]*ir.Table, schema, tableName, column return false } -// buildFunctionLookup returns case-insensitive lookup keys for newly added functions. -// Keys include both unqualified (function name only) and schema-qualified identifiers. -func buildFunctionLookup(functions []*ir.Function) map[string]struct{} { - if len(functions) == 0 { +// buildSchemaNameLookup builds a case-insensitive lookup map from schema/name pairs. +// Keys include both unqualified (name only) and schema-qualified identifiers. +func buildSchemaNameLookup(names []struct{ schema, name string }) map[string]struct{} { + if len(names) == 0 { return nil } - lookup := make(map[string]struct{}, len(functions)*2) - for _, fn := range functions { - if fn == nil || fn.Name == "" { + lookup := make(map[string]struct{}, len(names)*2) + for _, n := range names { + name := strings.ToLower(n.name) + if name == "" { continue } - - name := strings.ToLower(fn.Name) lookup[name] = struct{}{} - if fn.Schema != "" { - qualified := fmt.Sprintf("%s.%s", strings.ToLower(fn.Schema), name) - lookup[qualified] = struct{}{} + if n.schema != "" { + lookup[strings.ToLower(n.schema)+"."+name] = struct{}{} } } return lookup } +// buildFunctionLookup returns case-insensitive lookup keys for newly added functions. +func buildFunctionLookup(functions []*ir.Function) map[string]struct{} { + names := make([]struct{ schema, name string }, len(functions)) + for i, fn := range functions { + names[i] = struct{ schema, name string }{fn.Schema, fn.Name} + } + return buildSchemaNameLookup(names) +} + +// buildViewLookup returns case-insensitive lookup keys for newly added views. +func buildViewLookup(views []*ir.View) map[string]struct{} { + names := make([]struct{ schema, name string }, len(views)) + for i, v := range views { + names[i] = struct{ schema, name string }{v.Schema, v.Name} + } + return buildSchemaNameLookup(names) +} + +// functionReferencesNewView determines if a function references any newly added views +// in its return type or parameter types. This handles cases where functions use +// view composite types (e.g., RETURNS SETOF view_name or parameter of view_name type). +func functionReferencesNewView(fn *ir.Function, newViews map[string]struct{}) bool { + if len(newViews) == 0 || fn == nil { + return false + } + + // Check return type (e.g., "SETOF public.actor", "actor", "SETOF actor") + if fn.ReturnType != "" { + typeName := extractBaseTypeName(fn.ReturnType) + if typeMatchesLookup(typeName, fn.Schema, newViews) { + return true + } + } + + // Check parameter types + for _, param := range fn.Parameters { + if param.DataType != "" { + typeName := extractBaseTypeName(param.DataType) + if typeMatchesLookup(typeName, fn.Schema, newViews) { + return true + } + } + } + + return false +} + +// extractBaseTypeName extracts the base type name from a type expression, +// stripping SETOF prefix and array notation. +func extractBaseTypeName(typeExpr string) string { + t := strings.TrimSpace(typeExpr) + // Strip SETOF prefix (case-insensitive) + if len(t) > 6 && strings.EqualFold(t[:6], "setof ") { + t = strings.TrimSpace(t[6:]) + } + // Strip array notation + for len(t) > 2 && t[len(t)-2:] == "[]" { + t = t[:len(t)-2] + } + return t +} + +// typeMatchesLookup checks if a type name matches any entry in a lookup map, +// trying both unqualified and schema-qualified forms. +func typeMatchesLookup(typeName, defaultSchema string, lookup map[string]struct{}) bool { + if typeName == "" || len(lookup) == 0 { + return false + } + + lower := strings.ToLower(typeName) + if _, ok := lookup[lower]; ok { + return true + } + + // If unqualified, try with default schema + if !strings.Contains(lower, ".") && defaultSchema != "" { + qualified := fmt.Sprintf("%s.%s", strings.ToLower(defaultSchema), lower) + if _, ok := lookup[qualified]; ok { + return true + } + } + + return false +} + var functionCallRegex = regexp.MustCompile(`(?i)([a-z_][a-z0-9_$]*(?:\.[a-z_][a-z0-9_$]*)*)\s*\(`) // tableReferencesNewFunction determines if a table references any newly added functions diff --git a/ir/normalize.go b/ir/normalize.go index 8ac9af91..909654f3 100644 --- a/ir/normalize.go +++ b/ir/normalize.go @@ -294,6 +294,10 @@ func normalizeFunction(function *Function) { function.Language = strings.ToLower(function.Language) // Normalize return type to handle PostgreSQL-specific formats function.ReturnType = normalizeFunctionReturnType(function.ReturnType) + // Strip current schema qualifier from return type for consistent comparison. + // pg_get_function_result may or may not qualify types in the current schema + // depending on search_path (e.g., "SETOF public.actor" vs "SETOF actor"). + function.ReturnType = stripSchemaFromReturnType(function.ReturnType, function.Schema) // Normalize parameter types, modes, and default values for _, param := range function.Parameters { if param != nil { @@ -468,6 +472,52 @@ func normalizeFunctionReturnType(returnType string) string { return normalizePostgreSQLType(returnType) } +// stripSchemaFromReturnType strips the current schema qualifier from a function return type. +// This handles SETOF and array types, e.g., "SETOF public.actor" → "SETOF actor" +// when the function is in the public schema. +func stripSchemaFromReturnType(returnType, schema string) string { + if returnType == "" || schema == "" { + return returnType + } + + prefix := schema + "." + + // Handle SETOF prefix + if len(returnType) > 6 && strings.EqualFold(returnType[:6], "SETOF ") { + rest := strings.TrimSpace(returnType[6:]) + stripped := stripSchemaPrefix(rest, prefix) + if stripped != rest { + return returnType[:6] + stripped + } + return returnType + } + + // Handle TABLE(...) return types - strip schema from individual column types + if strings.HasPrefix(returnType, "TABLE(") { + return returnType // TABLE types are already handled by normalizeFunctionReturnType + } + + // Direct type name + return stripSchemaPrefix(returnType, prefix) +} + +// stripSchemaPrefix removes a schema prefix from a type name, preserving array notation. +func stripSchemaPrefix(typeName, prefix string) string { + // Separate base type from array suffix (e.g., "public.mytype[]" → "public.mytype" + "[]") + base := typeName + arrayStart := strings.Index(base, "[]") + arraySuffix := "" + if arrayStart >= 0 { + arraySuffix = base[arrayStart:] + base = base[:arrayStart] + } + + if strings.HasPrefix(base, prefix) { + return base[len(prefix):] + arraySuffix + } + return typeName +} + // normalizeTrigger normalizes trigger representation func normalizeTrigger(trigger *Trigger) { if trigger == nil { diff --git a/testdata/diff/dependency/issue_300_function_table_composite_type/diff.sql b/testdata/diff/dependency/issue_300_function_table_composite_type/diff.sql new file mode 100644 index 00000000..807cef7e --- /dev/null +++ b/testdata/diff/dependency/issue_300_function_table_composite_type/diff.sql @@ -0,0 +1,25 @@ +CREATE TABLE IF NOT EXISTS activity ( + id uuid, + author_id uuid, + CONSTRAINT activity_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS contact ( + id uuid, + name text NOT NULL, + CONSTRAINT contact_pkey PRIMARY KEY (id) +); + +CREATE OR REPLACE VIEW actor AS + SELECT id, + name + FROM contact; + +CREATE OR REPLACE FUNCTION get_actor( + activity activity +) +RETURNS SETOF actor +LANGUAGE sql +STABLE +AS $$ SELECT actor.* FROM actor WHERE actor.id = activity.author_id +$$; diff --git a/testdata/diff/dependency/issue_300_function_table_composite_type/new.sql b/testdata/diff/dependency/issue_300_function_table_composite_type/new.sql new file mode 100644 index 00000000..e31e5e1a --- /dev/null +++ b/testdata/diff/dependency/issue_300_function_table_composite_type/new.sql @@ -0,0 +1,25 @@ +-- Table whose composite type is used as a function parameter +CREATE TABLE public.activity ( + id uuid PRIMARY KEY, + author_id uuid +); + +-- Table used by the view +CREATE TABLE public.contact ( + id uuid PRIMARY KEY, + name text NOT NULL +); + +-- View referenced in the function's return type +CREATE OR REPLACE VIEW public.actor AS +SELECT id, name FROM public.contact; + +-- Function that uses the table composite type as a parameter +-- and references a view in its return type. +-- This function must be created AFTER: +-- 1. The activity table (for the composite type parameter) +-- 2. The actor view (for RETURNS SETOF actor) +CREATE OR REPLACE FUNCTION public.get_actor(activity activity) + RETURNS SETOF actor ROWS 1 + LANGUAGE sql STABLE + AS $$ SELECT actor.* FROM actor WHERE actor.id = activity.author_id $$; diff --git a/testdata/diff/dependency/issue_300_function_table_composite_type/old.sql b/testdata/diff/dependency/issue_300_function_table_composite_type/old.sql new file mode 100644 index 00000000..16479dab --- /dev/null +++ b/testdata/diff/dependency/issue_300_function_table_composite_type/old.sql @@ -0,0 +1 @@ +-- Empty schema (no objects) diff --git a/testdata/diff/dependency/issue_300_function_table_composite_type/plan.json b/testdata/diff/dependency/issue_300_function_table_composite_type/plan.json new file mode 100644 index 00000000..d7c550db --- /dev/null +++ b/testdata/diff/dependency/issue_300_function_table_composite_type/plan.json @@ -0,0 +1,38 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.7.1", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "965b1131737c955e24c7f827c55bd78e4cb49a75adfd04229e0ba297376f5085" + }, + "groups": [ + { + "steps": [ + { + "sql": "CREATE TABLE IF NOT EXISTS activity (\n id uuid,\n author_id uuid,\n CONSTRAINT activity_pkey PRIMARY KEY (id)\n);", + "type": "table", + "operation": "create", + "path": "public.activity" + }, + { + "sql": "CREATE TABLE IF NOT EXISTS contact (\n id uuid,\n name text NOT NULL,\n CONSTRAINT contact_pkey PRIMARY KEY (id)\n);", + "type": "table", + "operation": "create", + "path": "public.contact" + }, + { + "sql": "CREATE OR REPLACE VIEW actor AS\n SELECT id,\n name\n FROM contact;", + "type": "view", + "operation": "create", + "path": "public.actor" + }, + { + "sql": "CREATE OR REPLACE FUNCTION get_actor(\n activity activity\n)\nRETURNS SETOF actor\nLANGUAGE sql\nSTABLE\nAS $$ SELECT actor.* FROM actor WHERE actor.id = activity.author_id\n$$;", + "type": "function", + "operation": "create", + "path": "public.get_actor" + } + ] + } + ] +} diff --git a/testdata/diff/dependency/issue_300_function_table_composite_type/plan.sql b/testdata/diff/dependency/issue_300_function_table_composite_type/plan.sql new file mode 100644 index 00000000..807cef7e --- /dev/null +++ b/testdata/diff/dependency/issue_300_function_table_composite_type/plan.sql @@ -0,0 +1,25 @@ +CREATE TABLE IF NOT EXISTS activity ( + id uuid, + author_id uuid, + CONSTRAINT activity_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS contact ( + id uuid, + name text NOT NULL, + CONSTRAINT contact_pkey PRIMARY KEY (id) +); + +CREATE OR REPLACE VIEW actor AS + SELECT id, + name + FROM contact; + +CREATE OR REPLACE FUNCTION get_actor( + activity activity +) +RETURNS SETOF actor +LANGUAGE sql +STABLE +AS $$ SELECT actor.* FROM actor WHERE actor.id = activity.author_id +$$; diff --git a/testdata/diff/dependency/issue_300_function_table_composite_type/plan.txt b/testdata/diff/dependency/issue_300_function_table_composite_type/plan.txt new file mode 100644 index 00000000..82e42628 --- /dev/null +++ b/testdata/diff/dependency/issue_300_function_table_composite_type/plan.txt @@ -0,0 +1,45 @@ +Plan: 4 to add. + +Summary by type: + functions: 1 to add + tables: 2 to add + views: 1 to add + +Functions: + + get_actor + +Tables: + + activity + + contact + +Views: + + actor + +DDL to be executed: +-------------------------------------------------- + +CREATE TABLE IF NOT EXISTS activity ( + id uuid, + author_id uuid, + CONSTRAINT activity_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS contact ( + id uuid, + name text NOT NULL, + CONSTRAINT contact_pkey PRIMARY KEY (id) +); + +CREATE OR REPLACE VIEW actor AS + SELECT id, + name + FROM contact; + +CREATE OR REPLACE FUNCTION get_actor( + activity activity +) +RETURNS SETOF actor +LANGUAGE sql +STABLE +AS $$ SELECT actor.* FROM actor WHERE actor.id = activity.author_id +$$; diff --git a/testdata/diff/dependency/issue_300_view_depends_on_view/diff.sql b/testdata/diff/dependency/issue_300_view_depends_on_view/diff.sql new file mode 100644 index 00000000..f1a625ce --- /dev/null +++ b/testdata/diff/dependency/issue_300_view_depends_on_view/diff.sql @@ -0,0 +1,36 @@ +CREATE TABLE IF NOT EXISTS activity_x ( + id integer, + title text NOT NULL, + priority_user_id integer, + CONSTRAINT activity_x_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS priority ( + id integer, + name text NOT NULL, + level integer NOT NULL, + CONSTRAINT priority_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS priority_user ( + id integer, + user_id integer NOT NULL, + priority_id integer, + CONSTRAINT priority_user_pkey PRIMARY KEY (id), + CONSTRAINT priority_user_priority_id_fkey FOREIGN KEY (priority_id) REFERENCES priority (id) +); + +CREATE OR REPLACE VIEW priority_expanded AS + SELECT pu.id, + pu.user_id, + p.name AS priority_name, + p.level + FROM priority_user pu + JOIN priority p ON p.id = pu.priority_id; + +CREATE OR REPLACE VIEW activity AS + SELECT a.id, + a.title, + upe.priority_name + FROM activity_x a + JOIN priority_expanded upe ON upe.id = a.priority_user_id; diff --git a/testdata/diff/dependency/issue_300_view_depends_on_view/new.sql b/testdata/diff/dependency/issue_300_view_depends_on_view/new.sql new file mode 100644 index 00000000..2c035d10 --- /dev/null +++ b/testdata/diff/dependency/issue_300_view_depends_on_view/new.sql @@ -0,0 +1,33 @@ +-- Base table +CREATE TABLE public.priority ( + id integer PRIMARY KEY, + name text NOT NULL, + level integer NOT NULL +); + +-- Base table +CREATE TABLE public.priority_user ( + id integer PRIMARY KEY, + user_id integer NOT NULL, + priority_id integer REFERENCES public.priority(id) +); + +-- View that is depended upon (must be created first) +CREATE OR REPLACE VIEW public.priority_expanded AS +SELECT pu.id, pu.user_id, p.name AS priority_name, p.level +FROM public.priority_user pu +JOIN public.priority p ON p.id = pu.priority_id; + +-- Base table for activity +CREATE TABLE public.activity_x ( + id integer PRIMARY KEY, + title text NOT NULL, + priority_user_id integer +); + +-- View that depends on priority_expanded (must be created second) +-- Alphabetically "activity" comes before "priority_expanded" +CREATE OR REPLACE VIEW public.activity AS +SELECT a.id, a.title, upe.priority_name +FROM public.activity_x a +JOIN public.priority_expanded upe ON upe.id = a.priority_user_id; diff --git a/testdata/diff/dependency/issue_300_view_depends_on_view/old.sql b/testdata/diff/dependency/issue_300_view_depends_on_view/old.sql new file mode 100644 index 00000000..16479dab --- /dev/null +++ b/testdata/diff/dependency/issue_300_view_depends_on_view/old.sql @@ -0,0 +1 @@ +-- Empty schema (no objects) diff --git a/testdata/diff/dependency/issue_300_view_depends_on_view/plan.json b/testdata/diff/dependency/issue_300_view_depends_on_view/plan.json new file mode 100644 index 00000000..af8ce879 --- /dev/null +++ b/testdata/diff/dependency/issue_300_view_depends_on_view/plan.json @@ -0,0 +1,44 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.7.1", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "965b1131737c955e24c7f827c55bd78e4cb49a75adfd04229e0ba297376f5085" + }, + "groups": [ + { + "steps": [ + { + "sql": "CREATE TABLE IF NOT EXISTS activity_x (\n id integer,\n title text NOT NULL,\n priority_user_id integer,\n CONSTRAINT activity_x_pkey PRIMARY KEY (id)\n);", + "type": "table", + "operation": "create", + "path": "public.activity_x" + }, + { + "sql": "CREATE TABLE IF NOT EXISTS priority (\n id integer,\n name text NOT NULL,\n level integer NOT NULL,\n CONSTRAINT priority_pkey PRIMARY KEY (id)\n);", + "type": "table", + "operation": "create", + "path": "public.priority" + }, + { + "sql": "CREATE TABLE IF NOT EXISTS priority_user (\n id integer,\n user_id integer NOT NULL,\n priority_id integer,\n CONSTRAINT priority_user_pkey PRIMARY KEY (id),\n CONSTRAINT priority_user_priority_id_fkey FOREIGN KEY (priority_id) REFERENCES priority (id)\n);", + "type": "table", + "operation": "create", + "path": "public.priority_user" + }, + { + "sql": "CREATE OR REPLACE VIEW priority_expanded AS\n SELECT pu.id,\n pu.user_id,\n p.name AS priority_name,\n p.level\n FROM priority_user pu\n JOIN priority p ON p.id = pu.priority_id;", + "type": "view", + "operation": "create", + "path": "public.priority_expanded" + }, + { + "sql": "CREATE OR REPLACE VIEW activity AS\n SELECT a.id,\n a.title,\n upe.priority_name\n FROM activity_x a\n JOIN priority_expanded upe ON upe.id = a.priority_user_id;", + "type": "view", + "operation": "create", + "path": "public.activity" + } + ] + } + ] +} diff --git a/testdata/diff/dependency/issue_300_view_depends_on_view/plan.sql b/testdata/diff/dependency/issue_300_view_depends_on_view/plan.sql new file mode 100644 index 00000000..f1a625ce --- /dev/null +++ b/testdata/diff/dependency/issue_300_view_depends_on_view/plan.sql @@ -0,0 +1,36 @@ +CREATE TABLE IF NOT EXISTS activity_x ( + id integer, + title text NOT NULL, + priority_user_id integer, + CONSTRAINT activity_x_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS priority ( + id integer, + name text NOT NULL, + level integer NOT NULL, + CONSTRAINT priority_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS priority_user ( + id integer, + user_id integer NOT NULL, + priority_id integer, + CONSTRAINT priority_user_pkey PRIMARY KEY (id), + CONSTRAINT priority_user_priority_id_fkey FOREIGN KEY (priority_id) REFERENCES priority (id) +); + +CREATE OR REPLACE VIEW priority_expanded AS + SELECT pu.id, + pu.user_id, + p.name AS priority_name, + p.level + FROM priority_user pu + JOIN priority p ON p.id = pu.priority_id; + +CREATE OR REPLACE VIEW activity AS + SELECT a.id, + a.title, + upe.priority_name + FROM activity_x a + JOIN priority_expanded upe ON upe.id = a.priority_user_id; diff --git a/testdata/diff/dependency/issue_300_view_depends_on_view/plan.txt b/testdata/diff/dependency/issue_300_view_depends_on_view/plan.txt new file mode 100644 index 00000000..3f4566a5 --- /dev/null +++ b/testdata/diff/dependency/issue_300_view_depends_on_view/plan.txt @@ -0,0 +1,54 @@ +Plan: 5 to add. + +Summary by type: + tables: 3 to add + views: 2 to add + +Tables: + + activity_x + + priority + + priority_user + +Views: + + activity + + priority_expanded + +DDL to be executed: +-------------------------------------------------- + +CREATE TABLE IF NOT EXISTS activity_x ( + id integer, + title text NOT NULL, + priority_user_id integer, + CONSTRAINT activity_x_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS priority ( + id integer, + name text NOT NULL, + level integer NOT NULL, + CONSTRAINT priority_pkey PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS priority_user ( + id integer, + user_id integer NOT NULL, + priority_id integer, + CONSTRAINT priority_user_pkey PRIMARY KEY (id), + CONSTRAINT priority_user_priority_id_fkey FOREIGN KEY (priority_id) REFERENCES priority (id) +); + +CREATE OR REPLACE VIEW priority_expanded AS + SELECT pu.id, + pu.user_id, + p.name AS priority_name, + p.level + FROM priority_user pu + JOIN priority p ON p.id = pu.priority_id; + +CREATE OR REPLACE VIEW activity AS + SELECT a.id, + a.title, + upe.priority_name + FROM activity_x a + JOIN priority_expanded upe ON upe.id = a.priority_user_id;