Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0ead3fc
fix(deparser): deparse FETCH/MOVE ALL from the WASM AST (howMany 2147…
pyramation Oct 25, 2026
ffd037e
fix(plpgsql-deparser): keep multi-line literals, "char" and empty CAS…
pyramation Oct 25, 2026
b05c71d
test: round-trip the PostgreSQL regression corpus with a known-failur…
pyramation Oct 25, 2026
9aa53d8
fix(transform): normalize nodes under a "constructor" key in normaliz…
pyramation Oct 25, 2026
6226bba
test(regress): keep untouched entries on filtered updates, flag stale…
pyramation Oct 25, 2026
8f032e0
fix(deparser): keep AND CHAIN, trigger names, XML INDENT, SHOW quotin…
pyramation Oct 25, 2026
d30d6c2
feat(deparser): support JSON_QUERY, JSON_VALUE, JSON_EXISTS and JSON_…
pyramation Oct 25, 2026
e2c6d77
fix(deparser): support ALTER STATISTICS RENAME and quote/validate ope…
pyramation Oct 25, 2026
a6d04d9
Merge #364: fix(deparser): deparse FETCH/MOVE ALL from the WASM AST
pyramation Oct 25, 2026
bce641c
Merge #367: fix(transform): normalize nodes under a "constructor" key…
pyramation Oct 25, 2026
a797196
Merge #365: fix(plpgsql-deparser): keep multi-line literals, "char" a…
pyramation Oct 25, 2026
755c781
Merge #366: test: round-trip the PostgreSQL regression corpus with a …
pyramation Oct 25, 2026
7f04c9b
Merge #368: fix(deparser): keep AND CHAIN, trigger names, XML INDENT,…
pyramation Oct 25, 2026
1d89128
Merge #369: feat(deparser): support JSON_QUERY, JSON_VALUE, JSON_EXIS…
pyramation Oct 25, 2026
dc37e4a
Merge #370: fix(deparser): support ALTER STATISTICS RENAME and quote/…
pyramation Oct 25, 2026
61bbcd6
test(regress): tighten known-failures ratchet after folded deparser f…
pyramation Oct 25, 2026
85078d9
chore(pgsql-types): regenerate field metadata from new fixtures
pyramation Oct 25, 2026
d45f1c3
fix(deparser): quote tablespace option names; keep literals intact af…
pyramation Oct 28, 2026
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
21 changes: 21 additions & 0 deletions .agents/skills/testing-fixtures/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,27 @@ This generates two tests per case (pretty and non-pretty) and uses Jest snapshot
3. Run `npx jest` to verify all tests pass
4. Commit the `.sql` file, `generated.json`, and any new generated test files in `__tests__/kitchen-sink/`

## PostgreSQL Regression Corpus (`__fixtures__/regress/`)

A ratchet over the SQL that libpg_query vendors from PostgreSQL (`test/sql/postgres_regress`,
`test/sql/plpgsql_regress`) plus its own test SQL (`test/sql/deparse*`, `test/plpgsql_samples.sql`):

- `statements.json.gz`: unique parseable statements keyed like `generated.json`
(`postgres_regress/arrays-12.sql`), plus the libpg_query commit they came from. Built by
`cd packages/deparser && npm run fixtures:regress -- /path/to/libpg_query`. Check out the
`x-publish.libpgQueryCommit` of the pinned `libpg-query` release first.
- `known-failures.deparser.json` / `known-failures.plpgsql-deparser.json`: statements that do not
round-trip yet.
- Tests: `packages/deparser/__tests__/regress/regress-corpus.test.ts` (one test per source file,
pretty and non-pretty, via `TestUtils.expectAstMatch`) and
`packages/plpgsql-deparser/__tests__/regress-corpus.test.ts`. They run in the normal `test` script.

A new failure fails the test, and so does a known failure that now passes. After fixing deparser
bugs (or bumping `libpg-query` and regenerating the corpus), refresh the lists with
`UPDATE_KNOWN_FAILURES=1 npx jest __tests__/regress` (deparser) and
`UPDATE_KNOWN_FAILURES=1 npx jest __tests__/regress-corpus` (plpgsql-deparser), then review the diff.
Bug fixes still get a kitchen-sink fixture; the corpus only guards against regressions.

## PL/pgSQL Fixtures (`plpgsql-deparser`)

The PL/pgSQL deparser has its own fixture pipeline:
Expand Down
114 changes: 114 additions & 0 deletions __fixtures__/generated/generated.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions __fixtures__/kitchen-sink/misc/alter-statistics.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ALTER STATISTICS ab1_a_b_stats RENAME TO ab1_a_b_stats_new;
ALTER STATISTICS "My Schema"."My Stats" RENAME TO "New Stats";
ALTER STATISTICS public.s1 RENAME TO s2;
ALTER STATISTICS s1 SET SCHEMA s2;
ALTER STATISTICS s1 OWNER TO r1;
ALTER STATISTICS ab1_a_b_stats SET STATISTICS 0;
ALTER STATISTICS IF EXISTS ab1_a_b_stats_new SET STATISTICS -1;
ALTER STATISTICS s1 SET STATISTICS DEFAULT;
ALTER STATISTICS "My Schema"."My Stats" SET STATISTICS 5;
5 changes: 5 additions & 0 deletions __fixtures__/kitchen-sink/misc/alter-trigger-rename.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- ALTER TRIGGER name ON table RENAME TO (RenameStmt OBJECT_TRIGGER keeps subname)
-- Ref: PostgreSQL regress triggers.sql
ALTER TRIGGER p ON grandp RENAME TO q;
ALTER TRIGGER "Mixed Trig" ON "My Schema"."My Table" RENAME TO "New Trig";
ALTER TRIGGER modified_a ON main_table RENAME TO modified_modified_a;
5 changes: 5 additions & 0 deletions __fixtures__/kitchen-sink/misc/copy-option-values.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- COPY option values keep their meaning
COPY t FROM STDIN (HEADER false);
COPY t FROM STDIN (HEADER true, FREEZE false);
COPY t FROM STDIN (FORMAT "CSV");
COPY t FROM STDIN (FORMAT 'csv', HEADER off);
10 changes: 10 additions & 0 deletions __fixtures__/kitchen-sink/misc/fetch-with-ties.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- FETCH FIRST n ROWS WITH TIES (SelectStmt.limitOption)
-- Ref: PostgreSQL regress limit.sql
SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand FETCH FIRST 2 ROW WITH TIES;
SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand FETCH FIRST ROWS WITH TIES;
SELECT thousand FROM onek WHERE thousand < 5 ORDER BY thousand OFFSET 2 ROWS FETCH NEXT (1 + 1) ROWS WITH TIES;
CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST 5 ROWS WITH TIES OFFSET 10;
CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST (NULL+1) ROWS WITH TIES;
CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST NULL ROWS ONLY;
SELECT * FROM t ORDER BY a FETCH FIRST $1 ROWS WITH TIES;
SELECT * FROM t ORDER BY a FETCH FIRST -1 ROWS WITH TIES;
21 changes: 21 additions & 0 deletions __fixtures__/kitchen-sink/misc/json-query-functions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SELECT JSON_EXISTS(jsonb '{"a": 1}', '$.a');
SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]' ERROR ON ERROR);
SELECT JSON_EXISTS(jsonb '[1,2,3]', '$[*] ? (@ > $x)' PASSING 2 AS x, 'b' AS "Y" UNKNOWN ON ERROR);
SELECT JSON_EXISTS(js FORMAT JSON, '$.a' TRUE ON ERROR) FROM t;
SELECT JSON_VALUE(jsonb '"aaa"', '$');
SELECT JSON_VALUE(jsonb '123', '$' RETURNING int);
SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date DEFAULT '2000-01-01' ON EMPTY NULL ON ERROR);
SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' DEFAULT 0 + 1 ON ERROR);
SELECT JSON_VALUE(js, '$.a' PASSING x + 1 AS x RETURNING text ERROR ON EMPTY ERROR ON ERROR) FROM t;
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]');
SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES);
SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING);
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER);
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH CONDITIONAL ARRAY WRAPPER);
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH UNCONDITIONAL WRAPPER);
SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' WITHOUT WRAPPER);
SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ON EMPTY);
SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY EMPTY OBJECT ON ERROR);
SELECT JSON_QUERY(js FORMAT JSON, '$.a' PASSING 1 AS x RETURNING bytea FORMAT JSON ENCODING UTF8 WITH CONDITIONAL WRAPPER KEEP QUOTES DEFAULT '"x"' ON EMPTY ERROR ON ERROR) FROM t;
CREATE VIEW json_query_v AS SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING jsonb WITH WRAPPER NULL ON ERROR) AS q, JSON_VALUE(jsonb '1', '$' RETURNING int) AS v, JSON_EXISTS(jsonb '1', '$') AS e;
CREATE TABLE json_check (js jsonb CHECK (JSON_EXISTS(js, '$.a')), v text DEFAULT JSON_VALUE(jsonb '"x"', '$'));
11 changes: 11 additions & 0 deletions __fixtures__/kitchen-sink/misc/json-table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
SELECT * FROM JSON_TABLE(jsonb '[1, 2]', '$[*]' COLUMNS (a int PATH '$'));
SELECT * FROM JSON_TABLE(jsonb '[1, 2]', '$[*]' COLUMNS (id FOR ORDINALITY, a int)) jt;
SELECT * FROM JSON_TABLE(jsonb '[1, 2]', '$[*]' AS root COLUMNS (a int PATH '$')) AS jt (x);
SELECT * FROM JSON_TABLE(jsonb '{"a": [1]}', '$' PASSING 1 AS x, 'y' AS "Y" COLUMNS (a int PATH '$.a[*] ? (@ > $x)' DEFAULT -1 ON EMPTY ERROR ON ERROR) ERROR ON ERROR) jt;
SELECT * FROM JSON_TABLE(jsonb '{"a": 1}', '$' COLUMNS (e bool EXISTS PATH '$.a', e2 int EXISTS PATH '$.b' TRUE ON ERROR)) jt;
SELECT * FROM JSON_TABLE(jsonb '{"a": "x"}', '$' COLUMNS (js jsonb FORMAT JSON PATH '$.a' WITH CONDITIONAL WRAPPER OMIT QUOTES, js2 text FORMAT JSON ENCODING UTF8 PATH '$' WITHOUT WRAPPER KEEP QUOTES EMPTY OBJECT ON ERROR)) jt;
SELECT * FROM JSON_TABLE(jsonb '{"a": "x"}', '$' COLUMNS (q jsonb PATH '$.a' WITH WRAPPER EMPTY ARRAY ON EMPTY, "Mixed Col" text PATH '$.a' NULL ON ERROR)) jt;
SELECT * FROM JSON_TABLE(jsonb '{"a": [{"b": 1}]}', '$' COLUMNS (id FOR ORDINALITY, NESTED PATH '$.a[*]' COLUMNS (b int PATH '$.b', NESTED PATH '$.c[*]' AS c_path COLUMNS (c text)), NESTED '$.d' AS d COLUMNS (d int))) jt;
SELECT jt.* FROM t, LATERAL JSON_TABLE(t.js, '$[*]' COLUMNS (a text PATH '$.a')) AS jt;
SELECT jt.* FROM t CROSS JOIN JSON_TABLE(t.js FORMAT JSON, '$[*]' COLUMNS (a text)) jt;
CREATE VIEW json_table_view AS SELECT * FROM JSON_TABLE(jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" COLUMNS (id FOR ORDINALITY, "int" int PATH '$', js json PATH '$', jsb jsonb PATH '$', exists1 bool EXISTS PATH '$.aaa', NESTED PATH '$[1]' AS p1 COLUMNS (a1 int, NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text)))) jt;
18 changes: 18 additions & 0 deletions __fixtures__/kitchen-sink/misc/operator-names.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
SELECT a OPERATOR(pg_catalog.+) b FROM t;
SELECT a OPERATOR("My Schema".+) b FROM t;
SELECT a OPERATOR("S".===) b OPERATOR(public.*) c FROM t;
SELECT OPERATOR("S".-) a FROM t;
SELECT a OPERATOR("S".=) ANY (ARRAY[1]) FROM t;
SELECT a OPERATOR("S".=) ALL (SELECT 1) FROM t;
SELECT - -1, @ -1, 1 - -1, 2 ^ - 1;
CREATE OPERATOR "My Schema".=== (LEFTARG = int, RIGHTARG = int, PROCEDURE = f, COMMUTATOR = OPERATOR("My Schema".===), NEGATOR = OPERATOR("My Schema".!==));
CREATE OPERATOR === (LEFTARG = int, RIGHTARG = int, PROCEDURE = f, COMMUTATOR = ===, NEGATOR = !==);
ALTER OPERATOR === (int, int) SET (COMMUTATOR = OPERATOR("My Schema".===));
ALTER OPERATOR "S".=== (int, int) SET (NEGATOR = !==);
DROP OPERATOR "S".< (int, int);
COMMENT ON OPERATOR "My Schema".=== (int, int) IS 'x';
COMMENT ON OPERATOR - (NONE, int) IS 'x';
CREATE OPERATOR CLASS c FOR TYPE int USING btree AS OPERATOR 1 "S".< (int, int), OPERATOR 2 <=, FUNCTION 1 f(int, int);
ALTER OPERATOR FAMILY f USING btree ADD OPERATOR 1 "S".< (int, int);
ALTER TABLE t ADD EXCLUDE USING gist (c WITH OPERATOR("S".&&));
ALTER TABLE t ADD EXCLUDE USING gist (c WITH &&);
11 changes: 11 additions & 0 deletions __fixtures__/kitchen-sink/misc/show-quoting.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- SHOW keeps identifier quoting (VariableShowStmt.name)
SHOW "TimeZone";
SHOW "Foo"."Bar";
SHOW "my.ext".setting;
SHOW "ALL";
SHOW ALL;
SHOW TIME ZONE;
SHOW TRANSACTION ISOLATION LEVEL;
SHOW SESSION AUTHORIZATION;
SHOW search_path;
SHOW plpgsql.extra_warnings;
19 changes: 19 additions & 0 deletions __fixtures__/kitchen-sink/misc/tablespace-clauses.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- TABLESPACE / USING INDEX TABLESPACE / INCLUDE / WITH on table and constraint definitions
-- Ref: PostgreSQL regress tablespace.sql, create_table.sql, constraints.sql
CREATE TABLE testschema.foo (i int) TABLESPACE regress_tblspace;
CREATE TABLE t (a int) ON COMMIT DROP TABLESPACE ts;
CREATE TABLE t (a int) USING heap TABLESPACE ts;
CREATE TABLE testschema.dflt (a int PRIMARY KEY USING INDEX TABLESPACE regress_tblspace) PARTITION BY LIST (a) TABLESPACE pg_default;
CREATE TABLE testschema.part_1 PARTITION OF testschema.part FOR VALUES IN (1) TABLESPACE "My Space";
CREATE TABLE t (a int, b int, PRIMARY KEY (a) INCLUDE (b) WITH (fillfactor=10) USING INDEX TABLESPACE ts DEFERRABLE);
CREATE TABLE t (a int UNIQUE NULLS NOT DISTINCT USING INDEX TABLESPACE "My Space");
CREATE TABLE t (a int, CONSTRAINT u UNIQUE (a) INCLUDE (a) WITH (fillfactor=50));
CREATE TABLE t (a int, EXCLUDE USING gist (a WITH =) INCLUDE (a) WITH (fillfactor=10) USING INDEX TABLESPACE ts WHERE (a > 0));
ALTER TABLE testschema.atable ADD CONSTRAINT anindex_t UNIQUE (column1) USING INDEX TABLESPACE regress_tblspace;
ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY USING INDEX "My Index";
ALTER TABLE t ADD UNIQUE USING INDEX idx DEFERRABLE INITIALLY DEFERRED;
CREATE TABLESPACE "My Space" OWNER regress_user LOCATION '/tmp' WITH (random_page_cost = 3, seq_page_cost = '1.5');
ALTER TABLESPACE "My Space" SET (random_page_cost = 1.5, effective_io_concurrency = 2);
ALTER TABLESPACE "My Space" RESET (random_page_cost);
ALTER TABLESPACE "My Space" SET ("Random_Page_Cost" = 2);
DROP TABLESPACE IF EXISTS "My Space";
9 changes: 9 additions & 0 deletions __fixtures__/kitchen-sink/misc/transaction-chain.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- COMMIT/ROLLBACK AND CHAIN (TransactionStmt.chain)
-- Ref: PostgreSQL regress transactions.sql
START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE;
COMMIT AND CHAIN;
ROLLBACK AND CHAIN;
COMMIT AND NO CHAIN;
ROLLBACK AND NO CHAIN;
END AND CHAIN;
ABORT AND CHAIN;
6 changes: 6 additions & 0 deletions __fixtures__/kitchen-sink/misc/xmlserialize-indent.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- XMLSERIALIZE ... INDENT (XmlSerialize.indent)
-- Ref: PostgreSQL regress xml.sql
SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
SELECT xmlserialize(CONTENT '<foo><bar><val x="y">42</val></bar></foo>' AS text INDENT);
SELECT xmlserialize(DOCUMENT '<foo><bar><val x="y">42</val></bar></foo>' AS text NO INDENT);
SELECT xmlserialize(CONTENT doc AS varchar INDENT) FROM xmltest;
7 changes: 6 additions & 1 deletion __fixtures__/kitchen-sink/pretty/misc.sql
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,9 @@ GRANT EXECUTE ON FUNCTION dashboard_private.uuid_generate_seeded_uuid TO PUBLIC;


-- https://github.com/constructive-io/pgsql-parser/issues/217
SELECT CAST(t.date AT TIME ZONE $$America/New_York$$ AS text)::date FROM tbl t;
SELECT CAST(t.date AT TIME ZONE $$America/New_York$$ AS text)::date FROM tbl t;

-- FETCH/MOVE ALL (FETCH_ALL is 2147483647 in the WASM AST)
FETCH ALL FROM c;

MOVE BACKWARD ALL IN c;
4 changes: 4 additions & 0 deletions __fixtures__/plpgsql-generated/generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@
"plpgsql_deparser_fixes-65.sql": "CREATE FUNCTION test_alias_trigger() RETURNS trigger\nLANGUAGE plpgsql AS $$\nDECLARE\n prior ALIAS FOR old;\n updated ALIAS FOR new;\nBEGIN\n updated.updated_at := now();\n RETURN updated;\nEND$$",
"plpgsql_deparser_fixes-66.sql": "CREATE FUNCTION test_assign_named_args() RETURNS text\nLANGUAGE plpgsql AS $$\nDECLARE\n v_body text;\nBEGIN\n v_body := ast_helpers.create_function(v_schema_name := 'app_public', v_function_name := 'f');\n RETURN v_body;\nEND$$",
"plpgsql_deparser_fixes-67.sql": "CREATE FUNCTION test_assign_targets() RETURNS void\nLANGUAGE plpgsql AS $$\nDECLARE\n a int[] := ARRAY[1, 2];\n r record;\nBEGIN\n SELECT 1 AS f INTO r;\n a[2] := coalesce(nullif(3, 0), 4);\n r.f := greatest(1, 2);\nEND$$",
"plpgsql_deparser_fixes-68.sql": "CREATE FUNCTION test_multiline_literals(p_on boolean) RETURNS void\nLANGUAGE plpgsql AS $$\nBEGIN\n IF p_on THEN\n RAISE NOTICE 'line one\n line two';\n EXECUTE format('SELECT 1\n FROM %I', 'pg_class');\n PERFORM E'it''s\\\n escaped', $q$dollar\n quoted$q$;\n END IF;\nEND$$",
"plpgsql_deparser_fixes-69.sql": "CREATE FUNCTION test_quoted_char_type() RETURNS \"char\"\nLANGUAGE plpgsql AS $$\nDECLARE\n c \"char\" := 'a';\nBEGIN\n RETURN c;\nEND$$",
"plpgsql_deparser_fixes-70.sql": "CREATE FUNCTION test_case_empty_else(x int) RETURNS text\nLANGUAGE plpgsql AS $$\nBEGIN\n CASE x\n WHEN 1 THEN RETURN 'one';\n ELSE\n END CASE;\n RETURN 'other';\nEND$$",
"plpgsql_deparser_fixes-71.sql": "CREATE FUNCTION test_nested_comment_literal(p_on boolean) RETURNS void\nLANGUAGE plpgsql AS $$\nBEGIN\n IF p_on THEN\n PERFORM /* outer /* inner */ it's */ 'line one\n line two';\n END IF;\nEND$$",
"plpgsql_control-1.sql": "do $$\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": "do $$\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
45 changes: 45 additions & 0 deletions __fixtures__/plpgsql/plpgsql_deparser_fixes.sql
Original file line number Diff line number Diff line change
Expand Up @@ -837,3 +837,48 @@ BEGIN
a[2] := coalesce(nullif(3, 0), 4);
r.f := greatest(1, 2);
END$$;

-- Test 68: multi-line string literals keep their exact text inside nested blocks
CREATE FUNCTION test_multiline_literals(p_on boolean) RETURNS void
LANGUAGE plpgsql AS $$
BEGIN
IF p_on THEN
RAISE NOTICE 'line one
line two';
EXECUTE format('SELECT 1
FROM %I', 'pg_class');
PERFORM E'it''s\
escaped', $q$dollar
quoted$q$;
END IF;
END$$;

-- Test 69: quoted "char" type keeps its quotes
CREATE FUNCTION test_quoted_char_type() RETURNS "char"
LANGUAGE plpgsql AS $$
DECLARE
c "char" := 'a';
BEGIN
RETURN c;
END$$;

-- Test 70: CASE with an empty ELSE keeps the ELSE
CREATE FUNCTION test_case_empty_else(x int) RETURNS text
LANGUAGE plpgsql AS $$
BEGIN
CASE x
WHEN 1 THEN RETURN 'one';
ELSE
END CASE;
RETURN 'other';
END$$;

-- Test 71: nested block comments do not hide the string literal after them
CREATE FUNCTION test_nested_comment_literal(p_on boolean) RETURNS void
LANGUAGE plpgsql AS $$
BEGIN
IF p_on THEN
PERFORM /* outer /* inner */ it's */ 'line one
line two';
END IF;
END$$;
Loading
Loading