Skip to content
Merged
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
13 changes: 13 additions & 0 deletions internal/diff/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,13 @@ func generateFunctionSQL(function *ir.Function, targetSchema string) string {
}
// Note: Don't output PARALLEL UNSAFE (it's the default)

// Add SET search_path if specified
// Note: Output without outer quotes to handle multi-schema paths correctly
// e.g., "SET search_path = pg_catalog, public" not "SET search_path = 'pg_catalog, public'"
if function.SearchPath != "" {
stmt.WriteString(fmt.Sprintf("\nSET search_path = %s", function.SearchPath))
}

// Add the function body
if function.Definition != "" {
// Check if this uses RETURN clause syntax (PG14+)
Expand Down Expand Up @@ -393,6 +400,9 @@ func functionsEqual(old, new *ir.Function) bool {
if old.Parallel != new.Parallel {
return false
}
if old.SearchPath != new.SearchPath {
return false
}
if old.Comment != new.Comment {
return false
}
Expand Down Expand Up @@ -439,6 +449,9 @@ func functionsEqualExceptComment(old, new *ir.Function) bool {
if old.Parallel != new.Parallel {
return false
}
if old.SearchPath != new.SearchPath {
return false
}
// Note: We intentionally do NOT compare Comment here

oldInputParams := filterNonTableParameters(old.Parameters)
Expand Down
7 changes: 7 additions & 0 deletions ir/inspector.go
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,12 @@ func (i *Inspector) buildFunctions(ctx context.Context, schema *IR, targetSchema
// This signature includes all parameter information including modes, names, types, and defaults
parameters := i.parseParametersFromSignature(signature, schemaName)

// Handle search_path
searchPath := ""
if fn.SearchPath.Valid {
searchPath = fn.SearchPath.String
}

function := &Function{
Schema: schemaName,
Name: functionName,
Expand All @@ -947,6 +953,7 @@ func (i *Inspector) buildFunctions(ctx context.Context, schema *IR, targetSchema
IsSecurityDefiner: isSecurityDefiner,
IsLeakproof: isLeakproof,
Parallel: parallelMode,
SearchPath: searchPath,
}

// Use name(arguments) as key to support function overloading
Expand Down
1 change: 1 addition & 0 deletions ir/ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ type Function struct {
IsSecurityDefiner bool `json:"is_security_definer,omitempty"` // SECURITY DEFINER
IsLeakproof bool `json:"is_leakproof,omitempty"` // LEAKPROOF
Parallel string `json:"parallel,omitempty"` // SAFE, UNSAFE, RESTRICTED
SearchPath string `json:"search_path,omitempty"` // SET search_path value
}

// GetArguments returns the function arguments string (types only) for function identification.
Expand Down
3 changes: 2 additions & 1 deletion ir/queries/queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,8 @@ SELECT
p.proisstrict AS is_strict,
p.prosecdef AS is_security_definer,
p.proleakproof AS is_leakproof,
p.proparallel AS parallel_mode
p.proparallel AS parallel_mode,
(SELECT substring(cfg FROM 'search_path=(.*)') FROM unnest(p.proconfig) AS cfg WHERE cfg LIKE 'search_path=%') AS search_path
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern substring(cfg FROM 'search_path=(.*)') will greedily match everything after the equals sign, including trailing whitespace or other content if the proconfig array contains multiple settings. While this may work for single settings, it could capture unintended characters.

Consider using a more precise pattern that accounts for PostgreSQL's actual storage format, or trimming the result to handle edge cases more robustly.

Suggested change
(SELECT substring(cfg FROM 'search_path=(.*)') FROM unnest(p.proconfig) AS cfg WHERE cfg LIKE 'search_path=%') AS search_path
(SELECT btrim(split_part(cfg, '=', 2)) FROM unnest(p.proconfig) AS cfg WHERE cfg LIKE 'search_path=%') AS search_path

Copilot uses AI. Check for mistakes.
FROM information_schema.routines r
LEFT JOIN pg_proc p ON p.proname = r.routine_name
AND p.pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = r.routine_schema)
Expand Down
5 changes: 4 additions & 1 deletion ir/queries/queries.sql.go

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

1 change: 1 addition & 0 deletions testdata/diff/create_function/add_function/diff.sql
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ STRICT
SECURITY DEFINER
LEAKPROOF
PARALLEL RESTRICTED
SET search_path = pg_catalog, public
AS $$
DECLARE
total numeric;
Expand Down
1 change: 1 addition & 0 deletions testdata/diff/create_function/add_function/new.sql
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ STRICT
SECURITY DEFINER
LEAKPROOF
PARALLEL RESTRICTED
SET search_path = pg_catalog, public
AS $$
DECLARE
total numeric;
Expand Down
2 changes: 1 addition & 1 deletion testdata/diff/create_function/add_function/plan.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"path": "public.mask_sensitive_data"
},
{
"sql": "CREATE OR REPLACE FUNCTION process_order(\n order_id integer,\n discount_percent numeric DEFAULT 0,\n priority_level integer DEFAULT 1,\n note varchar DEFAULT '',\n status text DEFAULT 'pending',\n apply_tax boolean DEFAULT true,\n is_priority boolean DEFAULT false,\n expiry_date date DEFAULT (CURRENT_DATE + '1 year'::interval)\n)\nRETURNS numeric\nLANGUAGE plpgsql\nVOLATILE\nSTRICT\nSECURITY DEFINER\nLEAKPROOF\nPARALLEL RESTRICTED\nAS $$\nDECLARE\n total numeric;\nBEGIN\n SELECT amount INTO total FROM orders WHERE id = order_id;\n RETURN total - (total * discount_percent / 100);\nEND;\n$$;",
"sql": "CREATE OR REPLACE FUNCTION process_order(\n order_id integer,\n discount_percent numeric DEFAULT 0,\n priority_level integer DEFAULT 1,\n note varchar DEFAULT '',\n status text DEFAULT 'pending',\n apply_tax boolean DEFAULT true,\n is_priority boolean DEFAULT false,\n expiry_date date DEFAULT (CURRENT_DATE + '1 year'::interval)\n)\nRETURNS numeric\nLANGUAGE plpgsql\nVOLATILE\nSTRICT\nSECURITY DEFINER\nLEAKPROOF\nPARALLEL RESTRICTED\nSET search_path = pg_catalog, public\nAS $$\nDECLARE\n total numeric;\nBEGIN\n SELECT amount INTO total FROM orders WHERE id = order_id;\n RETURN total - (total * discount_percent / 100);\nEND;\n$$;",
"type": "function",
"operation": "create",
"path": "public.process_order"
Expand Down
1 change: 1 addition & 0 deletions testdata/diff/create_function/add_function/plan.sql
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ STRICT
SECURITY DEFINER
LEAKPROOF
PARALLEL RESTRICTED
SET search_path = pg_catalog, public
AS $$
DECLARE
total numeric;
Expand Down
1 change: 1 addition & 0 deletions testdata/diff/create_function/add_function/plan.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ STRICT
SECURITY DEFINER
LEAKPROOF
PARALLEL RESTRICTED
SET search_path = pg_catalog, public
AS $$
DECLARE
total numeric;
Expand Down