From 6e5676e5764bd7a0ddf9545c643ed83dc6f30783 Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Sun, 9 Nov 2025 05:13:15 -0800 Subject: [PATCH] fix: schema qualifier in function arguments --- ir/inspector.go | 47 +++++++++++++++++-- .../alter_function_same_signature/diff.sql | 5 +- .../alter_function_same_signature/new.sql | 7 ++- .../alter_function_same_signature/old.sql | 6 ++- .../alter_function_same_signature/plan.json | 6 +-- .../alter_function_same_signature/plan.sql | 5 +- .../alter_function_same_signature/plan.txt | 5 +- .../alter_function_same_signature/setup.sql | 4 ++ 8 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 testdata/diff/create_function/alter_function_same_signature/setup.sql diff --git a/ir/inspector.go b/ir/inspector.go index 5ff4b158..a3b00b46 100644 --- a/ir/inspector.go +++ b/ir/inspector.go @@ -915,7 +915,7 @@ func (i *Inspector) buildFunctions(ctx context.Context, schema *IR, targetSchema // Parse parameters from the complete signature provided by pg_get_function_arguments() // This signature includes all parameter information including modes, names, types, and defaults - parameters := i.parseParametersFromSignature(signature) + parameters := i.parseParametersFromSignature(signature, schemaName) function := &Function{ Schema: schemaName, @@ -1006,7 +1006,7 @@ func splitParameterString(signature string) []string { // parseParametersFromSignature parses function signature string into Parameter structs // Example signature: "order_id integer, discount_percent numeric DEFAULT 0" // Or with modes: "IN order_id integer, OUT result integer" -func (i *Inspector) parseParametersFromSignature(signature string) []*Parameter { +func (i *Inspector) parseParametersFromSignature(signature string, routineSchema string) []*Parameter { if signature == "" { return nil } @@ -1060,6 +1060,10 @@ func (i *Inspector) parseParametersFromSignature(signature string) []*Parameter param.DataType = remainingParts[0] } + // Normalize type by stripping schema prefix if it matches the routine's schema + // This ensures consistent comparison between database and source SQL representations + param.DataType = i.stripSameSchemaPrefix(param.DataType, routineSchema) + parameters = append(parameters, param) position++ } @@ -1067,6 +1071,43 @@ func (i *Inspector) parseParametersFromSignature(signature string) []*Parameter return parameters } +// stripSameSchemaPrefix removes schema qualification from a type name if the schema +// matches the routine's schema. This normalizes PostgreSQL's behavior of returning +// schema-qualified type names (e.g., "public.order_status") to unqualified names +// (e.g., "order_status") when the type is in the same schema as the function/procedure. +// +// Cross-schema type references are preserved (e.g., "utils.priority_level" stays qualified +// when the function is in the "public" schema). +// +// This ensures consistent comparison between database inspection (which may return qualified +// names) and source SQL (which typically uses unqualified names for same-schema types). +func (i *Inspector) stripSameSchemaPrefix(typeName, routineSchema string) string { + if typeName == "" || routineSchema == "" { + return typeName + } + + // Remove quotes from schema name for comparison + unquotedSchema := routineSchema + if strings.HasPrefix(routineSchema, `"`) && strings.HasSuffix(routineSchema, `"`) { + unquotedSchema = routineSchema[1 : len(routineSchema)-1] + } + + // Handle quoted schema prefix: "schema".typename + quotedPrefix := fmt.Sprintf(`"%s".`, unquotedSchema) + if strings.HasPrefix(typeName, quotedPrefix) { + return typeName[len(quotedPrefix):] + } + + // Handle unquoted schema prefix: schema.typename + unquotedPrefix := unquotedSchema + "." + if strings.HasPrefix(typeName, unquotedPrefix) { + return typeName[len(unquotedPrefix):] + } + + // No matching prefix - return as-is (could be cross-schema type or already unqualified) + return typeName +} + // lookupTypeNameFromOID converts PostgreSQL type OID to type name func (i *Inspector) lookupTypeNameFromOID(oid int64) string { // Common type OID mappings (can be extended as needed) @@ -1119,7 +1160,7 @@ func (i *Inspector) buildProcedures(ctx context.Context, schema *IR, targetSchem dbSchema := schema.getOrCreateSchema(schemaName) // Parse parameters from signature (same approach as functions) - parameters := i.parseParametersFromSignature(signature) + parameters := i.parseParametersFromSignature(signature, schemaName) procedure := &Procedure{ Schema: schemaName, diff --git a/testdata/diff/create_function/alter_function_same_signature/diff.sql b/testdata/diff/create_function/alter_function_same_signature/diff.sql index bd1c2103..c86be39a 100644 --- a/testdata/diff/create_function/alter_function_same_signature/diff.sql +++ b/testdata/diff/create_function/alter_function_same_signature/diff.sql @@ -1,6 +1,8 @@ CREATE OR REPLACE FUNCTION process_order( order_id integer, - discount_percent numeric DEFAULT 0 + discount_percent numeric DEFAULT 0, + status order_status DEFAULT 'pending', + priority utils.priority_level DEFAULT 'medium' ) RETURNS numeric LANGUAGE plpgsql @@ -12,6 +14,7 @@ DECLARE tax_rate numeric := 0.08; BEGIN -- Different logic: calculate with tax instead of just discount + -- Status and priority parameters are available but not used in this simplified version SELECT price INTO base_price FROM products WHERE id = order_id; RETURN base_price * (1 - discount_percent / 100) * (1 + tax_rate); END; diff --git a/testdata/diff/create_function/alter_function_same_signature/new.sql b/testdata/diff/create_function/alter_function_same_signature/new.sql index b0dd505b..0ae75938 100644 --- a/testdata/diff/create_function/alter_function_same_signature/new.sql +++ b/testdata/diff/create_function/alter_function_same_signature/new.sql @@ -1,6 +1,10 @@ +CREATE TYPE order_status AS ENUM ('pending', 'processing', 'completed', 'cancelled'); + CREATE FUNCTION process_order( order_id integer, - discount_percent numeric DEFAULT 0 + discount_percent numeric DEFAULT 0, + status order_status DEFAULT 'pending', + priority utils.priority_level DEFAULT 'medium' ) RETURNS numeric LANGUAGE plpgsql @@ -12,6 +16,7 @@ DECLARE tax_rate numeric := 0.08; BEGIN -- Different logic: calculate with tax instead of just discount + -- Status and priority parameters are available but not used in this simplified version SELECT price INTO base_price FROM products WHERE id = order_id; RETURN base_price * (1 - discount_percent / 100) * (1 + tax_rate); END; diff --git a/testdata/diff/create_function/alter_function_same_signature/old.sql b/testdata/diff/create_function/alter_function_same_signature/old.sql index 367cae29..1646d18b 100644 --- a/testdata/diff/create_function/alter_function_same_signature/old.sql +++ b/testdata/diff/create_function/alter_function_same_signature/old.sql @@ -1,6 +1,10 @@ +CREATE TYPE order_status AS ENUM ('pending', 'processing', 'completed', 'cancelled'); + CREATE FUNCTION process_order( order_id integer, - discount_percent numeric DEFAULT 0 + discount_percent numeric DEFAULT 0, + status order_status DEFAULT 'pending', + priority utils.priority_level DEFAULT 'medium' ) RETURNS numeric LANGUAGE plpgsql diff --git a/testdata/diff/create_function/alter_function_same_signature/plan.json b/testdata/diff/create_function/alter_function_same_signature/plan.json index b2bc0d5e..b2fc2ee6 100644 --- a/testdata/diff/create_function/alter_function_same_signature/plan.json +++ b/testdata/diff/create_function/alter_function_same_signature/plan.json @@ -1,15 +1,15 @@ { "version": "1.0.0", - "pgschema_version": "1.4.0", + "pgschema_version": "1.4.1", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "60f55cc9364a4c46fec3d3c3b819f5507eea75c2667985f2a9c1a2954f786e4e" + "hash": "6d0c7bd072cf940b17ecae1b64566b63a06bd9cd0aaf2c69a12af2fe77ae6d47" }, "groups": [ { "steps": [ { - "sql": "CREATE OR REPLACE FUNCTION process_order(\n order_id integer,\n discount_percent numeric DEFAULT 0\n)\nRETURNS numeric\nLANGUAGE plpgsql\nSECURITY INVOKER\nSTABLE\nAS $$\nDECLARE\n base_price numeric;\n tax_rate numeric := 0.08;\nBEGIN\n -- Different logic: calculate with tax instead of just discount\n SELECT price INTO base_price FROM products WHERE id = order_id;\n RETURN base_price * (1 - discount_percent / 100) * (1 + tax_rate);\nEND;\n$$;", + "sql": "CREATE OR REPLACE FUNCTION process_order(\n order_id integer,\n discount_percent numeric DEFAULT 0,\n status order_status DEFAULT 'pending',\n priority utils.priority_level DEFAULT 'medium'\n)\nRETURNS numeric\nLANGUAGE plpgsql\nSECURITY INVOKER\nSTABLE\nAS $$\nDECLARE\n base_price numeric;\n tax_rate numeric := 0.08;\nBEGIN\n -- Different logic: calculate with tax instead of just discount\n -- Status and priority parameters are available but not used in this simplified version\n SELECT price INTO base_price FROM products WHERE id = order_id;\n RETURN base_price * (1 - discount_percent / 100) * (1 + tax_rate);\nEND;\n$$;", "type": "function", "operation": "alter", "path": "public.process_order" diff --git a/testdata/diff/create_function/alter_function_same_signature/plan.sql b/testdata/diff/create_function/alter_function_same_signature/plan.sql index bd1c2103..c86be39a 100644 --- a/testdata/diff/create_function/alter_function_same_signature/plan.sql +++ b/testdata/diff/create_function/alter_function_same_signature/plan.sql @@ -1,6 +1,8 @@ CREATE OR REPLACE FUNCTION process_order( order_id integer, - discount_percent numeric DEFAULT 0 + discount_percent numeric DEFAULT 0, + status order_status DEFAULT 'pending', + priority utils.priority_level DEFAULT 'medium' ) RETURNS numeric LANGUAGE plpgsql @@ -12,6 +14,7 @@ DECLARE tax_rate numeric := 0.08; BEGIN -- Different logic: calculate with tax instead of just discount + -- Status and priority parameters are available but not used in this simplified version SELECT price INTO base_price FROM products WHERE id = order_id; RETURN base_price * (1 - discount_percent / 100) * (1 + tax_rate); END; diff --git a/testdata/diff/create_function/alter_function_same_signature/plan.txt b/testdata/diff/create_function/alter_function_same_signature/plan.txt index 27610d38..c5a872f0 100644 --- a/testdata/diff/create_function/alter_function_same_signature/plan.txt +++ b/testdata/diff/create_function/alter_function_same_signature/plan.txt @@ -11,7 +11,9 @@ DDL to be executed: CREATE OR REPLACE FUNCTION process_order( order_id integer, - discount_percent numeric DEFAULT 0 + discount_percent numeric DEFAULT 0, + status order_status DEFAULT 'pending', + priority utils.priority_level DEFAULT 'medium' ) RETURNS numeric LANGUAGE plpgsql @@ -23,6 +25,7 @@ DECLARE tax_rate numeric := 0.08; BEGIN -- Different logic: calculate with tax instead of just discount + -- Status and priority parameters are available but not used in this simplified version SELECT price INTO base_price FROM products WHERE id = order_id; RETURN base_price * (1 - discount_percent / 100) * (1 + tax_rate); END; diff --git a/testdata/diff/create_function/alter_function_same_signature/setup.sql b/testdata/diff/create_function/alter_function_same_signature/setup.sql new file mode 100644 index 00000000..98f09c51 --- /dev/null +++ b/testdata/diff/create_function/alter_function_same_signature/setup.sql @@ -0,0 +1,4 @@ +-- Create utils schema with a custom type for cross-schema testing +CREATE SCHEMA IF NOT EXISTS utils; + +CREATE TYPE utils.priority_level AS ENUM ('low', 'medium', 'high', 'critical');