pgsql-deparser 17.8.1 → 17.8.3

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.js CHANGED
@@ -1,9 +1,66 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Deparser = void 0;
4
+ const base_1 = require("./visitors/base");
4
5
  const sql_formatter_1 = require("./utils/sql-formatter");
5
6
  const quote_utils_1 = require("./utils/quote-utils");
6
7
  const list_utils_1 = require("./utils/list-utils");
8
+ /**
9
+ * List of real PostgreSQL built-in types as they appear in pg_catalog.pg_type.typname.
10
+ * These are stored in lowercase in PostgreSQL system catalogs.
11
+ * Use these for lookups, validations, or introspection logic.
12
+ */
13
+ const pgCatalogTypes = [
14
+ // Integers
15
+ 'int2', // smallint
16
+ 'int4', // integer
17
+ 'int8', // bigint
18
+ // Floating-point & numeric
19
+ 'float4', // real
20
+ 'float8', // double precision
21
+ 'numeric', // arbitrary precision (aka "decimal")
22
+ // Text & string
23
+ 'varchar', // variable-length string
24
+ 'char', // internal one-byte type (used in special cases)
25
+ 'bpchar', // blank-padded char(n)
26
+ 'text', // unlimited string
27
+ 'bool', // boolean
28
+ // Dates & times
29
+ 'date', // calendar date
30
+ 'time', // time without time zone
31
+ 'timetz', // time with time zone
32
+ 'timestamp', // timestamp without time zone
33
+ 'timestamptz', // timestamp with time zone
34
+ 'interval', // duration
35
+ // Binary & structured
36
+ 'bytea', // binary data
37
+ 'uuid', // universally unique identifier
38
+ // JSON & XML
39
+ 'json', // textual JSON
40
+ 'jsonb', // binary JSON
41
+ 'xml', // XML format
42
+ // Money & bitstrings
43
+ 'money', // currency value
44
+ 'bit', // fixed-length bit string
45
+ 'varbit', // variable-length bit string
46
+ // Network types
47
+ 'inet', // IPv4 or IPv6 address
48
+ 'cidr', // network address
49
+ 'macaddr', // MAC address (6 bytes)
50
+ 'macaddr8' // MAC address (8 bytes)
51
+ ];
52
+ /**
53
+ * Parser-level type aliases accepted by PostgreSQL SQL syntax,
54
+ * but not present in pg_catalog.pg_type. These are resolved to
55
+ * real types during parsing and never appear in introspection.
56
+ */
57
+ const pgCatalogTypeAliases = [
58
+ ['numeric', ['decimal', 'dec']],
59
+ ['int4', ['int', 'integer']],
60
+ ['float8', ['float']],
61
+ ['bpchar', ['character']],
62
+ ['varchar', ['character varying']]
63
+ ];
7
64
  // Type guards for better type safety
8
65
  function isParseResult(obj) {
9
66
  // A ParseResult is an object that could have stmts (but not required)
@@ -47,11 +104,9 @@ function isWrappedParseResult(obj) {
47
104
  * compatibility and wraps them internally for consistent processing.
48
105
  */
49
106
  class Deparser {
50
- formatter;
51
107
  tree;
52
108
  options;
53
109
  constructor(tree, opts = {}) {
54
- this.formatter = new sql_formatter_1.SqlFormatter(opts.newline, opts.tab, opts.pretty);
55
110
  // Set default options
56
111
  this.options = {
57
112
  functionDelimiter: '$$',
@@ -88,15 +143,17 @@ class Deparser {
88
143
  return new Deparser(query, opts).deparseQuery();
89
144
  }
90
145
  deparseQuery() {
146
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
147
+ const context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
91
148
  return this.tree
92
149
  .map(node => {
93
150
  // All nodes should go through the standard deparse method
94
151
  // which will route to the appropriate handler
95
- const result = this.deparse(node);
152
+ const result = this.deparse(node, context);
96
153
  return result || '';
97
154
  })
98
155
  .filter(result => result !== '')
99
- .join(this.formatter.newline() + this.formatter.newline());
156
+ .join(context.newline() + context.newline());
100
157
  }
101
158
  /**
102
159
  * Get the appropriate function delimiter based on the body content
@@ -110,10 +167,14 @@ class Deparser {
110
167
  }
111
168
  return delimiter;
112
169
  }
113
- deparse(node, context = { parentNodeTypes: [] }) {
170
+ deparse(node, context) {
114
171
  if (node == null) {
115
172
  return null;
116
173
  }
174
+ if (!context) {
175
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
176
+ context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
177
+ }
117
178
  if (typeof node === 'number' || node instanceof Number) {
118
179
  return node.toString();
119
180
  }
@@ -125,7 +186,11 @@ class Deparser {
125
186
  throw new Error(`Error deparsing ${nodeType}: ${error.message}`);
126
187
  }
127
188
  }
128
- visit(node, context = { parentNodeTypes: [] }) {
189
+ visit(node, context) {
190
+ if (!context) {
191
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
192
+ context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
193
+ }
129
194
  const nodeType = this.getNodeType(node);
130
195
  // Handle empty objects
131
196
  if (!nodeType) {
@@ -134,11 +199,7 @@ class Deparser {
134
199
  const nodeData = this.getNodeData(node);
135
200
  const methodName = nodeType;
136
201
  if (typeof this[methodName] === 'function') {
137
- const childContext = {
138
- ...context,
139
- parentNodeTypes: [...context.parentNodeTypes, nodeType]
140
- };
141
- const result = this[methodName](nodeData, childContext);
202
+ const result = this[methodName](nodeData, context);
142
203
  return result;
143
204
  }
144
205
  throw new Error(`Deparser does not handle node type: ${nodeType}`);
@@ -164,7 +225,7 @@ class Deparser {
164
225
  .filter((rawStmt) => rawStmt != null)
165
226
  .map((rawStmt) => this.RawStmt(rawStmt, context))
166
227
  .filter((result) => result !== '')
167
- .join(this.formatter.newline() + this.formatter.newline());
228
+ .join(context.newline() + context.newline());
168
229
  }
169
230
  RawStmt(node, context) {
170
231
  if (!node.stmt) {
@@ -184,7 +245,7 @@ class Deparser {
184
245
  }
185
246
  if (!node.op || node.op === 'SETOP_NONE') {
186
247
  if (node.valuesLists == null) {
187
- if (!this.formatter.isPretty() || !node.targetList) {
248
+ if (!context.isPretty() || !node.targetList) {
188
249
  output.push('SELECT');
189
250
  }
190
251
  }
@@ -204,7 +265,7 @@ class Deparser {
204
265
  node.rarg.limitOffset ||
205
266
  node.rarg.withClause);
206
267
  if (leftNeedsParens) {
207
- output.push(this.formatter.parens(leftStmt));
268
+ output.push(context.parens(leftStmt));
208
269
  }
209
270
  else {
210
271
  output.push(leftStmt);
@@ -226,7 +287,7 @@ class Deparser {
226
287
  output.push('ALL');
227
288
  }
228
289
  if (rightNeedsParens) {
229
- output.push(this.formatter.parens(rightStmt));
290
+ output.push(context.parens(rightStmt));
230
291
  }
231
292
  else {
232
293
  output.push(rightStmt);
@@ -238,20 +299,20 @@ class Deparser {
238
299
  const distinctClause = list_utils_1.ListUtils.unwrapList(node.distinctClause);
239
300
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
240
301
  const clause = distinctClause
241
- .map(e => this.visit(e, { ...context, select: true }))
302
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
242
303
  .join(', ');
243
- distinctPart = ' DISTINCT ON ' + this.formatter.parens(clause);
304
+ distinctPart = ' DISTINCT ON ' + context.parens(clause);
244
305
  }
245
306
  else {
246
307
  distinctPart = ' DISTINCT';
247
308
  }
248
- if (!this.formatter.isPretty()) {
309
+ if (!context.isPretty()) {
249
310
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
250
311
  output.push('DISTINCT ON');
251
312
  const clause = distinctClause
252
- .map(e => this.visit(e, { ...context, select: true }))
313
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
253
314
  .join(', ');
254
- output.push(this.formatter.parens(clause));
315
+ output.push(context.parens(clause));
255
316
  }
256
317
  else {
257
318
  output.push('DISTINCT');
@@ -260,22 +321,41 @@ class Deparser {
260
321
  }
261
322
  if (node.targetList) {
262
323
  const targetList = list_utils_1.ListUtils.unwrapList(node.targetList);
263
- if (this.formatter.isPretty()) {
264
- const targetStrings = targetList
265
- .map(e => {
266
- const targetStr = this.visit(e, { ...context, select: true });
267
- if (this.containsMultilineStringLiteral(targetStr)) {
268
- return targetStr;
324
+ if (context.isPretty()) {
325
+ if (targetList.length === 1) {
326
+ const targetNode = targetList[0];
327
+ const target = this.visit(targetNode, context.spawn('SelectStmt', { select: true }));
328
+ // Check if single target is complex - if so, use multiline format
329
+ if (this.isComplexSelectTarget(targetNode)) {
330
+ output.push('SELECT' + distinctPart);
331
+ if (this.containsMultilineStringLiteral(target)) {
332
+ output.push(target);
333
+ }
334
+ else {
335
+ output.push(context.indent(target));
336
+ }
269
337
  }
270
- return this.formatter.indent(targetStr);
271
- });
272
- const formattedTargets = targetStrings.join(',' + this.formatter.newline());
273
- output.push('SELECT' + distinctPart);
274
- output.push(formattedTargets);
338
+ else {
339
+ output.push('SELECT' + distinctPart + ' ' + target);
340
+ }
341
+ }
342
+ else {
343
+ const targetStrings = targetList
344
+ .map(e => {
345
+ const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
346
+ if (this.containsMultilineStringLiteral(targetStr)) {
347
+ return targetStr;
348
+ }
349
+ return context.indent(targetStr);
350
+ });
351
+ const formattedTargets = targetStrings.join(',' + context.newline());
352
+ output.push('SELECT' + distinctPart);
353
+ output.push(formattedTargets);
354
+ }
275
355
  }
276
356
  else {
277
357
  const targets = targetList
278
- .map(e => this.visit(e, { ...context, select: true }))
358
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
279
359
  .join(', ');
280
360
  output.push(targets);
281
361
  }
@@ -287,22 +367,22 @@ class Deparser {
287
367
  if (node.fromClause) {
288
368
  const fromList = list_utils_1.ListUtils.unwrapList(node.fromClause);
289
369
  const fromItems = fromList
290
- .map(e => this.deparse(e, { ...context, from: true }))
370
+ .map(e => this.deparse(e, context.spawn('SelectStmt', { from: true })))
291
371
  .join(', ');
292
372
  output.push('FROM ' + fromItems.trim());
293
373
  }
294
374
  if (node.whereClause) {
295
- if (this.formatter.isPretty()) {
375
+ if (context.isPretty()) {
296
376
  output.push('WHERE');
297
377
  const whereExpr = this.visit(node.whereClause, context);
298
- const lines = whereExpr.split(this.formatter.newline());
378
+ const lines = whereExpr.split(context.newline());
299
379
  const indentedLines = lines.map((line, index) => {
300
380
  if (index === 0) {
301
- return this.formatter.indent(line);
381
+ return context.indent(line);
302
382
  }
303
383
  return line;
304
384
  });
305
- output.push(indentedLines.join(this.formatter.newline()));
385
+ output.push(indentedLines.join(context.newline()));
306
386
  }
307
387
  else {
308
388
  output.push('WHERE');
@@ -310,45 +390,61 @@ class Deparser {
310
390
  }
311
391
  }
312
392
  if (node.valuesLists) {
313
- output.push('VALUES');
314
- const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
315
- const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
316
- return this.formatter.parens(values.join(', '));
317
- });
318
- output.push(lists.join(', '));
393
+ if (context.isPretty()) {
394
+ output.push('VALUES');
395
+ const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
396
+ const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
397
+ return context.parens(values.join(', '));
398
+ });
399
+ const indentedTuples = lists.map(tuple => {
400
+ if (this.containsMultilineStringLiteral(tuple)) {
401
+ return tuple;
402
+ }
403
+ return context.indent(tuple);
404
+ });
405
+ output.push(indentedTuples.join(',\n'));
406
+ }
407
+ else {
408
+ output.push('VALUES');
409
+ const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
410
+ const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
411
+ return context.parens(values.join(', '));
412
+ });
413
+ output.push(lists.join(', '));
414
+ }
319
415
  }
320
416
  if (node.groupClause) {
321
417
  const groupList = list_utils_1.ListUtils.unwrapList(node.groupClause);
322
- if (this.formatter.isPretty()) {
418
+ if (context.isPretty()) {
323
419
  const groupItems = groupList
324
420
  .map(e => {
325
- const groupStr = this.visit(e, { ...context, group: true });
421
+ const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
326
422
  if (this.containsMultilineStringLiteral(groupStr)) {
327
423
  return groupStr;
328
424
  }
329
- return this.formatter.indent(groupStr);
425
+ return context.indent(groupStr);
330
426
  })
331
- .join(',' + this.formatter.newline());
427
+ .join(',' + context.newline());
332
428
  output.push('GROUP BY');
333
429
  output.push(groupItems);
334
430
  }
335
431
  else {
336
432
  output.push('GROUP BY');
337
433
  const groupItems = groupList
338
- .map(e => this.visit(e, { ...context, group: true }))
434
+ .map(e => this.visit(e, context.spawn('SelectStmt', { group: true })))
339
435
  .join(', ');
340
436
  output.push(groupItems);
341
437
  }
342
438
  }
343
439
  if (node.havingClause) {
344
- if (this.formatter.isPretty()) {
440
+ if (context.isPretty()) {
345
441
  output.push('HAVING');
346
442
  const havingStr = this.visit(node.havingClause, context);
347
443
  if (this.containsMultilineStringLiteral(havingStr)) {
348
444
  output.push(havingStr);
349
445
  }
350
446
  else {
351
- output.push(this.formatter.indent(havingStr));
447
+ output.push(context.indent(havingStr));
352
448
  }
353
449
  }
354
450
  else {
@@ -366,23 +462,23 @@ class Deparser {
366
462
  }
367
463
  if (node.sortClause) {
368
464
  const sortList = list_utils_1.ListUtils.unwrapList(node.sortClause);
369
- if (this.formatter.isPretty()) {
465
+ if (context.isPretty()) {
370
466
  const sortItems = sortList
371
467
  .map(e => {
372
- const sortStr = this.visit(e, { ...context, sort: true });
468
+ const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
373
469
  if (this.containsMultilineStringLiteral(sortStr)) {
374
470
  return sortStr;
375
471
  }
376
- return this.formatter.indent(sortStr);
472
+ return context.indent(sortStr);
377
473
  })
378
- .join(',' + this.formatter.newline());
474
+ .join(',' + context.newline());
379
475
  output.push('ORDER BY');
380
476
  output.push(sortItems);
381
477
  }
382
478
  else {
383
479
  output.push('ORDER BY');
384
480
  const sortItems = sortList
385
- .map(e => this.visit(e, { ...context, sort: true }))
481
+ .map(e => this.visit(e, context.spawn('SelectStmt', { sort: true })))
386
482
  .join(', ');
387
483
  output.push(sortItems);
388
484
  }
@@ -400,9 +496,9 @@ class Deparser {
400
496
  .join(' ');
401
497
  output.push(lockingClauses);
402
498
  }
403
- if (this.formatter.isPretty()) {
499
+ if (context.isPretty()) {
404
500
  const filteredOutput = output.filter(item => item.trim() !== '');
405
- return filteredOutput.join(this.formatter.newline());
501
+ return filteredOutput.join(context.newline());
406
502
  }
407
503
  return output.join(' ');
408
504
  }
@@ -414,13 +510,13 @@ class Deparser {
414
510
  switch (kind) {
415
511
  case 'AEXPR_OP':
416
512
  if (lexpr && rexpr) {
417
- const operator = this.deparseOperatorName(name);
513
+ const operator = this.deparseOperatorName(name, context);
418
514
  let leftExpr = this.visit(lexpr, context);
419
515
  let rightExpr = this.visit(rexpr, context);
420
516
  // Check if left expression needs parentheses
421
517
  let leftNeedsParens = false;
422
518
  if (lexpr && 'A_Expr' in lexpr && lexpr.A_Expr?.kind === 'AEXPR_OP') {
423
- const leftOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(lexpr.A_Expr.name));
519
+ const leftOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(lexpr.A_Expr.name), context);
424
520
  if (this.needsParentheses(leftOp, operator, 'left')) {
425
521
  leftNeedsParens = true;
426
522
  }
@@ -429,12 +525,12 @@ class Deparser {
429
525
  leftNeedsParens = true;
430
526
  }
431
527
  if (leftNeedsParens) {
432
- leftExpr = this.formatter.parens(leftExpr);
528
+ leftExpr = context.parens(leftExpr);
433
529
  }
434
530
  // Check if right expression needs parentheses
435
531
  let rightNeedsParens = false;
436
532
  if (rexpr && 'A_Expr' in rexpr && rexpr.A_Expr?.kind === 'AEXPR_OP') {
437
- const rightOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(rexpr.A_Expr.name));
533
+ const rightOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(rexpr.A_Expr.name), context);
438
534
  if (this.needsParentheses(rightOp, operator, 'right')) {
439
535
  rightNeedsParens = true;
440
536
  }
@@ -443,42 +539,42 @@ class Deparser {
443
539
  rightNeedsParens = true;
444
540
  }
445
541
  if (rightNeedsParens) {
446
- rightExpr = this.formatter.parens(rightExpr);
542
+ rightExpr = context.parens(rightExpr);
447
543
  }
448
- return this.formatter.format([leftExpr, operator, rightExpr]);
544
+ return context.format([leftExpr, operator, rightExpr]);
449
545
  }
450
546
  else if (rexpr) {
451
- return this.formatter.format([
452
- this.deparseOperatorName(name),
547
+ return context.format([
548
+ this.deparseOperatorName(name, context),
453
549
  this.visit(rexpr, context)
454
550
  ]);
455
551
  }
456
552
  break;
457
553
  case 'AEXPR_OP_ANY':
458
- return this.formatter.format([
554
+ return context.format([
459
555
  this.visit(lexpr, context),
460
- this.deparseOperatorName(name),
556
+ this.deparseOperatorName(name, context),
461
557
  'ANY',
462
- this.formatter.parens(this.visit(rexpr, context))
558
+ context.parens(this.visit(rexpr, context))
463
559
  ]);
464
560
  case 'AEXPR_OP_ALL':
465
- return this.formatter.format([
561
+ return context.format([
466
562
  this.visit(lexpr, context),
467
- this.deparseOperatorName(name),
563
+ this.deparseOperatorName(name, context),
468
564
  'ALL',
469
- this.formatter.parens(this.visit(rexpr, context))
565
+ context.parens(this.visit(rexpr, context))
470
566
  ]);
471
567
  case 'AEXPR_DISTINCT': {
472
568
  let leftExpr = this.visit(lexpr, context);
473
569
  let rightExpr = this.visit(rexpr, context);
474
570
  // Add parentheses for complex expressions
475
571
  if (lexpr && this.isComplexExpression(lexpr)) {
476
- leftExpr = this.formatter.parens(leftExpr);
572
+ leftExpr = context.parens(leftExpr);
477
573
  }
478
574
  if (rexpr && this.isComplexExpression(rexpr)) {
479
- rightExpr = this.formatter.parens(rightExpr);
575
+ rightExpr = context.parens(rightExpr);
480
576
  }
481
- return this.formatter.format([
577
+ return context.format([
482
578
  leftExpr,
483
579
  'IS DISTINCT FROM',
484
580
  rightExpr
@@ -489,75 +585,75 @@ class Deparser {
489
585
  let rightExpr = this.visit(rexpr, context);
490
586
  // Add parentheses for complex expressions
491
587
  if (lexpr && this.isComplexExpression(lexpr)) {
492
- leftExpr = this.formatter.parens(leftExpr);
588
+ leftExpr = context.parens(leftExpr);
493
589
  }
494
590
  if (rexpr && this.isComplexExpression(rexpr)) {
495
- rightExpr = this.formatter.parens(rightExpr);
591
+ rightExpr = context.parens(rightExpr);
496
592
  }
497
- return this.formatter.format([
593
+ return context.format([
498
594
  leftExpr,
499
595
  'IS NOT DISTINCT FROM',
500
596
  rightExpr
501
597
  ]);
502
598
  }
503
599
  case 'AEXPR_NULLIF':
504
- return this.formatter.format([
600
+ return context.format([
505
601
  'NULLIF',
506
- this.formatter.parens([
602
+ context.parens([
507
603
  this.visit(lexpr, context),
508
604
  this.visit(rexpr, context)
509
605
  ].join(', '))
510
606
  ]);
511
607
  case 'AEXPR_IN':
512
- const inOperator = this.deparseOperatorName(name);
608
+ const inOperator = this.deparseOperatorName(name, context);
513
609
  if (inOperator === '<>' || inOperator === '!=') {
514
- return this.formatter.format([
610
+ return context.format([
515
611
  this.visit(lexpr, context),
516
612
  'NOT IN',
517
- this.formatter.parens(this.visit(rexpr, context))
613
+ context.parens(this.visit(rexpr, context))
518
614
  ]);
519
615
  }
520
616
  else {
521
- return this.formatter.format([
617
+ return context.format([
522
618
  this.visit(lexpr, context),
523
619
  'IN',
524
- this.formatter.parens(this.visit(rexpr, context))
620
+ context.parens(this.visit(rexpr, context))
525
621
  ]);
526
622
  }
527
623
  case 'AEXPR_LIKE':
528
- const likeOp = this.deparseOperatorName(name);
624
+ const likeOp = this.deparseOperatorName(name, context);
529
625
  if (likeOp === '!~~') {
530
- return this.formatter.format([
626
+ return context.format([
531
627
  this.visit(lexpr, context),
532
628
  'NOT LIKE',
533
629
  this.visit(rexpr, context)
534
630
  ]);
535
631
  }
536
632
  else {
537
- return this.formatter.format([
633
+ return context.format([
538
634
  this.visit(lexpr, context),
539
635
  'LIKE',
540
636
  this.visit(rexpr, context)
541
637
  ]);
542
638
  }
543
639
  case 'AEXPR_ILIKE':
544
- const ilikeOp = this.deparseOperatorName(name);
640
+ const ilikeOp = this.deparseOperatorName(name, context);
545
641
  if (ilikeOp === '!~~*') {
546
- return this.formatter.format([
642
+ return context.format([
547
643
  this.visit(lexpr, context),
548
644
  'NOT ILIKE',
549
645
  this.visit(rexpr, context)
550
646
  ]);
551
647
  }
552
648
  else {
553
- return this.formatter.format([
649
+ return context.format([
554
650
  this.visit(lexpr, context),
555
651
  'ILIKE',
556
652
  this.visit(rexpr, context)
557
653
  ]);
558
654
  }
559
655
  case 'AEXPR_SIMILAR':
560
- const similarOp = this.deparseOperatorName(name);
656
+ const similarOp = this.deparseOperatorName(name, context);
561
657
  let rightExpr;
562
658
  if (rexpr && 'FuncCall' in rexpr &&
563
659
  rexpr.FuncCall?.funcname?.length === 2 &&
@@ -573,39 +669,39 @@ class Deparser {
573
669
  rightExpr = this.visit(rexpr, context);
574
670
  }
575
671
  if (similarOp === '!~') {
576
- return this.formatter.format([
672
+ return context.format([
577
673
  this.visit(lexpr, context),
578
674
  'NOT SIMILAR TO',
579
675
  rightExpr
580
676
  ]);
581
677
  }
582
678
  else {
583
- return this.formatter.format([
679
+ return context.format([
584
680
  this.visit(lexpr, context),
585
681
  'SIMILAR TO',
586
682
  rightExpr
587
683
  ]);
588
684
  }
589
685
  case 'AEXPR_BETWEEN':
590
- return this.formatter.format([
686
+ return context.format([
591
687
  this.visit(lexpr, context),
592
688
  'BETWEEN',
593
689
  this.visitBetweenRange(rexpr, context)
594
690
  ]);
595
691
  case 'AEXPR_NOT_BETWEEN':
596
- return this.formatter.format([
692
+ return context.format([
597
693
  this.visit(lexpr, context),
598
694
  'NOT BETWEEN',
599
695
  this.visitBetweenRange(rexpr, context)
600
696
  ]);
601
697
  case 'AEXPR_BETWEEN_SYM':
602
- return this.formatter.format([
698
+ return context.format([
603
699
  this.visit(lexpr, context),
604
700
  'BETWEEN SYMMETRIC',
605
701
  this.visitBetweenRange(rexpr, context)
606
702
  ]);
607
703
  case 'AEXPR_NOT_BETWEEN_SYM':
608
- return this.formatter.format([
704
+ return context.format([
609
705
  this.visit(lexpr, context),
610
706
  'NOT BETWEEN SYMMETRIC',
611
707
  this.visitBetweenRange(rexpr, context)
@@ -613,7 +709,7 @@ class Deparser {
613
709
  }
614
710
  throw new Error(`Unhandled A_Expr kind: ${kind}`);
615
711
  }
616
- deparseOperatorName(name) {
712
+ deparseOperatorName(name, context) {
617
713
  if (!name || name.length === 0) {
618
714
  return '';
619
715
  }
@@ -621,7 +717,7 @@ class Deparser {
621
717
  if (n.String) {
622
718
  return n.String.sval || n.String.str;
623
719
  }
624
- return this.visit(n, { parentNodeTypes: [] });
720
+ return this.visit(n, context);
625
721
  });
626
722
  if (parts.length > 1) {
627
723
  return `OPERATOR(${parts.join('.')})`;
@@ -684,6 +780,64 @@ class Deparser {
684
780
  node.SubLink ||
685
781
  node.A_Expr);
686
782
  }
783
+ isComplexSelectTarget(node) {
784
+ if (!node)
785
+ return false;
786
+ if (node.ResTarget?.val) {
787
+ return this.isComplexExpression(node.ResTarget.val);
788
+ }
789
+ // Always complex: CASE expressions
790
+ if (node.CaseExpr)
791
+ return true;
792
+ // Always complex: Subqueries and subselects
793
+ if (node.SubLink)
794
+ return true;
795
+ // Always complex: Boolean tests and expressions
796
+ if (node.NullTest || node.BooleanTest || node.BoolExpr)
797
+ return true;
798
+ // COALESCE and similar functions - complex if multiple arguments
799
+ if (node.CoalesceExpr) {
800
+ const args = node.CoalesceExpr.args;
801
+ if (args && Array.isArray(args) && args.length > 1)
802
+ return true;
803
+ }
804
+ // Function calls - complex if multiple args or has clauses
805
+ if (node.FuncCall) {
806
+ const funcCall = node.FuncCall;
807
+ const args = funcCall.args ? (Array.isArray(funcCall.args) ? funcCall.args : [funcCall.args]) : [];
808
+ // Complex if has window clause, filter, order by, etc.
809
+ if (funcCall.over || funcCall.agg_filter || funcCall.agg_order || funcCall.agg_distinct) {
810
+ return true;
811
+ }
812
+ // Complex if multiple arguments
813
+ if (args.length > 1)
814
+ return true;
815
+ if (args.length === 1) {
816
+ return this.isComplexSelectTarget(args[0]);
817
+ }
818
+ }
819
+ if (node.A_Expr) {
820
+ const expr = node.A_Expr;
821
+ // Check if operands are complex
822
+ if (expr.lexpr && this.isComplexSelectTarget(expr.lexpr))
823
+ return true;
824
+ if (expr.rexpr && this.isComplexSelectTarget(expr.rexpr))
825
+ return true;
826
+ return false;
827
+ }
828
+ if (node.TypeCast) {
829
+ return this.isComplexSelectTarget(node.TypeCast.arg);
830
+ }
831
+ if (node.A_ArrayExpr)
832
+ return true;
833
+ if (node.A_Indirection) {
834
+ return this.isComplexSelectTarget(node.A_Indirection.arg);
835
+ }
836
+ if (node.A_Const || node.ColumnRef || node.ParamRef || node.A_Star) {
837
+ return false;
838
+ }
839
+ return false;
840
+ }
687
841
  visitBetweenRange(rexpr, context) {
688
842
  if (rexpr && 'List' in rexpr && rexpr.List?.items) {
689
843
  const items = rexpr.List.items.map((item) => this.visit(item, context));
@@ -700,9 +854,16 @@ class Deparser {
700
854
  output.push(this.RangeVar(node.relation, context));
701
855
  if (node.cols) {
702
856
  const cols = list_utils_1.ListUtils.unwrapList(node.cols);
703
- const insertContext = { ...context, insertColumns: true };
857
+ const insertContext = context.spawn('InsertStmt', { insertColumns: true });
704
858
  const columnNames = cols.map(col => this.visit(col, insertContext));
705
- output.push(this.formatter.parens(columnNames.join(', ')));
859
+ if (context.isPretty()) {
860
+ // Always format columns in multiline parentheses for pretty printing
861
+ const indentedColumns = columnNames.map(col => context.indent(col));
862
+ output.push('(\n' + indentedColumns.join(',\n') + '\n)');
863
+ }
864
+ else {
865
+ output.push(context.parens(columnNames.join(', ')));
866
+ }
706
867
  }
707
868
  if (node.selectStmt) {
708
869
  output.push(this.visit(node.selectStmt, context));
@@ -723,7 +884,7 @@ class Deparser {
723
884
  else if (infer.indexElems) {
724
885
  const elems = list_utils_1.ListUtils.unwrapList(infer.indexElems);
725
886
  const indexElems = elems.map(elem => this.visit(elem, context));
726
- output.push(this.formatter.parens(indexElems.join(', ')));
887
+ output.push(context.parens(indexElems.join(', ')));
727
888
  }
728
889
  // Handle WHERE clause for conflict detection
729
890
  if (infer.whereClause) {
@@ -739,12 +900,12 @@ class Deparser {
739
900
  if (firstTarget.ResTarget?.val?.MultiAssignRef && targetList.every(target => target.ResTarget?.val?.MultiAssignRef)) {
740
901
  const sortedTargets = targetList.sort((a, b) => a.ResTarget.val.MultiAssignRef.colno - b.ResTarget.val.MultiAssignRef.colno);
741
902
  const names = sortedTargets.map(target => target.ResTarget.name);
742
- output.push(this.formatter.parens(names.join(', ')));
903
+ output.push(context.parens(names.join(', ')));
743
904
  output.push('=');
744
905
  output.push(this.visit(firstTarget.ResTarget.val.MultiAssignRef.source, context));
745
906
  }
746
907
  else {
747
- const updateContext = { ...context, update: true };
908
+ const updateContext = context.spawn('UpdateStmt', { update: true });
748
909
  const targets = targetList.map(target => this.visit(target, updateContext));
749
910
  output.push(targets.join(', '));
750
911
  }
@@ -798,12 +959,12 @@ class Deparser {
798
959
  }
799
960
  }
800
961
  const names = relatedTargets.map(t => t.ResTarget.name);
801
- const multiAssignment = `${this.formatter.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
962
+ const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
802
963
  assignmentParts.push(multiAssignment);
803
964
  }
804
965
  else {
805
966
  // Handle regular single-column assignment
806
- assignmentParts.push(this.visit(target, { ...context, update: true }));
967
+ assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
807
968
  processedTargets.add(i);
808
969
  }
809
970
  }
@@ -895,14 +1056,14 @@ class Deparser {
895
1056
  }
896
1057
  if (node.ctes && node.ctes.length > 0) {
897
1058
  const ctes = list_utils_1.ListUtils.unwrapList(node.ctes);
898
- if (this.formatter.isPretty()) {
1059
+ if (context.isPretty()) {
899
1060
  const cteStrings = ctes.map((cte, index) => {
900
1061
  const cteStr = this.visit(cte, context);
901
- const prefix = index === 0 ? this.formatter.newline() : ',' + this.formatter.newline();
1062
+ const prefix = index === 0 ? context.newline() : ',' + context.newline();
902
1063
  if (this.containsMultilineStringLiteral(cteStr)) {
903
1064
  return prefix + cteStr;
904
1065
  }
905
- return prefix + this.formatter.indent(cteStr);
1066
+ return prefix + context.indent(cteStr);
906
1067
  });
907
1068
  output.push(cteStrings.join(''));
908
1069
  }
@@ -988,14 +1149,14 @@ class Deparser {
988
1149
  if (context.bool) {
989
1150
  formatStr = '(%s)';
990
1151
  }
991
- const boolContext = { ...context, bool: true };
1152
+ const boolContext = context.spawn('BoolExpr', { bool: true });
992
1153
  // explanation of our syntax/fix below:
993
1154
  // return formatStr.replace('%s', andArgs); // ❌ Interprets $ as special syntax
994
1155
  // return formatStr.replace('%s', () => andArgs); // ✅ Function callback prevents interpretation
995
1156
  switch (boolop) {
996
1157
  case 'AND_EXPR':
997
- if (this.formatter.isPretty() && args.length > 1) {
998
- const andArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' AND ');
1158
+ if (context.isPretty() && args.length > 1) {
1159
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('AND '));
999
1160
  return formatStr.replace('%s', () => andArgs);
1000
1161
  }
1001
1162
  else {
@@ -1003,8 +1164,8 @@ class Deparser {
1003
1164
  return formatStr.replace('%s', () => andArgs);
1004
1165
  }
1005
1166
  case 'OR_EXPR':
1006
- if (this.formatter.isPretty() && args.length > 1) {
1007
- const orArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' OR ');
1167
+ if (context.isPretty() && args.length > 1) {
1168
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('OR '));
1008
1169
  return formatStr.replace('%s', () => orArgs);
1009
1170
  }
1010
1171
  else {
@@ -1135,9 +1296,9 @@ class Deparser {
1135
1296
  const timezone = this.visit(args[0], context);
1136
1297
  // Add parentheses around timestamp if it contains arithmetic operations
1137
1298
  if (args[1] && 'A_Expr' in args[1] && args[1].A_Expr?.kind === 'AEXPR_OP') {
1138
- const op = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(args[1].A_Expr.name));
1299
+ const op = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(args[1].A_Expr.name), context);
1139
1300
  if (op === '+' || op === '-' || op === '*' || op === '/') {
1140
- timestamp = this.formatter.parens(timestamp);
1301
+ timestamp = context.parens(timestamp);
1141
1302
  }
1142
1303
  }
1143
1304
  return `${timestamp} AT TIME ZONE ${timezone}`;
@@ -1207,14 +1368,14 @@ class Deparser {
1207
1368
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1208
1369
  }
1209
1370
  // Handle window frame specifications using the dedicated formatWindowFrame method
1210
- const frameClause = this.formatWindowFrame(node.over);
1371
+ const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1211
1372
  if (frameClause) {
1212
1373
  windowParts.push(frameClause);
1213
1374
  }
1214
1375
  if (windowParts.length > 0) {
1215
- if (this.formatter.isPretty() && windowParts.length > 1) {
1216
- const formattedParts = windowParts.map(part => this.formatter.indent(part));
1217
- result += ` OVER (${this.formatter.newline()}${formattedParts.join(this.formatter.newline())}${this.formatter.newline()})`;
1376
+ if (context.isPretty() && windowParts.length > 1) {
1377
+ const formattedParts = windowParts.map(part => context.indent(part));
1378
+ result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1218
1379
  }
1219
1380
  else {
1220
1381
  result += ` OVER (${windowParts.join(' ')})`;
@@ -1494,9 +1655,6 @@ class Deparser {
1494
1655
  return output.join(' ');
1495
1656
  }
1496
1657
  if (catalog === 'pg_catalog') {
1497
- const builtinTypes = ['int2', 'int4', 'int8', 'float4', 'float8', 'numeric', 'decimal',
1498
- 'varchar', 'char', 'bpchar', 'text', 'bool', 'date', 'time', 'timestamp',
1499
- 'timestamptz', 'interval', 'bytea', 'uuid', 'json', 'jsonb'];
1500
1658
  let typeName = `${catalog}.${type}`;
1501
1659
  if (type === 'bpchar' && args) {
1502
1660
  typeName = 'char';
@@ -1582,7 +1740,7 @@ class Deparser {
1582
1740
  }
1583
1741
  return this.quoteIfNeeded(colStr);
1584
1742
  });
1585
- output.push('AS', this.quoteIfNeeded(name) + this.formatter.parens(quotedColnames.join(', ')));
1743
+ output.push('AS', this.quoteIfNeeded(name) + context.parens(quotedColnames.join(', ')));
1586
1744
  }
1587
1745
  else {
1588
1746
  output.push('AS', this.quoteIfNeeded(name));
@@ -1594,7 +1752,7 @@ class Deparser {
1594
1752
  // Handle ONLY keyword for inheritance control (but not for type definitions, ALTER TYPE, or CREATE FOREIGN TABLE)
1595
1753
  if (node && (!('inh' in node) || node.inh === undefined) &&
1596
1754
  !context.parentNodeTypes.includes('CompositeTypeStmt') &&
1597
- !context.parentNodeTypes.includes('AlterTypeStmt') &&
1755
+ (!context.parentNodeTypes.includes('AlterTypeStmt') && context.objtype !== 'OBJECT_TYPE') &&
1598
1756
  !context.parentNodeTypes.includes('CreateForeignTableStmt')) {
1599
1757
  output.push('ONLY');
1600
1758
  }
@@ -1791,6 +1949,18 @@ class Deparser {
1791
1949
  return `pg_catalog.${typeName}`;
1792
1950
  }
1793
1951
  }
1952
+ isPgCatalogType(typeName) {
1953
+ const cleanTypeName = typeName.replace(/^pg_catalog\./, '');
1954
+ if (pgCatalogTypes.includes(cleanTypeName)) {
1955
+ return true;
1956
+ }
1957
+ for (const [realType, aliases] of pgCatalogTypeAliases) {
1958
+ if (aliases.includes(cleanTypeName)) {
1959
+ return true;
1960
+ }
1961
+ }
1962
+ return false;
1963
+ }
1794
1964
  A_ArrayExpr(node, context) {
1795
1965
  const elements = list_utils_1.ListUtils.unwrapList(node.elements);
1796
1966
  const elementStrs = elements.map(el => this.visit(el, context));
@@ -1842,26 +2012,26 @@ class Deparser {
1842
2012
  output.push(this.visit(node.arg, context));
1843
2013
  }
1844
2014
  const args = list_utils_1.ListUtils.unwrapList(node.args);
1845
- if (this.formatter.isPretty() && args.length > 0) {
2015
+ if (context.isPretty() && args.length > 0) {
1846
2016
  for (const arg of args) {
1847
2017
  const whenClause = this.visit(arg, context);
1848
2018
  if (this.containsMultilineStringLiteral(whenClause)) {
1849
- output.push(this.formatter.newline() + whenClause);
2019
+ output.push(context.newline() + whenClause);
1850
2020
  }
1851
2021
  else {
1852
- output.push(this.formatter.newline() + this.formatter.indent(whenClause));
2022
+ output.push(context.newline() + context.indent(whenClause));
1853
2023
  }
1854
2024
  }
1855
2025
  if (node.defresult) {
1856
2026
  const elseResult = this.visit(node.defresult, context);
1857
2027
  if (this.containsMultilineStringLiteral(elseResult)) {
1858
- output.push(this.formatter.newline() + 'ELSE ' + elseResult);
2028
+ output.push(context.newline() + 'ELSE ' + elseResult);
1859
2029
  }
1860
2030
  else {
1861
- output.push(this.formatter.newline() + this.formatter.indent('ELSE ' + elseResult));
2031
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
1862
2032
  }
1863
2033
  }
1864
- output.push(this.formatter.newline() + 'END');
2034
+ output.push(context.newline() + 'END');
1865
2035
  return output.join(' ');
1866
2036
  }
1867
2037
  else {
@@ -1884,28 +2054,29 @@ class Deparser {
1884
2054
  TypeCast(node, context) {
1885
2055
  const arg = this.visit(node.arg, context);
1886
2056
  const typeName = this.TypeName(node.typeName, context);
1887
- // Check if this is a bpchar typecast that should use traditional char syntax
1888
- if (typeName === 'bpchar' && node.typeName && node.typeName.names) {
1889
- const names = list_utils_1.ListUtils.unwrapList(node.typeName.names);
1890
- if (names.length === 2 &&
1891
- names[0].String?.sval === 'pg_catalog' &&
1892
- names[1].String?.sval === 'bpchar') {
1893
- return `char ${arg}`;
2057
+ // Check if this is a bpchar typecast that should preserve original syntax for AST consistency
2058
+ if (typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
2059
+ const names = node.typeName?.names;
2060
+ const isQualifiedBpchar = names && names.length === 2 &&
2061
+ names[0]?.String?.sval === 'pg_catalog' &&
2062
+ names[1]?.String?.sval === 'bpchar';
2063
+ if (isQualifiedBpchar) {
2064
+ return `CAST(${arg} AS ${typeName})`;
1894
2065
  }
1895
2066
  }
1896
- // Check if the argument is a complex expression that should preserve CAST syntax
1897
- const argType = this.getNodeType(node.arg);
1898
- const isComplexExpression = argType === 'A_Expr' || argType === 'FuncCall' || argType === 'OpExpr';
1899
- if (!isComplexExpression && (typeName.startsWith('interval') ||
1900
- typeName.startsWith('char') ||
1901
- typeName === '"char"' ||
1902
- typeName.startsWith('bpchar') ||
1903
- typeName === 'bytea' ||
1904
- typeName === 'orderedarray' ||
1905
- typeName === 'date')) {
1906
- // Remove pg_catalog prefix for :: syntax
1907
- const cleanTypeName = typeName.replace('pg_catalog.', '');
1908
- return `${arg}::${cleanTypeName}`;
2067
+ if (this.isPgCatalogType(typeName)) {
2068
+ const argType = this.getNodeType(node.arg);
2069
+ const isSimpleArgument = argType === 'A_Const' || argType === 'ColumnRef';
2070
+ const isFunctionCall = argType === 'FuncCall';
2071
+ if (isSimpleArgument || isFunctionCall) {
2072
+ // For simple arguments, avoid :: syntax if they have complex structure
2073
+ if (isSimpleArgument && (arg.includes('(') || arg.startsWith('-'))) {
2074
+ }
2075
+ else {
2076
+ const cleanTypeName = typeName.replace('pg_catalog.', '');
2077
+ return `${arg}::${cleanTypeName}`;
2078
+ }
2079
+ }
1909
2080
  }
1910
2081
  return `CAST(${arg} AS ${typeName})`;
1911
2082
  }
@@ -1928,7 +2099,7 @@ class Deparser {
1928
2099
  }
1929
2100
  BooleanTest(node, context) {
1930
2101
  const output = [];
1931
- const boolContext = { ...context, bool: true };
2102
+ const boolContext = context.spawn('BooleanTest', { bool: true });
1932
2103
  output.push(this.visit(node.arg, boolContext));
1933
2104
  switch (node.booltesttype) {
1934
2105
  case 'IS_TRUE':
@@ -2079,24 +2250,31 @@ class Deparser {
2079
2250
  const elementStrs = elements.map(el => {
2080
2251
  return this.deparse(el, context);
2081
2252
  });
2082
- output.push(this.formatter.parens(elementStrs.join(', ')));
2253
+ output.push(context.parens(elementStrs.join(', ')));
2083
2254
  }
2084
2255
  }
2085
2256
  else if (node.tableElts) {
2086
2257
  const elements = list_utils_1.ListUtils.unwrapList(node.tableElts);
2087
2258
  const elementStrs = elements.map(el => {
2088
- return this.deparse(el, context);
2259
+ return this.deparse(el, context.spawn('CreateStmt'));
2089
2260
  });
2090
- if (this.formatter.isPretty()) {
2091
- const formattedElements = elementStrs.map(el => this.formatter.indent(el)).join(',' + this.formatter.newline());
2092
- output.push('(' + this.formatter.newline() + formattedElements + this.formatter.newline() + ')');
2261
+ if (context.isPretty()) {
2262
+ const formattedElements = elementStrs.map(el => {
2263
+ const trimmedEl = el.trim();
2264
+ // Remove leading newlines from constraint elements to avoid extra blank lines
2265
+ if (trimmedEl.startsWith('\n')) {
2266
+ return context.indent(trimmedEl.substring(1));
2267
+ }
2268
+ return context.indent(trimmedEl);
2269
+ }).join(',' + context.newline());
2270
+ output.push('(' + context.newline() + formattedElements + context.newline() + ')');
2093
2271
  }
2094
2272
  else {
2095
- output.push(this.formatter.parens(elementStrs.join(', ')));
2273
+ output.push(context.parens(elementStrs.join(', ')));
2096
2274
  }
2097
2275
  }
2098
2276
  else if (!node.partbound) {
2099
- output.push(this.formatter.parens(''));
2277
+ output.push(context.parens(''));
2100
2278
  }
2101
2279
  if (node.partbound && node.inhRelations && node.inhRelations.length > 0) {
2102
2280
  output.push('PARTITION OF');
@@ -2139,7 +2317,7 @@ class Deparser {
2139
2317
  output.push('INHERITS');
2140
2318
  const inherits = list_utils_1.ListUtils.unwrapList(node.inhRelations);
2141
2319
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2142
- output.push(this.formatter.parens(inheritStrs.join(', ')));
2320
+ output.push(context.parens(inheritStrs.join(', ')));
2143
2321
  }
2144
2322
  if (node.partspec) {
2145
2323
  output.push('PARTITION BY');
@@ -2177,7 +2355,7 @@ class Deparser {
2177
2355
  }
2178
2356
  // Handle table options like WITH (fillfactor=10)
2179
2357
  if (node.options && node.options.length > 0) {
2180
- const createStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateStmt'] };
2358
+ const createStmtContext = context.spawn('CreateStmt');
2181
2359
  const optionStrs = node.options.map((option) => {
2182
2360
  return this.deparse(option, createStmtContext);
2183
2361
  });
@@ -2200,7 +2378,7 @@ class Deparser {
2200
2378
  }
2201
2379
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2202
2380
  output.push('OPTIONS');
2203
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
2381
+ const columnContext = context.spawn('ColumnDef');
2204
2382
  const options = list_utils_1.ListUtils.unwrapList(node.fdwoptions).map(opt => this.visit(opt, columnContext));
2205
2383
  output.push(`(${options.join(', ')})`);
2206
2384
  }
@@ -2210,7 +2388,7 @@ class Deparser {
2210
2388
  if (node.constraints) {
2211
2389
  const constraints = list_utils_1.ListUtils.unwrapList(node.constraints);
2212
2390
  const constraintStrs = constraints.map(constraint => {
2213
- const columnConstraintContext = { ...context, isColumnConstraint: true };
2391
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
2214
2392
  return this.visit(constraint, columnConstraintContext);
2215
2393
  });
2216
2394
  output.push(...constraintStrs);
@@ -2250,9 +2428,25 @@ class Deparser {
2250
2428
  }
2251
2429
  break;
2252
2430
  case 'CONSTR_CHECK':
2253
- output.push('CHECK');
2431
+ if (context.isPretty() && !context.isColumnConstraint) {
2432
+ output.push('\n' + context.indent('CHECK'));
2433
+ }
2434
+ else {
2435
+ output.push('CHECK');
2436
+ }
2254
2437
  if (node.raw_expr) {
2255
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2438
+ if (context.isPretty()) {
2439
+ const checkExpr = this.visit(node.raw_expr, context);
2440
+ if (checkExpr.includes('\n')) {
2441
+ output.push('(\n' + context.indent(checkExpr) + '\n)');
2442
+ }
2443
+ else {
2444
+ output.push(`(${checkExpr})`);
2445
+ }
2446
+ }
2447
+ else {
2448
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2449
+ }
2256
2450
  }
2257
2451
  // Handle NOT VALID for check constraints
2258
2452
  if (node.skip_validation) {
@@ -2273,7 +2467,7 @@ class Deparser {
2273
2467
  }
2274
2468
  output.push('AS');
2275
2469
  if (node.raw_expr) {
2276
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2470
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2277
2471
  }
2278
2472
  output.push('STORED');
2279
2473
  break;
@@ -2331,7 +2525,12 @@ class Deparser {
2331
2525
  }
2332
2526
  break;
2333
2527
  case 'CONSTR_UNIQUE':
2334
- output.push('UNIQUE');
2528
+ if (context.isPretty() && !context.isColumnConstraint) {
2529
+ output.push('\n' + context.indent('UNIQUE'));
2530
+ }
2531
+ else {
2532
+ output.push('UNIQUE');
2533
+ }
2335
2534
  if (node.nulls_not_distinct) {
2336
2535
  output.push('NULLS NOT DISTINCT');
2337
2536
  }
@@ -2349,33 +2548,77 @@ class Deparser {
2349
2548
  case 'CONSTR_FOREIGN':
2350
2549
  // Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
2351
2550
  if (!context.isColumnConstraint) {
2352
- output.push('FOREIGN KEY');
2353
- if (node.fk_attrs && node.fk_attrs.length > 0) {
2354
- const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2355
- .map(attr => this.visit(attr, context))
2356
- .join(', ');
2357
- output.push(`(${fkAttrs})`);
2551
+ if (context.isPretty()) {
2552
+ output.push('\n' + context.indent('FOREIGN KEY'));
2553
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2554
+ const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2555
+ .map(attr => this.visit(attr, context))
2556
+ .join(', ');
2557
+ output.push(`(${fkAttrs})`);
2558
+ }
2559
+ output.push('\n' + context.indent('REFERENCES'));
2358
2560
  }
2561
+ else {
2562
+ output.push('FOREIGN KEY');
2563
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2564
+ const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2565
+ .map(attr => this.visit(attr, context))
2566
+ .join(', ');
2567
+ output.push(`(${fkAttrs})`);
2568
+ }
2569
+ output.push('REFERENCES');
2570
+ }
2571
+ }
2572
+ else {
2573
+ output.push('REFERENCES');
2359
2574
  }
2360
- output.push('REFERENCES');
2361
2575
  if (node.pktable) {
2362
- output.push(this.RangeVar(node.pktable, context));
2576
+ if (context.isPretty() && !context.isColumnConstraint) {
2577
+ const lastIndex = output.length - 1;
2578
+ if (lastIndex >= 0 && output[lastIndex].includes('REFERENCES')) {
2579
+ output[lastIndex] += ' ' + this.RangeVar(node.pktable, context);
2580
+ }
2581
+ else {
2582
+ output.push(this.RangeVar(node.pktable, context));
2583
+ }
2584
+ }
2585
+ else {
2586
+ output.push(this.RangeVar(node.pktable, context));
2587
+ }
2363
2588
  }
2364
2589
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2365
2590
  const pkAttrs = list_utils_1.ListUtils.unwrapList(node.pk_attrs)
2366
2591
  .map(attr => this.visit(attr, context))
2367
2592
  .join(', ');
2368
- output.push(`(${pkAttrs})`);
2593
+ if (context.isPretty() && !context.isColumnConstraint) {
2594
+ const lastIndex = output.length - 1;
2595
+ if (lastIndex >= 0) {
2596
+ output[lastIndex] += ` (${pkAttrs})`;
2597
+ }
2598
+ else {
2599
+ output.push(`(${pkAttrs})`);
2600
+ }
2601
+ }
2602
+ else {
2603
+ output.push(`(${pkAttrs})`);
2604
+ }
2369
2605
  }
2370
2606
  if (node.fk_matchtype && node.fk_matchtype !== 's') {
2607
+ let matchClause = '';
2371
2608
  switch (node.fk_matchtype) {
2372
2609
  case 'f':
2373
- output.push('MATCH FULL');
2610
+ matchClause = 'MATCH FULL';
2374
2611
  break;
2375
2612
  case 'p':
2376
- output.push('MATCH PARTIAL');
2613
+ matchClause = 'MATCH PARTIAL';
2377
2614
  break;
2378
2615
  }
2616
+ if (context.isPretty() && !context.isColumnConstraint) {
2617
+ output.push('\n' + context.indent(matchClause));
2618
+ }
2619
+ else {
2620
+ output.push(matchClause);
2621
+ }
2379
2622
  }
2380
2623
  if (node.fk_upd_action && node.fk_upd_action !== 'a') {
2381
2624
  let updateClause = 'ON UPDATE ';
@@ -2393,8 +2636,8 @@ class Deparser {
2393
2636
  updateClause += 'SET DEFAULT';
2394
2637
  break;
2395
2638
  }
2396
- if (this.formatter.isPretty()) {
2397
- output.push('\n' + this.formatter.indent(updateClause));
2639
+ if (context.isPretty()) {
2640
+ output.push('\n' + context.indent(updateClause));
2398
2641
  }
2399
2642
  else {
2400
2643
  output.push('ON UPDATE');
@@ -2417,8 +2660,8 @@ class Deparser {
2417
2660
  deleteClause += 'SET DEFAULT';
2418
2661
  break;
2419
2662
  }
2420
- if (this.formatter.isPretty()) {
2421
- output.push('\n' + this.formatter.indent(deleteClause));
2663
+ if (context.isPretty()) {
2664
+ output.push('\n' + context.indent(deleteClause));
2422
2665
  }
2423
2666
  else {
2424
2667
  output.push('ON DELETE');
@@ -2427,7 +2670,12 @@ class Deparser {
2427
2670
  }
2428
2671
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2429
2672
  if (node.skip_validation && !context.isDomainConstraint) {
2430
- output.push('NOT VALID');
2673
+ if (context.isPretty() && !context.isColumnConstraint) {
2674
+ output.push('\n' + context.indent('NOT VALID'));
2675
+ }
2676
+ else {
2677
+ output.push('NOT VALID');
2678
+ }
2431
2679
  }
2432
2680
  break;
2433
2681
  case 'CONSTR_ATTR_DEFERRABLE':
@@ -2481,13 +2729,13 @@ class Deparser {
2481
2729
  // Handle deferrable constraints for all constraint types that support it
2482
2730
  if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2483
2731
  if (node.deferrable) {
2484
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2485
- output.push('\n' + this.formatter.indent('DEFERRABLE'));
2732
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2733
+ output.push('\n' + context.indent('DEFERRABLE'));
2486
2734
  if (node.initdeferred === true) {
2487
- output.push('\n' + this.formatter.indent('INITIALLY DEFERRED'));
2735
+ output.push('\n' + context.indent('INITIALLY DEFERRED'));
2488
2736
  }
2489
2737
  else if (node.initdeferred === false) {
2490
- output.push('\n' + this.formatter.indent('INITIALLY IMMEDIATE'));
2738
+ output.push('\n' + context.indent('INITIALLY IMMEDIATE'));
2491
2739
  }
2492
2740
  }
2493
2741
  else {
@@ -2501,15 +2749,15 @@ class Deparser {
2501
2749
  }
2502
2750
  }
2503
2751
  else if (node.deferrable === false) {
2504
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2505
- output.push('\n' + this.formatter.indent('NOT DEFERRABLE'));
2752
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2753
+ output.push('\n' + context.indent('NOT DEFERRABLE'));
2506
2754
  }
2507
2755
  else {
2508
2756
  output.push('NOT DEFERRABLE');
2509
2757
  }
2510
2758
  }
2511
2759
  }
2512
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2760
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2513
2761
  let result = '';
2514
2762
  for (let i = 0; i < output.length; i++) {
2515
2763
  if (output[i].startsWith('\n')) {
@@ -2527,12 +2775,12 @@ class Deparser {
2527
2775
  return output.join(' ');
2528
2776
  }
2529
2777
  SubLink(node, context) {
2530
- const subselect = this.formatter.parens(this.visit(node.subselect, context));
2778
+ const subselect = context.parens(this.visit(node.subselect, context));
2531
2779
  switch (node.subLinkType) {
2532
2780
  case 'ANY_SUBLINK':
2533
2781
  if (node.testexpr && node.operName) {
2534
2782
  const testExpr = this.visit(node.testexpr, context);
2535
- const operator = this.deparseOperatorName(node.operName);
2783
+ const operator = this.deparseOperatorName(node.operName, context);
2536
2784
  return `${testExpr} ${operator} ANY ${subselect}`;
2537
2785
  }
2538
2786
  else if (node.testexpr) {
@@ -2543,7 +2791,7 @@ class Deparser {
2543
2791
  case 'ALL_SUBLINK':
2544
2792
  if (node.testexpr && node.operName) {
2545
2793
  const testExpr = this.visit(node.testexpr, context);
2546
- const operator = this.deparseOperatorName(node.operName);
2794
+ const operator = this.deparseOperatorName(node.operName, context);
2547
2795
  return `${testExpr} ${operator} ALL ${subselect}`;
2548
2796
  }
2549
2797
  return subselect;
@@ -2585,7 +2833,7 @@ class Deparser {
2585
2833
  }
2586
2834
  // Only add frame clause if frameOptions indicates non-default framing
2587
2835
  if (node.frameOptions && node.frameOptions !== 1058) {
2588
- const frameClause = this.formatWindowFrame(node);
2836
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2589
2837
  if (frameClause) {
2590
2838
  windowParts.push(frameClause);
2591
2839
  }
@@ -2605,7 +2853,7 @@ class Deparser {
2605
2853
  }
2606
2854
  return output.join(' ');
2607
2855
  }
2608
- formatWindowFrame(node) {
2856
+ formatWindowFrame(node, context) {
2609
2857
  if (!node.frameOptions)
2610
2858
  return null;
2611
2859
  const frameOptions = node.frameOptions;
@@ -2614,7 +2862,7 @@ class Deparser {
2614
2862
  if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
2615
2863
  frameParts.push('RANGE');
2616
2864
  }
2617
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2865
+ else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2618
2866
  frameParts.push('ROWS');
2619
2867
  }
2620
2868
  else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
@@ -2635,8 +2883,8 @@ class Deparser {
2635
2883
  }
2636
2884
  else if (frameOptions === 18453) {
2637
2885
  if (node.startOffset && node.endOffset) {
2638
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2639
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2886
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2887
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2640
2888
  }
2641
2889
  }
2642
2890
  else if (frameOptions === 1557) {
@@ -2646,7 +2894,7 @@ class Deparser {
2646
2894
  else if (frameOptions === 16917) {
2647
2895
  boundsParts.push('CURRENT ROW');
2648
2896
  if (node.endOffset) {
2649
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2897
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2650
2898
  }
2651
2899
  }
2652
2900
  else if (frameOptions === 1058) {
@@ -2656,13 +2904,13 @@ class Deparser {
2656
2904
  // Handle start bound - prioritize explicit offset values over bit flags
2657
2905
  if (node.startOffset) {
2658
2906
  if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
2659
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2907
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2660
2908
  }
2661
2909
  else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
2662
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} FOLLOWING`);
2910
+ boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
2663
2911
  }
2664
2912
  else {
2665
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2913
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2666
2914
  }
2667
2915
  }
2668
2916
  else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
@@ -2675,13 +2923,13 @@ class Deparser {
2675
2923
  if (node.endOffset) {
2676
2924
  if (boundsParts.length > 0) {
2677
2925
  if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
2678
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} PRECEDING`);
2926
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
2679
2927
  }
2680
2928
  else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
2681
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2929
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2682
2930
  }
2683
2931
  else {
2684
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2932
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2685
2933
  }
2686
2934
  }
2687
2935
  }
@@ -2766,7 +3014,7 @@ class Deparser {
2766
3014
  const colnames = list_utils_1.ListUtils.unwrapList(node.aliascolnames);
2767
3015
  const colnameStrs = colnames.map(col => this.visit(col, context));
2768
3016
  // Don't add space before column list parentheses to match original formatting
2769
- output[output.length - 1] += this.formatter.parens(colnameStrs.join(', '));
3017
+ output[output.length - 1] += context.parens(colnameStrs.join(', '));
2770
3018
  }
2771
3019
  output.push('AS');
2772
3020
  // Handle materialization clauses
@@ -2777,7 +3025,7 @@ class Deparser {
2777
3025
  output.push('MATERIALIZED');
2778
3026
  }
2779
3027
  if (node.ctequery) {
2780
- output.push(this.formatter.parens(this.visit(node.ctequery, context)));
3028
+ output.push(context.parens(this.visit(node.ctequery, context)));
2781
3029
  }
2782
3030
  return output.join(' ');
2783
3031
  }
@@ -2853,7 +3101,7 @@ class Deparser {
2853
3101
  DistinctExpr(node, context) {
2854
3102
  const args = list_utils_1.ListUtils.unwrapList(node.args);
2855
3103
  if (args.length === 2) {
2856
- const literalContext = { ...context, isStringLiteral: true };
3104
+ const literalContext = context.spawn('DistinctExpr', { isStringLiteral: true });
2857
3105
  const left = this.visit(args[0], literalContext);
2858
3106
  const right = this.visit(args[1], literalContext);
2859
3107
  return `${left} IS DISTINCT FROM ${right}`;
@@ -2863,7 +3111,7 @@ class Deparser {
2863
3111
  NullIfExpr(node, context) {
2864
3112
  const args = list_utils_1.ListUtils.unwrapList(node.args);
2865
3113
  if (args.length === 2) {
2866
- const literalContext = { ...context, isStringLiteral: true };
3114
+ const literalContext = context.spawn('NullIfExpr', { isStringLiteral: true });
2867
3115
  const left = this.visit(args[0], literalContext);
2868
3116
  const right = this.visit(args[1], literalContext);
2869
3117
  return `NULLIF(${left}, ${right})`;
@@ -2942,7 +3190,7 @@ class Deparser {
2942
3190
  }
2943
3191
  RelabelType(node, context) {
2944
3192
  if (node.arg) {
2945
- const literalContext = { ...context, isStringLiteral: true };
3193
+ const literalContext = context.spawn('RelabelType', { isStringLiteral: true });
2946
3194
  return this.visit(node.arg, literalContext);
2947
3195
  }
2948
3196
  return '';
@@ -2961,7 +3209,7 @@ class Deparser {
2961
3209
  }
2962
3210
  ConvertRowtypeExpr(node, context) {
2963
3211
  if (node.arg) {
2964
- const literalContext = { ...context, isStringLiteral: true };
3212
+ const literalContext = context.spawn('ConvertRowtypeExpr', { isStringLiteral: true });
2965
3213
  return this.visit(node.arg, literalContext);
2966
3214
  }
2967
3215
  return '';
@@ -2992,10 +3240,10 @@ class Deparser {
2992
3240
  }
2993
3241
  if (node.aliases && node.aliases.length > 0) {
2994
3242
  const aliasStrs = list_utils_1.ListUtils.unwrapList(node.aliases).map(alias => this.visit(alias, context));
2995
- output.push(this.formatter.parens(aliasStrs.join(', ')));
3243
+ output.push(context.parens(aliasStrs.join(', ')));
2996
3244
  }
2997
3245
  if (node.options && node.options.length > 0) {
2998
- const viewContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ViewStmt'] };
3246
+ const viewContext = context.spawn('ViewStmt');
2999
3247
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
3000
3248
  .map(option => this.visit(option, viewContext));
3001
3249
  output.push(`WITH (${optionStrs.join(', ')})`);
@@ -3042,22 +3290,22 @@ class Deparser {
3042
3290
  }
3043
3291
  if (node.indexParams && node.indexParams.length > 0) {
3044
3292
  const paramStrs = list_utils_1.ListUtils.unwrapList(node.indexParams).map(param => this.visit(param, context));
3045
- output.push(this.formatter.parens(paramStrs.join(', ')));
3293
+ output.push(context.parens(paramStrs.join(', ')));
3046
3294
  }
3047
3295
  if (node.indexIncludingParams && node.indexIncludingParams.length > 0) {
3048
3296
  const includeStrs = list_utils_1.ListUtils.unwrapList(node.indexIncludingParams).map(param => this.visit(param, context));
3049
3297
  output.push('INCLUDE');
3050
- output.push(this.formatter.parens(includeStrs.join(', ')));
3298
+ output.push(context.parens(includeStrs.join(', ')));
3051
3299
  }
3052
3300
  if (node.whereClause) {
3053
3301
  output.push('WHERE');
3054
3302
  output.push(this.visit(node.whereClause, context));
3055
3303
  }
3056
3304
  if (node.options && node.options.length > 0) {
3057
- const indexContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'IndexStmt'] };
3305
+ const indexContext = context.spawn('IndexStmt');
3058
3306
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options).map(option => this.visit(option, indexContext));
3059
3307
  output.push('WITH');
3060
- output.push(this.formatter.parens(optionStrs.join(', ')));
3308
+ output.push(context.parens(optionStrs.join(', ')));
3061
3309
  }
3062
3310
  if (node.nulls_not_distinct) {
3063
3311
  output.push('NULLS NOT DISTINCT');
@@ -3074,7 +3322,7 @@ class Deparser {
3074
3322
  output.push(quote_utils_1.QuoteUtils.quote(node.name));
3075
3323
  }
3076
3324
  else if (node.expr) {
3077
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3325
+ output.push(context.parens(this.visit(node.expr, context)));
3078
3326
  }
3079
3327
  if (node.collation && node.collation.length > 0) {
3080
3328
  const collationStrs = list_utils_1.ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3091,7 +3339,7 @@ class Deparser {
3091
3339
  const stringData = this.getNodeData(opt.DefElem.arg);
3092
3340
  return `${opt.DefElem.defname}='${stringData.sval}'`;
3093
3341
  }
3094
- return this.visit(opt, context);
3342
+ return this.visit(opt, context.spawn('IndexElem'));
3095
3343
  });
3096
3344
  opclassStr += `(${opclassOpts.join(', ')})`;
3097
3345
  }
@@ -3125,7 +3373,7 @@ class Deparser {
3125
3373
  output.push(quote_utils_1.QuoteUtils.quote(node.name));
3126
3374
  }
3127
3375
  else if (node.expr) {
3128
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3376
+ output.push(context.parens(this.visit(node.expr, context)));
3129
3377
  }
3130
3378
  if (node.collation && node.collation.length > 0) {
3131
3379
  const collationStrs = list_utils_1.ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3273,16 +3521,16 @@ class Deparser {
3273
3521
  if (node.rarg && 'JoinExpr' in node.rarg && !node.rarg.JoinExpr.alias) {
3274
3522
  rargStr = `(${rargStr})`;
3275
3523
  }
3276
- if (this.formatter.isPretty()) {
3277
- output.push(this.formatter.newline() + joinStr + ' ' + rargStr);
3524
+ if (context.isPretty()) {
3525
+ output.push(context.newline() + joinStr + ' ' + rargStr);
3278
3526
  }
3279
3527
  else {
3280
3528
  output.push(joinStr + ' ' + rargStr);
3281
3529
  }
3282
3530
  }
3283
3531
  else {
3284
- if (this.formatter.isPretty()) {
3285
- output.push(this.formatter.newline() + joinStr);
3532
+ if (context.isPretty()) {
3533
+ output.push(context.newline() + joinStr);
3286
3534
  }
3287
3535
  else {
3288
3536
  output.push(joinStr);
@@ -3291,7 +3539,7 @@ class Deparser {
3291
3539
  if (node.usingClause && node.usingClause.length > 0) {
3292
3540
  const usingList = list_utils_1.ListUtils.unwrapList(node.usingClause);
3293
3541
  const columnNames = usingList.map(col => this.visit(col, context));
3294
- if (this.formatter.isPretty()) {
3542
+ if (context.isPretty()) {
3295
3543
  output.push(` USING (${columnNames.join(', ')})`);
3296
3544
  }
3297
3545
  else {
@@ -3300,14 +3548,14 @@ class Deparser {
3300
3548
  }
3301
3549
  else if (node.quals) {
3302
3550
  const qualsStr = this.visit(node.quals, context);
3303
- if (this.formatter.isPretty()) {
3551
+ if (context.isPretty()) {
3304
3552
  // For complex JOIN conditions, format with proper indentation
3305
3553
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3306
3554
  if (this.containsMultilineStringLiteral(qualsStr)) {
3307
3555
  output.push(` ON ${qualsStr}`);
3308
3556
  }
3309
3557
  else {
3310
- output.push(` ON${this.formatter.newline()}${this.formatter.indent(qualsStr)}`);
3558
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3311
3559
  }
3312
3560
  }
3313
3561
  else {
@@ -3319,7 +3567,7 @@ class Deparser {
3319
3567
  }
3320
3568
  }
3321
3569
  let result;
3322
- if (this.formatter.isPretty()) {
3570
+ if (context.isPretty()) {
3323
3571
  result = output.join('');
3324
3572
  }
3325
3573
  else {
@@ -3426,8 +3674,8 @@ class Deparser {
3426
3674
  else if (nodeData.sval !== undefined) {
3427
3675
  // Handle nested sval structure: { sval: { sval: "value" } }
3428
3676
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3429
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3430
- boolValue = stringValue === 'on' || stringValue === 'true';
3677
+ const stringValue = svalValue.replace(/'/g, '');
3678
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3431
3679
  }
3432
3680
  }
3433
3681
  return boolValue ? 'READ ONLY' : 'READ WRITE';
@@ -3450,8 +3698,8 @@ class Deparser {
3450
3698
  else if (nodeData.sval !== undefined) {
3451
3699
  // Handle nested sval structure: { sval: { sval: "value" } }
3452
3700
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3453
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3454
- boolValue = stringValue === 'on' || stringValue === 'true';
3701
+ const stringValue = svalValue.replace(/'/g, '');
3702
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3455
3703
  }
3456
3704
  }
3457
3705
  return boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE';
@@ -3518,8 +3766,8 @@ class Deparser {
3518
3766
  else if (nodeData.sval !== undefined) {
3519
3767
  // Handle nested sval structure: { sval: { sval: "value" } }
3520
3768
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3521
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3522
- boolValue = stringValue === 'on' || stringValue === 'true';
3769
+ const stringValue = svalValue.replace(/'/g, '');
3770
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3523
3771
  }
3524
3772
  }
3525
3773
  transactionOptions.push(boolValue ? 'READ ONLY' : 'READ WRITE');
@@ -3537,8 +3785,8 @@ class Deparser {
3537
3785
  else if (nodeData.sval !== undefined) {
3538
3786
  // Handle nested sval structure: { sval: { sval: "value" } }
3539
3787
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3540
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3541
- boolValue = stringValue === 'on' || stringValue === 'true';
3788
+ const stringValue = svalValue.replace(/'/g, '');
3789
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3542
3790
  }
3543
3791
  }
3544
3792
  transactionOptions.push(boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE');
@@ -3604,7 +3852,7 @@ class Deparser {
3604
3852
  }
3605
3853
  switch (node.roletype) {
3606
3854
  case 'ROLESPEC_PUBLIC':
3607
- return 'public';
3855
+ return 'PUBLIC';
3608
3856
  case 'ROLESPEC_CURRENT_USER':
3609
3857
  return 'CURRENT_USER';
3610
3858
  case 'ROLESPEC_SESSION_USER':
@@ -3612,7 +3860,7 @@ class Deparser {
3612
3860
  case 'ROLESPEC_CURRENT_ROLE':
3613
3861
  return 'CURRENT_ROLE';
3614
3862
  default:
3615
- return 'public';
3863
+ return 'PUBLIC';
3616
3864
  }
3617
3865
  }
3618
3866
  roletype(node, context) {
@@ -3922,7 +4170,7 @@ class Deparser {
3922
4170
  }).filter((name) => name && name.trim());
3923
4171
  return items.join('.');
3924
4172
  }
3925
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DropStmt'], objtype: node.removeType };
4173
+ const objContext = context.spawn('DropStmt', { objtype: node.removeType });
3926
4174
  const objName = this.visit(objList, objContext);
3927
4175
  return objName;
3928
4176
  }).filter((name) => name && name.trim()).join(', ');
@@ -4022,7 +4270,7 @@ class Deparser {
4022
4270
  if (node.options && node.options.length > 0) {
4023
4271
  output.push('WITH');
4024
4272
  const optionsStr = list_utils_1.ListUtils.unwrapList(node.options)
4025
- .map(opt => this.visit(opt, context))
4273
+ .map(opt => this.visit(opt, context.spawn('CopyStmt')))
4026
4274
  .join(', ');
4027
4275
  output.push(`(${optionsStr})`);
4028
4276
  }
@@ -4068,18 +4316,31 @@ class Deparser {
4068
4316
  if (node.missing_ok) {
4069
4317
  output.push('IF EXISTS');
4070
4318
  }
4071
- const alterContext = node.objtype === 'OBJECT_TYPE'
4072
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
4073
- : context;
4319
+ const alterContext = context.spawn('AlterTableStmt', { objtype: node.objtype });
4074
4320
  if (node.relation) {
4075
4321
  const relationStr = this.RangeVar(node.relation, alterContext);
4076
4322
  output.push(relationStr);
4077
4323
  }
4078
4324
  if (node.cmds && node.cmds.length > 0) {
4079
- const commandsStr = list_utils_1.ListUtils.unwrapList(node.cmds)
4080
- .map(cmd => this.visit(cmd, alterContext))
4081
- .join(', ');
4082
- output.push(commandsStr);
4325
+ const commands = list_utils_1.ListUtils.unwrapList(node.cmds);
4326
+ if (context.isPretty()) {
4327
+ const commandsStr = commands
4328
+ .map(cmd => {
4329
+ const cmdStr = this.visit(cmd, alterContext);
4330
+ if (cmdStr.startsWith('ADD CONSTRAINT') || cmdStr.startsWith('ADD ')) {
4331
+ return context.newline() + context.indent(cmdStr);
4332
+ }
4333
+ return cmdStr;
4334
+ })
4335
+ .join(',');
4336
+ output.push(commandsStr);
4337
+ }
4338
+ else {
4339
+ const commandsStr = commands
4340
+ .map(cmd => this.visit(cmd, alterContext))
4341
+ .join(', ');
4342
+ output.push(commandsStr);
4343
+ }
4083
4344
  }
4084
4345
  return output.join(' ');
4085
4346
  }
@@ -4088,7 +4349,7 @@ class Deparser {
4088
4349
  if (node.subtype) {
4089
4350
  switch (node.subtype) {
4090
4351
  case 'AT_AddColumn':
4091
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4352
+ if (context.objtype === 'OBJECT_TYPE') {
4092
4353
  output.push('ADD ATTRIBUTE');
4093
4354
  }
4094
4355
  else {
@@ -4108,14 +4369,14 @@ class Deparser {
4108
4369
  }
4109
4370
  if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4110
4371
  parts.push('OPTIONS');
4111
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
4372
+ const columnContext = context.spawn('ColumnDef');
4112
4373
  const options = list_utils_1.ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4113
4374
  parts.push(`(${options.join(', ')})`);
4114
4375
  }
4115
4376
  if (colDefData.constraints) {
4116
4377
  const constraints = list_utils_1.ListUtils.unwrapList(colDefData.constraints);
4117
4378
  const constraintStrs = constraints.map(constraint => {
4118
- const columnConstraintContext = { ...context, isColumnConstraint: true };
4379
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4119
4380
  return this.visit(constraint, columnConstraintContext);
4120
4381
  });
4121
4382
  parts.push(...constraintStrs);
@@ -4135,7 +4396,7 @@ class Deparser {
4135
4396
  break;
4136
4397
  case 'AT_DropColumn':
4137
4398
  if (node.missing_ok) {
4138
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4399
+ if (context.objtype === 'OBJECT_TYPE') {
4139
4400
  output.push('DROP ATTRIBUTE IF EXISTS');
4140
4401
  }
4141
4402
  else {
@@ -4143,7 +4404,7 @@ class Deparser {
4143
4404
  }
4144
4405
  }
4145
4406
  else {
4146
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4407
+ if (context.objtype === 'OBJECT_TYPE') {
4147
4408
  output.push('DROP ATTRIBUTE');
4148
4409
  }
4149
4410
  else {
@@ -4161,7 +4422,7 @@ class Deparser {
4161
4422
  }
4162
4423
  break;
4163
4424
  case 'AT_AlterColumnType':
4164
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4425
+ if (context.objtype === 'OBJECT_TYPE') {
4165
4426
  output.push('ALTER ATTRIBUTE');
4166
4427
  }
4167
4428
  else {
@@ -4225,7 +4486,7 @@ class Deparser {
4225
4486
  case 'AT_SetRelOptions':
4226
4487
  output.push('SET');
4227
4488
  if (node.def) {
4228
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetRelOptions' };
4489
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetRelOptions' });
4229
4490
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4230
4491
  .map(option => this.visit(option, alterTableContext))
4231
4492
  .join(', ');
@@ -4238,7 +4499,7 @@ class Deparser {
4238
4499
  case 'AT_ResetRelOptions':
4239
4500
  output.push('RESET');
4240
4501
  if (node.def) {
4241
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetRelOptions' };
4502
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetRelOptions' });
4242
4503
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4243
4504
  .map(option => this.visit(option, alterTableContext))
4244
4505
  .join(', ');
@@ -4333,7 +4594,7 @@ class Deparser {
4333
4594
  }
4334
4595
  output.push('SET');
4335
4596
  if (node.def) {
4336
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetOptions' };
4597
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetOptions' });
4337
4598
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4338
4599
  .map(option => this.visit(option, alterTableContext))
4339
4600
  .join(', ');
@@ -4350,7 +4611,7 @@ class Deparser {
4350
4611
  }
4351
4612
  output.push('RESET');
4352
4613
  if (node.def) {
4353
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetOptions' };
4614
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetOptions' });
4354
4615
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4355
4616
  .map(option => this.visit(option, alterTableContext))
4356
4617
  .join(', ');
@@ -4591,7 +4852,7 @@ class Deparser {
4591
4852
  }
4592
4853
  output.push('OPTIONS');
4593
4854
  if (node.def) {
4594
- const alterColumnContext = { ...context, alterColumnOptions: true };
4855
+ const alterColumnContext = context.spawn('AlterTableCmd', { alterColumnOptions: true });
4595
4856
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4596
4857
  .map(option => this.visit(option, alterColumnContext))
4597
4858
  .join(', ');
@@ -4631,7 +4892,7 @@ class Deparser {
4631
4892
  case 'AT_GenericOptions':
4632
4893
  output.push('OPTIONS');
4633
4894
  if (node.def) {
4634
- const alterTableContext = { ...context, alterTableOptions: true };
4895
+ const alterTableContext = context.spawn('AlterTableCmd', { alterTableOptions: true });
4635
4896
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4636
4897
  .map(option => this.visit(option, alterTableContext))
4637
4898
  .join(', ');
@@ -4735,7 +4996,7 @@ class Deparser {
4735
4996
  output.push(this.TypeName(node.returnType, context));
4736
4997
  }
4737
4998
  if (node.options && node.options.length > 0) {
4738
- const funcContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFunctionStmt'] };
4999
+ const funcContext = context.spawn('CreateFunctionStmt');
4739
5000
  const options = node.options.map((opt) => this.visit(opt, funcContext));
4740
5001
  output.push(...options);
4741
5002
  }
@@ -4822,7 +5083,7 @@ class Deparser {
4822
5083
  }
4823
5084
  output.push('AS', 'ENUM');
4824
5085
  if (node.vals && node.vals.length > 0) {
4825
- const enumContext = { ...context, isEnumValue: true };
5086
+ const enumContext = context.spawn('CreateEnumStmt', { isEnumValue: true });
4826
5087
  const values = list_utils_1.ListUtils.unwrapList(node.vals)
4827
5088
  .map(val => this.visit(val, enumContext))
4828
5089
  .join(', ');
@@ -4877,9 +5138,8 @@ class Deparser {
4877
5138
  output.push(roleName);
4878
5139
  }
4879
5140
  if (node.options) {
4880
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateRoleStmt'] };
4881
5141
  const options = list_utils_1.ListUtils.unwrapList(node.options)
4882
- .map(option => this.visit(option, roleContext))
5142
+ .map(option => this.visit(option, context.spawn('CreateRoleStmt')))
4883
5143
  .join(' ');
4884
5144
  if (options) {
4885
5145
  output.push('WITH');
@@ -4910,7 +5170,7 @@ class Deparser {
4910
5170
  const stringData = this.getNodeData(node.arg);
4911
5171
  return `${node.defname}='${stringData.sval}'`;
4912
5172
  }
4913
- return `${node.defname}=${this.visit(node.arg, { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] })}`;
5173
+ return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
4914
5174
  }
4915
5175
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
4916
5176
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -4926,13 +5186,13 @@ class Deparser {
4926
5186
  if (!node.arg) {
4927
5187
  return `NO ${node.defname.toUpperCase()}`;
4928
5188
  }
4929
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5189
+ const defElemContext = context.spawn('DefElem');
4930
5190
  const argValue = this.visit(node.arg, defElemContext);
4931
5191
  return `${node.defname.toUpperCase()} ${argValue}`;
4932
5192
  }
4933
5193
  // Handle OPTIONS clause - use space format, not equals format
4934
5194
  if (node.arg) {
4935
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5195
+ const defElemContext = context.spawn('DefElem');
4936
5196
  const argValue = this.visit(node.arg, defElemContext);
4937
5197
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
4938
5198
  const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
@@ -4986,7 +5246,7 @@ class Deparser {
4986
5246
  if (!node.arg) {
4987
5247
  return 'PASSWORD NULL';
4988
5248
  }
4989
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5249
+ const defElemContext = context.spawn('DefElem');
4990
5250
  const argValue = this.visit(node.arg, defElemContext);
4991
5251
  const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
4992
5252
  ? `'${argValue}'`
@@ -4995,7 +5255,7 @@ class Deparser {
4995
5255
  }
4996
5256
  }
4997
5257
  if (node.arg) {
4998
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5258
+ const defElemContext = context.spawn('DefElem');
4999
5259
  const argValue = this.visit(node.arg, defElemContext);
5000
5260
  if (context.parentNodeTypes.includes('AlterOperatorStmt')) {
5001
5261
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
@@ -5071,7 +5331,7 @@ class Deparser {
5071
5331
  if (node.defname === 'sysid') {
5072
5332
  return `SYSID ${argValue}`;
5073
5333
  }
5074
- if (argValue === 'true') {
5334
+ if (String(argValue) === 'true') {
5075
5335
  // Handle special cases where the positive form has a different name
5076
5336
  if (node.defname === 'isreplication') {
5077
5337
  return 'REPLICATION';
@@ -5081,7 +5341,7 @@ class Deparser {
5081
5341
  }
5082
5342
  return node.defname.toUpperCase();
5083
5343
  }
5084
- else if (argValue === 'false') {
5344
+ else if (String(argValue) === 'false') {
5085
5345
  // Handle special cases where the negative form has a different name
5086
5346
  if (node.defname === 'canlogin') {
5087
5347
  return 'NOLOGIN';
@@ -5145,7 +5405,7 @@ class Deparser {
5145
5405
  }
5146
5406
  if (context.parentNodeTypes.includes('DoStmt')) {
5147
5407
  if (node.defname === 'as') {
5148
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5408
+ const defElemContext = context.spawn('DefElem');
5149
5409
  const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
5150
5410
  if (Array.isArray(argValue)) {
5151
5411
  const bodyParts = argValue;
@@ -5436,7 +5696,7 @@ class Deparser {
5436
5696
  }
5437
5697
  if (node.options && node.options.length > 0) {
5438
5698
  output.push('WITH');
5439
- const tsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateTableSpaceStmt'] };
5699
+ const tsContext = context.spawn('CreateTableSpaceStmt');
5440
5700
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5441
5701
  .map(option => this.visit(option, tsContext))
5442
5702
  .join(', ');
@@ -5466,7 +5726,7 @@ class Deparser {
5466
5726
  output.push('SET');
5467
5727
  }
5468
5728
  if (node.options && node.options.length > 0) {
5469
- const tablespaceContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableSpaceOptionsStmt'] };
5729
+ const tablespaceContext = context.spawn('AlterTableSpaceOptionsStmt');
5470
5730
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5471
5731
  .map(option => this.visit(option, tablespaceContext))
5472
5732
  .join(', ');
@@ -5483,7 +5743,7 @@ class Deparser {
5483
5743
  output.push(this.quoteIfNeeded(node.extname));
5484
5744
  }
5485
5745
  if (node.options && node.options.length > 0) {
5486
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateExtensionStmt'] };
5746
+ const extContext = context.spawn('CreateExtensionStmt');
5487
5747
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5488
5748
  .map(option => this.visit(option, extContext))
5489
5749
  .join(' ');
@@ -5497,7 +5757,7 @@ class Deparser {
5497
5757
  output.push(this.quoteIfNeeded(node.extname));
5498
5758
  }
5499
5759
  if (node.options && node.options.length > 0) {
5500
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterExtensionStmt'] };
5760
+ const extContext = context.spawn('AlterExtensionStmt');
5501
5761
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5502
5762
  .map(option => this.visit(option, extContext))
5503
5763
  .join(' ');
@@ -5511,7 +5771,7 @@ class Deparser {
5511
5771
  output.push(node.fdwname);
5512
5772
  }
5513
5773
  if (node.func_options && node.func_options.length > 0) {
5514
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5774
+ const fdwContext = context.spawn('CreateFdwStmt');
5515
5775
  const funcOptions = list_utils_1.ListUtils.unwrapList(node.func_options)
5516
5776
  .map(option => this.visit(option, fdwContext))
5517
5777
  .join(' ');
@@ -5519,7 +5779,7 @@ class Deparser {
5519
5779
  }
5520
5780
  if (node.options && node.options.length > 0) {
5521
5781
  output.push('OPTIONS');
5522
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5782
+ const fdwContext = context.spawn('CreateFdwStmt');
5523
5783
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5524
5784
  .map(option => this.visit(option, fdwContext))
5525
5785
  .join(', ');
@@ -5621,7 +5881,7 @@ class Deparser {
5621
5881
  output.push('ADD');
5622
5882
  if (node.def) {
5623
5883
  // Pass domain context to avoid adding constraint names for domain constraints
5624
- const domainContext = { ...context, isDomainConstraint: true };
5884
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5625
5885
  output.push(this.visit(node.def, domainContext));
5626
5886
  }
5627
5887
  break;
@@ -5647,7 +5907,7 @@ class Deparser {
5647
5907
  output.push('ADD');
5648
5908
  if (node.def) {
5649
5909
  // Pass domain context to avoid adding constraint names for domain constraints
5650
- const domainContext = { ...context, isDomainConstraint: true };
5910
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5651
5911
  output.push(this.visit(node.def, domainContext));
5652
5912
  }
5653
5913
  break;
@@ -6009,7 +6269,7 @@ class Deparser {
6009
6269
  output.push(`${operatorName}(${args.join(', ')})`);
6010
6270
  }
6011
6271
  else {
6012
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CommentStmt'], objtype: node.objtype };
6272
+ const objContext = context.spawn('CommentStmt', { objtype: node.objtype });
6013
6273
  output.push(this.visit(node.object, objContext));
6014
6274
  }
6015
6275
  }
@@ -6055,13 +6315,13 @@ class Deparser {
6055
6315
  const output = [];
6056
6316
  const initialParts = ['CREATE', 'POLICY'];
6057
6317
  if (node.policy_name) {
6058
- initialParts.push(`"${node.policy_name}"`);
6318
+ initialParts.push(quote_utils_1.QuoteUtils.quote(node.policy_name));
6059
6319
  }
6060
6320
  output.push(initialParts.join(' '));
6061
6321
  // Add ON clause on new line in pretty mode
6062
6322
  if (node.table) {
6063
- if (this.formatter.isPretty()) {
6064
- output.push(this.formatter.newline() + this.formatter.indent(`ON ${this.RangeVar(node.table, context)}`));
6323
+ if (context.isPretty()) {
6324
+ output.push(context.newline() + context.indent(`ON ${this.RangeVar(node.table, context)}`));
6065
6325
  }
6066
6326
  else {
6067
6327
  output.push('ON');
@@ -6070,24 +6330,24 @@ class Deparser {
6070
6330
  }
6071
6331
  // Handle AS RESTRICTIVE/PERMISSIVE clause
6072
6332
  if (node.permissive === undefined) {
6073
- if (this.formatter.isPretty()) {
6074
- output.push(this.formatter.newline() + this.formatter.indent('AS RESTRICTIVE'));
6333
+ if (context.isPretty()) {
6334
+ output.push(context.newline() + context.indent('AS RESTRICTIVE'));
6075
6335
  }
6076
6336
  else {
6077
6337
  output.push('AS', 'RESTRICTIVE');
6078
6338
  }
6079
6339
  }
6080
6340
  else if (node.permissive === true) {
6081
- if (this.formatter.isPretty()) {
6082
- output.push(this.formatter.newline() + this.formatter.indent('AS PERMISSIVE'));
6341
+ if (context.isPretty()) {
6342
+ output.push(context.newline() + context.indent('AS PERMISSIVE'));
6083
6343
  }
6084
6344
  else {
6085
6345
  output.push('AS', 'PERMISSIVE');
6086
6346
  }
6087
6347
  }
6088
6348
  if (node.cmd_name) {
6089
- if (this.formatter.isPretty()) {
6090
- output.push(this.formatter.newline() + this.formatter.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6349
+ if (context.isPretty()) {
6350
+ output.push(context.newline() + context.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6091
6351
  }
6092
6352
  else {
6093
6353
  output.push('FOR', node.cmd_name.toUpperCase());
@@ -6095,8 +6355,8 @@ class Deparser {
6095
6355
  }
6096
6356
  if (node.roles && node.roles.length > 0) {
6097
6357
  const roles = list_utils_1.ListUtils.unwrapList(node.roles).map(role => this.visit(role, context));
6098
- if (this.formatter.isPretty()) {
6099
- output.push(this.formatter.newline() + this.formatter.indent(`TO ${roles.join(', ')}`));
6358
+ if (context.isPretty()) {
6359
+ output.push(context.newline() + context.indent(`TO ${roles.join(', ')}`));
6100
6360
  }
6101
6361
  else {
6102
6362
  output.push('TO');
@@ -6104,11 +6364,11 @@ class Deparser {
6104
6364
  }
6105
6365
  }
6106
6366
  if (node.qual) {
6107
- if (this.formatter.isPretty()) {
6367
+ if (context.isPretty()) {
6108
6368
  const qualExpr = this.visit(node.qual, context);
6109
- output.push(this.formatter.newline() + this.formatter.indent('USING ('));
6110
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(qualExpr)));
6111
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6369
+ output.push(context.newline() + context.indent('USING ('));
6370
+ output.push(context.newline() + context.indent(context.indent(qualExpr)));
6371
+ output.push(context.newline() + context.indent(')'));
6112
6372
  }
6113
6373
  else {
6114
6374
  output.push('USING');
@@ -6116,23 +6376,23 @@ class Deparser {
6116
6376
  }
6117
6377
  }
6118
6378
  if (node.with_check) {
6119
- if (this.formatter.isPretty()) {
6379
+ if (context.isPretty()) {
6120
6380
  const checkExpr = this.visit(node.with_check, context);
6121
- output.push(this.formatter.newline() + this.formatter.indent('WITH CHECK ('));
6122
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(checkExpr)));
6123
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6381
+ output.push(context.newline() + context.indent('WITH CHECK ('));
6382
+ output.push(context.newline() + context.indent(context.indent(checkExpr)));
6383
+ output.push(context.newline() + context.indent(')'));
6124
6384
  }
6125
6385
  else {
6126
6386
  output.push('WITH CHECK');
6127
6387
  output.push(`(${this.visit(node.with_check, context)})`);
6128
6388
  }
6129
6389
  }
6130
- return this.formatter.isPretty() ? output.join('') : output.join(' ');
6390
+ return context.isPretty() ? output.join('') : output.join(' ');
6131
6391
  }
6132
6392
  AlterPolicyStmt(node, context) {
6133
6393
  const output = ['ALTER', 'POLICY'];
6134
6394
  if (node.policy_name) {
6135
- output.push(`"${node.policy_name}"`);
6395
+ output.push(quote_utils_1.QuoteUtils.quote(node.policy_name));
6136
6396
  }
6137
6397
  if (node.table) {
6138
6398
  output.push('ON');
@@ -6172,7 +6432,7 @@ class Deparser {
6172
6432
  }
6173
6433
  if (node.options && node.options.length > 0) {
6174
6434
  output.push('OPTIONS');
6175
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateUserMappingStmt'] };
6435
+ const userMappingContext = context.spawn('CreateUserMappingStmt');
6176
6436
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6177
6437
  output.push(`(${options.join(', ')})`);
6178
6438
  }
@@ -6355,7 +6615,7 @@ class Deparser {
6355
6615
  DoStmt(node, context) {
6356
6616
  const output = ['DO'];
6357
6617
  if (node.args && node.args.length > 0) {
6358
- const doContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DoStmt'] };
6618
+ const doContext = context.spawn('DoStmt');
6359
6619
  const args = list_utils_1.ListUtils.unwrapList(node.args);
6360
6620
  const processedArgs = [];
6361
6621
  for (const arg of args) {
@@ -6660,7 +6920,7 @@ class Deparser {
6660
6920
  ObjectWithArgs(node, context) {
6661
6921
  let result = '';
6662
6922
  if (node.objname && node.objname.length > 0) {
6663
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ObjectWithArgs'] };
6923
+ const objContext = context.spawn('ObjectWithArgs');
6664
6924
  const names = list_utils_1.ListUtils.unwrapList(node.objname).map(name => this.visit(name, objContext));
6665
6925
  result = names.join('.');
6666
6926
  }
@@ -6702,7 +6962,7 @@ class Deparser {
6702
6962
  }
6703
6963
  output.push('SET');
6704
6964
  if (node.options && node.options.length > 0) {
6705
- const alterOpContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterOperatorStmt'] };
6965
+ const alterOpContext = context.spawn('AlterOperatorStmt');
6706
6966
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, alterOpContext));
6707
6967
  output.push(`(${options.join(', ')})`);
6708
6968
  }
@@ -6714,13 +6974,13 @@ class Deparser {
6714
6974
  output.push(quote_utils_1.QuoteUtils.quote(node.fdwname));
6715
6975
  }
6716
6976
  if (node.func_options && node.func_options.length > 0) {
6717
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
6977
+ const fdwContext = context.spawn('AlterFdwStmt');
6718
6978
  const funcOptions = list_utils_1.ListUtils.unwrapList(node.func_options).map(opt => this.visit(opt, fdwContext));
6719
6979
  output.push(funcOptions.join(' '));
6720
6980
  }
6721
6981
  if (node.options && node.options.length > 0) {
6722
6982
  output.push('OPTIONS');
6723
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
6983
+ const fdwContext = context.spawn('AlterFdwStmt');
6724
6984
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, fdwContext));
6725
6985
  output.push(`(${options.join(', ')})`);
6726
6986
  }
@@ -6746,7 +7006,7 @@ class Deparser {
6746
7006
  if (node.options && node.options.length > 0) {
6747
7007
  output.push('OPTIONS');
6748
7008
  output.push('(');
6749
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignServerStmt'] };
7009
+ const optionsContext = context.spawn('CreateForeignServerStmt');
6750
7010
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6751
7011
  output.push(options.join(', '));
6752
7012
  output.push(')');
@@ -6764,7 +7024,7 @@ class Deparser {
6764
7024
  if (node.options && node.options.length > 0) {
6765
7025
  output.push('OPTIONS');
6766
7026
  output.push('(');
6767
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterForeignServerStmt'] };
7027
+ const optionsContext = context.spawn('AlterForeignServerStmt');
6768
7028
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6769
7029
  output.push(options.join(', '));
6770
7030
  output.push(')');
@@ -6785,7 +7045,7 @@ class Deparser {
6785
7045
  }
6786
7046
  if (node.options && node.options.length > 0) {
6787
7047
  output.push('OPTIONS');
6788
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterUserMappingStmt'] };
7048
+ const userMappingContext = context.spawn('AlterUserMappingStmt');
6789
7049
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6790
7050
  output.push(`(${options.join(', ')})`);
6791
7051
  }
@@ -6845,7 +7105,7 @@ class Deparser {
6845
7105
  output.push(quote_utils_1.QuoteUtils.quote(node.local_schema));
6846
7106
  }
6847
7107
  if (node.options && node.options.length > 0) {
6848
- const importSchemaContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ImportForeignSchemaStmt'] };
7108
+ const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
6849
7109
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, importSchemaContext));
6850
7110
  output.push(`OPTIONS (${options.join(', ')})`);
6851
7111
  }
@@ -6880,7 +7140,7 @@ class Deparser {
6880
7140
  ExplainStmt(node, context) {
6881
7141
  const output = ['EXPLAIN'];
6882
7142
  if (node.options && node.options.length > 0) {
6883
- const explainContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ExplainStmt'] };
7143
+ const explainContext = context.spawn('ExplainStmt');
6884
7144
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(option => this.visit(option, explainContext));
6885
7145
  output.push(`(${options.join(', ')})`);
6886
7146
  }
@@ -7138,9 +7398,7 @@ class Deparser {
7138
7398
  output.push(this.RangeVar(node.relation, context));
7139
7399
  }
7140
7400
  else if (node.relation) {
7141
- const rangeVarContext = node.relationType === 'OBJECT_TYPE'
7142
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
7143
- : context;
7401
+ const rangeVarContext = context.spawn('RenameStmt', { objtype: node.relationType });
7144
7402
  // Add ON keyword for policy operations
7145
7403
  if (node.renameType === 'OBJECT_POLICY') {
7146
7404
  output.push('ON');
@@ -7322,7 +7580,7 @@ class Deparser {
7322
7580
  }
7323
7581
  }
7324
7582
  if (node.privileges && node.privileges.length > 0) {
7325
- const privilegeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'GrantStmt'] };
7583
+ const privilegeContext = context.spawn('GrantStmt');
7326
7584
  const privileges = list_utils_1.ListUtils.unwrapList(node.privileges)
7327
7585
  .map(priv => this.visit(priv, privilegeContext))
7328
7586
  .join(', ');
@@ -7630,7 +7888,12 @@ class Deparser {
7630
7888
  }
7631
7889
  if (node.action) {
7632
7890
  const actionStr = this.GrantStmt(node.action, context);
7633
- output.push(actionStr);
7891
+ if (context.isPretty()) {
7892
+ return output.join(' ') + context.newline() + context.indent(actionStr);
7893
+ }
7894
+ else {
7895
+ output.push(actionStr);
7896
+ }
7634
7897
  }
7635
7898
  return output.join(' ');
7636
7899
  }
@@ -7777,84 +8040,158 @@ class Deparser {
7777
8040
  if (node.trigname) {
7778
8041
  output.push(quote_utils_1.QuoteUtils.quote(node.trigname));
7779
8042
  }
7780
- const timing = [];
7781
- if (node.timing & 2)
7782
- timing.push('BEFORE');
7783
- else if (node.timing & 64)
7784
- timing.push('INSTEAD OF');
7785
- else
7786
- timing.push('AFTER'); // Default timing when no specific timing is set
7787
- output.push(timing.join(' '));
7788
- const events = [];
7789
- if (node.events & 4)
7790
- events.push('INSERT');
7791
- if (node.events & 8)
7792
- events.push('DELETE');
7793
- if (node.events & 16)
7794
- events.push('UPDATE');
7795
- if (node.events & 32)
7796
- events.push('TRUNCATE');
7797
- output.push(events.join(' OR '));
7798
- if (node.columns && node.columns.length > 0) {
7799
- output.push('OF');
7800
- const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
7801
- .map(col => this.visit(col, context))
7802
- .join(', ');
7803
- output.push(columnNames);
7804
- }
7805
- output.push('ON');
7806
- if (node.relation) {
7807
- output.push(this.RangeVar(node.relation, context));
7808
- }
7809
- if (node.constrrel) {
7810
- output.push('FROM');
7811
- output.push(this.RangeVar(node.constrrel, context));
7812
- }
7813
- if (node.deferrable) {
7814
- output.push('DEFERRABLE');
7815
- }
7816
- if (node.initdeferred) {
7817
- output.push('INITIALLY DEFERRED');
7818
- }
7819
- // Handle REFERENCING clauses
7820
- if (node.transitionRels && node.transitionRels.length > 0) {
7821
- output.push('REFERENCING');
7822
- const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
7823
- .map(rel => this.visit(rel, context))
7824
- .join(' ');
7825
- output.push(transitionClauses);
7826
- }
7827
- if (node.row) {
7828
- output.push('FOR EACH ROW');
7829
- }
7830
- else {
7831
- output.push('FOR EACH STATEMENT');
7832
- }
7833
- if (node.whenClause) {
7834
- output.push('WHEN');
7835
- output.push('(');
7836
- output.push(this.visit(node.whenClause, context));
7837
- output.push(')');
7838
- }
7839
- output.push('EXECUTE');
7840
- if (node.funcname && node.funcname.length > 0) {
7841
- const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
7842
- .map(name => this.visit(name, context))
7843
- .join('.');
7844
- output.push('FUNCTION', funcName);
7845
- }
7846
- if (node.args && node.args.length > 0) {
7847
- output.push('(');
7848
- const args = list_utils_1.ListUtils.unwrapList(node.args)
7849
- .map(arg => this.visit(arg, context))
7850
- .join(', ');
7851
- output.push(args);
7852
- output.push(')');
8043
+ if (context.isPretty()) {
8044
+ const components = [];
8045
+ const timing = [];
8046
+ if (node.timing & 2)
8047
+ timing.push('BEFORE');
8048
+ else if (node.timing & 64)
8049
+ timing.push('INSTEAD OF');
8050
+ else
8051
+ timing.push('AFTER');
8052
+ const events = [];
8053
+ if (node.events & 4)
8054
+ events.push('INSERT');
8055
+ if (node.events & 8)
8056
+ events.push('DELETE');
8057
+ if (node.events & 16) {
8058
+ let updateStr = 'UPDATE';
8059
+ if (node.columns && node.columns.length > 0) {
8060
+ const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
8061
+ .map(col => this.visit(col, context))
8062
+ .join(', ');
8063
+ updateStr += ' OF ' + columnNames;
8064
+ }
8065
+ events.push(updateStr);
8066
+ }
8067
+ if (node.events & 32)
8068
+ events.push('TRUNCATE');
8069
+ components.push(context.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8070
+ if (node.relation) {
8071
+ components.push(context.indent('ON ' + this.RangeVar(node.relation, context)));
8072
+ }
8073
+ if (node.transitionRels && node.transitionRels.length > 0) {
8074
+ const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
8075
+ .map(rel => this.visit(rel, context))
8076
+ .join(' ');
8077
+ components.push(context.indent('REFERENCING ' + transitionClauses));
8078
+ }
8079
+ if (node.deferrable) {
8080
+ components.push(context.indent('DEFERRABLE'));
8081
+ }
8082
+ if (node.initdeferred) {
8083
+ components.push(context.indent('INITIALLY DEFERRED'));
8084
+ }
8085
+ if (node.row) {
8086
+ components.push(context.indent('FOR EACH ROW'));
8087
+ }
8088
+ else {
8089
+ components.push(context.indent('FOR EACH STATEMENT'));
8090
+ }
8091
+ if (node.whenClause) {
8092
+ const whenStr = 'WHEN (' + this.visit(node.whenClause, context) + ')';
8093
+ components.push(context.indent(whenStr));
8094
+ }
8095
+ let executeStr = 'EXECUTE';
8096
+ if (node.funcname && node.funcname.length > 0) {
8097
+ const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
8098
+ .map(name => this.visit(name, context))
8099
+ .join('.');
8100
+ executeStr += ' PROCEDURE ' + funcName;
8101
+ }
8102
+ if (node.args && node.args.length > 0) {
8103
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8104
+ const args = list_utils_1.ListUtils.unwrapList(node.args)
8105
+ .map(arg => this.visit(arg, argContext))
8106
+ .join(', ');
8107
+ executeStr += '(' + args + ')';
8108
+ }
8109
+ else {
8110
+ executeStr += '()';
8111
+ }
8112
+ components.push(context.indent(executeStr));
8113
+ return output.join(' ') + context.newline() + components.join(context.newline());
7853
8114
  }
7854
8115
  else {
7855
- output.push('()');
8116
+ const timing = [];
8117
+ if (node.timing & 2)
8118
+ timing.push('BEFORE');
8119
+ else if (node.timing & 64)
8120
+ timing.push('INSTEAD OF');
8121
+ else
8122
+ timing.push('AFTER');
8123
+ output.push(timing.join(' '));
8124
+ const events = [];
8125
+ if (node.events & 4)
8126
+ events.push('INSERT');
8127
+ if (node.events & 8)
8128
+ events.push('DELETE');
8129
+ if (node.events & 16)
8130
+ events.push('UPDATE');
8131
+ if (node.events & 32)
8132
+ events.push('TRUNCATE');
8133
+ output.push(events.join(' OR '));
8134
+ if (node.columns && node.columns.length > 0) {
8135
+ output.push('OF');
8136
+ const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
8137
+ .map(col => this.visit(col, context))
8138
+ .join(', ');
8139
+ output.push(columnNames);
8140
+ }
8141
+ output.push('ON');
8142
+ if (node.relation) {
8143
+ output.push(this.RangeVar(node.relation, context));
8144
+ }
8145
+ if (node.constrrel) {
8146
+ output.push('FROM');
8147
+ output.push(this.RangeVar(node.constrrel, context));
8148
+ }
8149
+ if (node.deferrable) {
8150
+ output.push('DEFERRABLE');
8151
+ }
8152
+ if (node.initdeferred) {
8153
+ output.push('INITIALLY DEFERRED');
8154
+ }
8155
+ if (node.transitionRels && node.transitionRels.length > 0) {
8156
+ output.push('REFERENCING');
8157
+ const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
8158
+ .map(rel => this.visit(rel, context))
8159
+ .join(' ');
8160
+ output.push(transitionClauses);
8161
+ }
8162
+ if (node.row) {
8163
+ output.push('FOR EACH ROW');
8164
+ }
8165
+ else {
8166
+ output.push('FOR EACH STATEMENT');
8167
+ }
8168
+ if (node.whenClause) {
8169
+ output.push('WHEN');
8170
+ output.push('(');
8171
+ output.push(this.visit(node.whenClause, context));
8172
+ output.push(')');
8173
+ }
8174
+ output.push('EXECUTE');
8175
+ if (node.funcname && node.funcname.length > 0) {
8176
+ const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
8177
+ .map(name => this.visit(name, context))
8178
+ .join('.');
8179
+ output.push('FUNCTION', funcName);
8180
+ }
8181
+ if (node.args && node.args.length > 0) {
8182
+ output.push('(');
8183
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8184
+ const args = list_utils_1.ListUtils.unwrapList(node.args)
8185
+ .map(arg => this.visit(arg, argContext))
8186
+ .join(', ');
8187
+ output.push(args);
8188
+ output.push(')');
8189
+ }
8190
+ else {
8191
+ output.push('()');
8192
+ }
8193
+ return output.join(' ');
7856
8194
  }
7857
- return output.join(' ');
7858
8195
  }
7859
8196
  TriggerTransition(node, context) {
7860
8197
  const output = [];
@@ -7880,7 +8217,7 @@ class Deparser {
7880
8217
  }
7881
8218
  if (node.whenclause && node.whenclause.length > 0) {
7882
8219
  output.push('WHEN');
7883
- const eventTriggerContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateEventTrigStmt'] };
8220
+ const eventTriggerContext = context.spawn('CreateEventTrigStmt');
7884
8221
  const conditions = list_utils_1.ListUtils.unwrapList(node.whenclause)
7885
8222
  .map(condition => this.visit(condition, eventTriggerContext))
7886
8223
  .join(' AND ');
@@ -8069,7 +8406,7 @@ class Deparser {
8069
8406
  output.push(sequenceName.join('.'));
8070
8407
  }
8071
8408
  if (node.options && node.options.length > 0) {
8072
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateSeqStmt'] };
8409
+ const seqContext = context.spawn('CreateSeqStmt');
8073
8410
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
8074
8411
  .filter(option => option != null && this.getNodeType(option) !== 'undefined')
8075
8412
  .map(option => {
@@ -8106,7 +8443,7 @@ class Deparser {
8106
8443
  output.push(sequenceName.join('.'));
8107
8444
  }
8108
8445
  if (node.options && node.options.length > 0) {
8109
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterSeqStmt'] };
8446
+ const seqContext = context.spawn('AlterSeqStmt');
8110
8447
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
8111
8448
  .filter(option => option && option !== undefined)
8112
8449
  .map(option => {
@@ -8135,7 +8472,7 @@ class Deparser {
8135
8472
  CompositeTypeStmt(node, context) {
8136
8473
  const output = ['CREATE', 'TYPE'];
8137
8474
  if (node.typevar) {
8138
- const typeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CompositeTypeStmt'] };
8475
+ const typeContext = context.spawn('CompositeTypeStmt');
8139
8476
  output.push(this.RangeVar(node.typevar, typeContext));
8140
8477
  }
8141
8478
  output.push('AS');
@@ -8236,7 +8573,7 @@ class Deparser {
8236
8573
  output.push(this.RoleSpec(node.role, context));
8237
8574
  }
8238
8575
  if (node.options) {
8239
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterRoleStmt'] };
8576
+ const roleContext = context.spawn('AlterRoleStmt');
8240
8577
  // Handle GROUP operations specially based on action value
8241
8578
  if (isGroupStatement) {
8242
8579
  const roleMembersOption = list_utils_1.ListUtils.unwrapList(node.options).find(option => option.DefElem && option.DefElem.defname === 'rolemembers');
@@ -8432,14 +8769,14 @@ class Deparser {
8432
8769
  AccessPriv(node, context) {
8433
8770
  const output = [];
8434
8771
  if (node.priv_name) {
8435
- output.push(node.priv_name);
8772
+ output.push(node.priv_name.toUpperCase());
8436
8773
  }
8437
8774
  else {
8438
8775
  output.push('ALL');
8439
8776
  }
8440
8777
  if (node.cols && node.cols.length > 0) {
8441
8778
  output.push('(');
8442
- const colContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AccessPriv'] };
8779
+ const colContext = context.spawn('AccessPriv');
8443
8780
  const columns = list_utils_1.ListUtils.unwrapList(node.cols).map(col => this.visit(col, colContext));
8444
8781
  output.push(columns.join(', '));
8445
8782
  output.push(')');
@@ -8519,7 +8856,7 @@ class Deparser {
8519
8856
  output.push(list_utils_1.ListUtils.unwrapList(node.defnames).map(name => this.visit(name, context)).join('.'));
8520
8857
  }
8521
8858
  if (node.definition && node.definition.length > 0) {
8522
- const defineStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefineStmt'] };
8859
+ const defineStmtContext = context.spawn('DefineStmt');
8523
8860
  const definitions = list_utils_1.ListUtils.unwrapList(node.definition).map(def => {
8524
8861
  return this.visit(def, defineStmtContext);
8525
8862
  });
@@ -9005,7 +9342,7 @@ class Deparser {
9005
9342
  const operatorNumber = node.number !== undefined ? node.number : 0;
9006
9343
  output.push(operatorNumber.toString());
9007
9344
  if (node.name) {
9008
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9345
+ const opClassContext = context.spawn('CreateOpClassItem');
9009
9346
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9010
9347
  }
9011
9348
  }
@@ -9015,7 +9352,7 @@ class Deparser {
9015
9352
  const functionNumber = node.number !== undefined ? node.number : 0;
9016
9353
  output.push(functionNumber.toString());
9017
9354
  if (node.name) {
9018
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9355
+ const opClassContext = context.spawn('CreateOpClassItem');
9019
9356
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9020
9357
  }
9021
9358
  }
@@ -9713,7 +10050,7 @@ class Deparser {
9713
10050
  output.push(this.ObjectWithArgs(node.func, context));
9714
10051
  }
9715
10052
  if (node.actions && node.actions.length > 0) {
9716
- const alterFunctionContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFunctionStmt'] };
10053
+ const alterFunctionContext = context.spawn('AlterFunctionStmt');
9717
10054
  const actionStrs = list_utils_1.ListUtils.unwrapList(node.actions).map(action => this.visit(action, alterFunctionContext));
9718
10055
  output.push(actionStrs.join(' '));
9719
10056
  }
@@ -9957,7 +10294,7 @@ class Deparser {
9957
10294
  CreateForeignTableStmt(node, context) {
9958
10295
  const output = ['CREATE FOREIGN TABLE'];
9959
10296
  if (node.base && node.base.relation) {
9960
- const relationContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10297
+ const relationContext = context.spawn('CreateForeignTableStmt');
9961
10298
  // Handle relation node directly as RangeVar since it contains the RangeVar properties
9962
10299
  output.push(this.RangeVar(node.base.relation, relationContext));
9963
10300
  }
@@ -9987,7 +10324,7 @@ class Deparser {
9987
10324
  output.push(quote_utils_1.QuoteUtils.quote(node.servername));
9988
10325
  }
9989
10326
  if (node.options && node.options.length > 0) {
9990
- const foreignTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10327
+ const foreignTableContext = context.spawn('CreateForeignTableStmt');
9991
10328
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, foreignTableContext));
9992
10329
  output.push(`OPTIONS (${optionStrs.join(', ')})`);
9993
10330
  }