diff --git a/internal/diff/function.go b/internal/diff/function.go index eb9db267..ad09cd66 100644 --- a/internal/diff/function.go +++ b/internal/diff/function.go @@ -63,8 +63,27 @@ func generateDropFunctionsSQL(functions []*ir.Function, targetSchema string, col for _, function := range sortedFunctions { functionName := qualifyEntityName(function.Schema, function.Name, targetSchema) var sql string - if function.Arguments != "" { - sql = fmt.Sprintf("DROP FUNCTION IF EXISTS %s(%s);", functionName, function.Arguments) + + // Build argument list for DROP statement using normalized Parameters array + var argsList string + if len(function.Parameters) > 0 { + // Format parameters for DROP (omit names and defaults, include only types) + // Per PostgreSQL docs, DROP FUNCTION only needs input arguments (IN, INOUT, VARIADIC) + // Exclude OUT and TABLE mode parameters as they're part of the return signature + var argTypes []string + for _, param := range function.Parameters { + // Include only input parameter modes: IN (empty/implicit), INOUT, VARIADIC + if param.Mode == "" || param.Mode == "IN" || param.Mode == "INOUT" || param.Mode == "VARIADIC" { + argTypes = append(argTypes, param.DataType) + } + } + argsList = strings.Join(argTypes, ", ") + } else if function.Arguments != "" { + argsList = function.Arguments + } + + if argsList != "" { + sql = fmt.Sprintf("DROP FUNCTION IF EXISTS %s(%s);", functionName, argsList) } else { sql = fmt.Sprintf("DROP FUNCTION IF EXISTS %s();", functionName) } @@ -90,8 +109,22 @@ func generateFunctionSQL(function *ir.Function, targetSchema string) string { functionName := qualifyEntityName(function.Schema, function.Name, targetSchema) stmt.WriteString(fmt.Sprintf("CREATE OR REPLACE FUNCTION %s", functionName)) - // Add parameters using detailed signature if available - if function.Signature != "" { + // Add parameters - prefer structured Parameters array for normalized types + if len(function.Parameters) > 0 { + // Build parameter list from structured Parameters array + // Exclude TABLE mode parameters as they're part of RETURNS clause + var paramParts []string + for _, param := range function.Parameters { + if param.Mode != "TABLE" { + paramParts = append(paramParts, formatFunctionParameter(param, true)) + } + } + if len(paramParts) > 0 { + stmt.WriteString(fmt.Sprintf("(\n %s\n)", strings.Join(paramParts, ",\n "))) + } else { + stmt.WriteString("()") + } + } else if function.Signature != "" { stmt.WriteString(fmt.Sprintf("(\n %s\n)", strings.ReplaceAll(function.Signature, ", ", ",\n "))) } else if function.Arguments != "" { stmt.WriteString(fmt.Sprintf("(%s)", function.Arguments)) @@ -184,6 +217,32 @@ func containsParameterReferences(body string) bool { return false } +// formatFunctionParameter formats a single function parameter with name, type, and optional default value +// For functions, mode is typically omitted (unlike procedures) unless it's OUT/INOUT +// includeDefault controls whether DEFAULT clauses are included in the output +func formatFunctionParameter(param *ir.Parameter, includeDefault bool) string { + var part string + + // For functions, only include mode if it's OUT or INOUT (IN is implicit) + if param.Mode == "OUT" || param.Mode == "INOUT" || param.Mode == "VARIADIC" { + part = param.Mode + " " + } + + // Add parameter name and type + if param.Name != "" { + part += param.Name + " " + param.DataType + } else { + part += param.DataType + } + + // Add DEFAULT value if present and requested + if includeDefault && param.DefaultValue != nil { + part += " DEFAULT " + *param.DefaultValue + } + + return part +} + // functionsEqual compares two functions for equality func functionsEqual(old, new *ir.Function) bool { if old.Schema != new.Schema { @@ -201,11 +260,76 @@ func functionsEqual(old, new *ir.Function) bool { if old.Language != new.Language { return false } + + // For RETURNS TABLE functions, the Parameters array includes TABLE output columns + // which can cause comparison issues. In this case, rely on ReturnType comparison instead. + isTableReturn := strings.HasPrefix(old.ReturnType, "TABLE(") || strings.HasPrefix(new.ReturnType, "TABLE(") + + if !isTableReturn { + // For non-TABLE functions, compare using normalized Parameters array + // This ensures type aliases like "character varying" vs "varchar" are treated as equal + hasOldParams := len(old.Parameters) > 0 + hasNewParams := len(new.Parameters) > 0 + + if hasOldParams && hasNewParams { + // Both have Parameters - compare them + return parametersEqual(old.Parameters, new.Parameters) + } else if hasOldParams || hasNewParams { + // One has Parameters, one doesn't - they're different + return false + } + } + + // For TABLE functions or functions without Parameters, fall back to Arguments/Signature if old.Arguments != new.Arguments { return false } if old.Signature != new.Signature { return false } + + return true +} + +// parametersEqual compares two parameter arrays for equality +func parametersEqual(oldParams, newParams []*ir.Parameter) bool { + if len(oldParams) != len(newParams) { + return false + } + + for i := range oldParams { + if !parameterEqual(oldParams[i], newParams[i]) { + return false + } + } + + return true +} + +// parameterEqual compares two parameters for equality +func parameterEqual(old, new *ir.Parameter) bool { + if old.Name != new.Name { + return false + } + + // Compare data types (already normalized by ir.normalizeFunction) + if old.DataType != new.DataType { + return false + } + + if old.Mode != new.Mode { + return false + } + + // Compare default values + if (old.DefaultValue == nil) != (new.DefaultValue == nil) { + return false + } + if old.DefaultValue != nil && new.DefaultValue != nil { + if *old.DefaultValue != *new.DefaultValue { + return false + } + } + return true } diff --git a/ir/normalize.go b/ir/normalize.go index d5552e5b..262f56f3 100644 --- a/ir/normalize.go +++ b/ir/normalize.go @@ -245,6 +245,27 @@ func normalizeFunction(function *Function) { param.DataType = normalizePostgreSQLType(param.DataType) } } + // Normalize function body to handle whitespace differences + function.Definition = normalizeFunctionDefinition(function.Definition) +} + +// normalizeFunctionDefinition normalizes function body whitespace +// PostgreSQL stores function bodies with specific whitespace that may differ from source +func normalizeFunctionDefinition(def string) string { + if def == "" { + return def + } + + // Only trim trailing whitespace from each line, preserving the line structure + // This ensures leading/trailing blank lines are preserved (matching PostgreSQL storage) + lines := strings.Split(def, "\n") + var normalized []string + for _, line := range lines { + // Trim trailing whitespace but preserve leading whitespace for indentation + normalized = append(normalized, strings.TrimRight(line, " \t")) + } + + return strings.Join(normalized, "\n") } // normalizeProcedure normalizes procedure representation diff --git a/ir/queries/queries.sql b/ir/queries/queries.sql index bac9af20..5c279fa6 100644 --- a/ir/queries/queries.sql +++ b/ir/queries/queries.sql @@ -741,7 +741,7 @@ ORDER BY s.schemaname, s.sequencename; SELECT r.routine_schema, r.routine_name, - p.prosrc AS routine_definition, + 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, @@ -760,7 +760,7 @@ SELECT p.proargnames, p.proallargtypes::oid[]::text[] as proallargtypes FROM information_schema.routines r -LEFT JOIN pg_proc p ON p.proname = r.routine_name +LEFT JOIN pg_proc p ON p.proname = r.routine_name AND p.pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = r.routine_schema) LEFT JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e' LEFT JOIN pg_description desc_func ON desc_func.objoid = p.oid AND desc_func.classoid = 'pg_proc'::regclass @@ -774,14 +774,14 @@ ORDER BY r.routine_schema, r.routine_name; SELECT r.routine_schema, r.routine_name, - p.prosrc AS routine_definition, + 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, oidvectortypes(p.proargtypes) AS procedure_arguments, pg_get_function_arguments(p.oid) AS procedure_signature FROM information_schema.routines r -LEFT JOIN pg_proc p ON p.proname = r.routine_name +LEFT JOIN pg_proc p ON p.proname = r.routine_name AND p.pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = r.routine_schema) LEFT JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e' LEFT JOIN pg_description desc_proc ON desc_proc.objoid = p.oid AND desc_proc.classoid = 'pg_proc'::regclass diff --git a/ir/queries/queries.sql.go b/ir/queries/queries.sql.go index 5d78c767..0c5ffddf 100644 --- a/ir/queries/queries.sql.go +++ b/ir/queries/queries.sql.go @@ -1185,7 +1185,7 @@ const getFunctionsForSchema = `-- name: GetFunctionsForSchema :many SELECT r.routine_schema, r.routine_name, - p.prosrc AS routine_definition, + 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, @@ -1204,7 +1204,7 @@ SELECT p.proargnames, p.proallargtypes::oid[]::text[] as proallargtypes FROM information_schema.routines r -LEFT JOIN pg_proc p ON p.proname = r.routine_name +LEFT JOIN pg_proc p ON p.proname = r.routine_name AND p.pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = r.routine_schema) LEFT JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e' LEFT JOIN pg_description desc_func ON desc_func.objoid = p.oid AND desc_func.classoid = 'pg_proc'::regclass @@ -1217,7 +1217,7 @@ ORDER BY r.routine_schema, r.routine_name type GetFunctionsForSchemaRow struct { RoutineSchema interface{} `db:"routine_schema" json:"routine_schema"` RoutineName interface{} `db:"routine_name" json:"routine_name"` - RoutineDefinition string `db:"routine_definition" json:"routine_definition"` + RoutineDefinition sql.NullString `db:"routine_definition" json:"routine_definition"` RoutineType interface{} `db:"routine_type" json:"routine_type"` DataType sql.NullString `db:"data_type" json:"data_type"` ExternalLanguage interface{} `db:"external_language" json:"external_language"` @@ -1633,14 +1633,14 @@ const getProceduresForSchema = `-- name: GetProceduresForSchema :many SELECT r.routine_schema, r.routine_name, - p.prosrc AS routine_definition, + 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, oidvectortypes(p.proargtypes) AS procedure_arguments, pg_get_function_arguments(p.oid) AS procedure_signature FROM information_schema.routines r -LEFT JOIN pg_proc p ON p.proname = r.routine_name +LEFT JOIN pg_proc p ON p.proname = r.routine_name AND p.pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = r.routine_schema) LEFT JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e' LEFT JOIN pg_description desc_proc ON desc_proc.objoid = p.oid AND desc_proc.classoid = 'pg_proc'::regclass @@ -1653,7 +1653,7 @@ ORDER BY r.routine_schema, r.routine_name type GetProceduresForSchemaRow struct { RoutineSchema interface{} `db:"routine_schema" json:"routine_schema"` RoutineName interface{} `db:"routine_name" json:"routine_name"` - RoutineDefinition string `db:"routine_definition" json:"routine_definition"` + RoutineDefinition sql.NullString `db:"routine_definition" json:"routine_definition"` RoutineType interface{} `db:"routine_type" json:"routine_type"` ExternalLanguage interface{} `db:"external_language" json:"external_language"` ProcedureComment sql.NullString `db:"procedure_comment" json:"procedure_comment"` diff --git a/testdata/diff/create_function/drop_function/diff.sql b/testdata/diff/create_function/drop_function/diff.sql index 1d07c065..ecdbb813 100644 --- a/testdata/diff/create_function/drop_function/diff.sql +++ b/testdata/diff/create_function/drop_function/diff.sql @@ -1 +1,3 @@ +DROP FUNCTION IF EXISTS get_user_stats(integer); DROP FUNCTION IF EXISTS process_order(integer, numeric); +DROP FUNCTION IF EXISTS process_payment(integer, text); diff --git a/testdata/diff/create_function/drop_function/old.sql b/testdata/diff/create_function/drop_function/old.sql index 367cae29..a5b0db11 100644 --- a/testdata/diff/create_function/drop_function/old.sql +++ b/testdata/diff/create_function/drop_function/old.sql @@ -13,4 +13,45 @@ BEGIN SELECT amount INTO total FROM orders WHERE id = order_id; RETURN total - (total * discount_percent / 100); END; +$$; + +-- Function with OUT parameters - tests that DROP only includes IN parameters +CREATE FUNCTION get_user_stats( + user_id integer, + OUT total_orders integer, + OUT total_amount numeric, + OUT last_order_date timestamp +) +LANGUAGE plpgsql +SECURITY INVOKER +STABLE +AS $$ +BEGIN + SELECT + COUNT(*), + COALESCE(SUM(amount), 0), + MAX(order_date) + INTO total_orders, total_amount, last_order_date + FROM orders + WHERE orders.user_id = get_user_stats.user_id; +END; +$$; + +-- Function with mixed IN, OUT, and INOUT parameters +CREATE FUNCTION process_payment( + IN order_id integer, + INOUT transaction_id text, + OUT status text, + OUT processed_at timestamp +) +LANGUAGE plpgsql +SECURITY INVOKER +VOLATILE +AS $$ +BEGIN + -- Process payment logic + transaction_id := 'TXN-' || transaction_id; + status := 'SUCCESS'; + processed_at := NOW(); +END; $$; \ No newline at end of file diff --git a/testdata/diff/create_function/drop_function/plan.json b/testdata/diff/create_function/drop_function/plan.json index 5bbd0fdc..32a0ce45 100644 --- a/testdata/diff/create_function/drop_function/plan.json +++ b/testdata/diff/create_function/drop_function/plan.json @@ -3,16 +3,28 @@ "pgschema_version": "1.4.0", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "912618f23b4d55b8abcf56d0155b6be724ffe7e43548af3391ec0b7cbcff5f6d" + "hash": "088aa747b7e56f2117edee2741301754f9890301077cbc83f29339c7aeccb41e" }, "groups": [ { "steps": [ + { + "sql": "DROP FUNCTION IF EXISTS get_user_stats(integer);", + "type": "function", + "operation": "drop", + "path": "public.get_user_stats" + }, { "sql": "DROP FUNCTION IF EXISTS process_order(integer, numeric);", "type": "function", "operation": "drop", "path": "public.process_order" + }, + { + "sql": "DROP FUNCTION IF EXISTS process_payment(integer, text);", + "type": "function", + "operation": "drop", + "path": "public.process_payment" } ] } diff --git a/testdata/diff/create_function/drop_function/plan.sql b/testdata/diff/create_function/drop_function/plan.sql index 1d07c065..6c482a5a 100644 --- a/testdata/diff/create_function/drop_function/plan.sql +++ b/testdata/diff/create_function/drop_function/plan.sql @@ -1 +1,5 @@ +DROP FUNCTION IF EXISTS get_user_stats(integer); + DROP FUNCTION IF EXISTS process_order(integer, numeric); + +DROP FUNCTION IF EXISTS process_payment(integer, text); diff --git a/testdata/diff/create_function/drop_function/plan.txt b/testdata/diff/create_function/drop_function/plan.txt index d1dd4e3a..b5f5e5b6 100644 --- a/testdata/diff/create_function/drop_function/plan.txt +++ b/testdata/diff/create_function/drop_function/plan.txt @@ -1,12 +1,18 @@ -Plan: 1 to drop. +Plan: 3 to drop. Summary by type: - functions: 1 to drop + functions: 3 to drop Functions: + - get_user_stats - process_order + - process_payment DDL to be executed: -------------------------------------------------- +DROP FUNCTION IF EXISTS get_user_stats(integer); + DROP FUNCTION IF EXISTS process_order(integer, numeric); + +DROP FUNCTION IF EXISTS process_payment(integer, text); diff --git a/testdata/diff/migrate/v4/plan.json b/testdata/diff/migrate/v4/plan.json index 7ef3b022..27c84d47 100644 --- a/testdata/diff/migrate/v4/plan.json +++ b/testdata/diff/migrate/v4/plan.json @@ -97,7 +97,7 @@ "path": "public.salary.salary_log_trigger" }, { - "sql": "CREATE OR REPLACE FUNCTION log_dml_operations()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY INVOKER\nVOLATILE\nAS $$\nDECLARE\n table_category TEXT;\n log_level TEXT;\nBEGIN\n -- Get arguments passed from trigger (if any)\n -- TG_ARGV[0] is the first argument, TG_ARGV[1] is the second\n table_category := COALESCE(TG_ARGV[0], 'default');\n log_level := COALESCE(TG_ARGV[1], 'standard');\n \n IF (TG_OP = 'INSERT') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'INSERT [' || table_category || ':' || log_level || ']', \n current_query(), \n current_user\n );\n RETURN NEW;\n ELSIF (TG_OP = 'UPDATE') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'UPDATE [' || table_category || ':' || log_level || ']', \n current_query(), \n current_user\n );\n RETURN NEW;\n ELSIF (TG_OP = 'DELETE') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'DELETE [' || table_category || ':' || log_level || ']', \n current_query(), \n current_user\n );\n RETURN OLD;\n END IF;\n RETURN NULL;\nEND;\n$$;", + "sql": "CREATE OR REPLACE FUNCTION log_dml_operations()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY INVOKER\nVOLATILE\nAS $$\nDECLARE\n table_category TEXT;\n log_level TEXT;\nBEGIN\n -- Get arguments passed from trigger (if any)\n -- TG_ARGV[0] is the first argument, TG_ARGV[1] is the second\n table_category := COALESCE(TG_ARGV[0], 'default');\n log_level := COALESCE(TG_ARGV[1], 'standard');\n\n IF (TG_OP = 'INSERT') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'INSERT [' || table_category || ':' || log_level || ']',\n current_query(),\n current_user\n );\n RETURN NEW;\n ELSIF (TG_OP = 'UPDATE') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'UPDATE [' || table_category || ':' || log_level || ']',\n current_query(),\n current_user\n );\n RETURN NEW;\n ELSIF (TG_OP = 'DELETE') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'DELETE [' || table_category || ':' || log_level || ']',\n current_query(),\n current_user\n );\n RETURN OLD;\n END IF;\n RETURN NULL;\nEND;\n$$;", "type": "function", "operation": "alter", "path": "public.log_dml_operations" diff --git a/testdata/diff/migrate/v4/plan.sql b/testdata/diff/migrate/v4/plan.sql index afde305e..7adbba49 100644 --- a/testdata/diff/migrate/v4/plan.sql +++ b/testdata/diff/migrate/v4/plan.sql @@ -96,28 +96,28 @@ BEGIN -- TG_ARGV[0] is the first argument, TG_ARGV[1] is the second table_category := COALESCE(TG_ARGV[0], 'default'); log_level := COALESCE(TG_ARGV[1], 'standard'); - + IF (TG_OP = 'INSERT') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'INSERT [' || table_category || ':' || log_level || ']', - current_query(), + 'INSERT [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN NEW; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'UPDATE [' || table_category || ':' || log_level || ']', - current_query(), + 'UPDATE [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN NEW; ELSIF (TG_OP = 'DELETE') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'DELETE [' || table_category || ':' || log_level || ']', - current_query(), + 'DELETE [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN OLD; diff --git a/testdata/diff/migrate/v4/plan.txt b/testdata/diff/migrate/v4/plan.txt index c35b842b..522cac0c 100644 --- a/testdata/diff/migrate/v4/plan.txt +++ b/testdata/diff/migrate/v4/plan.txt @@ -135,28 +135,28 @@ BEGIN -- TG_ARGV[0] is the first argument, TG_ARGV[1] is the second table_category := COALESCE(TG_ARGV[0], 'default'); log_level := COALESCE(TG_ARGV[1], 'standard'); - + IF (TG_OP = 'INSERT') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'INSERT [' || table_category || ':' || log_level || ']', - current_query(), + 'INSERT [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN NEW; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'UPDATE [' || table_category || ':' || log_level || ']', - current_query(), + 'UPDATE [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN NEW; ELSIF (TG_OP = 'DELETE') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'DELETE [' || table_category || ':' || log_level || ']', - current_query(), + 'DELETE [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN OLD; diff --git a/testdata/diff/migrate/v5/plan.json b/testdata/diff/migrate/v5/plan.json index 40483069..af365a85 100644 --- a/testdata/diff/migrate/v5/plan.json +++ b/testdata/diff/migrate/v5/plan.json @@ -3,7 +3,7 @@ "pgschema_version": "1.4.0", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "d52cafb6d1d287bbe0a19d56d4c2ee878602a34329b795803ba57cd6eade2636" + "hash": "e3d1814aca3de8f3c5c86812161fabcc15764d3d41ef4f141f892a3cef6c2d38" }, "groups": [ { diff --git a/testdata/dump/employee/pgschema.sql b/testdata/dump/employee/pgschema.sql index 69a7b5f7..4de25970 100644 --- a/testdata/dump/employee/pgschema.sql +++ b/testdata/dump/employee/pgschema.sql @@ -165,28 +165,28 @@ BEGIN -- TG_ARGV[0] is the first argument, TG_ARGV[1] is the second table_category := COALESCE(TG_ARGV[0], 'default'); log_level := COALESCE(TG_ARGV[1], 'standard'); - + IF (TG_OP = 'INSERT') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'INSERT [' || table_category || ':' || log_level || ']', - current_query(), + 'INSERT [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN NEW; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'UPDATE [' || table_category || ':' || log_level || ']', - current_query(), + 'UPDATE [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN NEW; ELSIF (TG_OP = 'DELETE') THEN INSERT INTO audit (operation, query, user_name) VALUES ( - 'DELETE [' || table_category || ':' || log_level || ']', - current_query(), + 'DELETE [' || table_category || ':' || log_level || ']', + current_query(), current_user ); RETURN OLD; diff --git a/testdata/dump/sakila/pgschema.sql b/testdata/dump/sakila/pgschema.sql index d0ba9a1d..f0db0ba1 100644 --- a/testdata/dump/sakila/pgschema.sql +++ b/testdata/dump/sakila/pgschema.sql @@ -659,7 +659,7 @@ $_$; CREATE OR REPLACE FUNCTION get_customer_balance( p_customer_id integer, - p_effective_date timestamp with time zone + p_effective_date timestamptz ) RETURNS numeric LANGUAGE plpgsql @@ -723,7 +723,8 @@ BEGIN AND inventory_id = p_inventory_id; RETURN v_customer_id; -END $$; +END +$$; -- -- Name: inventory_in_stock; Type: FUNCTION; Schema: -; Owner: - @@ -762,7 +763,8 @@ BEGIN ELSE RETURN TRUE; END IF; -END $$; +END +$$; -- -- Name: last_day; Type: FUNCTION; Schema: -; Owner: - @@ -798,7 +800,8 @@ AS $$ BEGIN NEW.last_update = CURRENT_TIMESTAMP; RETURN NEW; -END $$; +END +$$; -- -- Name: rewards_report; Type: FUNCTION; Schema: -; Owner: -