pgsql-deparser 17.1.0 → 17.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/deparser.d.ts CHANGED
@@ -4,19 +4,64 @@ import * as t from '@pgsql/types';
4
4
  export interface DeparserOptions {
5
5
  newline?: string;
6
6
  tab?: string;
7
+ functionDelimiter?: string;
8
+ functionDelimiterFallback?: string;
7
9
  }
10
+ /**
11
+ * Deparser - Converts PostgreSQL AST nodes back to SQL strings
12
+ *
13
+ * Entry Points:
14
+ * 1. ParseResult (from libpg-query) - The complete parse result
15
+ * Structure: { version: number, stmts: RawStmt[] }
16
+ * Note: stmts is "repeated RawStmt" in protobuf, so array contains RawStmt
17
+ * objects inline (not wrapped as { RawStmt: ... } nodes)
18
+ * Example: { version: 170004, stmts: [{ stmt: {...}, stmt_len: 32 }] }
19
+ *
20
+ * 2. Wrapped ParseResult - When explicitly wrapped as a Node
21
+ * Structure: { ParseResult: { version: number, stmts: RawStmt[] } }
22
+ *
23
+ * 3. Wrapped RawStmt - When explicitly wrapped as a Node
24
+ * Structure: { RawStmt: { stmt: Node, stmt_len?: number } }
25
+ *
26
+ * 4. Array of Nodes - Multiple statements to deparse
27
+ * Can be: Node[] (e.g., SelectStmt, InsertStmt, etc.)
28
+ *
29
+ * 5. Single Node - Individual statement node
30
+ * Example: { SelectStmt: {...} }, { InsertStmt: {...} }, etc.
31
+ *
32
+ * The deparser automatically detects bare ParseResult objects for backward
33
+ * compatibility and wraps them internally for consistent processing.
34
+ */
8
35
  export declare class Deparser implements DeparserVisitor {
9
36
  private formatter;
10
37
  private tree;
11
- constructor(tree: Node | Node[], opts?: DeparserOptions);
12
- static deparse(query: Node | Node[], opts?: DeparserOptions): string;
38
+ private options;
39
+ constructor(tree: Node | Node[] | t.ParseResult, opts?: DeparserOptions);
40
+ /**
41
+ * Static method to deparse PostgreSQL AST nodes to SQL
42
+ * @param query - Can be:
43
+ * - ParseResult from libpg-query (e.g., { version: 170004, stmts: [...] })
44
+ * - Wrapped ParseResult node (e.g., { ParseResult: {...} })
45
+ * - Wrapped RawStmt node (e.g., { RawStmt: {...} })
46
+ * - Array of Nodes
47
+ * - Single Node (e.g., { SelectStmt: {...} })
48
+ * @param opts - Deparser options for formatting
49
+ * @returns The deparsed SQL string
50
+ */
51
+ static deparse(query: Node | Node[] | t.ParseResult, opts?: DeparserOptions): string;
13
52
  deparseQuery(): string;
53
+ /**
54
+ * Get the appropriate function delimiter based on the body content
55
+ * @param body The function body to check
56
+ * @returns The delimiter to use
57
+ */
58
+ private getFunctionDelimiter;
14
59
  deparse(node: Node, context?: DeparserContext): string | null;
15
60
  visit(node: Node, context?: DeparserContext): string;
16
61
  getNodeType(node: Node): string;
17
62
  getNodeData(node: Node): any;
63
+ ParseResult(node: t.ParseResult, context: DeparserContext): string;
18
64
  RawStmt(node: t.RawStmt, context: DeparserContext): string;
19
- stmt(node: any, context?: DeparserContext): string;
20
65
  SelectStmt(node: t.SelectStmt, context: DeparserContext): string;
21
66
  A_Expr(node: t.A_Expr, context: DeparserContext): string;
22
67
  deparseOperatorName(name: t.Node[]): string;
@@ -249,5 +294,4 @@ export declare class Deparser implements DeparserVisitor {
249
294
  AlterObjectSchemaStmt(node: t.AlterObjectSchemaStmt, context: DeparserContext): string;
250
295
  AlterRoleSetStmt(node: t.AlterRoleSetStmt, context: DeparserContext): string;
251
296
  CreateForeignTableStmt(node: t.CreateForeignTableStmt, context: DeparserContext): string;
252
- version(node: any, context: any): string;
253
297
  }
package/deparser.js CHANGED
@@ -4,27 +4,112 @@ exports.Deparser = void 0;
4
4
  const sql_formatter_1 = require("./utils/sql-formatter");
5
5
  const quote_utils_1 = require("./utils/quote-utils");
6
6
  const list_utils_1 = require("./utils/list-utils");
7
+ // Type guards for better type safety
8
+ function isParseResult(obj) {
9
+ // A ParseResult is an object that could have stmts (but not required)
10
+ // and is not already wrapped as a Node
11
+ // IMPORTANT: ParseResult.stmts is "repeated RawStmt" in protobuf, meaning
12
+ // the array contains RawStmt objects inline (not wrapped as { RawStmt: ... })
13
+ // Example: { version: 170004, stmts: [{ stmt: {...}, stmt_len: 32 }] }
14
+ return obj && typeof obj === 'object' &&
15
+ !Array.isArray(obj) &&
16
+ !('ParseResult' in obj) &&
17
+ !('RawStmt' in obj) &&
18
+ // Check if it looks like a ParseResult (has stmts or version)
19
+ ('stmts' in obj || 'version' in obj);
20
+ }
21
+ function isWrappedParseResult(obj) {
22
+ return obj && typeof obj === 'object' && 'ParseResult' in obj;
23
+ }
24
+ /**
25
+ * Deparser - Converts PostgreSQL AST nodes back to SQL strings
26
+ *
27
+ * Entry Points:
28
+ * 1. ParseResult (from libpg-query) - The complete parse result
29
+ * Structure: { version: number, stmts: RawStmt[] }
30
+ * Note: stmts is "repeated RawStmt" in protobuf, so array contains RawStmt
31
+ * objects inline (not wrapped as { RawStmt: ... } nodes)
32
+ * Example: { version: 170004, stmts: [{ stmt: {...}, stmt_len: 32 }] }
33
+ *
34
+ * 2. Wrapped ParseResult - When explicitly wrapped as a Node
35
+ * Structure: { ParseResult: { version: number, stmts: RawStmt[] } }
36
+ *
37
+ * 3. Wrapped RawStmt - When explicitly wrapped as a Node
38
+ * Structure: { RawStmt: { stmt: Node, stmt_len?: number } }
39
+ *
40
+ * 4. Array of Nodes - Multiple statements to deparse
41
+ * Can be: Node[] (e.g., SelectStmt, InsertStmt, etc.)
42
+ *
43
+ * 5. Single Node - Individual statement node
44
+ * Example: { SelectStmt: {...} }, { InsertStmt: {...} }, etc.
45
+ *
46
+ * The deparser automatically detects bare ParseResult objects for backward
47
+ * compatibility and wraps them internally for consistent processing.
48
+ */
7
49
  class Deparser {
8
50
  formatter;
9
51
  tree;
52
+ options;
10
53
  constructor(tree, opts = {}) {
11
54
  this.formatter = new sql_formatter_1.SqlFormatter(opts.newline, opts.tab);
12
- // Handle parsed query objects that contain both version and stmts
13
- if (tree && typeof tree === 'object' && !Array.isArray(tree) && 'stmts' in tree) {
14
- this.tree = tree.stmts;
55
+ // Set default options
56
+ this.options = {
57
+ functionDelimiter: '$$',
58
+ functionDelimiterFallback: '$EOFCODE$',
59
+ ...opts
60
+ };
61
+ // Handle different input types
62
+ if (isParseResult(tree)) {
63
+ // Duck-typed ParseResult (backward compatibility)
64
+ // Wrap it as a proper Node for consistent handling
65
+ this.tree = [{ ParseResult: tree }];
15
66
  }
16
- else {
17
- this.tree = Array.isArray(tree) ? tree : [tree];
67
+ else if (Array.isArray(tree)) {
68
+ // Array of Nodes
69
+ this.tree = tree;
18
70
  }
19
- }
71
+ else {
72
+ // Single Node (including wrapped ParseResult)
73
+ this.tree = [tree];
74
+ }
75
+ }
76
+ /**
77
+ * Static method to deparse PostgreSQL AST nodes to SQL
78
+ * @param query - Can be:
79
+ * - ParseResult from libpg-query (e.g., { version: 170004, stmts: [...] })
80
+ * - Wrapped ParseResult node (e.g., { ParseResult: {...} })
81
+ * - Wrapped RawStmt node (e.g., { RawStmt: {...} })
82
+ * - Array of Nodes
83
+ * - Single Node (e.g., { SelectStmt: {...} })
84
+ * @param opts - Deparser options for formatting
85
+ * @returns The deparsed SQL string
86
+ */
20
87
  static deparse(query, opts = {}) {
21
88
  return new Deparser(query, opts).deparseQuery();
22
89
  }
23
90
  deparseQuery() {
24
91
  return this.tree
25
- .map(node => this.deparse(node))
92
+ .map(node => {
93
+ // All nodes should go through the standard deparse method
94
+ // which will route to the appropriate handler
95
+ const result = this.deparse(node);
96
+ return result || '';
97
+ })
98
+ .filter(result => result !== '')
26
99
  .join(this.formatter.newline() + this.formatter.newline());
27
100
  }
101
+ /**
102
+ * Get the appropriate function delimiter based on the body content
103
+ * @param body The function body to check
104
+ * @returns The delimiter to use
105
+ */
106
+ getFunctionDelimiter(body) {
107
+ const delimiter = this.options.functionDelimiter || '$$';
108
+ if (body.includes(delimiter)) {
109
+ return this.options.functionDelimiterFallback || '$EOFCODE$';
110
+ }
111
+ return delimiter;
112
+ }
28
113
  deparse(node, context = { parentNodeTypes: [] }) {
29
114
  if (node == null) {
30
115
  return null;
@@ -42,6 +127,10 @@ class Deparser {
42
127
  }
43
128
  visit(node, context = { parentNodeTypes: [] }) {
44
129
  const nodeType = this.getNodeType(node);
130
+ // Handle empty objects
131
+ if (!nodeType) {
132
+ return '';
133
+ }
45
134
  const nodeData = this.getNodeData(node);
46
135
  const methodName = nodeType;
47
136
  if (typeof this[methodName] === 'function') {
@@ -64,24 +153,29 @@ class Deparser {
64
153
  }
65
154
  return node;
66
155
  }
67
- RawStmt(node, context) {
68
- if (node.stmt_len) {
69
- return this.deparse(node.stmt, context) + ';';
156
+ ParseResult(node, context) {
157
+ if (!node.stmts || node.stmts.length === 0) {
158
+ return '';
70
159
  }
71
- return this.deparse(node.stmt, context);
160
+ // Deparse each RawStmt in the ParseResult
161
+ // Note: node.stmts is "repeated RawStmt" so contains RawStmt objects inline
162
+ // Each element has structure: { stmt: Node, stmt_len?: number, stmt_location?: number }
163
+ return node.stmts
164
+ .filter((rawStmt) => rawStmt != null)
165
+ .map((rawStmt) => this.RawStmt(rawStmt, context))
166
+ .filter((result) => result !== '')
167
+ .join(this.formatter.newline() + this.formatter.newline());
72
168
  }
73
- stmt(node, context = { parentNodeTypes: [] }) {
74
- // Handle stmt wrapper nodes that contain the actual statement
75
- const keys = Object.keys(node);
76
- if (keys.length === 1) {
77
- const statementType = keys[0];
78
- const methodName = statementType;
79
- if (typeof this[methodName] === 'function') {
80
- return this[methodName](node[statementType], context);
81
- }
82
- throw new Error(`Deparser does not handle statement type: ${statementType}`);
169
+ RawStmt(node, context) {
170
+ if (!node.stmt) {
171
+ return '';
83
172
  }
84
- return '';
173
+ const deparsedStmt = this.deparse(node.stmt, context);
174
+ // Add semicolon if stmt_len is provided (indicates it had one in original)
175
+ if (node.stmt_len) {
176
+ return deparsedStmt + ';';
177
+ }
178
+ return deparsedStmt;
85
179
  }
86
180
  SelectStmt(node, context) {
87
181
  const output = [];
@@ -1193,7 +1287,7 @@ class Deparser {
1193
1287
  }
1194
1288
  let args = null;
1195
1289
  if (node.typmods) {
1196
- const isInterval = names.some(name => {
1290
+ const isInterval = names.some((name) => {
1197
1291
  const nameStr = typeof name === 'string' ? name : (name.String?.sval || name.String?.str);
1198
1292
  return nameStr === 'interval';
1199
1293
  });
@@ -4789,17 +4883,17 @@ class Deparser {
4789
4883
  }
4790
4884
  if (context.parentNodeTypes.includes('DoStmt')) {
4791
4885
  if (node.defname === 'as') {
4886
+ const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
4887
+ const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
4792
4888
  if (Array.isArray(argValue)) {
4793
4889
  const bodyParts = argValue;
4794
- if (bodyParts.length === 1) {
4795
- return `$$${bodyParts[0]}$$`;
4796
- }
4797
- else {
4798
- return `$$${bodyParts.join('')}$$`;
4799
- }
4890
+ const body = bodyParts.join('');
4891
+ const delimiter = this.getFunctionDelimiter(body);
4892
+ return `${delimiter}${body}${delimiter}`;
4800
4893
  }
4801
4894
  else {
4802
- return `$$${argValue}$$`;
4895
+ const delimiter = this.getFunctionDelimiter(argValue);
4896
+ return `${delimiter}${argValue}${delimiter}`;
4803
4897
  }
4804
4898
  }
4805
4899
  return '';
@@ -4818,16 +4912,14 @@ class Deparser {
4818
4912
  });
4819
4913
  if (bodyParts.length === 1) {
4820
4914
  const body = bodyParts[0];
4821
- // Check if body contains $$ to avoid conflicts
4822
- if (body.includes('$$')) {
4823
- return `AS '${body.replace(/'/g, "''")}'`;
4824
- }
4825
- else {
4826
- return `AS $$${body}$$`;
4827
- }
4915
+ const delimiter = this.getFunctionDelimiter(body);
4916
+ return `AS ${delimiter}${body}${delimiter}`;
4828
4917
  }
4829
4918
  else {
4830
- return `AS ${bodyParts.map((part) => `$$${part}$$`).join(', ')}`;
4919
+ return `AS ${bodyParts.map((part) => {
4920
+ const delimiter = this.getFunctionDelimiter(part);
4921
+ return `${delimiter}${part}${delimiter}`;
4922
+ }).join(', ')}`;
4831
4923
  }
4832
4924
  }
4833
4925
  // Handle Array type (legacy support)
@@ -4835,27 +4927,20 @@ class Deparser {
4835
4927
  const bodyParts = argValue;
4836
4928
  if (bodyParts.length === 1) {
4837
4929
  const body = bodyParts[0];
4838
- // Check if body contains $$ to avoid conflicts
4839
- if (body.includes('$$')) {
4840
- return `AS '${body.replace(/'/g, "''")}'`;
4841
- }
4842
- else {
4843
- return `AS $$${body}$$`;
4844
- }
4930
+ const delimiter = this.getFunctionDelimiter(body);
4931
+ return `AS ${delimiter}${body}${delimiter}`;
4845
4932
  }
4846
4933
  else {
4847
- return `AS ${bodyParts.map(part => `$$${part}$$`).join(', ')}`;
4934
+ return `AS ${bodyParts.map(part => {
4935
+ const delimiter = this.getFunctionDelimiter(part);
4936
+ return `${delimiter}${part}${delimiter}`;
4937
+ }).join(', ')}`;
4848
4938
  }
4849
4939
  }
4850
4940
  // Handle String type (single function body)
4851
4941
  else {
4852
- // Check if argValue contains $$ to avoid conflicts
4853
- if (argValue.includes('$$')) {
4854
- return `AS '${argValue.replace(/'/g, "''")}'`;
4855
- }
4856
- else {
4857
- return `AS $$${argValue}$$`;
4858
- }
4942
+ const delimiter = this.getFunctionDelimiter(argValue);
4943
+ return `AS ${delimiter}${argValue}${delimiter}`;
4859
4944
  }
4860
4945
  }
4861
4946
  if (node.defname === 'language') {
@@ -5965,12 +6050,12 @@ class Deparser {
5965
6050
  processedArgs.push(`LANGUAGE ${langValue}`);
5966
6051
  }
5967
6052
  else if (defElem.defname === 'as') {
5968
- // Handle code block with dollar quoting
6053
+ // Handle code block with configurable delimiter
5969
6054
  const argNodeType = this.getNodeType(defElem.arg);
5970
6055
  if (argNodeType === 'String') {
5971
6056
  const stringNode = this.getNodeData(defElem.arg);
5972
- const dollarTag = this.generateUniqueDollarTag(stringNode.sval);
5973
- processedArgs.push(`${dollarTag}${stringNode.sval}${dollarTag}`);
6057
+ const delimiter = this.getFunctionDelimiter(stringNode.sval);
6058
+ processedArgs.push(`${delimiter}${stringNode.sval}${delimiter}`);
5974
6059
  }
5975
6060
  else {
5976
6061
  processedArgs.push(this.visit(defElem.arg, doContext));
@@ -6004,9 +6089,11 @@ class Deparser {
6004
6089
  }
6005
6090
  InlineCodeBlock(node, context) {
6006
6091
  if (node.source_text) {
6007
- return `$$${node.source_text}$$`;
6092
+ const delimiter = this.getFunctionDelimiter(node.source_text);
6093
+ return `${delimiter}${node.source_text}${delimiter}`;
6008
6094
  }
6009
- return '$$$$';
6095
+ const delimiter = this.options.functionDelimiter || '$$';
6096
+ return `${delimiter}${delimiter}`;
6010
6097
  }
6011
6098
  CallContext(node, context) {
6012
6099
  if (node.atomic !== undefined) {
@@ -9589,18 +9676,5 @@ class Deparser {
9589
9676
  }
9590
9677
  return output.join(' ');
9591
9678
  }
9592
- version(node, context) {
9593
- // Handle version node - typically just return the version number
9594
- if (typeof node === 'number') {
9595
- return node.toString();
9596
- }
9597
- if (typeof node === 'string') {
9598
- return node;
9599
- }
9600
- if (node && typeof node === 'object' && node.version) {
9601
- return node.version.toString();
9602
- }
9603
- return '';
9604
- }
9605
9679
  }
9606
9680
  exports.Deparser = Deparser;
package/esm/deparser.js CHANGED
@@ -1,27 +1,112 @@
1
1
  import { SqlFormatter } from './utils/sql-formatter';
2
2
  import { QuoteUtils } from './utils/quote-utils';
3
3
  import { ListUtils } from './utils/list-utils';
4
+ // Type guards for better type safety
5
+ function isParseResult(obj) {
6
+ // A ParseResult is an object that could have stmts (but not required)
7
+ // and is not already wrapped as a Node
8
+ // IMPORTANT: ParseResult.stmts is "repeated RawStmt" in protobuf, meaning
9
+ // the array contains RawStmt objects inline (not wrapped as { RawStmt: ... })
10
+ // Example: { version: 170004, stmts: [{ stmt: {...}, stmt_len: 32 }] }
11
+ return obj && typeof obj === 'object' &&
12
+ !Array.isArray(obj) &&
13
+ !('ParseResult' in obj) &&
14
+ !('RawStmt' in obj) &&
15
+ // Check if it looks like a ParseResult (has stmts or version)
16
+ ('stmts' in obj || 'version' in obj);
17
+ }
18
+ function isWrappedParseResult(obj) {
19
+ return obj && typeof obj === 'object' && 'ParseResult' in obj;
20
+ }
21
+ /**
22
+ * Deparser - Converts PostgreSQL AST nodes back to SQL strings
23
+ *
24
+ * Entry Points:
25
+ * 1. ParseResult (from libpg-query) - The complete parse result
26
+ * Structure: { version: number, stmts: RawStmt[] }
27
+ * Note: stmts is "repeated RawStmt" in protobuf, so array contains RawStmt
28
+ * objects inline (not wrapped as { RawStmt: ... } nodes)
29
+ * Example: { version: 170004, stmts: [{ stmt: {...}, stmt_len: 32 }] }
30
+ *
31
+ * 2. Wrapped ParseResult - When explicitly wrapped as a Node
32
+ * Structure: { ParseResult: { version: number, stmts: RawStmt[] } }
33
+ *
34
+ * 3. Wrapped RawStmt - When explicitly wrapped as a Node
35
+ * Structure: { RawStmt: { stmt: Node, stmt_len?: number } }
36
+ *
37
+ * 4. Array of Nodes - Multiple statements to deparse
38
+ * Can be: Node[] (e.g., SelectStmt, InsertStmt, etc.)
39
+ *
40
+ * 5. Single Node - Individual statement node
41
+ * Example: { SelectStmt: {...} }, { InsertStmt: {...} }, etc.
42
+ *
43
+ * The deparser automatically detects bare ParseResult objects for backward
44
+ * compatibility and wraps them internally for consistent processing.
45
+ */
4
46
  export class Deparser {
5
47
  formatter;
6
48
  tree;
49
+ options;
7
50
  constructor(tree, opts = {}) {
8
51
  this.formatter = new SqlFormatter(opts.newline, opts.tab);
9
- // Handle parsed query objects that contain both version and stmts
10
- if (tree && typeof tree === 'object' && !Array.isArray(tree) && 'stmts' in tree) {
11
- this.tree = tree.stmts;
52
+ // Set default options
53
+ this.options = {
54
+ functionDelimiter: '$$',
55
+ functionDelimiterFallback: '$EOFCODE$',
56
+ ...opts
57
+ };
58
+ // Handle different input types
59
+ if (isParseResult(tree)) {
60
+ // Duck-typed ParseResult (backward compatibility)
61
+ // Wrap it as a proper Node for consistent handling
62
+ this.tree = [{ ParseResult: tree }];
12
63
  }
13
- else {
14
- this.tree = Array.isArray(tree) ? tree : [tree];
64
+ else if (Array.isArray(tree)) {
65
+ // Array of Nodes
66
+ this.tree = tree;
15
67
  }
16
- }
68
+ else {
69
+ // Single Node (including wrapped ParseResult)
70
+ this.tree = [tree];
71
+ }
72
+ }
73
+ /**
74
+ * Static method to deparse PostgreSQL AST nodes to SQL
75
+ * @param query - Can be:
76
+ * - ParseResult from libpg-query (e.g., { version: 170004, stmts: [...] })
77
+ * - Wrapped ParseResult node (e.g., { ParseResult: {...} })
78
+ * - Wrapped RawStmt node (e.g., { RawStmt: {...} })
79
+ * - Array of Nodes
80
+ * - Single Node (e.g., { SelectStmt: {...} })
81
+ * @param opts - Deparser options for formatting
82
+ * @returns The deparsed SQL string
83
+ */
17
84
  static deparse(query, opts = {}) {
18
85
  return new Deparser(query, opts).deparseQuery();
19
86
  }
20
87
  deparseQuery() {
21
88
  return this.tree
22
- .map(node => this.deparse(node))
89
+ .map(node => {
90
+ // All nodes should go through the standard deparse method
91
+ // which will route to the appropriate handler
92
+ const result = this.deparse(node);
93
+ return result || '';
94
+ })
95
+ .filter(result => result !== '')
23
96
  .join(this.formatter.newline() + this.formatter.newline());
24
97
  }
98
+ /**
99
+ * Get the appropriate function delimiter based on the body content
100
+ * @param body The function body to check
101
+ * @returns The delimiter to use
102
+ */
103
+ getFunctionDelimiter(body) {
104
+ const delimiter = this.options.functionDelimiter || '$$';
105
+ if (body.includes(delimiter)) {
106
+ return this.options.functionDelimiterFallback || '$EOFCODE$';
107
+ }
108
+ return delimiter;
109
+ }
25
110
  deparse(node, context = { parentNodeTypes: [] }) {
26
111
  if (node == null) {
27
112
  return null;
@@ -39,6 +124,10 @@ export class Deparser {
39
124
  }
40
125
  visit(node, context = { parentNodeTypes: [] }) {
41
126
  const nodeType = this.getNodeType(node);
127
+ // Handle empty objects
128
+ if (!nodeType) {
129
+ return '';
130
+ }
42
131
  const nodeData = this.getNodeData(node);
43
132
  const methodName = nodeType;
44
133
  if (typeof this[methodName] === 'function') {
@@ -61,24 +150,29 @@ export class Deparser {
61
150
  }
62
151
  return node;
63
152
  }
64
- RawStmt(node, context) {
65
- if (node.stmt_len) {
66
- return this.deparse(node.stmt, context) + ';';
153
+ ParseResult(node, context) {
154
+ if (!node.stmts || node.stmts.length === 0) {
155
+ return '';
67
156
  }
68
- return this.deparse(node.stmt, context);
157
+ // Deparse each RawStmt in the ParseResult
158
+ // Note: node.stmts is "repeated RawStmt" so contains RawStmt objects inline
159
+ // Each element has structure: { stmt: Node, stmt_len?: number, stmt_location?: number }
160
+ return node.stmts
161
+ .filter((rawStmt) => rawStmt != null)
162
+ .map((rawStmt) => this.RawStmt(rawStmt, context))
163
+ .filter((result) => result !== '')
164
+ .join(this.formatter.newline() + this.formatter.newline());
69
165
  }
70
- stmt(node, context = { parentNodeTypes: [] }) {
71
- // Handle stmt wrapper nodes that contain the actual statement
72
- const keys = Object.keys(node);
73
- if (keys.length === 1) {
74
- const statementType = keys[0];
75
- const methodName = statementType;
76
- if (typeof this[methodName] === 'function') {
77
- return this[methodName](node[statementType], context);
78
- }
79
- throw new Error(`Deparser does not handle statement type: ${statementType}`);
166
+ RawStmt(node, context) {
167
+ if (!node.stmt) {
168
+ return '';
80
169
  }
81
- return '';
170
+ const deparsedStmt = this.deparse(node.stmt, context);
171
+ // Add semicolon if stmt_len is provided (indicates it had one in original)
172
+ if (node.stmt_len) {
173
+ return deparsedStmt + ';';
174
+ }
175
+ return deparsedStmt;
82
176
  }
83
177
  SelectStmt(node, context) {
84
178
  const output = [];
@@ -1190,7 +1284,7 @@ export class Deparser {
1190
1284
  }
1191
1285
  let args = null;
1192
1286
  if (node.typmods) {
1193
- const isInterval = names.some(name => {
1287
+ const isInterval = names.some((name) => {
1194
1288
  const nameStr = typeof name === 'string' ? name : (name.String?.sval || name.String?.str);
1195
1289
  return nameStr === 'interval';
1196
1290
  });
@@ -4786,17 +4880,17 @@ export class Deparser {
4786
4880
  }
4787
4881
  if (context.parentNodeTypes.includes('DoStmt')) {
4788
4882
  if (node.defname === 'as') {
4883
+ const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
4884
+ const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
4789
4885
  if (Array.isArray(argValue)) {
4790
4886
  const bodyParts = argValue;
4791
- if (bodyParts.length === 1) {
4792
- return `$$${bodyParts[0]}$$`;
4793
- }
4794
- else {
4795
- return `$$${bodyParts.join('')}$$`;
4796
- }
4887
+ const body = bodyParts.join('');
4888
+ const delimiter = this.getFunctionDelimiter(body);
4889
+ return `${delimiter}${body}${delimiter}`;
4797
4890
  }
4798
4891
  else {
4799
- return `$$${argValue}$$`;
4892
+ const delimiter = this.getFunctionDelimiter(argValue);
4893
+ return `${delimiter}${argValue}${delimiter}`;
4800
4894
  }
4801
4895
  }
4802
4896
  return '';
@@ -4815,16 +4909,14 @@ export class Deparser {
4815
4909
  });
4816
4910
  if (bodyParts.length === 1) {
4817
4911
  const body = bodyParts[0];
4818
- // Check if body contains $$ to avoid conflicts
4819
- if (body.includes('$$')) {
4820
- return `AS '${body.replace(/'/g, "''")}'`;
4821
- }
4822
- else {
4823
- return `AS $$${body}$$`;
4824
- }
4912
+ const delimiter = this.getFunctionDelimiter(body);
4913
+ return `AS ${delimiter}${body}${delimiter}`;
4825
4914
  }
4826
4915
  else {
4827
- return `AS ${bodyParts.map((part) => `$$${part}$$`).join(', ')}`;
4916
+ return `AS ${bodyParts.map((part) => {
4917
+ const delimiter = this.getFunctionDelimiter(part);
4918
+ return `${delimiter}${part}${delimiter}`;
4919
+ }).join(', ')}`;
4828
4920
  }
4829
4921
  }
4830
4922
  // Handle Array type (legacy support)
@@ -4832,27 +4924,20 @@ export class Deparser {
4832
4924
  const bodyParts = argValue;
4833
4925
  if (bodyParts.length === 1) {
4834
4926
  const body = bodyParts[0];
4835
- // Check if body contains $$ to avoid conflicts
4836
- if (body.includes('$$')) {
4837
- return `AS '${body.replace(/'/g, "''")}'`;
4838
- }
4839
- else {
4840
- return `AS $$${body}$$`;
4841
- }
4927
+ const delimiter = this.getFunctionDelimiter(body);
4928
+ return `AS ${delimiter}${body}${delimiter}`;
4842
4929
  }
4843
4930
  else {
4844
- return `AS ${bodyParts.map(part => `$$${part}$$`).join(', ')}`;
4931
+ return `AS ${bodyParts.map(part => {
4932
+ const delimiter = this.getFunctionDelimiter(part);
4933
+ return `${delimiter}${part}${delimiter}`;
4934
+ }).join(', ')}`;
4845
4935
  }
4846
4936
  }
4847
4937
  // Handle String type (single function body)
4848
4938
  else {
4849
- // Check if argValue contains $$ to avoid conflicts
4850
- if (argValue.includes('$$')) {
4851
- return `AS '${argValue.replace(/'/g, "''")}'`;
4852
- }
4853
- else {
4854
- return `AS $$${argValue}$$`;
4855
- }
4939
+ const delimiter = this.getFunctionDelimiter(argValue);
4940
+ return `AS ${delimiter}${argValue}${delimiter}`;
4856
4941
  }
4857
4942
  }
4858
4943
  if (node.defname === 'language') {
@@ -5962,12 +6047,12 @@ export class Deparser {
5962
6047
  processedArgs.push(`LANGUAGE ${langValue}`);
5963
6048
  }
5964
6049
  else if (defElem.defname === 'as') {
5965
- // Handle code block with dollar quoting
6050
+ // Handle code block with configurable delimiter
5966
6051
  const argNodeType = this.getNodeType(defElem.arg);
5967
6052
  if (argNodeType === 'String') {
5968
6053
  const stringNode = this.getNodeData(defElem.arg);
5969
- const dollarTag = this.generateUniqueDollarTag(stringNode.sval);
5970
- processedArgs.push(`${dollarTag}${stringNode.sval}${dollarTag}`);
6054
+ const delimiter = this.getFunctionDelimiter(stringNode.sval);
6055
+ processedArgs.push(`${delimiter}${stringNode.sval}${delimiter}`);
5971
6056
  }
5972
6057
  else {
5973
6058
  processedArgs.push(this.visit(defElem.arg, doContext));
@@ -6001,9 +6086,11 @@ export class Deparser {
6001
6086
  }
6002
6087
  InlineCodeBlock(node, context) {
6003
6088
  if (node.source_text) {
6004
- return `$$${node.source_text}$$`;
6089
+ const delimiter = this.getFunctionDelimiter(node.source_text);
6090
+ return `${delimiter}${node.source_text}${delimiter}`;
6005
6091
  }
6006
- return '$$$$';
6092
+ const delimiter = this.options.functionDelimiter || '$$';
6093
+ return `${delimiter}${delimiter}`;
6007
6094
  }
6008
6095
  CallContext(node, context) {
6009
6096
  if (node.atomic !== undefined) {
@@ -9586,17 +9673,4 @@ export class Deparser {
9586
9673
  }
9587
9674
  return output.join(' ');
9588
9675
  }
9589
- version(node, context) {
9590
- // Handle version node - typically just return the version number
9591
- if (typeof node === 'number') {
9592
- return node.toString();
9593
- }
9594
- if (typeof node === 'string') {
9595
- return node;
9596
- }
9597
- if (node && typeof node === 'object' && node.version) {
9598
- return node.version.toString();
9599
- }
9600
- return '';
9601
- }
9602
9676
  }
package/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { Deparser } from "./deparser";
1
+ import { Deparser, DeparserOptions } from "./deparser";
2
2
  declare const deparse: typeof Deparser.deparse;
3
- export { deparse, Deparser };
3
+ export { deparse, Deparser, DeparserOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgsql-deparser",
3
- "version": "17.1.0",
3
+ "version": "17.4.0",
4
4
  "author": "Dan Lynch <pyramation@gmail.com>",
5
5
  "description": "PostgreSQL AST Deparser",
6
6
  "main": "index.js",
@@ -48,7 +48,7 @@
48
48
  "libpg-query": "17.3.3"
49
49
  },
50
50
  "dependencies": {
51
- "@pgsql/types": "^17.1.0"
51
+ "@pgsql/types": "^17.4.0"
52
52
  },
53
- "gitHead": "40ee618df37a501169847056a50558c453cd30a6"
53
+ "gitHead": "3725e93239a76bdc02d2d9159209e01d0142e364"
54
54
  }