Skip to content

fix: add 21 plpgsql deparser edge case fixtures and fix 7 schema_rename failures#291

Merged
pyramation merged 3 commits intomainfrom
devin/1775519727-plpgsql-deparser-edge-cases
Apr 7, 2026
Merged

fix: add 21 plpgsql deparser edge case fixtures and fix 7 schema_rename failures#291
pyramation merged 3 commits intomainfrom
devin/1775519727-plpgsql-deparser-edge-cases

Conversation

@pyramation
Copy link
Copy Markdown
Collaborator

@pyramation pyramation commented Apr 7, 2026

Summary

Adds 21 new PL/pgSQL deparser test fixtures to prevent regression of the END; bug fix and improve coverage of previously untested statement types. Also fixes two deparser bugs that caused 7 pre-existing plpgsql_schema_rename-* fixture failures — all 225 fixtures now pass round-trip testing with no allowlist.

5 groups of fixtures (Tests 17–37):

Group Fixtures Priority What's tested
Nested Block Compositions 17–22 CRITICAL END; followed by RETURN, IF, RAISE, PERFORM, assignment; labeled blocks
Blocks Inside Control Structures 23–25 HIGH Block+exception inside IF, LOOP, CASE
Deep Nesting & Sequential Blocks 26–28 HIGH Sequential blocks, triple nesting, block inside exception handler
Untested Statement Types 29–35 MEDIUM FOR loops (integer/query/labeled), RETURN NEXT, RETURN QUERY, ASSERT, CALL
Real-World Patterns 36–37 CRITICAL Permission bitnum trigger (the original bug-exposing function), multi-step sign-in

Bug fixes

Two deparser bugs were causing 7 plpgsql_schema_rename-* fixtures to fail on main. Instead of allowlisting them, both root causes were fixed:

  1. cleanPlpgsqlTree query normalization (test-utils/index.ts): The transform function applied normalizeQueryWhitespace to all keys named query, but PLpgSQL_stmt_return_query.query is an object (wrapping PLpgSQL_expr), not a string. normalizeQueryWhitespace returned non-strings unchanged without recursing, so the inner PLpgSQL_expr.query string was never normalized — causing false AST mismatches on multi-line SQL. Fixed by making the query handler recurse into objects via transform(val, cleanProps).

  2. getDiagItemKindName string/number mismatch (plpgsql-deparser.ts): The parser returns diag_item.kind as a string (e.g. "ROW_COUNT"), but the deparser only handled numeric DiagItemKind enum values — so GET DIAGNOSTICS deleted_count = ROW_COUNT was deparsed as GET DIAGNOSTICS deleted_count = ; (empty kind). Added a string-name lookup path alongside the existing numeric switch. Four pre-existing snapshots were updated to reflect the corrected output.

Review & Testing Checklist for Human

  • Verify cleanPlpgsqlTree recursion fix is safe: The new query handler in cleanProps recurses into objects when query is not a string. Confirm this doesn't accidentally normalize non-query objects or cause infinite recursion (it delegates back to transform with the same props, which only recurses on object/array types). Note: this fix is in the test comparison utility, not the deparser itself — multi-line SQL may still be formatted differently after deparsing, but the round-trip AST comparison now correctly normalizes both sides.
  • Verify getDiagItemKindName string values match parser output: The string map keys ('ROW_COUNT', 'PG_CONTEXT', etc.) must exactly match what @libpg-query/parser returns in PLpgSQL_diag_item.kind. Cross-check against the parser's protobuf definitions if possible.
  • Spot-check updated snapshots: 4 snapshots changed across hydrate-demo.test.ts.snap, schema-rename-mapped.test.ts.snap, and plpgsql-pretty.test.ts.snap — all diffs should only show = ;= ROW_COUNT;.
  • RETURN NEXT limitation: Fixture 32 uses bare RETURN NEXT (with OUT params) instead of RETURN NEXT rec because the deparser drops the expression from RETURN NEXT <variable>. Decide if you'd prefer a deliberately-failing fixture to document this gap instead.
  • Regenerated generated.json: The diff includes 12 plpgsql_schema_rename-* entries — these are NOT new fixtures from this PR; they were already in __fixtures__/plpgsql/plpgsql_schema_rename.sql and are now picked up by the regenerated JSON. Verify no fixtures were accidentally removed.

Recommended test plan: cd packages/plpgsql-deparser && pnpm fixtures && npx jest

Notes

  • All 225 generated fixtures pass round-trip testing (0 known failures)
  • 83 total tests pass across 8 test suites (40 snapshot tests in deparser-fixes.test.ts, 6 in plpgsql-deparser.test.ts, plus hydrate, schema-rename, pretty tests, etc.)
  • Each snapshot test in deparser-fixes.test.ts duplicates its SQL inline rather than loading from generated.json — this matches the existing pattern

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

Add comprehensive test coverage for nested block compositions, blocks inside
control structures, deep nesting patterns, untested statement types, and
real-world patterns that exercise the END; bug class.

Group 1 - Nested Block Compositions (CRITICAL):
- Nested block followed by RETURN, IF, RAISE, PERFORM, assignment
- Labeled nested block

Group 2 - Blocks Inside Control Structures (HIGH):
- Block inside IF THEN branch with exception handler
- Block inside LOOP body with exception handler
- Block inside CASE WHEN

Group 3 - Deep Nesting & Sequential Blocks:
- Two sequential nested blocks
- Triple-nested blocks
- Block inside exception handler action

Group 4 - Untested Statement Types:
- FOR integer loop, FOR query loop, labeled FOR loop
- RETURN NEXT with OUT parameters, RETURN QUERY
- ASSERT statement, CALL statement

Group 5 - Real-World Patterns:
- Permission bitnum trigger pattern (the original bug-exposing function)
- Multi-step sign-in pattern with nested IF chains

Also adds pre-existing plpgsql_schema_rename failures to KNOWN_FAILING_FIXTURES
allowlist (7 fixtures with whitespace mismatch in query expressions).
@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

Two root causes fixed:

1. cleanPlpgsqlTree query normalization bug: The transform function
   applied normalizeQueryWhitespace to ALL keys named 'query', but
   PLpgSQL_stmt_return_query.query is an object (wrapping PLpgSQL_expr),
   not a string. normalizeQueryWhitespace returned non-strings unchanged
   WITHOUT recursing, so the inner PLpgSQL_expr.query string was never
   normalized. Fixed by making the query handler recurse into objects.

2. GET DIAGNOSTICS kind mapping: The parser returns diag_item.kind as a
   string (e.g. 'ROW_COUNT') but getDiagItemKindName() only handled
   numeric enum values. Added string-based mapping so both forms work.

Also removed KNOWN_FAILING_FIXTURES allowlist — all 225 fixtures now
pass round-trip testing.
@devin-ai-integration devin-ai-integration bot changed the title test: add 21 plpgsql deparser edge case fixtures for END; bug regression fix: add 21 plpgsql deparser edge case fixtures and fix 7 schema_rename failures Apr 7, 2026
4 snapshots updated: all show the corrected output where
GET DIAGNOSTICS now includes ROW_COUNT instead of an empty string.
@pyramation pyramation merged commit 55fe8b7 into main Apr 7, 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