Skip to content

Commit 426f297

Browse files
committed
feat: scaffold plpgsql-deparser package for PL/pgSQL AST deparsing
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.
1 parent e5fdba1 commit 426f297

8 files changed

Lines changed: 2364 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# plpgsql-deparser
2+
3+
PL/pgSQL AST Deparser - Converts PL/pgSQL function ASTs back to SQL strings.
4+
5+
## Overview
6+
7+
This package provides a deparser for PL/pgSQL (PostgreSQL's procedural language) AST structures. It works with the AST output from `parsePlPgSQL` function in `@libpg-query/parser` (or `libpg-query-full`).
8+
9+
The PL/pgSQL AST is different from the regular SQL AST - it represents the internal structure of PL/pgSQL function bodies, including:
10+
11+
- Variable declarations (DECLARE section)
12+
- Control flow statements (IF, CASE, LOOP, WHILE, FOR, FOREACH)
13+
- Exception handling (BEGIN...EXCEPTION...END)
14+
- Cursor operations (OPEN, FETCH, CLOSE)
15+
- Return statements (RETURN, RETURN NEXT, RETURN QUERY)
16+
- Dynamic SQL (EXECUTE)
17+
- And more...
18+
19+
## Installation
20+
21+
```bash
22+
npm install plpgsql-deparser
23+
```
24+
25+
## Usage
26+
27+
### Basic Usage
28+
29+
```typescript
30+
import { parsePlPgSQL } from '@libpg-query/parser';
31+
import { deparse, PLpgSQLDeparser } from 'plpgsql-deparser';
32+
33+
// Parse a PL/pgSQL function
34+
const funcSql = `
35+
CREATE OR REPLACE FUNCTION test_func()
36+
RETURNS INTEGER AS $$
37+
DECLARE
38+
sum int := 0;
39+
BEGIN
40+
FOR n IN 1..10 LOOP
41+
sum := sum + n;
42+
END LOOP;
43+
RETURN sum;
44+
END;
45+
$$ LANGUAGE plpgsql;
46+
`;
47+
48+
const parseResult = await parsePlPgSQL(funcSql);
49+
50+
// Deparse the function body
51+
const deparsed = await deparse(parseResult);
52+
console.log(deparsed);
53+
```
54+
55+
### Synchronous Usage
56+
57+
```typescript
58+
import { deparseSync, PLpgSQLDeparser } from 'plpgsql-deparser';
59+
60+
const deparsed = deparseSync(parseResult);
61+
```
62+
63+
### With Options
64+
65+
```typescript
66+
import { PLpgSQLDeparser } from 'plpgsql-deparser';
67+
68+
const deparser = new PLpgSQLDeparser({
69+
indent: ' ', // 4 spaces instead of default 2
70+
newline: '\n', // newline character
71+
uppercase: false, // lowercase keywords
72+
});
73+
74+
const deparsed = deparser.deparseResult(parseResult);
75+
```
76+
77+
### Deparse a Single Function
78+
79+
```typescript
80+
import { PLpgSQLDeparser } from 'plpgsql-deparser';
81+
82+
// If you have just the function body AST
83+
const funcBody = parseResult.plpgsql_funcs[0].PLpgSQL_function;
84+
const deparsed = PLpgSQLDeparser.deparseFunction(funcBody);
85+
```
86+
87+
## Supported PL/pgSQL Constructs
88+
89+
### Declarations
90+
- Variable declarations with types, defaults, and constraints
91+
- CONSTANT, NOT NULL modifiers
92+
- RECORD types
93+
- Cursor declarations
94+
95+
### Control Flow
96+
- IF / ELSIF / ELSE / END IF
97+
- CASE (simple and searched)
98+
- LOOP / END LOOP
99+
- WHILE ... LOOP
100+
- FOR i IN ... LOOP (integer range)
101+
- FOR rec IN query LOOP
102+
- FOR rec IN cursor LOOP
103+
- FOREACH ... IN ARRAY
104+
105+
### Exception Handling
106+
- BEGIN ... EXCEPTION ... END blocks
107+
- WHEN condition THEN handlers
108+
- Multiple exception conditions
109+
110+
### Cursor Operations
111+
- OPEN cursor
112+
- FETCH cursor INTO
113+
- CLOSE cursor
114+
- MOVE cursor
115+
116+
### Return Statements
117+
- RETURN expression
118+
- RETURN NEXT
119+
- RETURN QUERY
120+
- RETURN QUERY EXECUTE
121+
122+
### Other Statements
123+
- Assignment (:=)
124+
- RAISE (DEBUG, LOG, INFO, NOTICE, WARNING, EXCEPTION)
125+
- ASSERT
126+
- PERFORM
127+
- EXECUTE (dynamic SQL)
128+
- GET DIAGNOSTICS
129+
- COMMIT / ROLLBACK
130+
- EXIT / CONTINUE
131+
132+
## API Reference
133+
134+
### `deparse(parseResult, options?)`
135+
136+
Async function to deparse a PL/pgSQL parse result.
137+
138+
### `deparseSync(parseResult, options?)`
139+
140+
Synchronous version of `deparse`.
141+
142+
### `deparseFunction(func, options?)`
143+
144+
Deparse a single PL/pgSQL function body.
145+
146+
### `deparseFunctionSync(func, options?)`
147+
148+
Synchronous version of `deparseFunction`.
149+
150+
### `PLpgSQLDeparser`
151+
152+
The main deparser class with full control over the deparsing process.
153+
154+
### `PLpgSQLDeparserOptions`
155+
156+
```typescript
157+
interface PLpgSQLDeparserOptions {
158+
indent?: string; // Indentation string (default: ' ')
159+
newline?: string; // Newline character (default: '\n')
160+
uppercase?: boolean; // Uppercase keywords (default: true)
161+
}
162+
```
163+
164+
## Note on AST Structure
165+
166+
The PL/pgSQL AST returned by `parsePlPgSQL` represents the internal structure of function bodies, not the `CREATE FUNCTION` statement itself. To get a complete function definition, you would need to:
167+
168+
1. Parse the `CREATE FUNCTION` statement with the regular `parse()` function
169+
2. Extract the function body
170+
3. Parse the body with `parsePlPgSQL()`
171+
4. Deparse the body with this package
172+
5. Combine with the outer `CREATE FUNCTION` statement
173+
174+
## License
175+
176+
MIT

0 commit comments

Comments
 (0)