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
9 changes: 9 additions & 0 deletions __fixtures__/plpgsql-generated/generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@
"plpgsql_deparser_fixes-35.sql": "-- Test 35: CALL statement\nCREATE FUNCTION test_call_statement() RETURNS void\nLANGUAGE plpgsql AS $$\nBEGIN\n CALL my_procedure(1, 'hello');\n RETURN;\nEND$$",
"plpgsql_deparser_fixes-36.sql": "-- =============================================================================\n-- Edge Case Tests: Real-World Patterns\n-- =============================================================================\n\n-- Test 36: Permission bitnum trigger pattern (the function that exposed the END; bug)\nCREATE FUNCTION test_permission_bitnum_trigger() RETURNS trigger\nLANGUAGE plpgsql AS $$\nDECLARE\n bitlen int;\n v_len int;\nBEGIN\n v_len := 32;\n BEGIN\n bitlen := bit_length(NEW.bitstr);\n EXCEPTION\n WHEN others THEN\n bitlen := 0;\n END;\n IF bitlen = 0 THEN\n NEW.bitstr := lpad('', v_len, '0');\n END IF;\n RETURN NEW;\nEND$$",
"plpgsql_deparser_fixes-37.sql": "-- Test 37: Multi-step sign-in pattern (deeply nested IF chains)\nCREATE FUNCTION test_signin_pattern(v_email text) RETURNS record\nLANGUAGE plpgsql AS $$\nDECLARE\n v_user record;\n v_secret record;\nBEGIN\n SELECT * INTO v_user FROM users WHERE email = v_email;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'USER_NOT_FOUND';\n END IF;\n SELECT * INTO v_secret FROM secrets WHERE user_id = v_user.id;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'NO_CREDENTIALS';\n END IF;\n IF v_secret.locked_at IS NOT NULL THEN\n RAISE EXCEPTION 'ACCOUNT_LOCKED';\n END IF;\n RETURN v_user;\nEND$$",
"plpgsql_deparser_fixes-38.sql": "-- Test 38: INSERT ... RETURNING ... INTO (INTO must be re-inserted after RETURNING)\nCREATE FUNCTION test_insert_returning_into() RETURNS uuid\nLANGUAGE plpgsql AS $$\nDECLARE\n v_id uuid;\nBEGIN\n INSERT INTO s.t (name) VALUES ('x') RETURNING id INTO v_id;\n RETURN v_id;\nEND$$",
"plpgsql_deparser_fixes-39.sql": "-- Test 39: UPDATE ... RETURNING ... INTO\nCREATE FUNCTION test_update_returning_into() RETURNS uuid\nLANGUAGE plpgsql AS $$\nDECLARE\n v_id uuid;\nBEGIN\n UPDATE s.t SET name = 'y' WHERE name = 'x' RETURNING id INTO v_id;\n RETURN v_id;\nEND$$",
"plpgsql_deparser_fixes-40.sql": "-- Test 40: DELETE ... RETURNING ... INTO\nCREATE FUNCTION test_delete_returning_into() RETURNS uuid\nLANGUAGE plpgsql AS $$\nDECLARE\n v_id uuid;\nBEGIN\n DELETE FROM s.t WHERE name = 'x' RETURNING id INTO v_id;\n RETURN v_id;\nEND$$",
"plpgsql_deparser_fixes-41.sql": "-- Test 41: INSERT ... RETURNING ... INTO STRICT\nCREATE FUNCTION test_insert_returning_into_strict() RETURNS uuid\nLANGUAGE plpgsql AS $$\nDECLARE\n v_id uuid;\nBEGIN\n INSERT INTO s.t (name) VALUES ('x') RETURNING id INTO STRICT v_id;\n RETURN v_id;\nEND$$",
"plpgsql_deparser_fixes-42.sql": "-- Test 42: INSERT ... RETURNING multiple columns INTO\nCREATE FUNCTION test_insert_returning_multi_into() RETURNS void\nLANGUAGE plpgsql AS $$\nDECLARE\n v_id uuid;\n v_name text;\nBEGIN\n INSERT INTO s.t (name) VALUES ('x') RETURNING id, name INTO v_id, v_name;\nEND$$",
"plpgsql_deparser_fixes-43.sql": "-- Test 43: INSERT ... RETURNING expression with subquery INTO (INTO must not land inside the subquery)\nCREATE FUNCTION test_insert_returning_subquery_into() RETURNS void\nLANGUAGE plpgsql AS $$\nDECLARE\n v_total bigint;\nBEGIN\n INSERT INTO s.t (name) VALUES ('x') RETURNING (SELECT count(*) FROM s.t WHERE name = 'x') INTO v_total;\nEND$$",
"plpgsql_deparser_fixes-44.sql": "-- Test 44: Trigger function with no final return (implicit compiler RETURN must not be emitted)\nCREATE FUNCTION test_trigger_no_final_return() RETURNS trigger\nLANGUAGE plpgsql AS $$\nBEGIN\n IF TG_OP = 'INSERT' THEN\n RETURN NEW;\n END IF;\nEND$$",
"plpgsql_deparser_fixes-45.sql": "-- Test 45: Void function with explicit trailing RETURN (must be preserved)\nCREATE FUNCTION test_void_explicit_return() RETURNS void\nLANGUAGE plpgsql AS $$\nBEGIN\n RAISE NOTICE 'hi';\n RETURN;\nEND$$",
"plpgsql_deparser_fixes-46.sql": "-- Test 46: Trigger function ending in RETURN NEW (unchanged)\nCREATE FUNCTION test_trigger_return_new() RETURNS trigger\nLANGUAGE plpgsql AS $$\nBEGIN\n NEW.updated_at := now();\n RETURN NEW;\nEND$$",
"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$$",
"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$$",
"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$$",
Expand Down
84 changes: 84 additions & 0 deletions __fixtures__/plpgsql/plpgsql_deparser_fixes.sql
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,87 @@ BEGIN
END IF;
RETURN v_user;
END$$;

-- Test 38: INSERT ... RETURNING ... INTO (INTO must be re-inserted after RETURNING)
CREATE FUNCTION test_insert_returning_into() RETURNS uuid
LANGUAGE plpgsql AS $$
DECLARE
v_id uuid;
BEGIN
INSERT INTO s.t (name) VALUES ('x') RETURNING id INTO v_id;
RETURN v_id;
END$$;

-- Test 39: UPDATE ... RETURNING ... INTO
CREATE FUNCTION test_update_returning_into() RETURNS uuid
LANGUAGE plpgsql AS $$
DECLARE
v_id uuid;
BEGIN
UPDATE s.t SET name = 'y' WHERE name = 'x' RETURNING id INTO v_id;
RETURN v_id;
END$$;

-- Test 40: DELETE ... RETURNING ... INTO
CREATE FUNCTION test_delete_returning_into() RETURNS uuid
LANGUAGE plpgsql AS $$
DECLARE
v_id uuid;
BEGIN
DELETE FROM s.t WHERE name = 'x' RETURNING id INTO v_id;
RETURN v_id;
END$$;

-- Test 41: INSERT ... RETURNING ... INTO STRICT
CREATE FUNCTION test_insert_returning_into_strict() RETURNS uuid
LANGUAGE plpgsql AS $$
DECLARE
v_id uuid;
BEGIN
INSERT INTO s.t (name) VALUES ('x') RETURNING id INTO STRICT v_id;
RETURN v_id;
END$$;

-- Test 42: INSERT ... RETURNING multiple columns INTO
CREATE FUNCTION test_insert_returning_multi_into() RETURNS void
LANGUAGE plpgsql AS $$
DECLARE
v_id uuid;
v_name text;
BEGIN
INSERT INTO s.t (name) VALUES ('x') RETURNING id, name INTO v_id, v_name;
END$$;

-- Test 43: INSERT ... RETURNING expression with subquery INTO (INTO must not land inside the subquery)
CREATE FUNCTION test_insert_returning_subquery_into() RETURNS void
LANGUAGE plpgsql AS $$
DECLARE
v_total bigint;
BEGIN
INSERT INTO s.t (name) VALUES ('x') RETURNING (SELECT count(*) FROM s.t WHERE name = 'x') INTO v_total;
END$$;

-- Test 44: Trigger function with no final return (implicit compiler RETURN must not be emitted)
CREATE FUNCTION test_trigger_no_final_return() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
RETURN NEW;
END IF;
END$$;

-- Test 45: Void function with explicit trailing RETURN (must be preserved)
CREATE FUNCTION test_void_explicit_return() RETURNS void
LANGUAGE plpgsql AS $$
BEGIN
RAISE NOTICE 'hi';
RETURN;
END$$;

-- Test 46: Trigger function ending in RETURN NEW (unchanged)
CREATE FUNCTION test_trigger_return_new() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
NEW.updated_at := now();
RETURN NEW;
END$$;
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`plpgsql-deparser bug fixes DML RETURNING ... INTO re-insertion should handle multi-column RETURNING ... INTO 1`] = `
"DECLARE
v_id uuid;
v_name text;
BEGIN
INSERT INTO s.t (name) VALUES ('x') RETURNING id, name INTO v_id, v_name;
END"
`;

exports[`plpgsql-deparser bug fixes DML RETURNING ... INTO re-insertion should not insert INTO inside a RETURNING subquery 1`] = `
"DECLARE
v_total bigint;
BEGIN
INSERT INTO s.t (name) VALUES ('x') RETURNING (SELECT count(*) FROM s.t WHERE name = 'x') INTO v_total;
END"
`;

exports[`plpgsql-deparser bug fixes DML RETURNING ... INTO re-insertion should preserve STRICT in RETURNING ... INTO STRICT 1`] = `
"DECLARE
v_id uuid;
BEGIN
INSERT INTO s.t (name) VALUES ('x') RETURNING id INTO STRICT v_id;
RETURN v_id;
END"
`;

exports[`plpgsql-deparser bug fixes DML RETURNING ... INTO re-insertion should re-insert INTO after RETURNING for DELETE 1`] = `
"DECLARE
v_id uuid;
BEGIN
DELETE FROM s.t WHERE name = 'x' RETURNING id INTO v_id;
RETURN v_id;
END"
`;

exports[`plpgsql-deparser bug fixes DML RETURNING ... INTO re-insertion should re-insert INTO after RETURNING for INSERT 1`] = `
"DECLARE
v_id uuid;
BEGIN
INSERT INTO s.t (name) VALUES ('x') RETURNING id INTO v_id;
RETURN v_id;
END"
`;

exports[`plpgsql-deparser bug fixes DML RETURNING ... INTO re-insertion should re-insert INTO after RETURNING for UPDATE 1`] = `
"DECLARE
v_id uuid;
BEGIN
UPDATE s.t SET name = 'y' WHERE name = 'x' RETURNING id INTO v_id;
RETURN v_id;
END"
`;

exports[`plpgsql-deparser bug fixes INTO clause depth-aware scanner should handle INTO STRICT 1`] = `
"DECLARE
v_id integer;
Expand Down Expand Up @@ -74,7 +127,6 @@ exports[`plpgsql-deparser bug fixes OUT parameters with SELECT INTO multiple var
"BEGIN
SELECT u.name, u.email INTO STRICT name, email FROM users u
WHERE u.id = p_id;
RETURN;
END"
`;

Expand All @@ -98,21 +150,18 @@ exports[`plpgsql-deparser bug fixes PERFORM SELECT fix should handle PERFORM wit
"BEGIN
PERFORM set_config('search_path', 'public', true);
PERFORM nextval('my_sequence');
RETURN;
END"
`;

exports[`plpgsql-deparser bug fixes PERFORM SELECT fix should handle PERFORM with subquery 1`] = `
"BEGIN
PERFORM 1 FROM users WHERE id = 1;
RETURN;
END"
`;

exports[`plpgsql-deparser bug fixes PERFORM SELECT fix should strip SELECT keyword from PERFORM statements 1`] = `
"BEGIN
PERFORM pg_sleep(1);
RETURN;
END"
`;

Expand All @@ -139,7 +188,6 @@ BEGIN
FOR r IN SELECT id, name FROM users LOOP
RAISE NOTICE 'User: % - %', r.id, r.name;
END LOOP;
RETURN;
END"
`;

Expand Down Expand Up @@ -241,7 +289,6 @@ exports[`plpgsql-deparser bug fixes deep nesting and sequential blocks should ha
RAISE NOTICE 'even logging failed';
END;
END;
RETURN;
END"
`;

Expand Down Expand Up @@ -269,6 +316,28 @@ exports[`plpgsql-deparser bug fixes deep nesting and sequential blocks should ha
END"
`;

exports[`plpgsql-deparser bug fixes implicit trailing RETURN suppression should leave trigger function ending in RETURN NEW unchanged 1`] = `
"BEGIN
NEW.updated_at := now();
RETURN NEW;
END"
`;

exports[`plpgsql-deparser bug fixes implicit trailing RETURN suppression should not emit implicit compiler-generated RETURN in trigger function 1`] = `
"BEGIN
IF TG_OP = 'INSERT' THEN
RETURN NEW;
END IF;
END"
`;

exports[`plpgsql-deparser bug fixes implicit trailing RETURN suppression should preserve explicit trailing RETURN in void function 1`] = `
"BEGIN
RAISE NOTICE 'hi';
RETURN;
END"
`;

exports[`plpgsql-deparser bug fixes nested block compositions (END; bug class) should handle labeled nested block 1`] = `
"BEGIN
<<inner>>
Expand Down Expand Up @@ -423,7 +492,6 @@ END"
exports[`plpgsql-deparser bug fixes untested statement types should handle RETURN QUERY 1`] = `
"BEGIN
RETURN QUERY SELECT id, name FROM my_table WHERE active = TRUE;
RETURN;
END"
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,5 @@ BEGIN
END IF;
RAISE EXCEPTION;
END;
RETURN;
END$$"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ CREATE FUNCTION myapp_v2.update_user_status(
changed_at
) VALUES
(p_user_id, p_status, now());
RETURN;
END$$;

CREATE FUNCTION myapp_v2.cleanup_old_sessions(
Expand Down Expand Up @@ -403,6 +402,5 @@ BEGIN
UPDATE myapp_v2.batch_items SET status = 'processed' WHERE id = item.id;
END LOOP;
UPDATE myapp_v2.batches SET status = 'completed',completed_at = now() WHERE id = p_batch_id;
RETURN;
END$$;"
`;
Loading
Loading