Skip to content

Conversation

@pyramation
Copy link
Collaborator

@pyramation pyramation commented Dec 31, 2025

feat: scaffold plpgsql-deparser package for PL/pgSQL AST deparsing

Summary

This PR adds a new plpgsql-deparser package that converts PL/pgSQL function ASTs back to SQL strings. The PL/pgSQL AST (returned by parsePlPgSQL from @libpg-query/parser) is structurally different from the regular SQL AST and represents the internal structure of PL/pgSQL function bodies.

Background: Investigation confirmed that @libpg-query/parser (libpg-query-node's full/ package) exports parsePlPgSQL and parsePlPgSQLSync functions, but pgsql-parser does not currently re-export these. This package provides the deparsing counterpart for PL/pgSQL ASTs.

Supported constructs include:

  • Variable declarations (DECLARE section with types, defaults, CONSTANT, NOT NULL)
  • Control flow (IF/ELSIF/ELSE, CASE, LOOP, WHILE, FOR, FOREACH)
  • Exception handling (BEGIN...EXCEPTION...END blocks)
  • Cursor operations (OPEN, FETCH, CLOSE, MOVE)
  • Return statements (RETURN, RETURN NEXT, RETURN QUERY)
  • Dynamic SQL (EXECUTE with USING)
  • RAISE statements with all log levels
  • And 30+ other PL/pgSQL statement types

Updates since last revision

  • Fixed CALL statement deparser: Removed duplicate CALL keyword - the parsed expression already contains CALL from the parser
  • Improved loop variable detection: Now only excludes implicitly declared loop variables (where var.lineno === stmt.lineno). Explicitly declared variables in DECLARE sections are preserved even when used in FOR loops.
  • Added plpgsql-pretty fixtures: Created __fixtures__/plpgsql-pretty/ with sample SQL files (simple-function.sql, if-else-function.sql, loop-function.sql)
  • Added snapshot testing infrastructure:
    • PlpgsqlPrettyTest utility class for generating snapshot tests
    • Snapshot tests for uppercase/lowercase formatting modes
    • 12 tests now pass (6 round-trip + 6 snapshot tests)

Review & Testing Checklist for Human

  • Verify loop variable lineno matching logic - The fix uses var.lineno === stmt.lineno to detect implicitly declared loop variables. This heuristic could fail with same-line declarations or minified SQL. Check collectLoopVariables() in plpgsql-deparser.ts.
  • Test CALL statement edge cases - The fix assumes the parsed expression always contains the CALL keyword. Verify this holds for all CALL statement variants.
  • Review snapshot test coverage - The snapshot tests only cover uppercase/lowercase formatting. The deparser doesn't have a "pretty" mode yet, so these test formatting consistency rather than pretty-printing.
  • Verify round-trip testing catches real issues - Only plpgsql_domain fixtures pass full round-trip testing. Other categories (plpgsql_control, plpgsql_transaction) still fail due to cursor declarations and complex SQL expression handling.

Recommended test plan:

  1. Run pnpm install and pnpm test in packages/plpgsql-deparser/
  2. Verify 12 tests pass (6 fixture tests + 6 snapshot tests)
  3. Run npm run fixtures to verify fixture generation works
  4. Test a function with explicit DECLARE + FOR loop using same variable to verify the lineno fix

Notes

  • This is a scaffolding PR - the deparser is functional but has known issues with cursor declarations and complex SQL expressions that cause some round-trip tests to fail
  • The round-trip testing framework is working correctly and catching real deparser bugs
  • The package does not integrate with pgsql-parser exports; users need to use @libpg-query/parser directly for parsing
  • Link to Devin run: https://app.devin.ai/sessions/50192c7bb0454b26845853867bd795ce
  • Requested by: Dan Lynch (@pyramation)

This adds a new package for deparsing PL/pgSQL function ASTs back to SQL strings.
The PL/pgSQL AST is different from the regular SQL AST and represents the internal
structure of PL/pgSQL function bodies.

Supported constructs:
- Variable declarations (DECLARE section)
- Control flow (IF, CASE, LOOP, WHILE, FOR, FOREACH)
- Exception handling (BEGIN...EXCEPTION...END)
- Cursor operations (OPEN, FETCH, CLOSE)
- Return statements (RETURN, RETURN NEXT, RETURN QUERY)
- Dynamic SQL (EXECUTE)
- RAISE statements
- And more...

The deparser follows the same patterns as the existing pgsql-deparser package.
@devin-ai-integration
Copy link
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

…rser

- Add @libpg-query/parser as dev dependency for parsing PL/pgSQL
- Create test-utils with fixture pipeline that loads from __fixtures__/plpgsql/
- Add jest.config.js for TypeScript support in tests
- Update tests to use real parser output instead of hardcoded JSON fixtures
- Tests now demonstrate proven pipeline: SQL fixtures -> parser -> deparser
- Add make-fixtures.ts script that generates fixtures from __fixtures__/plpgsql/
- Create generated.json with 176 valid PL/pgSQL statements (full SQL statements)
- Add comprehensive test-utils with cleanPlpgsqlTree for AST comparison
- Implement expectAstMatch for round-trip testing (parse -> deparse -> reparse)
- Add FixtureTestUtils class for loading and running fixture tests
- Update tests to use generated.json with proper round-trip validation
- Add 'fixtures' npm script to package.json
…ests

- Fix CALL statement deparser to avoid duplicate CALL keyword
- Improve loop variable detection to only exclude implicitly declared vars
  (variables where lineno matches the loop statement's lineno)
- Add plpgsql-pretty fixtures folder with sample SQL files
- Add PlpgsqlPrettyTest utility for snapshot testing
- Add snapshot tests for uppercase/lowercase formatting
@pyramation pyramation merged commit c3a9431 into main Dec 31, 2025
28 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.

2 participants