Skip to content

fix: recover RETURN NEXT variable lost by libpg-query + CoalesceExpr/RangeFunction test fixtures#296

Closed
pyramation wants to merge 3 commits into
mainfrom
feat/plpgsql-coalesce-rangefunction-tests
Closed

fix: recover RETURN NEXT variable lost by libpg-query + CoalesceExpr/RangeFunction test fixtures#296
pyramation wants to merge 3 commits into
mainfrom
feat/plpgsql-coalesce-rangefunction-tests

Conversation

@pyramation
Copy link
Copy Markdown
Collaborator

@pyramation pyramation commented May 19, 2026

UPDATE: don't do this, inferReturnNextVar is attempt, not actually correct in all cases.

we need to wait for an upstream PR in libpg-query

Summary

Three commits, telling the story: broken test → fix → pass.

Commit 1: test: add CoalesceExpr and RangeFunction round-trip fixtures

9 test fixtures (Tests 38–46) confirming pgsql-parser correctly handles CoalesceExpr in FuncCall args and FuncCall in RangeFunction/FROM clauses. All round-trip correctly.

Commit 2: test: demonstrate RETURN NEXT retvarno loss (proves the bug)

Adds fixtures 47-48 and snapshot tests that prove the bug exists: libpg-query does not serialize retvarno for PLpgSQL_stmt_return_next, so RETURN NEXT v_entry deparses as bare RETURN NEXT;. The snapshots capture the broken output:

RETURN NEXT;      -- should be: RETURN NEXT v_entry;
RETURN NEXT;      -- should be: RETURN NEXT v_val;

Fixtures are added to KNOWN_FAILING_FIXTURES in this commit.

Commit 3: fix: recover RETURN NEXT variable when retvarno is lost

Fixes the deparser to recover the variable:

  • Thread enclosingForVarName through all FOR loop contexts
  • inferReturnNextVar() recovers from: (1) enclosing FOR loop variable, (2) single user-declared variable
  • Safety: only infers when returnInfo is provided with kind='setof' — OUT-param functions keep bare RETURN NEXT;
  • extractReturnInfo() in test-utils extracts return type from CREATE FUNCTION SQL for round-trip tests
  • Removes fixtures 47-48 from KNOWN_FAILING_FIXTURES — they now pass
  • Updates snapshots to show recovered output: RETURN NEXT v_entry;, RETURN NEXT v_val;

All 236 fixtures round-trip. 95 tests pass. 65 snapshots match.

Review & Testing Checklist for Human

  • Check commit 2 in isolation — its tests assert the broken output (RETURN NEXT; without variable). This proves the bug.
  • Check commit 3 — same test functions now assert the fixed output (RETURN NEXT v_entry). Snapshots updated.
  • Verify RETURN NEXT; stays bare for OUT-param functions (test: "should leave RETURN NEXT bare for OUT-param functions")
  • Verify inference does NOT happen when returnInfo is not provided (test: "should leave RETURN NEXT bare when returnInfo is not provided")

Notes

The extractReturnInfo() helper only returns returnInfo for SETOF and OUT-param functions. Other return types (scalar, void, trigger) intentionally return undefined to avoid changing existing RETURN statement behaviour.

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

Add 9 new PL/pgSQL test fixtures exercising expression patterns that
were causing UNSUPPORTED_EXPRESSION errors in constructive-db's deparser:

- Test 38: COALESCE inside FuncCall arguments (RETURN QUERY)
- Test 39: FuncCall in FROM clause (RangeFunction)
- Test 40: COALESCE + FuncCall in FROM clause (combined)
- Test 41: COALESCE + SRF in SELECT INTO
- Test 42: Multiple SRFs in FROM clause
- Test 43: COALESCE with function call in assignment
- Test 44: Nested COALESCE expressions
- Test 45: SELECT INTO from SRF in FROM clause
- Test 46: FOR loop with SRF and WHERE filter

All 9 patterns round-trip correctly through parse → deparse → reparse,
confirming pgsql-parser handles these AST node combinations. The issue
in constructive-db is in the SQL-level deparser, not upstream.

8 new snapshot tests added to deparser-fixes.test.ts with explicit
assertions for COALESCE, FROM, and function name preservation.

Ref: constructive-io/constructive-planning#896
@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 and CI monitoring

@devin-ai-integration devin-ai-integration Bot changed the title test: add CoalesceExpr and RangeFunction PL/pgSQL round-trip fixtures fix: recover RETURN NEXT variable lost by libpg-query + CoalesceExpr/RangeFunction test fixtures May 19, 2026
libpg-query does not serialize the retvarno field of
PLpgSQL_stmt_return_next. This causes RETURN NEXT v_entry to deparse
as bare RETURN NEXT; — the variable reference is silently dropped.

Adds:
- Test fixtures 47-48 in plpgsql_deparser_fixes.sql
- Two snapshot tests asserting the broken output (RETURN NEXT; instead
  of RETURN NEXT v_entry; and RETURN NEXT v_val;)
- Fixtures 47-48 added to KNOWN_FAILING_FIXTURES in round-trip test

These tests document the current broken behaviour. The next commit
fixes the deparser to recover the variable.
Thread enclosingForVarName through FOR loop contexts and add
inferReturnNextVar() to recover the variable from:
1. Enclosing FOR loop variable (most common pattern)
2. Single user-declared variable (SETOF scalar, no loop)

Only infers when returnInfo is provided (kind='setof'), preventing
false inference on OUT-param functions where bare RETURN NEXT is
correct.

Also adds extractReturnInfo() to the test infrastructure so round-trip
tests can extract the function's return type from the CREATE FUNCTION
SQL and pass it to the deparser.

Removes fixtures 47-48 from KNOWN_FAILING — they now pass.
Updates snapshot tests from bug-demonstration to fix-verification.
@devin-ai-integration devin-ai-integration Bot force-pushed the feat/plpgsql-coalesce-rangefunction-tests branch from ebe7ea0 to 7f0336e Compare May 19, 2026 02:53
@pyramation pyramation marked this pull request as draft May 20, 2026 01:06
@pyramation pyramation closed this May 20, 2026
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