Skip to content

fix(plpgsql-deparser): re-insert INTO after DML RETURNING + skip implicit final RETURN - #301

Merged
pyramation merged 1 commit into
mainfrom
feat/plpgsql-deparser-into-return-fixes
Jul 19, 2026
Merged

fix(plpgsql-deparser): re-insert INTO after DML RETURNING + skip implicit final RETURN#301
pyramation merged 1 commit into
mainfrom
feat/plpgsql-deparser-into-return-fixes

Conversation

@pyramation

Copy link
Copy Markdown
Collaborator

Summary

Fixes two PL/pgSQL round-trip corruption bugs in plpgsql-deparser:

Bug 1 (critical): INTO silently dropped from INSERT/UPDATE/DELETE ... RETURNING ... INTO

libpg_query strips INTO <target> from the statement text and records it as exec.into/exec.target; deparseExecSql() re-inserts it via findIntoInsertionPoint(). That scanner's depth-0 INTO check matched the INTO of INSERT INTO s.t ... itself and returned -1 ("already present"), so the target clause was dropped:

INSERT INTO s.t (name) VALUES ('x') RETURNING id INTO v_id;
  →  INSERT INTO s.t (name) VALUES ('x') RETURNING id;   -- 42601 at runtime

Fix in findIntoInsertionPoint: when the first depth-0 command keyword is INSERT/UPDATE/DELETE/MERGE, append INTO <target> at the end of the statement (after the RETURNING list) instead of running the SELECT clause-keyword scan (which would also misfire on DELETE FROM / UPDATE ... FROM). The scanner remains quote/comment/paren-depth aware, so INTO is never inserted inside a RETURNING subquery.

Bug 2: implicit trailing bare RETURN emitted (syntax error in trigger functions)

libpg_query's PL/pgSQL compiler appends an implicit final {"PLpgSQL_stmt_return":{}} (no expr, no lineno) to every function body; the deparser printed it as RETURN;, which is invalid inside RETURNS trigger functions.

Fix: deparseFunction now calls stripImplicitFinalReturn(), which drops a trailing top-level PLpgSQL_stmt_return only when it has no expr, no lineno, and no retvarno >= 0. An explicit user-written RETURN; carries a lineno and is preserved.

Tests

  • 9 new fixtures (Tests 38–46) in __fixtures__/plpgsql/plpgsql_deparser_fixes.sql + regenerated generated.json: INSERT/UPDATE/DELETE RETURNING ... INTO, INTO STRICT, multi-column RETURNING, RETURNING with subquery, trigger fn with no final return, void fn with explicit trailing RETURN;, trigger fn ending in RETURN NEW.
  • New snapshot tests in deparser-fixes.test.ts including an INTO/RETURNING keyword-count invariant across the round-trip.
  • Existing snapshots updated: the only diff is removal of the spurious trailing RETURN;.
  • Full workspace test suite passes (pgsql-cli/pgsql-types report "No tests found" — pre-existing, those packages have no test files).

Context: found while making constructive-db's schema-rename transform use the parser/deparser path by default; Bug 1 silently corrupted 24 generated files and Bug 2 broke trigger deploys.

Link to Devin session: https://app.devin.ai/sessions/eeb8deba0c04475091df703c88877302
Requested by: @pyramation

@pyramation pyramation self-assigned this Jul 19, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit 741352a into main Jul 19, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant