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
15 changes: 12 additions & 3 deletions internal/diff/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,19 @@ func generateFunctionSQL(function *ir.Function, targetSchema string) string {
stmt.WriteString("\nSTRICT")
}

// Add the function body with proper dollar quoting
// Add the function body
if function.Definition != "" {
tag := generateDollarQuoteTag(function.Definition)
stmt.WriteString(fmt.Sprintf("\nAS %s%s%s;", tag, function.Definition, tag))
// Check if this uses RETURN clause syntax (PG14+)
// pg_get_function_sqlbody returns "RETURN expression" which should not be wrapped
// Use case-insensitive comparison to handle all variations
trimmedDef := strings.TrimSpace(function.Definition)
if len(trimmedDef) >= 7 && strings.EqualFold(trimmedDef[:7], "RETURN ") {
stmt.WriteString(fmt.Sprintf("\n%s;", trimmedDef))
} else {
// Traditional AS $$ ... $$ syntax
tag := generateDollarQuoteTag(function.Definition)
stmt.WriteString(fmt.Sprintf("\nAS %s%s%s;", tag, function.Definition, tag))
}
} else {
stmt.WriteString("\nAS $$$$;")
}
Expand Down
15 changes: 12 additions & 3 deletions internal/diff/procedure.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,19 @@ func generateProcedureSQL(procedure *ir.Procedure, targetSchema string) string {
// Note: Procedures don't have SECURITY DEFINER/INVOKER in PostgreSQL
// This is a function-only feature

// Add the procedure body with proper dollar quoting
// Add the procedure body
if procedure.Definition != "" {
tag := generateProcedureDollarQuoteTag(procedure.Definition)
stmt.WriteString(fmt.Sprintf("\nAS %s%s%s;", tag, procedure.Definition, tag))
// Check if this uses RETURN clause syntax (PG14+)
// pg_get_function_sqlbody returns "RETURN expression" which should not be wrapped
// Use case-insensitive comparison to handle all variations
trimmedDef := strings.TrimSpace(procedure.Definition)
if len(trimmedDef) >= 7 && strings.EqualFold(trimmedDef[:7], "RETURN ") {
stmt.WriteString(fmt.Sprintf("\n%s;", trimmedDef))
} else {
// Traditional AS $$ ... $$ syntax
tag := generateProcedureDollarQuoteTag(procedure.Definition)
stmt.WriteString(fmt.Sprintf("\nAS %s%s%s;", tag, procedure.Definition, tag))
}
} else {
stmt.WriteString("\nAS $$$$;")
}
Expand Down
14 changes: 12 additions & 2 deletions ir/queries/queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,12 @@ ORDER BY s.schemaname, s.sequencename;
SELECT
r.routine_schema,
r.routine_name,
CASE WHEN p.prosrc ~ E'\n$' THEN p.prosrc ELSE p.prosrc || E'\n' END AS routine_definition,
-- Use pg_get_function_sqlbody for RETURN clause syntax (PG14+)
-- Fall back to prosrc for traditional AS $$ ... $$ syntax
COALESCE(
pg_get_function_sqlbody(p.oid),
CASE WHEN p.prosrc ~ E'\n$' THEN p.prosrc ELSE p.prosrc || E'\n' END
) AS routine_definition,
r.routine_type,
COALESCE(pg_get_function_result(p.oid), r.data_type) AS data_type,
r.external_language,
Expand Down Expand Up @@ -774,7 +779,12 @@ ORDER BY r.routine_schema, r.routine_name;
SELECT
r.routine_schema,
r.routine_name,
CASE WHEN p.prosrc ~ E'\n$' THEN p.prosrc ELSE p.prosrc || E'\n' END AS routine_definition,
-- Use pg_get_function_sqlbody for RETURN clause syntax (PG14+)
-- Fall back to prosrc for traditional AS $$ ... $$ syntax
COALESCE(
pg_get_function_sqlbody(p.oid),
CASE WHEN p.prosrc ~ E'\n$' THEN p.prosrc ELSE p.prosrc || E'\n' END
) AS routine_definition,
r.routine_type,
r.external_language,
COALESCE(desc_proc.description, '') AS procedure_comment,
Expand Down
14 changes: 12 additions & 2 deletions ir/queries/queries.sql.go

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

7 changes: 7 additions & 0 deletions testdata/diff/create_function/add_function/diff.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
CREATE OR REPLACE FUNCTION days_since_special_date()
RETURNS SETOF timestamp with time zone
LANGUAGE sql
SECURITY INVOKER
STABLE
RETURN generate_series((date_trunc('day'::text, '2025-01-01 00:00:00'::timestamp without time zone))::timestamp with time zone, date_trunc('day'::text, now()), '1 day'::interval);

CREATE OR REPLACE FUNCTION process_order(
order_id integer,
discount_percent numeric DEFAULT 0,
Expand Down
7 changes: 6 additions & 1 deletion testdata/diff/create_function/add_function/new.sql
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ BEGIN
SELECT amount INTO total FROM orders WHERE id = order_id;
RETURN total - (total * discount_percent / 100);
END;
$$;
$$;

-- Table function with RETURN clause (bug report test case)
CREATE FUNCTION days_since_special_date() RETURNS SETOF timestamptz
LANGUAGE sql STABLE PARALLEL SAFE
RETURN generate_series(date_trunc('day', '2025-01-01'::timestamp), date_trunc('day', NOW()), '1 day'::interval);
6 changes: 6 additions & 0 deletions testdata/diff/create_function/add_function/plan.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
"groups": [
{
"steps": [
{
"sql": "CREATE OR REPLACE FUNCTION days_since_special_date()\nRETURNS SETOF timestamp with time zone\nLANGUAGE sql\nSECURITY INVOKER\nSTABLE\nRETURN generate_series((date_trunc('day'::text, '2025-01-01 00:00:00'::timestamp without time zone))::timestamp with time zone, date_trunc('day'::text, now()), '1 day'::interval);",
"type": "function",
"operation": "create",
"path": "public.days_since_special_date"
},
{
"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)\nRETURNS numeric\nLANGUAGE plpgsql\nSECURITY DEFINER\nVOLATILE\nSTRICT\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",
Expand Down
7 changes: 7 additions & 0 deletions testdata/diff/create_function/add_function/plan.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
CREATE OR REPLACE FUNCTION days_since_special_date()
RETURNS SETOF timestamp with time zone
LANGUAGE sql
SECURITY INVOKER
STABLE
RETURN generate_series((date_trunc('day'::text, '2025-01-01 00:00:00'::timestamp without time zone))::timestamp with time zone, date_trunc('day'::text, now()), '1 day'::interval);

CREATE OR REPLACE FUNCTION process_order(
order_id integer,
discount_percent numeric DEFAULT 0,
Expand Down
12 changes: 10 additions & 2 deletions testdata/diff/create_function/add_function/plan.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
Plan: 1 to add.
Plan: 2 to add.

Summary by type:
functions: 1 to add
functions: 2 to add

Functions:
+ days_since_special_date
+ process_order

DDL to be executed:
--------------------------------------------------

CREATE OR REPLACE FUNCTION days_since_special_date()
RETURNS SETOF timestamp with time zone
LANGUAGE sql
SECURITY INVOKER
STABLE
RETURN generate_series((date_trunc('day'::text, '2025-01-01 00:00:00'::timestamp without time zone))::timestamp with time zone, date_trunc('day'::text, now()), '1 day'::interval);

CREATE OR REPLACE FUNCTION process_order(
order_id integer,
discount_percent numeric DEFAULT 0,
Expand Down