pg_query_parse_plpgsql() regressions in 18.0.0
Three regressions in pg_query_parse_plpgsql() that all work correctly in 17-6.1.0.
Tested via the WASM build (@libpg-query/parser) but the bugs are in the C library's JSON serialization — the WASM wrapper just passes through the raw result.plpgsql_funcs string.
Bug 1: Trigger functions produce malformed JSON
Any function returning trigger emits invalid JSON. The PLpgSQL_rec datums for NEW/OLD records have trailing empty {} objects with mismatched braces.
SQL:
CREATE FUNCTION audit_trigger() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NEW.updated_at := now();
RETURN NEW;
END;
$$
PG 17 (valid JSON):
{"plpgsql_funcs":[
{"PLpgSQL_function":{"new_varno":1,"old_varno":2,"datums":[
{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}},
{"PLpgSQL_rec":{"refname":"new","dno":1}},
{"PLpgSQL_rec":{"refname":"old","dno":2}},
{"PLpgSQL_recfield":{"fieldname":"updated_at","recparentno":1}}
],"action":{...}}}
]}
PG 18 (MALFORMED — JSON.parse fails):
{"plpgsql_funcs":[
{"PLpgSQL_function":{"old_varno":1,"datums":[
{"PLpgSQL_rec":{"refname":"new"}},
{"PLpgSQL_rec":{"refname":"old","dno":1}},
{}},{}},{}},{}},{}},{}},{}},{}},{}},{}},
{"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"bool"}}}},
{"PLpgSQL_recfield":{"fieldname":"updated_at"}}
],"action":{...}}}
]}
Note the 10 extra {} with mismatched closing braces after each PLpgSQL_rec. This causes JSON.parse to throw: "Expected ',' or ']' after array element in JSON".
Bug 2: Qualified type names with non-public schemas return "Not implemented"
Any DECLARE variable using a schema-qualified type outside pg_catalog/public fails entirely — the function returns a plain error string instead of JSON.
SQL:
CREATE FUNCTION test_func() RETURNS void LANGUAGE plpgsql AS $$
DECLARE
v_user "my_schema".users;
BEGIN
RETURN;
END;
$$
PG 17: Parses successfully → {"typname":"\"my_schema\".users"}
PG 18: Returns the literal string (not JSON):
Not implemented (LookupExplicitNamespace only supports pg_catalog and public)
Bug 3: RETURN <variable> drops the expression from JSON output
When a PL/pgSQL function uses RETURN <variable>, PG 18 drops both the expr and retvarno fields from PLpgSQL_stmt_return. Literal and complex expression returns are fine.
SQL:
CREATE FUNCTION t() RETURNS int LANGUAGE plpgsql AS $$
DECLARE v int := 1;
BEGIN
RETURN v;
END;
$$
PG 17:
{"PLpgSQL_stmt_return":{"lineno":1,"expr":{"PLpgSQL_expr":{"query":"v","parseMode":2}}}}
PG 18:
{"PLpgSQL_stmt_return":{"lineno":1}}
The expr is completely missing. This also affects RETURN NEW in trigger functions.
| Return expression |
PG 17 |
PG 18 |
RETURN 42 |
✅ expr preserved |
✅ expr preserved |
RETURN x + 1 |
✅ expr preserved |
✅ expr preserved |
RETURN v (declared var) |
✅ expr preserved |
❌ expr dropped |
RETURN NEW (trigger) |
✅ expr preserved |
❌ expr dropped |
This makes it impossible to reconstruct the original PL/pgSQL source from the JSON AST.
Additional structural changes (may be intentional)
PG 18 also changed several fields in the PL/pgSQL JSON output. These may be intentional PG 18 changes rather than bugs, but they break backward compatibility:
| Field |
PG 17 |
PG 18 |
new_varno on trigger functions |
Present (e.g. 1) |
Missing |
PLpgSQL_recfield.recparentno |
Present (links field → parent record) |
Missing for NEW fields, present for OLD |
PLpgSQL_type.typname |
Schema-qualified: pg_catalog.int4, pg_catalog."boolean" |
Simplified: int4, bool |
| User-declared types |
Original name: int |
Canonical name: int4 |
Environment
- libpg_query tag:
18.0.0 (compared against 17-6.1.0)
- Reproduced via WASM build (
@libpg-query/parser@18.0.0 vs @libpg-query/parser@17.6.3)
- The WASM wrapper (
wasm_parse_plpgsql) just wraps result.plpgsql_funcs in {"plpgsql_funcs":%s} — the malformed JSON comes from the C library itself
Reproduction
mkdir plpgsql-repro && cd plpgsql-repro
cat > package.json << 'EOF'
{
"dependencies": {
"@libpg-query/parser-v17": "npm:@libpg-query/parser@17.6.3",
"@libpg-query/parser-v18": "npm:@libpg-query/parser@18.0.0"
}
}
EOF
npm install
const v17 = require('@libpg-query/parser-v17');
const v18 = require('@libpg-query/parser-v18');
async function main() {
await v17.loadModule();
await v18.loadModule();
const sql = `CREATE FUNCTION t() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN NEW.updated_at := now(); RETURN NEW; END; $$`;
console.log('PG 17:', JSON.stringify(v17.parsePlPgSQLSync(sql), null, 2));
console.log('PG 18:', v18.parsePlPgSQLSync(sql)); // throws
}
main();
pg_query_parse_plpgsql() regressions in 18.0.0
Three regressions in
pg_query_parse_plpgsql()that all work correctly in17-6.1.0.Tested via the WASM build (
@libpg-query/parser) but the bugs are in the C library's JSON serialization — the WASM wrapper just passes through the rawresult.plpgsql_funcsstring.Bug 1: Trigger functions produce malformed JSON
Any function returning
triggeremits invalid JSON. ThePLpgSQL_recdatums for NEW/OLD records have trailing empty{}objects with mismatched braces.SQL:
PG 17 (valid JSON):
{"plpgsql_funcs":[ {"PLpgSQL_function":{"new_varno":1,"old_varno":2,"datums":[ {"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"pg_catalog.\"boolean\""}}}}, {"PLpgSQL_rec":{"refname":"new","dno":1}}, {"PLpgSQL_rec":{"refname":"old","dno":2}}, {"PLpgSQL_recfield":{"fieldname":"updated_at","recparentno":1}} ],"action":{...}}} ]}PG 18 (MALFORMED —
JSON.parsefails):{"plpgsql_funcs":[ {"PLpgSQL_function":{"old_varno":1,"datums":[ {"PLpgSQL_rec":{"refname":"new"}}, {"PLpgSQL_rec":{"refname":"old","dno":1}}, {}},{}},{}},{}},{}},{}},{}},{}},{}},{}}, {"PLpgSQL_var":{"refname":"found","datatype":{"PLpgSQL_type":{"typname":"bool"}}}}, {"PLpgSQL_recfield":{"fieldname":"updated_at"}} ],"action":{...}}} ]}Note the 10 extra
{}with mismatched closing braces after eachPLpgSQL_rec. This causesJSON.parseto throw: "Expected ',' or ']' after array element in JSON".Bug 2: Qualified type names with non-public schemas return "Not implemented"
Any
DECLAREvariable using a schema-qualified type outsidepg_catalog/publicfails entirely — the function returns a plain error string instead of JSON.SQL:
PG 17: Parses successfully →
{"typname":"\"my_schema\".users"}PG 18: Returns the literal string (not JSON):
Bug 3:
RETURN <variable>drops the expression from JSON outputWhen a PL/pgSQL function uses
RETURN <variable>, PG 18 drops both theexprandretvarnofields fromPLpgSQL_stmt_return. Literal and complex expression returns are fine.SQL:
PG 17:
{"PLpgSQL_stmt_return":{"lineno":1,"expr":{"PLpgSQL_expr":{"query":"v","parseMode":2}}}}PG 18:
{"PLpgSQL_stmt_return":{"lineno":1}}The
expris completely missing. This also affectsRETURN NEWin trigger functions.RETURN 42RETURN x + 1RETURN v(declared var)RETURN NEW(trigger)This makes it impossible to reconstruct the original PL/pgSQL source from the JSON AST.
Additional structural changes (may be intentional)
PG 18 also changed several fields in the PL/pgSQL JSON output. These may be intentional PG 18 changes rather than bugs, but they break backward compatibility:
new_varnoon trigger functions1)PLpgSQL_recfield.recparentnoPLpgSQL_type.typnamepg_catalog.int4,pg_catalog."boolean"int4,boolintint4Environment
18.0.0(compared against17-6.1.0)@libpg-query/parser@18.0.0vs@libpg-query/parser@17.6.3)wasm_parse_plpgsql) just wrapsresult.plpgsql_funcsin{"plpgsql_funcs":%s}— the malformed JSON comes from the C library itselfReproduction