Skip to content

Commit ac04253

Browse files
committed
fix(plpgsql-deparser): unwrap the compiler-generated wrapper block around top-level EXCEPTION blocks
1 parent 79ea724 commit ac04253

7 files changed

Lines changed: 595 additions & 470 deletions

File tree

‎__fixtures__/plpgsql-generated/generated.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@
157157
"plpgsql_deparser_fixes-56.sql": "-- Test 56: RAISE EXCEPTION with a named condition (must stay a bare identifier)\nCREATE FUNCTION test_raise_named_condition() RETURNS void\nLANGUAGE plpgsql AS $$\nBEGIN\n RAISE EXCEPTION division_by_zero;\nEND$$",
158158
"plpgsql_deparser_fixes-57.sql": "-- Test 57: Bare RETURN NEXT with OUT parameters (retvarno points at out_param_varno; must stay bare)\nCREATE FUNCTION test_return_next_out_params(OUT x integer, OUT y text) RETURNS SETOF record\nLANGUAGE plpgsql AS $$\nBEGIN\n FOR i IN 1..5 LOOP\n x := i;\n y := 'item_' || i::text;\n RETURN NEXT;\n END LOOP;\n RETURN;\nEND$$",
159159
"plpgsql_deparser_fixes-58.sql": "-- Test 58: RETURN NEXT with a variable (retvarno must be emitted as the variable name)\nCREATE FUNCTION test_return_next_var() RETURNS SETOF integer\nLANGUAGE plpgsql AS $$\nDECLARE\n r integer;\nBEGIN\n FOR r IN SELECT g FROM generate_series(1, 3) g LOOP\n RETURN NEXT r;\n END LOOP;\nEND$$",
160+
"plpgsql_deparser_fixes-59.sql": "-- Test 59: Top-level block with EXCEPTION clause (compiler wraps it in a synthetic outer block; must not deparse a nested BEGIN)\nCREATE FUNCTION test_toplevel_exception(a numeric, b numeric) RETURNS numeric\nLANGUAGE plpgsql AS $$\nDECLARE\n v_result numeric;\nBEGIN\n v_result := a / b;\n RETURN v_result;\nEXCEPTION\n WHEN division_by_zero THEN\n RETURN NULL;\nEND$$",
161+
"plpgsql_deparser_fixes-60.sql": "-- Test 60: Explicit nested block with EXCEPTION inside a top-level block (nesting must be preserved)\nCREATE FUNCTION test_explicit_nested_exception(p_id integer) RETURNS text\nLANGUAGE plpgsql AS $$\nDECLARE\n v_result text;\nBEGIN\n v_result := 'unknown';\n BEGIN\n SELECT status INTO v_result FROM items WHERE id = p_id;\n EXCEPTION\n WHEN no_data_found THEN\n v_result := 'not_found';\n END;\n RETURN v_result;\nEND$$",
160162
"plpgsql_control-1.sql": "--\n-- Tests for PL/pgSQL control structures\n--\n\n-- integer FOR loop\n\ndo $$\nbegin\n -- basic case\n for i in 1..3 loop\n raise notice '1..3: i = %', i;\n end loop;\n -- with BY, end matches exactly\n for i in 1..10 by 3 loop\n raise notice '1..10 by 3: i = %', i;\n end loop;\n -- with BY, end does not match\n for i in 1..11 by 3 loop\n raise notice '1..11 by 3: i = %', i;\n end loop;\n -- zero iterations\n for i in 1..0 by 3 loop\n raise notice '1..0 by 3: i = %', i;\n end loop;\n -- REVERSE\n for i in reverse 10..0 by 3 loop\n raise notice 'reverse 10..0 by 3: i = %', i;\n end loop;\n -- potential overflow\n for i in 2147483620..2147483647 by 10 loop\n raise notice '2147483620..2147483647 by 10: i = %', i;\n end loop;\n -- potential overflow, reverse direction\n for i in reverse -2147483620..-2147483647 by 10 loop\n raise notice 'reverse -2147483620..-2147483647 by 10: i = %', i;\n end loop;\nend$$",
161163
"plpgsql_control-2.sql": "-- BY can't be zero or negative\ndo $$\nbegin\n for i in 1..3 by 0 loop\n raise notice '1..3 by 0: i = %', i;\n end loop;\nend$$",
162164
"plpgsql_control-3.sql": "do $$\nbegin\n for i in 1..3 by -1 loop\n raise notice '1..3 by -1: i = %', i;\n end loop;\nend$$",

‎__fixtures__/plpgsql/plpgsql_deparser_fixes.sql‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,3 +725,32 @@ BEGIN
725725
RETURN NEXT r;
726726
END LOOP;
727727
END$$;
728+
729+
-- Test 59: Top-level block with EXCEPTION clause (compiler wraps it in a synthetic outer block; must not deparse a nested BEGIN)
730+
CREATE FUNCTION test_toplevel_exception(a numeric, b numeric) RETURNS numeric
731+
LANGUAGE plpgsql AS $$
732+
DECLARE
733+
v_result numeric;
734+
BEGIN
735+
v_result := a / b;
736+
RETURN v_result;
737+
EXCEPTION
738+
WHEN division_by_zero THEN
739+
RETURN NULL;
740+
END$$;
741+
742+
-- Test 60: Explicit nested block with EXCEPTION inside a top-level block (nesting must be preserved)
743+
CREATE FUNCTION test_explicit_nested_exception(p_id integer) RETURNS text
744+
LANGUAGE plpgsql AS $$
745+
DECLARE
746+
v_result text;
747+
BEGIN
748+
v_result := 'unknown';
749+
BEGIN
750+
SELECT status INTO v_result FROM items WHERE id = p_id;
751+
EXCEPTION
752+
WHEN no_data_found THEN
753+
v_result := 'not_found';
754+
END;
755+
RETURN v_result;
756+
END$$;

‎packages/plpgsql-deparser/__tests__/__snapshots__/deparser-fixes.test.ts.snap‎

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -238,25 +238,21 @@ END"
238238

239239
exports[`plpgsql-deparser bug fixes SQLSTATE exception conditions should emit SQLSTATE codes with the SQLSTATE keyword 1`] = `
240240
"BEGIN
241-
BEGIN
242-
RETURN 1;
243-
EXCEPTION
244-
WHEN unique_violation OR SQLSTATE '23503' THEN
245-
RETURN -1;
246-
WHEN SQLSTATE 'P0001' THEN
247-
RETURN -2;
248-
END;
241+
RETURN 1;
242+
EXCEPTION
243+
WHEN unique_violation OR SQLSTATE '23503' THEN
244+
RETURN -1;
245+
WHEN SQLSTATE 'P0001' THEN
246+
RETURN -2;
249247
END"
250248
`;
251249

252250
exports[`plpgsql-deparser bug fixes bare RAISE re-throw should keep a bare RAISE bare (not RAISE EXCEPTION;) 1`] = `
253251
"BEGIN
254-
BEGIN
255-
PERFORM 1;
256-
EXCEPTION
257-
WHEN others THEN
258-
RAISE;
259-
END;
252+
PERFORM 1;
253+
EXCEPTION
254+
WHEN others THEN
255+
RAISE;
260256
END"
261257
`;
262258

@@ -370,17 +366,15 @@ END"
370366

371367
exports[`plpgsql-deparser bug fixes deep nesting and sequential blocks should handle block inside exception handler 1`] = `
372368
"BEGIN
373-
BEGIN
374-
PERFORM risky();
375-
EXCEPTION
376-
WHEN others THEN
377-
BEGIN
378-
PERFORM log_error();
379-
EXCEPTION
380-
WHEN others THEN
381-
RAISE NOTICE 'even logging failed';
382-
END;
383-
END;
369+
PERFORM risky();
370+
EXCEPTION
371+
WHEN others THEN
372+
BEGIN
373+
PERFORM log_error();
374+
EXCEPTION
375+
WHEN others THEN
376+
RAISE NOTICE 'even logging failed';
377+
END;
384378
END"
385379
`;
386380

@@ -548,6 +542,33 @@ BEGIN
548542
END"
549543
`;
550544
545+
exports[`plpgsql-deparser bug fixes top-level EXCEPTION wrapper block should not emit a nested BEGIN for a top-level block with EXCEPTION 1`] = `
546+
"DECLARE
547+
v_result numeric;
548+
BEGIN
549+
v_result := a / b;
550+
RETURN v_result;
551+
EXCEPTION
552+
WHEN division_by_zero THEN
553+
RETURN NULL;
554+
END"
555+
`;
556+
557+
exports[`plpgsql-deparser bug fixes top-level EXCEPTION wrapper block should preserve an explicit nested block with EXCEPTION 1`] = `
558+
"DECLARE
559+
v_result text;
560+
BEGIN
561+
v_result := 'unknown';
562+
BEGIN
563+
SELECT status INTO v_result FROM items WHERE id = p_id;
564+
EXCEPTION
565+
WHEN no_data_found THEN
566+
v_result := 'not_found';
567+
END;
568+
RETURN v_result;
569+
END"
570+
`;
571+
551572
exports[`plpgsql-deparser bug fixes untested statement types should handle ASSERT statement 1`] = `
552573
"BEGIN
553574
ASSERT p_x > 0, 'x must be positive';

0 commit comments

Comments
 (0)