@@ -14,6 +14,48 @@ export interface DeparserOptions {
1414 functionDelimiterFallback ?: string ; // Default: '$EOFCODE$'
1515}
1616
17+ // Type guards for better type safety
18+ function isParseResult ( obj : any ) : obj is t . ParseResult {
19+ // A ParseResult is an object that could have stmts (but not required)
20+ // and is not already wrapped as a Node
21+ // IMPORTANT: ParseResult.stmts contains RawStmt objects directly (not wrapped)
22+ // Example: { version: 170004, stmts: [{ stmt: {...}, stmt_len: 32 }] }
23+ return obj && typeof obj === 'object' &&
24+ ! Array . isArray ( obj ) &&
25+ ! ( 'ParseResult' in obj ) &&
26+ ! ( 'RawStmt' in obj ) &&
27+ // Check if it looks like a ParseResult (has stmts or version)
28+ ( 'stmts' in obj || 'version' in obj ) ;
29+ }
30+
31+ function isWrappedParseResult ( obj : any ) : obj is { ParseResult : t . ParseResult } {
32+ return obj && typeof obj === 'object' && 'ParseResult' in obj ;
33+ }
34+
35+ /**
36+ * Deparser - Converts PostgreSQL AST nodes back to SQL strings
37+ *
38+ * Entry Points:
39+ * 1. ParseResult (from libpg-query) - The complete parse result
40+ * Structure: { version: number, stmts: RawStmt[] }
41+ * Note: stmts contains RawStmt objects directly, NOT wrapped as { RawStmt: ... }
42+ * Example: { version: 170004, stmts: [{ stmt: {...}, stmt_len: 32 }] }
43+ *
44+ * 2. Wrapped ParseResult - When explicitly wrapped as a Node
45+ * Structure: { ParseResult: { version: number, stmts: RawStmt[] } }
46+ *
47+ * 3. Wrapped RawStmt - When explicitly wrapped as a Node
48+ * Structure: { RawStmt: { stmt: Node, stmt_len?: number } }
49+ *
50+ * 4. Array of Nodes - Multiple statements to deparse
51+ * Can be: Node[] (e.g., SelectStmt, InsertStmt, etc.)
52+ *
53+ * 5. Single Node - Individual statement node
54+ * Example: { SelectStmt: {...} }, { InsertStmt: {...} }, etc.
55+ *
56+ * The deparser automatically detects bare ParseResult objects for backward
57+ * compatibility and wraps them internally for consistent processing.
58+ */
1759export class Deparser implements DeparserVisitor {
1860 private formatter : SqlFormatter ;
1961 private tree : Node [ ] ;
@@ -29,30 +71,44 @@ export class Deparser implements DeparserVisitor {
2971 ...opts
3072 } ;
3173
32- // Handle ParseResult objects
33- if ( tree && typeof tree === 'object' && ! Array . isArray ( tree ) && 'stmts' in tree ) {
34- // This is a ParseResult
35- const parseResult = tree as t . ParseResult ;
36- // Extract the actual Node from each RawStmt
37- this . tree = ( parseResult . stmts || [ ] ) . map ( rawStmt => rawStmt . stmt ) . filter ( stmt => stmt !== undefined ) as Node [ ] ;
38- }
39- // Handle arrays of Node
40- else if ( Array . isArray ( tree ) ) {
74+ // Handle different input types
75+ if ( isParseResult ( tree ) ) {
76+ // Duck-typed ParseResult (backward compatibility)
77+ // Wrap it as a proper Node for consistent handling
78+ this . tree = [ { ParseResult : tree } as Node ] ;
79+ } else if ( Array . isArray ( tree ) ) {
80+ // Array of Nodes
4181 this . tree = tree ;
42- }
43- // Handle single Node
44- else {
82+ } else {
83+ // Single Node (including wrapped ParseResult)
4584 this . tree = [ tree as Node ] ;
4685 }
4786 }
4887
88+ /**
89+ * Static method to deparse PostgreSQL AST nodes to SQL
90+ * @param query - Can be:
91+ * - ParseResult from libpg-query (e.g., { version: 170004, stmts: [...] })
92+ * - Wrapped ParseResult node (e.g., { ParseResult: {...} })
93+ * - Wrapped RawStmt node (e.g., { RawStmt: {...} })
94+ * - Array of Nodes
95+ * - Single Node (e.g., { SelectStmt: {...} })
96+ * @param opts - Deparser options for formatting
97+ * @returns The deparsed SQL string
98+ */
4999 static deparse ( query : Node | Node [ ] | t . ParseResult , opts : DeparserOptions = { } ) : string {
50100 return new Deparser ( query , opts ) . deparseQuery ( ) ;
51101 }
52102
53103 deparseQuery ( ) : string {
54104 return this . tree
55- . map ( node => this . deparse ( node ) )
105+ . map ( node => {
106+ // All nodes should go through the standard deparse method
107+ // which will route to the appropriate handler
108+ const result = this . deparse ( node ) ;
109+ return result || '' ;
110+ } )
111+ . filter ( result => result !== '' )
56112 . join ( this . formatter . newline ( ) + this . formatter . newline ( ) ) ;
57113 }
58114
@@ -88,6 +144,12 @@ export class Deparser implements DeparserVisitor {
88144
89145 visit ( node : Node , context : DeparserContext = { parentNodeTypes : [ ] } ) : string {
90146 const nodeType = this . getNodeType ( node ) ;
147+
148+ // Handle empty objects
149+ if ( ! nodeType ) {
150+ return '' ;
151+ }
152+
91153 const nodeData = this . getNodeData ( node ) ;
92154
93155 const methodName = nodeType as keyof this;
@@ -116,27 +178,37 @@ export class Deparser implements DeparserVisitor {
116178 return node ;
117179 }
118180
119- RawStmt ( node : t . RawStmt , context : DeparserContext ) : string {
120- if ( node . stmt_len ) {
121- return this . deparse ( node . stmt , context ) + '; ';
181+ ParseResult ( node : t . ParseResult , context : DeparserContext ) : string {
182+ if ( ! node . stmts || node . stmts . length === 0 ) {
183+ return ' ';
122184 }
123- return this . deparse ( node . stmt , context ) ;
185+
186+ // Deparse each RawStmt in the ParseResult
187+ // Note: node.stmts contains RawStmt objects directly (not wrapped)
188+ // Each element has structure: { stmt: Node, stmt_len?: number, stmt_location?: number }
189+ return node . stmts
190+ . filter ( ( rawStmt : t . RawStmt ) => rawStmt != null )
191+ . map ( ( rawStmt : t . RawStmt ) => this . RawStmt ( rawStmt , context ) )
192+ . filter ( ( result : string ) => result !== '' )
193+ . join ( this . formatter . newline ( ) + this . formatter . newline ( ) ) ;
124194 }
125195
126- stmt ( node : any , context : DeparserContext = { parentNodeTypes : [ ] } ) : string {
127- // Handle stmt wrapper nodes that contain the actual statement
128- const keys = Object . keys ( node ) ;
129- if ( keys . length === 1 ) {
130- const statementType = keys [ 0 ] ;
131- const methodName = statementType as keyof this;
132- if ( typeof this [ methodName ] === 'function' ) {
133- return ( this [ methodName ] as any ) ( node [ statementType ] , context ) ;
134- }
135- throw new Error ( `Deparser does not handle statement type: ${ statementType } ` ) ;
196+ RawStmt ( node : t . RawStmt , context : DeparserContext ) : string {
197+ if ( ! node . stmt ) {
198+ return '' ;
136199 }
137- return '' ;
200+
201+ const deparsedStmt = this . deparse ( node . stmt , context ) ;
202+
203+ // Add semicolon if stmt_len is provided (indicates it had one in original)
204+ if ( node . stmt_len ) {
205+ return deparsedStmt + ';' ;
206+ }
207+ return deparsedStmt ;
138208 }
139209
210+
211+
140212 SelectStmt ( node : t . SelectStmt , context : DeparserContext ) : string {
141213 const output : string [ ] = [ ] ;
142214
@@ -1326,7 +1398,7 @@ export class Deparser implements DeparserVisitor {
13261398
13271399 let args : string | null = null ;
13281400 if ( node . typmods ) {
1329- const isInterval = names . some ( name => {
1401+ const isInterval = names . some ( ( name : any ) => {
13301402 const nameStr = typeof name === 'string' ? name : ( name . String ?. sval || name . String ?. str ) ;
13311403 return nameStr === 'interval' ;
13321404 } ) ;
@@ -10599,18 +10671,6 @@ export class Deparser implements DeparserVisitor {
1059910671 return output . join ( ' ' ) ;
1060010672 }
1060110673
10602- version ( node : any , context : any ) : string {
10603- // Handle version node - typically just return the version number
10604- if ( typeof node === 'number' ) {
10605- return node . toString ( ) ;
10606- }
10607- if ( typeof node === 'string' ) {
10608- return node ;
10609- }
10610- if ( node && typeof node === 'object' && node . version ) {
10611- return node . version . toString ( ) ;
10612- }
10613- return '' ;
10614- }
10674+
1061510675
1061610676}
0 commit comments