diff --git a/cmd/dump/dump.go b/cmd/dump/dump.go index ecb13202..f497a41d 100644 --- a/cmd/dump/dump.go +++ b/cmd/dump/dump.go @@ -59,7 +59,7 @@ func init() { DumpCmd.Flags().BoolVar(&multiFile, "multi-file", false, "Output schema to multiple files organized by object type") DumpCmd.Flags().StringVar(&file, "file", "", "Output file path (required when --multi-file is used)") DumpCmd.Flags().BoolVar(&noComments, "no-comments", false, "Do not output object comment headers") - DumpCmd.Flags().BoolVar(&qualifySchema, "qualify-schema", false, "Always schema-qualify object identifiers in the dump, even for the target schema (does not affect same-schema type references, which the IR stores without a schema)") + DumpCmd.Flags().BoolVar(&qualifySchema, "qualify-schema", false, "Always schema-qualify object identifiers and type references in the dump, even for the target schema (function/procedure parameter and return types are not yet qualified)") DumpCmd.Flags().StringVar(&sslmode, "sslmode", "prefer", "SSL mode for database connection (disable, allow, prefer, require, verify-ca, verify-full) (env: PGSSLMODE)") } diff --git a/cmd/dump/qualify_schema_integration_test.go b/cmd/dump/qualify_schema_integration_test.go new file mode 100644 index 00000000..62cb279b --- /dev/null +++ b/cmd/dump/qualify_schema_integration_test.go @@ -0,0 +1,121 @@ +package dump + +// Positive integration coverage for `dump --qualify-schema` type references (#493). +// +// Unlike the builder-level tests in internal/diff (which hand the builder a +// pre-qualified IR), this test drives the whole pipeline: pgdump SQL → embedded +// database → inspector → IR → dump. It proves the inspector now *preserves* the +// schema of same-schema user-defined type references for the four "Mechanism A" +// slices (column type, domain base type, composite attribute, aggregate state +// type), so --qualify-schema can emit them fully qualified — while the default +// (smart-qualification) dump keeps them bare. + +import ( + "context" + "strings" + "testing" + + "github.com/pgplex/pgschema/testutil" +) + +const qualifySchemaSetupSQL = ` +CREATE TYPE color AS ENUM ('r', 'g', 'b'); +-- column type: same-schema enum (scalar and array) +CREATE TABLE swatch ( + id integer PRIMARY KEY, + shade color, + shades color[] +); +-- domain base type: same-schema enum (scalar and array) +CREATE DOMAIN color_domain AS color; +CREATE DOMAIN color_list AS color[]; +-- composite attribute: same-schema enum (scalar and array, plus a built-in that must stay bare) +CREATE TYPE money_amount AS ( + amount numeric, + currency color, + palette color[] +); +-- aggregate state type: same-schema composite +CREATE TYPE acc AS (n integer); +CREATE FUNCTION acc_add(acc, integer) RETURNS acc + LANGUAGE sql IMMUTABLE AS $$ SELECT ROW(($1).n + $2)::acc $$; +CREATE AGGREGATE mysum(integer) (SFUNC = acc_add, STYPE = acc); +` + +func TestDumpCommand_QualifySchemaTypeReferences(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + embeddedPG := testutil.SetupPostgres(t) + defer embeddedPG.Stop() + + conn, host, port, dbname, user, password := testutil.ConnectToPostgres(t, embeddedPG) + defer conn.Close() + + if _, err := conn.ExecContext(context.Background(), qualifySchemaSetupSQL); err != nil { + t.Fatalf("Failed to set up schema: %v", err) + } + + baseConfig := func(qualify bool) *DumpConfig { + return &DumpConfig{ + Host: host, + Port: port, + DB: dbname, + User: user, + Password: password, + Schema: "public", + MultiFile: false, + File: "", + QualifySchema: qualify, + } + } + + // --qualify-schema: same-schema type references are fully qualified. + qualified, err := ExecuteDump(baseConfig(true)) + if err != nil { + t.Fatalf("qualified dump failed: %v", err) + } + for _, want := range []string{ + "shade public.color", // column type + "shades public.color[]", // column type: same-schema array + "AS public.color;", // domain base type: scalar (CREATE DOMAIN ... AS public.color) + "AS public.color[]", // domain base type: array (CREATE DOMAIN ... AS public.color[]) + "currency public.color", // composite attribute: scalar + "palette public.color[]", // composite attribute: array + "STYPE = public.acc", // aggregate state type + } { + if !strings.Contains(qualified, want) { + t.Errorf("qualified dump missing %q\n---\n%s", want, qualified) + } + } + // Built-in types must never be schema-qualified. + if strings.Contains(qualified, "pg_catalog.") { + t.Errorf("qualified dump must not qualify built-in types with pg_catalog:\n%s", qualified) + } + if !strings.Contains(qualified, "amount numeric") { + t.Errorf("qualified dump should keep built-in composite attr type bare (amount numeric):\n%s", qualified) + } + + // Default (smart qualification): the same references stay bare. + def, err := ExecuteDump(baseConfig(false)) + if err != nil { + t.Fatalf("default dump failed: %v", err) + } + for _, want := range []string{ + "shade color", + "shades color[]", + "AS color;", // domain base type: scalar + "AS color[]", // domain base type: array + "currency color", + "palette color[]", + "STYPE = acc", + } { + if !strings.Contains(def, want) { + t.Errorf("default dump missing bare form %q\n---\n%s", want, def) + } + } + if strings.Contains(def, "public.color") || strings.Contains(def, "public.acc") { + t.Errorf("default dump must not qualify same-schema type references:\n%s", def) + } +} diff --git a/internal/diff/qualify_schema_test.go b/internal/diff/qualify_schema_test.go index a3d856bc..dc882a74 100644 --- a/internal/diff/qualify_schema_test.go +++ b/internal/diff/qualify_schema_test.go @@ -10,10 +10,13 @@ import ( // These tests cover the `dump --qualify-schema` behavior at the SQL-builder level: // when qualifySchema is true, structural entity names are schema-qualified, even for // the target schema; when false (the default), the standard "smart qualification" -// omits the target-schema prefix. Type references that arrive from the inspector -// without schema identity stay bare (the flag cannot invent a schema for them) until -// the IR preserves their schema separately (#493). The false cases double as the -// byte-identical default-output guardrail for each builder. +// omits the target-schema prefix. For type references whose schema the IR preserves +// (columns, domain base types, composite attributes, aggregate state types — #493), +// forced qualification keeps the schema and the default strips it. Function/procedure +// parameter and return types still arrive from the inspector without schema identity +// (it strips them during signature parsing), so for those the flag cannot invent a +// schema and the reference stays bare. The false cases double as the byte-identical +// default-output guardrail for each builder. func TestQualifySchema_ForeignKeyReference(t *testing.T) { fk := &ir.Constraint{ @@ -48,9 +51,11 @@ func TestQualifySchema_TableAndColumnType(t *testing.T) { Name: "account", Columns: []*ir.Column{ {Name: "id", Position: 1, DataType: "integer", IsNullable: false}, - // Same-schema user-defined type references arrive from the inspector - // without schema identity, so the IR stores them bare (#493). - {Name: "kind", Position: 2, DataType: "user_kind", IsNullable: true}, + // Post-#493 the inspector preserves the schema of same-schema + // user-defined type references, so the IR stores them qualified + // (e.g. public.user_kind). The render seam strips the target-schema + // prefix in default mode and keeps it under forced qualification. + {Name: "kind", Position: 2, DataType: "public.user_kind", IsNullable: true}, }, } empty := map[string]bool{} @@ -62,18 +67,41 @@ func TestQualifySchema_TableAndColumnType(t *testing.T) { if strings.Contains(def, "public.account") || strings.Contains(def, "public.user_kind") { t.Errorf("default should not qualify the target schema: %q", def) } + if !strings.Contains(def, "kind user_kind") { + t.Errorf("default should strip the target-schema prefix from the type ref: %q", def) + } qualified, _ := generateTableSQL(table, "public", true, empty, empty, empty, nil) if !strings.Contains(qualified, "CREATE TABLE IF NOT EXISTS public.account (") { t.Errorf("forced qualification should qualify the table name: %q", qualified) } - // #493 limitation: forced qualification cannot *add* a schema to a bare - // same-schema type reference, so the column type stays bare. - if strings.Contains(qualified, "public.user_kind") { - t.Errorf("forced qualification must not invent a schema for a bare type ref: %q", qualified) + // #493: forced qualification keeps the schema the IR now carries on the + // same-schema type reference. + if !strings.Contains(qualified, "kind public.user_kind") { + t.Errorf("forced qualification should qualify the same-schema type ref: %q", qualified) } - if !strings.Contains(qualified, "kind user_kind") { - t.Errorf("forced qualification should preserve the bare same-schema type ref: %q", qualified) +} + +func TestStripSchemaPrefixMode_QuotedTargetSchema(t *testing.T) { + // The inspector qualifies same-schema type references via quote_ident, so a + // target schema whose name requires quoting arrives as "My Schema".user_kind. + // Default (smart-qualification) mode must still strip it; forced + // qualification keeps it. (Regression: previously only the raw, unquoted + // prefix was stripped, so quoted target schemas leaked into default output.) + const typeRef = `"My Schema".user_kind` + if got := stripSchemaPrefixMode(typeRef, "My Schema", false); got != "user_kind" { + t.Errorf("default should strip quoted target-schema prefix: got %q", got) + } + if got := stripSchemaPrefixMode(typeRef, "My Schema", true); got != typeRef { + t.Errorf("forced qualification should keep the ref: got %q", got) + } + // Cast form (e.g. a parameter default) with a quoted schema. + if got := stripSchemaPrefixMode(`'a'::"My Schema".mood`, "My Schema", false); got != `'a'::mood` { + t.Errorf("default should strip quoted schema in cast: got %q", got) + } + // Lowercase schema (no quoting required) is unaffected. + if got := stripSchemaPrefixMode("public.user_kind", "public", false); got != "user_kind" { + t.Errorf("default should strip unquoted prefix: got %q", got) } } diff --git a/internal/diff/table.go b/internal/diff/table.go index 405cf855..97b28f75 100644 --- a/internal/diff/table.go +++ b/internal/diff/table.go @@ -25,17 +25,29 @@ func stripSchemaPrefixMode(typeName, targetSchema string, qualifySchema bool) st return typeName } - // Check if the type has the target schema prefix at the beginning - prefix := targetSchema + "." - if after, found := strings.CutPrefix(typeName, prefix); found { - return after - } - - // Also handle type casts within expressions: ::schema.typename -> ::typename - // This is needed for function parameter default values like 'value'::schema.enum_type - castPrefix := "::" + targetSchema + "." - if strings.Contains(typeName, castPrefix) { - return strings.ReplaceAll(typeName, castPrefix, "::") + // The inspector emits schema prefixes on type references via quote_ident, so + // the prefix is quoted when the schema name requires it (e.g. "My Schema"). + // Strip both the raw and quote_ident forms so default (smart-qualification) + // output omits the target-schema prefix regardless of whether it needs quoting. + // QuoteIdentifier is a no-op for names that don't require quoting, so the two + // forms coincide for the common lowercase case. + forms := []string{targetSchema} + if quoted := ir.QuoteIdentifier(targetSchema); quoted != targetSchema { + forms = append(forms, quoted) + } + for _, schema := range forms { + // Check if the type has the target schema prefix at the beginning + prefix := schema + "." + if after, found := strings.CutPrefix(typeName, prefix); found { + return after + } + + // Also handle type casts within expressions: ::schema.typename -> ::typename + // This is needed for function parameter default values like 'value'::schema.enum_type + castPrefix := "::" + schema + "." + if strings.Contains(typeName, castPrefix) { + return strings.ReplaceAll(typeName, castPrefix, "::") + } } return typeName diff --git a/internal/diff/topological.go b/internal/diff/topological.go index 50c1b335..f629d8f1 100644 --- a/internal/diff/topological.go +++ b/internal/diff/topological.go @@ -265,7 +265,7 @@ func topologicallySortTypes(types []*ir.Type) []*ir.Type { typeMap := make(map[string]*ir.Type) var insertionOrder []string for _, t := range types { - key := t.Schema + "." + t.Name + key := typeGraphKey(t.Schema, t.Name) typeMap[key] = t insertionOrder = append(insertionOrder, key) } @@ -387,8 +387,18 @@ func topologicallySortTypes(types []*ir.Type) []*ir.Type { return sortedTypes } -// extractTypeName extracts a fully qualified type name from a data type string -// It handles array notation (e.g., "status_type[]") and schema prefixes +// typeGraphKey builds the lookup key used to identify a type in the dependency +// graph. It joins schema and name with a NUL byte, which cannot appear in a +// PostgreSQL identifier, so distinct (schema, name) pairs never collide even when +// an identifier legally contains a '.' (e.g. schema "a.b" type "c" vs schema "a" +// type "b.c"). +func typeGraphKey(schema, name string) string { + return schema + "\x00" + name +} + +// extractTypeName maps a data type string to its dependency-graph key +// (see typeGraphKey). It handles array notation (e.g., "status_type[]") and +// schema prefixes. func extractTypeName(dataType, defaultSchema string) string { if dataType == "" { return "" @@ -400,23 +410,56 @@ func extractTypeName(dataType, defaultSchema string) string { typeName = typeName[:len(typeName)-2] } - // Check if it's a schema-qualified name - if idx := findLastDot(typeName); idx != -1 { - return typeName // Already fully qualified + // Strip a trailing typmod suffix (e.g. "(10,2)") that the inspector appends + // when qualifying user-defined scalar types, so it matches the bare typeMap + // key. Only a purely numeric/comma/space typmod is stripped, so a quoted + // identifier that legally contains parentheses (which ends in '"', not ')') + // is left intact. + if i := strings.LastIndexByte(typeName, '('); i > 0 && strings.HasSuffix(typeName, ")") { + inner := typeName[i+1 : len(typeName)-1] + if inner != "" && strings.IndexFunc(inner, func(r rune) bool { + return !(r >= '0' && r <= '9' || r == ',' || r == ' ') + }) == -1 { + typeName = typeName[:i] + } + } + + // Check if it's a schema-qualified name. The inspector emits user-defined + // type references via quote_ident (e.g. public."user"), so normalize each + // component by stripping quote_ident quoting before building the key. + if idx := findLastUnquotedDot(typeName); idx != -1 { + return typeGraphKey(unquoteIdent(typeName[:idx]), unquoteIdent(typeName[idx+1:])) } // Not qualified - use default schema - return defaultSchema + "." + typeName + return typeGraphKey(unquoteIdent(defaultSchema), unquoteIdent(typeName)) } -// findLastDot finds the last dot in a string, returning -1 if not found -func findLastDot(s string) int { - for i := len(s) - 1; i >= 0; i-- { - if s[i] == '.' { - return i - } +// findLastUnquotedDot finds the last '.' that is not inside a double-quoted +// identifier, returning -1 if none. This avoids splitting on a dot that is part +// of a quoted identifier (e.g. the name in public."odd.name"). +func findLastUnquotedDot(s string) int { + inQuote := false + last := -1 + for i := 0; i < len(s); i++ { + switch { + case s[i] == '"': + inQuote = !inQuote + case s[i] == '.' && !inQuote: + last = i + } + } + return last +} + +// unquoteIdent removes quote_ident double-quoting from a single identifier +// (e.g. "user" -> user, "Weird""Name" -> Weird"Name). Bare identifiers are +// returned unchanged so this is safe on already-unquoted names. +func unquoteIdent(s string) string { + if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' { + return strings.ReplaceAll(s[1:len(s)-1], `""`, `"`) } - return -1 + return s } // topologicallySortFunctions sorts functions across all schemas in dependency order diff --git a/internal/diff/topological_test.go b/internal/diff/topological_test.go index adc145a0..98d47c63 100644 --- a/internal/diff/topological_test.go +++ b/internal/diff/topological_test.go @@ -187,6 +187,77 @@ func TestTopologicallySortTypesCompositeWithMultipleDependencies(t *testing.T) { assertBefore("task", "project") } +func TestExtractTypeNameNormalizesQuoting(t *testing.T) { + // Post-#493 the inspector emits user-defined type references via + // quote_ident (schema-qualified). extractTypeName must normalize them to the + // same delimiter-safe key that typeGraphKey builds for typeMap entries. + cases := []struct{ in, schema, wantSchema, wantName string }{ + {"user_kind", "public", "public", "user_kind"}, // bare -> default schema + {"public.user_kind", "public", "public", "user_kind"}, // already qualified + {`public."user"`, "public", "public", "user"}, // quoted reserved-word name + {`"MySchema"."MyType"`, "public", "MySchema", "MyType"}, // quoted schema + name + {"public.user_kind[]", "public", "public", "user_kind"}, // array notation stripped + {`other."odd.name"`, "public", "other", "odd.name"}, // dot inside quotes is not a separator + {`"a.b".c`, "public", "a.b", "c"}, // dotted schema, bare type + {"public.vector(384)", "public", "public", "vector"}, // typmod suffix stripped + {"s.num(10, 2)", "public", "s", "num"}, // typmod with comma/space stripped + {`a."b"".c"`, "public", "a", `b".c`}, // escaped quote ("") + dot inside a quoted ident + } + for _, c := range cases { + want := typeGraphKey(c.wantSchema, c.wantName) + if got := extractTypeName(c.in, c.schema); got != want { + t.Errorf("extractTypeName(%q, %q) = %q, want %q", c.in, c.schema, got, want) + } + } + + // The delimiter-safe key keeps legal-but-dotted identifiers distinct: + // schema "a.b" type "c" must not collide with schema "a" type "b.c". + if k1, k2 := extractTypeName(`"a.b".c`, "public"), extractTypeName(`a."b.c"`, "public"); k1 == k2 { + t.Errorf("dotted identifiers collided: both keyed as %q", k1) + } +} + +func TestTopologicallySortTypesQualifiedQuotedRefs(t *testing.T) { + // The inspector now qualifies same-schema composite-attribute and domain + // base-type references (e.g. public."odd name"). Dependency edges must + // still be found against bare typeMap keys. + referenced := &ir.Type{ + Schema: "public", + Name: "odd name", // needs quote_ident + Kind: ir.TypeKindEnum, + EnumValues: []string{"a", "b"}, + } + composite := &ir.Type{ + Schema: "public", + Name: "holder", + Kind: ir.TypeKindComposite, + Columns: []*ir.TypeColumn{ + {Name: "c", DataType: `public."odd name"`, Position: 1}, + }, + } + dom := &ir.Type{ + Schema: "public", + Name: "odd domain", + Kind: ir.TypeKindDomain, + BaseType: `public."odd name"`, + } + + sorted := topologicallySortTypes([]*ir.Type{composite, referenced, dom}) + if len(sorted) != 3 { + t.Fatalf("expected 3 types, got %d", len(sorted)) + } + order := make(map[string]int, len(sorted)) + for idx, typ := range sorted { + order[typ.Name] = idx + } + if order["odd name"] >= order["holder"] { + t.Fatalf(`expected "odd name" before holder in %v`, order) + } + if order["odd name"] >= order["odd domain"] { + t.Fatalf(`expected "odd name" before "odd domain" in %v`, order) + } +} + func newTestEnumType(name string) *ir.Type { return &ir.Type{ Schema: "public", diff --git a/ir/queries/queries.sql b/ir/queries/queries.sql index 585153cd..213bea58 100644 --- a/ir/queries/queries.sql +++ b/ir/queries/queries.sql @@ -74,13 +74,9 @@ WITH column_base AS ( COALESCE(d.description, '') AS column_comment, CASE WHEN dt.typtype = 'd' THEN - CASE WHEN dn.nspname = c.table_schema THEN quote_ident(dt.typname) - ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) - END + quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) WHEN dt.typtype = 'e' OR dt.typtype = 'c' THEN - CASE WHEN dn.nspname = c.table_schema THEN quote_ident(dt.typname) - ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) - END + quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) WHEN dt.typtype = 'b' AND dt.typcategory = 'A' THEN -- Array types: apply same schema qualification logic to element type -- Use typcategory = 'A' rather than typelem <> 0; the latter is true @@ -88,7 +84,6 @@ WITH column_base AS ( -- Use format_type to preserve typmod for element types (e.g., varchar(128)[] for character varying(128)[]) CASE WHEN en.nspname = 'pg_catalog' THEN et.typname - WHEN en.nspname = c.table_schema THEN quote_ident(et.typname) ELSE quote_ident(en.nspname) || '.' || quote_ident(et.typname) END || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') || '[]' WHEN dt.typtype = 'b' THEN @@ -193,13 +188,9 @@ WITH column_base AS ( COALESCE(d.description, '') AS column_comment, CASE WHEN dt.typtype = 'd' THEN - CASE WHEN dn.nspname = c.table_schema THEN quote_ident(dt.typname) - ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) - END + quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) WHEN dt.typtype = 'e' OR dt.typtype = 'c' THEN - CASE WHEN dn.nspname = c.table_schema THEN quote_ident(dt.typname) - ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) - END + quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) WHEN dt.typtype = 'b' AND dt.typcategory = 'A' THEN -- Array types: apply same schema qualification logic to element type -- Use typcategory = 'A' rather than typelem <> 0; the latter is true @@ -207,7 +198,6 @@ WITH column_base AS ( -- Use format_type to preserve typmod for element types (e.g., varchar(128)[] for character varying(128)[]) CASE WHEN en.nspname = 'pg_catalog' THEN et.typname - WHEN en.nspname = c.table_schema THEN quote_ident(et.typname) ELSE quote_ident(en.nspname) || '.' || quote_ident(et.typname) END || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') || '[]' WHEN dt.typtype = 'b' THEN @@ -625,7 +615,16 @@ SELECT COALESCE(tf.proname, '') AS transition_function, COALESCE(tfn.nspname, '') AS transition_function_schema, -- Get state type - format_type(a.aggtranstype, NULL) AS state_type, + CASE + WHEN stn.nspname IS NULL OR stn.nspname = 'pg_catalog' THEN + format_type(a.aggtranstype, NULL) + WHEN stt.typcategory = 'A' THEN + CASE + WHEN sten.nspname = 'pg_catalog' THEN stet.typname + ELSE quote_ident(sten.nspname) || '.' || quote_ident(stet.typname) + END || COALESCE(substring(format_type(a.aggtranstype, NULL) FROM '\([^)]*\)'), '') || '[]' + ELSE quote_ident(stn.nspname) || '.' || quote_ident(stt.typname) + END AS state_type, -- Get initial condition a.agginitval AS initial_condition, -- Get final function if exists @@ -636,6 +635,10 @@ SELECT FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid JOIN pg_aggregate a ON a.aggfnoid = p.oid +LEFT JOIN pg_type stt ON stt.oid = a.aggtranstype +LEFT JOIN pg_namespace stn ON stt.typnamespace = stn.oid +LEFT JOIN pg_type stet ON stt.typelem = stet.oid +LEFT JOIN pg_namespace sten ON stet.typnamespace = sten.oid LEFT JOIN pg_proc tf ON a.aggtransfn = tf.oid LEFT JOIN pg_namespace tfn ON tf.pronamespace = tfn.oid LEFT JOIN pg_proc ff ON a.aggfinalfn = ff.oid @@ -714,11 +717,26 @@ SELECT t.typname AS type_name, a.attname AS column_name, a.attnum AS column_position, - format_type(a.atttypid, a.atttypmod) AS column_type + CASE + WHEN atn.nspname IS NULL OR atn.nspname = 'pg_catalog' THEN + format_type(a.atttypid, a.atttypmod) + WHEN at.typcategory = 'A' THEN + CASE + WHEN aen.nspname = 'pg_catalog' THEN aet.typname + ELSE quote_ident(aen.nspname) || '.' || quote_ident(aet.typname) + END || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') || '[]' + ELSE + quote_ident(atn.nspname) || '.' || quote_ident(at.typname) + || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') + END AS column_type FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid JOIN pg_class c ON t.typrelid = c.oid JOIN pg_attribute a ON c.oid = a.attrelid +LEFT JOIN pg_type at ON at.oid = a.atttypid +LEFT JOIN pg_namespace atn ON at.typnamespace = atn.oid +LEFT JOIN pg_type aet ON at.typelem = aet.oid +LEFT JOIN pg_namespace aen ON aet.typnamespace = aen.oid WHERE t.typtype = 'c' -- composite types only AND c.relkind = 'c' -- only true composite types, not table types AND a.attnum > 0 -- exclude system columns @@ -892,12 +910,27 @@ ORDER BY n.nspname, c.relname, pol.polname; SELECT n.nspname AS domain_schema, t.typname AS domain_name, - format_type(t.typbasetype, t.typtypmod) AS base_type, + CASE + WHEN bn.nspname IS NULL OR bn.nspname = 'pg_catalog' THEN + format_type(t.typbasetype, t.typtypmod) + WHEN bt.typcategory = 'A' THEN + CASE + WHEN ben.nspname = 'pg_catalog' THEN bet.typname + ELSE quote_ident(ben.nspname) || '.' || quote_ident(bet.typname) + END || COALESCE(substring(format_type(t.typbasetype, t.typtypmod) FROM '\([^)]*\)'), '') || '[]' + ELSE + quote_ident(bn.nspname) || '.' || quote_ident(bt.typname) + || COALESCE(substring(format_type(t.typbasetype, t.typtypmod) FROM '\([^)]*\)'), '') + END AS base_type, t.typnotnull AS not_null, t.typdefault AS default_value, COALESCE(d.description, '') AS domain_comment FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid +LEFT JOIN pg_type bt ON bt.oid = t.typbasetype +LEFT JOIN pg_namespace bn ON bt.typnamespace = bn.oid +LEFT JOIN pg_type bet ON bt.typelem = bet.oid +LEFT JOIN pg_namespace ben ON bet.typnamespace = ben.oid LEFT JOIN pg_description d ON d.objoid = t.oid AND d.classoid = 'pg_type'::regclass WHERE t.typtype = 'd' -- Domain types only AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') @@ -1165,7 +1198,16 @@ SELECT -- Transition function and state CASE WHEN tfn.nspname = n.nspname THEN quote_ident(tf.proname) ELSE quote_ident(tfn.nspname) || '.' || quote_ident(tf.proname) END AS transition_function, - format_type(a.aggtranstype, NULL) AS state_type, + CASE + WHEN stn.nspname IS NULL OR stn.nspname = 'pg_catalog' THEN + format_type(a.aggtranstype, NULL) + WHEN stt.typcategory = 'A' THEN + CASE + WHEN sten.nspname = 'pg_catalog' THEN stet.typname + ELSE quote_ident(sten.nspname) || '.' || quote_ident(stet.typname) + END || COALESCE(substring(format_type(a.aggtranstype, NULL) FROM '\([^)]*\)'), '') || '[]' + ELSE quote_ident(stn.nspname) || '.' || quote_ident(stt.typname) + END AS state_type, a.aggtransspace AS state_space, a.agginitval AS initial_condition, -- Final function @@ -1191,7 +1233,16 @@ SELECT CASE WHEN a.aggminvtransfn = 0 THEN '' WHEN mitfn.nspname = n.nspname THEN quote_ident(mitf.proname) ELSE quote_ident(mitfn.nspname) || '.' || quote_ident(mitf.proname) END AS minv_transition_function, - CASE WHEN a.aggmtransfn = 0 THEN '' ELSE format_type(a.aggmtranstype, NULL) END AS mstate_type, + CASE WHEN a.aggmtransfn = 0 THEN '' + WHEN mstn.nspname IS NULL OR mstn.nspname = 'pg_catalog' THEN + format_type(a.aggmtranstype, NULL) + WHEN mstt.typcategory = 'A' THEN + CASE + WHEN msten.nspname = 'pg_catalog' THEN mstet.typname + ELSE quote_ident(msten.nspname) || '.' || quote_ident(mstet.typname) + END || COALESCE(substring(format_type(a.aggmtranstype, NULL) FROM '\([^)]*\)'), '') || '[]' + ELSE quote_ident(mstn.nspname) || '.' || quote_ident(mstt.typname) + END AS mstate_type, a.aggmtransspace AS mstate_space, CASE WHEN a.aggmfinalfn = 0 THEN '' WHEN mffn.nspname = n.nspname THEN quote_ident(mff.proname) @@ -1207,6 +1258,10 @@ SELECT FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid JOIN pg_aggregate a ON a.aggfnoid = p.oid +LEFT JOIN pg_type stt ON stt.oid = a.aggtranstype +LEFT JOIN pg_namespace stn ON stt.typnamespace = stn.oid +LEFT JOIN pg_type stet ON stt.typelem = stet.oid +LEFT JOIN pg_namespace sten ON stet.typnamespace = sten.oid LEFT JOIN pg_proc tf ON a.aggtransfn = tf.oid LEFT JOIN pg_namespace tfn ON tf.pronamespace = tfn.oid LEFT JOIN pg_proc ff ON a.aggfinalfn = ff.oid @@ -1223,6 +1278,10 @@ LEFT JOIN pg_proc mitf ON a.aggminvtransfn = mitf.oid LEFT JOIN pg_namespace mitfn ON mitf.pronamespace = mitfn.oid LEFT JOIN pg_proc mff ON a.aggmfinalfn = mff.oid LEFT JOIN pg_namespace mffn ON mff.pronamespace = mffn.oid +LEFT JOIN pg_type mstt ON mstt.oid = a.aggmtranstype +LEFT JOIN pg_namespace mstn ON mstt.typnamespace = mstn.oid +LEFT JOIN pg_type mstet ON mstt.typelem = mstet.oid +LEFT JOIN pg_namespace msten ON mstet.typnamespace = msten.oid LEFT JOIN pg_operator op ON op.oid = a.aggsortop LEFT JOIN pg_namespace opn ON op.oprnamespace = opn.oid LEFT JOIN pg_description d ON d.objoid = p.oid AND d.classoid = 'pg_proc'::regclass AND d.objsubid = 0 @@ -1350,12 +1409,27 @@ ORDER BY n.nspname, t.typname; SELECT n.nspname AS domain_schema, t.typname AS domain_name, - format_type(t.typbasetype, t.typtypmod) AS base_type, + CASE + WHEN bn.nspname IS NULL OR bn.nspname = 'pg_catalog' THEN + format_type(t.typbasetype, t.typtypmod) + WHEN bt.typcategory = 'A' THEN + CASE + WHEN ben.nspname = 'pg_catalog' THEN bet.typname + ELSE quote_ident(ben.nspname) || '.' || quote_ident(bet.typname) + END || COALESCE(substring(format_type(t.typbasetype, t.typtypmod) FROM '\([^)]*\)'), '') || '[]' + ELSE + quote_ident(bn.nspname) || '.' || quote_ident(bt.typname) + || COALESCE(substring(format_type(t.typbasetype, t.typtypmod) FROM '\([^)]*\)'), '') + END AS base_type, t.typnotnull AS not_null, t.typdefault AS default_value, COALESCE(d.description, '') AS domain_comment FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid +LEFT JOIN pg_type bt ON bt.oid = t.typbasetype +LEFT JOIN pg_namespace bn ON bt.typnamespace = bn.oid +LEFT JOIN pg_type bet ON bt.typelem = bet.oid +LEFT JOIN pg_namespace ben ON bet.typnamespace = ben.oid LEFT JOIN pg_description d ON d.objoid = t.oid AND d.classoid = 'pg_type'::regclass WHERE t.typtype = 'd' -- Domain types only AND n.nspname = $1 @@ -1395,11 +1469,26 @@ SELECT t.typname AS type_name, a.attname AS column_name, a.attnum AS column_position, - format_type(a.atttypid, a.atttypmod) AS column_type + CASE + WHEN atn.nspname IS NULL OR atn.nspname = 'pg_catalog' THEN + format_type(a.atttypid, a.atttypmod) + WHEN at.typcategory = 'A' THEN + CASE + WHEN aen.nspname = 'pg_catalog' THEN aet.typname + ELSE quote_ident(aen.nspname) || '.' || quote_ident(aet.typname) + END || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') || '[]' + ELSE + quote_ident(atn.nspname) || '.' || quote_ident(at.typname) + || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') + END AS column_type FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid JOIN pg_class c ON t.typrelid = c.oid JOIN pg_attribute a ON c.oid = a.attrelid +LEFT JOIN pg_type at ON at.oid = a.atttypid +LEFT JOIN pg_namespace atn ON at.typnamespace = atn.oid +LEFT JOIN pg_type aet ON at.typelem = aet.oid +LEFT JOIN pg_namespace aen ON aet.typnamespace = aen.oid WHERE t.typtype = 'c' -- composite types only AND c.relkind = 'c' -- only true composite types, not table types AND a.attnum > 0 -- exclude system columns diff --git a/ir/queries/queries.sql.go b/ir/queries/queries.sql.go index 918599a8..e4dac1b2 100644 --- a/ir/queries/queries.sql.go +++ b/ir/queries/queries.sql.go @@ -23,7 +23,16 @@ SELECT COALESCE(tf.proname, '') AS transition_function, COALESCE(tfn.nspname, '') AS transition_function_schema, -- Get state type - format_type(a.aggtranstype, NULL) AS state_type, + CASE + WHEN stn.nspname IS NULL OR stn.nspname = 'pg_catalog' THEN + format_type(a.aggtranstype, NULL) + WHEN stt.typcategory = 'A' THEN + CASE + WHEN sten.nspname = 'pg_catalog' THEN stet.typname + ELSE quote_ident(sten.nspname) || '.' || quote_ident(stet.typname) + END || COALESCE(substring(format_type(a.aggtranstype, NULL) FROM '\([^)]*\)'), '') || '[]' + ELSE quote_ident(stn.nspname) || '.' || quote_ident(stt.typname) + END AS state_type, -- Get initial condition a.agginitval AS initial_condition, -- Get final function if exists @@ -34,6 +43,10 @@ SELECT FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid JOIN pg_aggregate a ON a.aggfnoid = p.oid +LEFT JOIN pg_type stt ON stt.oid = a.aggtranstype +LEFT JOIN pg_namespace stn ON stt.typnamespace = stn.oid +LEFT JOIN pg_type stet ON stt.typelem = stet.oid +LEFT JOIN pg_namespace sten ON stet.typnamespace = sten.oid LEFT JOIN pg_proc tf ON a.aggtransfn = tf.oid LEFT JOIN pg_namespace tfn ON tf.pronamespace = tfn.oid LEFT JOIN pg_proc ff ON a.aggfinalfn = ff.oid @@ -114,7 +127,16 @@ SELECT -- Transition function and state CASE WHEN tfn.nspname = n.nspname THEN quote_ident(tf.proname) ELSE quote_ident(tfn.nspname) || '.' || quote_ident(tf.proname) END AS transition_function, - format_type(a.aggtranstype, NULL) AS state_type, + CASE + WHEN stn.nspname IS NULL OR stn.nspname = 'pg_catalog' THEN + format_type(a.aggtranstype, NULL) + WHEN stt.typcategory = 'A' THEN + CASE + WHEN sten.nspname = 'pg_catalog' THEN stet.typname + ELSE quote_ident(sten.nspname) || '.' || quote_ident(stet.typname) + END || COALESCE(substring(format_type(a.aggtranstype, NULL) FROM '\([^)]*\)'), '') || '[]' + ELSE quote_ident(stn.nspname) || '.' || quote_ident(stt.typname) + END AS state_type, a.aggtransspace AS state_space, a.agginitval AS initial_condition, -- Final function @@ -140,7 +162,16 @@ SELECT CASE WHEN a.aggminvtransfn = 0 THEN '' WHEN mitfn.nspname = n.nspname THEN quote_ident(mitf.proname) ELSE quote_ident(mitfn.nspname) || '.' || quote_ident(mitf.proname) END AS minv_transition_function, - CASE WHEN a.aggmtransfn = 0 THEN '' ELSE format_type(a.aggmtranstype, NULL) END AS mstate_type, + CASE WHEN a.aggmtransfn = 0 THEN '' + WHEN mstn.nspname IS NULL OR mstn.nspname = 'pg_catalog' THEN + format_type(a.aggmtranstype, NULL) + WHEN mstt.typcategory = 'A' THEN + CASE + WHEN msten.nspname = 'pg_catalog' THEN mstet.typname + ELSE quote_ident(msten.nspname) || '.' || quote_ident(mstet.typname) + END || COALESCE(substring(format_type(a.aggmtranstype, NULL) FROM '\([^)]*\)'), '') || '[]' + ELSE quote_ident(mstn.nspname) || '.' || quote_ident(mstt.typname) + END AS mstate_type, a.aggmtransspace AS mstate_space, CASE WHEN a.aggmfinalfn = 0 THEN '' WHEN mffn.nspname = n.nspname THEN quote_ident(mff.proname) @@ -156,6 +187,10 @@ SELECT FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid JOIN pg_aggregate a ON a.aggfnoid = p.oid +LEFT JOIN pg_type stt ON stt.oid = a.aggtranstype +LEFT JOIN pg_namespace stn ON stt.typnamespace = stn.oid +LEFT JOIN pg_type stet ON stt.typelem = stet.oid +LEFT JOIN pg_namespace sten ON stet.typnamespace = sten.oid LEFT JOIN pg_proc tf ON a.aggtransfn = tf.oid LEFT JOIN pg_namespace tfn ON tf.pronamespace = tfn.oid LEFT JOIN pg_proc ff ON a.aggfinalfn = ff.oid @@ -172,6 +207,10 @@ LEFT JOIN pg_proc mitf ON a.aggminvtransfn = mitf.oid LEFT JOIN pg_namespace mitfn ON mitf.pronamespace = mitfn.oid LEFT JOIN pg_proc mff ON a.aggmfinalfn = mff.oid LEFT JOIN pg_namespace mffn ON mff.pronamespace = mffn.oid +LEFT JOIN pg_type mstt ON mstt.oid = a.aggmtranstype +LEFT JOIN pg_namespace mstn ON mstt.typnamespace = mstn.oid +LEFT JOIN pg_type mstet ON mstt.typelem = mstet.oid +LEFT JOIN pg_namespace msten ON mstet.typnamespace = msten.oid LEFT JOIN pg_operator op ON op.oid = a.aggsortop LEFT JOIN pg_namespace opn ON op.oprnamespace = opn.oid LEFT JOIN pg_description d ON d.objoid = p.oid AND d.classoid = 'pg_proc'::regclass AND d.objsubid = 0 @@ -350,13 +389,9 @@ WITH column_base AS ( COALESCE(d.description, '') AS column_comment, CASE WHEN dt.typtype = 'd' THEN - CASE WHEN dn.nspname = c.table_schema THEN quote_ident(dt.typname) - ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) - END + quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) WHEN dt.typtype = 'e' OR dt.typtype = 'c' THEN - CASE WHEN dn.nspname = c.table_schema THEN quote_ident(dt.typname) - ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) - END + quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) WHEN dt.typtype = 'b' AND dt.typcategory = 'A' THEN -- Array types: apply same schema qualification logic to element type -- Use typcategory = 'A' rather than typelem <> 0; the latter is true @@ -364,7 +399,6 @@ WITH column_base AS ( -- Use format_type to preserve typmod for element types (e.g., varchar(128)[] for character varying(128)[]) CASE WHEN en.nspname = 'pg_catalog' THEN et.typname - WHEN en.nspname = c.table_schema THEN quote_ident(et.typname) ELSE quote_ident(en.nspname) || '.' || quote_ident(et.typname) END || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') || '[]' WHEN dt.typtype = 'b' THEN @@ -541,13 +575,9 @@ WITH column_base AS ( COALESCE(d.description, '') AS column_comment, CASE WHEN dt.typtype = 'd' THEN - CASE WHEN dn.nspname = c.table_schema THEN quote_ident(dt.typname) - ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) - END + quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) WHEN dt.typtype = 'e' OR dt.typtype = 'c' THEN - CASE WHEN dn.nspname = c.table_schema THEN quote_ident(dt.typname) - ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) - END + quote_ident(dn.nspname) || '.' || quote_ident(dt.typname) WHEN dt.typtype = 'b' AND dt.typcategory = 'A' THEN -- Array types: apply same schema qualification logic to element type -- Use typcategory = 'A' rather than typelem <> 0; the latter is true @@ -555,7 +585,6 @@ WITH column_base AS ( -- Use format_type to preserve typmod for element types (e.g., varchar(128)[] for character varying(128)[]) CASE WHEN en.nspname = 'pg_catalog' THEN et.typname - WHEN en.nspname = c.table_schema THEN quote_ident(et.typname) ELSE quote_ident(en.nspname) || '.' || quote_ident(et.typname) END || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') || '[]' WHEN dt.typtype = 'b' THEN @@ -732,11 +761,26 @@ SELECT t.typname AS type_name, a.attname AS column_name, a.attnum AS column_position, - format_type(a.atttypid, a.atttypmod) AS column_type + CASE + WHEN atn.nspname IS NULL OR atn.nspname = 'pg_catalog' THEN + format_type(a.atttypid, a.atttypmod) + WHEN at.typcategory = 'A' THEN + CASE + WHEN aen.nspname = 'pg_catalog' THEN aet.typname + ELSE quote_ident(aen.nspname) || '.' || quote_ident(aet.typname) + END || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') || '[]' + ELSE + quote_ident(atn.nspname) || '.' || quote_ident(at.typname) + || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') + END AS column_type FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid JOIN pg_class c ON t.typrelid = c.oid JOIN pg_attribute a ON c.oid = a.attrelid +LEFT JOIN pg_type at ON at.oid = a.atttypid +LEFT JOIN pg_namespace atn ON at.typnamespace = atn.oid +LEFT JOIN pg_type aet ON at.typelem = aet.oid +LEFT JOIN pg_namespace aen ON aet.typnamespace = aen.oid WHERE t.typtype = 'c' -- composite types only AND c.relkind = 'c' -- only true composite types, not table types AND a.attnum > 0 -- exclude system columns @@ -786,16 +830,31 @@ func (q *Queries) GetCompositeTypeColumns(ctx context.Context) ([]GetCompositeTy } const getCompositeTypeColumnsForSchema = `-- name: GetCompositeTypeColumnsForSchema :many -SELECT +SELECT n.nspname AS type_schema, t.typname AS type_name, a.attname AS column_name, a.attnum AS column_position, - format_type(a.atttypid, a.atttypmod) AS column_type + CASE + WHEN atn.nspname IS NULL OR atn.nspname = 'pg_catalog' THEN + format_type(a.atttypid, a.atttypmod) + WHEN at.typcategory = 'A' THEN + CASE + WHEN aen.nspname = 'pg_catalog' THEN aet.typname + ELSE quote_ident(aen.nspname) || '.' || quote_ident(aet.typname) + END || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') || '[]' + ELSE + quote_ident(atn.nspname) || '.' || quote_ident(at.typname) + || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '') + END AS column_type FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid JOIN pg_class c ON t.typrelid = c.oid JOIN pg_attribute a ON c.oid = a.attrelid +LEFT JOIN pg_type at ON at.oid = a.atttypid +LEFT JOIN pg_namespace atn ON at.typnamespace = atn.oid +LEFT JOIN pg_type aet ON at.typelem = aet.oid +LEFT JOIN pg_namespace aen ON aet.typnamespace = aen.oid WHERE t.typtype = 'c' -- composite types only AND c.relkind = 'c' -- only true composite types, not table types AND a.attnum > 0 -- exclude system columns @@ -1294,15 +1353,30 @@ func (q *Queries) GetDomainConstraintsForSchema(ctx context.Context, dollar_1 sq } const getDomains = `-- name: GetDomains :many -SELECT +SELECT n.nspname AS domain_schema, t.typname AS domain_name, - format_type(t.typbasetype, t.typtypmod) AS base_type, + CASE + WHEN bn.nspname IS NULL OR bn.nspname = 'pg_catalog' THEN + format_type(t.typbasetype, t.typtypmod) + WHEN bt.typcategory = 'A' THEN + CASE + WHEN ben.nspname = 'pg_catalog' THEN bet.typname + ELSE quote_ident(ben.nspname) || '.' || quote_ident(bet.typname) + END || COALESCE(substring(format_type(t.typbasetype, t.typtypmod) FROM '\([^)]*\)'), '') || '[]' + ELSE + quote_ident(bn.nspname) || '.' || quote_ident(bt.typname) + || COALESCE(substring(format_type(t.typbasetype, t.typtypmod) FROM '\([^)]*\)'), '') + END AS base_type, t.typnotnull AS not_null, t.typdefault AS default_value, COALESCE(d.description, '') AS domain_comment FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid +LEFT JOIN pg_type bt ON bt.oid = t.typbasetype +LEFT JOIN pg_namespace bn ON bt.typnamespace = bn.oid +LEFT JOIN pg_type bet ON bt.typelem = bet.oid +LEFT JOIN pg_namespace ben ON bet.typnamespace = ben.oid LEFT JOIN pg_description d ON d.objoid = t.oid AND d.classoid = 'pg_type'::regclass WHERE t.typtype = 'd' -- Domain types only AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') @@ -1352,15 +1426,30 @@ func (q *Queries) GetDomains(ctx context.Context) ([]GetDomainsRow, error) { } const getDomainsForSchema = `-- name: GetDomainsForSchema :many -SELECT +SELECT n.nspname AS domain_schema, t.typname AS domain_name, - format_type(t.typbasetype, t.typtypmod) AS base_type, + CASE + WHEN bn.nspname IS NULL OR bn.nspname = 'pg_catalog' THEN + format_type(t.typbasetype, t.typtypmod) + WHEN bt.typcategory = 'A' THEN + CASE + WHEN ben.nspname = 'pg_catalog' THEN bet.typname + ELSE quote_ident(ben.nspname) || '.' || quote_ident(bet.typname) + END || COALESCE(substring(format_type(t.typbasetype, t.typtypmod) FROM '\([^)]*\)'), '') || '[]' + ELSE + quote_ident(bn.nspname) || '.' || quote_ident(bt.typname) + || COALESCE(substring(format_type(t.typbasetype, t.typtypmod) FROM '\([^)]*\)'), '') + END AS base_type, t.typnotnull AS not_null, t.typdefault AS default_value, COALESCE(d.description, '') AS domain_comment FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid +LEFT JOIN pg_type bt ON bt.oid = t.typbasetype +LEFT JOIN pg_namespace bn ON bt.typnamespace = bn.oid +LEFT JOIN pg_type bet ON bt.typelem = bet.oid +LEFT JOIN pg_namespace ben ON bet.typnamespace = ben.oid LEFT JOIN pg_description d ON d.objoid = t.oid AND d.classoid = 'pg_type'::regclass WHERE t.typtype = 'd' -- Domain types only AND n.nspname = $1 diff --git a/testdata/diff/create_function/issue_360_returns_table_custom_type/plan.json b/testdata/diff/create_function/issue_360_returns_table_custom_type/plan.json index 2bf5c377..d31a8057 100644 --- a/testdata/diff/create_function/issue_360_returns_table_custom_type/plan.json +++ b/testdata/diff/create_function/issue_360_returns_table_custom_type/plan.json @@ -3,7 +3,7 @@ "pgschema_version": "1.12.0", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "bc4fc478f2d7ae4cc204de3447d992dface8f485a9227504fed99b21817cb888" + "hash": "2469dd87b8bff74b211d0c95a21c6921979bbc2098ae9b32ea391cdbdbd41c13" }, "groups": null } diff --git a/testdata/diff/create_function/issue_399_schema_qualified_body/plan.json b/testdata/diff/create_function/issue_399_schema_qualified_body/plan.json index bfc18d43..32d314b9 100644 --- a/testdata/diff/create_function/issue_399_schema_qualified_body/plan.json +++ b/testdata/diff/create_function/issue_399_schema_qualified_body/plan.json @@ -3,7 +3,7 @@ "pgschema_version": "1.12.0", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "eb148b37b7b6325bdd5f0c1c120dfe0bd71a062ce69951aa946c452aff2dc662" + "hash": "6fedb719a3222c246e84fb6e5e84961e3041c6da2c767ce83d868d8631fb5ac7" }, "groups": [ { diff --git a/testdata/diff/online/add_partial_index/plan.json b/testdata/diff/online/add_partial_index/plan.json index c62fcb07..d0180319 100644 --- a/testdata/diff/online/add_partial_index/plan.json +++ b/testdata/diff/online/add_partial_index/plan.json @@ -3,7 +3,7 @@ "pgschema_version": "1.12.0", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "d3818157c6994cfe669982e5174d7e36ada8c106bb8d340c392c7cbe64ebc135" + "hash": "1e4c207c91e5defc052872fd14ab387c061b0cbe470188ccee38f24bc2cfe149" }, "groups": [ {