pgsql-deparser 17.8.2 → 17.8.4

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/esm/deparser.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { DeparserContext } from './visitors/base';
1
2
  import { SqlFormatter } from './utils/sql-formatter';
2
3
  import { QuoteUtils } from './utils/quote-utils';
3
4
  import { ListUtils } from './utils/list-utils';
@@ -100,11 +101,9 @@ function isWrappedParseResult(obj) {
100
101
  * compatibility and wraps them internally for consistent processing.
101
102
  */
102
103
  export class Deparser {
103
- formatter;
104
104
  tree;
105
105
  options;
106
106
  constructor(tree, opts = {}) {
107
- this.formatter = new SqlFormatter(opts.newline, opts.tab, opts.pretty);
108
107
  // Set default options
109
108
  this.options = {
110
109
  functionDelimiter: '$$',
@@ -141,15 +140,17 @@ export class Deparser {
141
140
  return new Deparser(query, opts).deparseQuery();
142
141
  }
143
142
  deparseQuery() {
143
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
144
+ const context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
144
145
  return this.tree
145
146
  .map(node => {
146
147
  // All nodes should go through the standard deparse method
147
148
  // which will route to the appropriate handler
148
- const result = this.deparse(node);
149
+ const result = this.deparse(node, context);
149
150
  return result || '';
150
151
  })
151
152
  .filter(result => result !== '')
152
- .join(this.formatter.newline() + this.formatter.newline());
153
+ .join(context.newline() + context.newline());
153
154
  }
154
155
  /**
155
156
  * Get the appropriate function delimiter based on the body content
@@ -163,10 +164,14 @@ export class Deparser {
163
164
  }
164
165
  return delimiter;
165
166
  }
166
- deparse(node, context = { parentNodeTypes: [] }) {
167
+ deparse(node, context) {
167
168
  if (node == null) {
168
169
  return null;
169
170
  }
171
+ if (!context) {
172
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
173
+ context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
174
+ }
170
175
  if (typeof node === 'number' || node instanceof Number) {
171
176
  return node.toString();
172
177
  }
@@ -178,7 +183,11 @@ export class Deparser {
178
183
  throw new Error(`Error deparsing ${nodeType}: ${error.message}`);
179
184
  }
180
185
  }
181
- visit(node, context = { parentNodeTypes: [] }) {
186
+ visit(node, context) {
187
+ if (!context) {
188
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
189
+ context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
190
+ }
182
191
  const nodeType = this.getNodeType(node);
183
192
  // Handle empty objects
184
193
  if (!nodeType) {
@@ -187,11 +196,7 @@ export class Deparser {
187
196
  const nodeData = this.getNodeData(node);
188
197
  const methodName = nodeType;
189
198
  if (typeof this[methodName] === 'function') {
190
- const childContext = {
191
- ...context,
192
- parentNodeTypes: [...context.parentNodeTypes, nodeType]
193
- };
194
- const result = this[methodName](nodeData, childContext);
199
+ const result = this[methodName](nodeData, context);
195
200
  return result;
196
201
  }
197
202
  throw new Error(`Deparser does not handle node type: ${nodeType}`);
@@ -217,7 +222,7 @@ export class Deparser {
217
222
  .filter((rawStmt) => rawStmt != null)
218
223
  .map((rawStmt) => this.RawStmt(rawStmt, context))
219
224
  .filter((result) => result !== '')
220
- .join(this.formatter.newline() + this.formatter.newline());
225
+ .join(context.newline() + context.newline());
221
226
  }
222
227
  RawStmt(node, context) {
223
228
  if (!node.stmt) {
@@ -237,7 +242,7 @@ export class Deparser {
237
242
  }
238
243
  if (!node.op || node.op === 'SETOP_NONE') {
239
244
  if (node.valuesLists == null) {
240
- if (!this.formatter.isPretty() || !node.targetList) {
245
+ if (!context.isPretty() || !node.targetList) {
241
246
  output.push('SELECT');
242
247
  }
243
248
  }
@@ -257,7 +262,7 @@ export class Deparser {
257
262
  node.rarg.limitOffset ||
258
263
  node.rarg.withClause);
259
264
  if (leftNeedsParens) {
260
- output.push(this.formatter.parens(leftStmt));
265
+ output.push(context.parens(leftStmt));
261
266
  }
262
267
  else {
263
268
  output.push(leftStmt);
@@ -279,7 +284,7 @@ export class Deparser {
279
284
  output.push('ALL');
280
285
  }
281
286
  if (rightNeedsParens) {
282
- output.push(this.formatter.parens(rightStmt));
287
+ output.push(context.parens(rightStmt));
283
288
  }
284
289
  else {
285
290
  output.push(rightStmt);
@@ -291,20 +296,20 @@ export class Deparser {
291
296
  const distinctClause = ListUtils.unwrapList(node.distinctClause);
292
297
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
293
298
  const clause = distinctClause
294
- .map(e => this.visit(e, { ...context, select: true }))
299
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
295
300
  .join(', ');
296
- distinctPart = ' DISTINCT ON ' + this.formatter.parens(clause);
301
+ distinctPart = ' DISTINCT ON ' + context.parens(clause);
297
302
  }
298
303
  else {
299
304
  distinctPart = ' DISTINCT';
300
305
  }
301
- if (!this.formatter.isPretty()) {
306
+ if (!context.isPretty()) {
302
307
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
303
308
  output.push('DISTINCT ON');
304
309
  const clause = distinctClause
305
- .map(e => this.visit(e, { ...context, select: true }))
310
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
306
311
  .join(', ');
307
- output.push(this.formatter.parens(clause));
312
+ output.push(context.parens(clause));
308
313
  }
309
314
  else {
310
315
  output.push('DISTINCT');
@@ -313,10 +318,10 @@ export class Deparser {
313
318
  }
314
319
  if (node.targetList) {
315
320
  const targetList = ListUtils.unwrapList(node.targetList);
316
- if (this.formatter.isPretty()) {
321
+ if (context.isPretty()) {
317
322
  if (targetList.length === 1) {
318
323
  const targetNode = targetList[0];
319
- const target = this.visit(targetNode, { ...context, select: true });
324
+ const target = this.visit(targetNode, context.spawn('SelectStmt', { select: true }));
320
325
  // Check if single target is complex - if so, use multiline format
321
326
  if (this.isComplexSelectTarget(targetNode)) {
322
327
  output.push('SELECT' + distinctPart);
@@ -324,7 +329,7 @@ export class Deparser {
324
329
  output.push(target);
325
330
  }
326
331
  else {
327
- output.push(this.formatter.indent(target));
332
+ output.push(context.indent(target));
328
333
  }
329
334
  }
330
335
  else {
@@ -334,20 +339,20 @@ export class Deparser {
334
339
  else {
335
340
  const targetStrings = targetList
336
341
  .map(e => {
337
- const targetStr = this.visit(e, { ...context, select: true });
342
+ const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
338
343
  if (this.containsMultilineStringLiteral(targetStr)) {
339
344
  return targetStr;
340
345
  }
341
- return this.formatter.indent(targetStr);
346
+ return context.indent(targetStr);
342
347
  });
343
- const formattedTargets = targetStrings.join(',' + this.formatter.newline());
348
+ const formattedTargets = targetStrings.join(',' + context.newline());
344
349
  output.push('SELECT' + distinctPart);
345
350
  output.push(formattedTargets);
346
351
  }
347
352
  }
348
353
  else {
349
354
  const targets = targetList
350
- .map(e => this.visit(e, { ...context, select: true }))
355
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
351
356
  .join(', ');
352
357
  output.push(targets);
353
358
  }
@@ -359,22 +364,22 @@ export class Deparser {
359
364
  if (node.fromClause) {
360
365
  const fromList = ListUtils.unwrapList(node.fromClause);
361
366
  const fromItems = fromList
362
- .map(e => this.deparse(e, { ...context, from: true }))
367
+ .map(e => this.deparse(e, context.spawn('SelectStmt', { from: true })))
363
368
  .join(', ');
364
369
  output.push('FROM ' + fromItems.trim());
365
370
  }
366
371
  if (node.whereClause) {
367
- if (this.formatter.isPretty()) {
372
+ if (context.isPretty()) {
368
373
  output.push('WHERE');
369
374
  const whereExpr = this.visit(node.whereClause, context);
370
- const lines = whereExpr.split(this.formatter.newline());
375
+ const lines = whereExpr.split(context.newline());
371
376
  const indentedLines = lines.map((line, index) => {
372
377
  if (index === 0) {
373
- return this.formatter.indent(line);
378
+ return context.indent(line);
374
379
  }
375
380
  return line;
376
381
  });
377
- output.push(indentedLines.join(this.formatter.newline()));
382
+ output.push(indentedLines.join(context.newline()));
378
383
  }
379
384
  else {
380
385
  output.push('WHERE');
@@ -382,17 +387,17 @@ export class Deparser {
382
387
  }
383
388
  }
384
389
  if (node.valuesLists) {
385
- if (this.formatter.isPretty()) {
390
+ if (context.isPretty()) {
386
391
  output.push('VALUES');
387
392
  const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
388
393
  const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
389
- return this.formatter.parens(values.join(', '));
394
+ return context.parens(values.join(', '));
390
395
  });
391
396
  const indentedTuples = lists.map(tuple => {
392
397
  if (this.containsMultilineStringLiteral(tuple)) {
393
398
  return tuple;
394
399
  }
395
- return this.formatter.indent(tuple);
400
+ return context.indent(tuple);
396
401
  });
397
402
  output.push(indentedTuples.join(',\n'));
398
403
  }
@@ -400,43 +405,43 @@ export class Deparser {
400
405
  output.push('VALUES');
401
406
  const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
402
407
  const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
403
- return this.formatter.parens(values.join(', '));
408
+ return context.parens(values.join(', '));
404
409
  });
405
410
  output.push(lists.join(', '));
406
411
  }
407
412
  }
408
413
  if (node.groupClause) {
409
414
  const groupList = ListUtils.unwrapList(node.groupClause);
410
- if (this.formatter.isPretty()) {
415
+ if (context.isPretty()) {
411
416
  const groupItems = groupList
412
417
  .map(e => {
413
- const groupStr = this.visit(e, { ...context, group: true });
418
+ const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
414
419
  if (this.containsMultilineStringLiteral(groupStr)) {
415
420
  return groupStr;
416
421
  }
417
- return this.formatter.indent(groupStr);
422
+ return context.indent(groupStr);
418
423
  })
419
- .join(',' + this.formatter.newline());
424
+ .join(',' + context.newline());
420
425
  output.push('GROUP BY');
421
426
  output.push(groupItems);
422
427
  }
423
428
  else {
424
429
  output.push('GROUP BY');
425
430
  const groupItems = groupList
426
- .map(e => this.visit(e, { ...context, group: true }))
431
+ .map(e => this.visit(e, context.spawn('SelectStmt', { group: true })))
427
432
  .join(', ');
428
433
  output.push(groupItems);
429
434
  }
430
435
  }
431
436
  if (node.havingClause) {
432
- if (this.formatter.isPretty()) {
437
+ if (context.isPretty()) {
433
438
  output.push('HAVING');
434
439
  const havingStr = this.visit(node.havingClause, context);
435
440
  if (this.containsMultilineStringLiteral(havingStr)) {
436
441
  output.push(havingStr);
437
442
  }
438
443
  else {
439
- output.push(this.formatter.indent(havingStr));
444
+ output.push(context.indent(havingStr));
440
445
  }
441
446
  }
442
447
  else {
@@ -454,23 +459,23 @@ export class Deparser {
454
459
  }
455
460
  if (node.sortClause) {
456
461
  const sortList = ListUtils.unwrapList(node.sortClause);
457
- if (this.formatter.isPretty()) {
462
+ if (context.isPretty()) {
458
463
  const sortItems = sortList
459
464
  .map(e => {
460
- const sortStr = this.visit(e, { ...context, sort: true });
465
+ const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
461
466
  if (this.containsMultilineStringLiteral(sortStr)) {
462
467
  return sortStr;
463
468
  }
464
- return this.formatter.indent(sortStr);
469
+ return context.indent(sortStr);
465
470
  })
466
- .join(',' + this.formatter.newline());
471
+ .join(',' + context.newline());
467
472
  output.push('ORDER BY');
468
473
  output.push(sortItems);
469
474
  }
470
475
  else {
471
476
  output.push('ORDER BY');
472
477
  const sortItems = sortList
473
- .map(e => this.visit(e, { ...context, sort: true }))
478
+ .map(e => this.visit(e, context.spawn('SelectStmt', { sort: true })))
474
479
  .join(', ');
475
480
  output.push(sortItems);
476
481
  }
@@ -488,9 +493,9 @@ export class Deparser {
488
493
  .join(' ');
489
494
  output.push(lockingClauses);
490
495
  }
491
- if (this.formatter.isPretty()) {
496
+ if (context.isPretty()) {
492
497
  const filteredOutput = output.filter(item => item.trim() !== '');
493
- return filteredOutput.join(this.formatter.newline());
498
+ return filteredOutput.join(context.newline());
494
499
  }
495
500
  return output.join(' ');
496
501
  }
@@ -502,13 +507,13 @@ export class Deparser {
502
507
  switch (kind) {
503
508
  case 'AEXPR_OP':
504
509
  if (lexpr && rexpr) {
505
- const operator = this.deparseOperatorName(name);
510
+ const operator = this.deparseOperatorName(name, context);
506
511
  let leftExpr = this.visit(lexpr, context);
507
512
  let rightExpr = this.visit(rexpr, context);
508
513
  // Check if left expression needs parentheses
509
514
  let leftNeedsParens = false;
510
515
  if (lexpr && 'A_Expr' in lexpr && lexpr.A_Expr?.kind === 'AEXPR_OP') {
511
- const leftOp = this.deparseOperatorName(ListUtils.unwrapList(lexpr.A_Expr.name));
516
+ const leftOp = this.deparseOperatorName(ListUtils.unwrapList(lexpr.A_Expr.name), context);
512
517
  if (this.needsParentheses(leftOp, operator, 'left')) {
513
518
  leftNeedsParens = true;
514
519
  }
@@ -517,12 +522,12 @@ export class Deparser {
517
522
  leftNeedsParens = true;
518
523
  }
519
524
  if (leftNeedsParens) {
520
- leftExpr = this.formatter.parens(leftExpr);
525
+ leftExpr = context.parens(leftExpr);
521
526
  }
522
527
  // Check if right expression needs parentheses
523
528
  let rightNeedsParens = false;
524
529
  if (rexpr && 'A_Expr' in rexpr && rexpr.A_Expr?.kind === 'AEXPR_OP') {
525
- const rightOp = this.deparseOperatorName(ListUtils.unwrapList(rexpr.A_Expr.name));
530
+ const rightOp = this.deparseOperatorName(ListUtils.unwrapList(rexpr.A_Expr.name), context);
526
531
  if (this.needsParentheses(rightOp, operator, 'right')) {
527
532
  rightNeedsParens = true;
528
533
  }
@@ -531,42 +536,42 @@ export class Deparser {
531
536
  rightNeedsParens = true;
532
537
  }
533
538
  if (rightNeedsParens) {
534
- rightExpr = this.formatter.parens(rightExpr);
539
+ rightExpr = context.parens(rightExpr);
535
540
  }
536
- return this.formatter.format([leftExpr, operator, rightExpr]);
541
+ return context.format([leftExpr, operator, rightExpr]);
537
542
  }
538
543
  else if (rexpr) {
539
- return this.formatter.format([
540
- this.deparseOperatorName(name),
544
+ return context.format([
545
+ this.deparseOperatorName(name, context),
541
546
  this.visit(rexpr, context)
542
547
  ]);
543
548
  }
544
549
  break;
545
550
  case 'AEXPR_OP_ANY':
546
- return this.formatter.format([
551
+ return context.format([
547
552
  this.visit(lexpr, context),
548
- this.deparseOperatorName(name),
553
+ this.deparseOperatorName(name, context),
549
554
  'ANY',
550
- this.formatter.parens(this.visit(rexpr, context))
555
+ context.parens(this.visit(rexpr, context))
551
556
  ]);
552
557
  case 'AEXPR_OP_ALL':
553
- return this.formatter.format([
558
+ return context.format([
554
559
  this.visit(lexpr, context),
555
- this.deparseOperatorName(name),
560
+ this.deparseOperatorName(name, context),
556
561
  'ALL',
557
- this.formatter.parens(this.visit(rexpr, context))
562
+ context.parens(this.visit(rexpr, context))
558
563
  ]);
559
564
  case 'AEXPR_DISTINCT': {
560
565
  let leftExpr = this.visit(lexpr, context);
561
566
  let rightExpr = this.visit(rexpr, context);
562
567
  // Add parentheses for complex expressions
563
568
  if (lexpr && this.isComplexExpression(lexpr)) {
564
- leftExpr = this.formatter.parens(leftExpr);
569
+ leftExpr = context.parens(leftExpr);
565
570
  }
566
571
  if (rexpr && this.isComplexExpression(rexpr)) {
567
- rightExpr = this.formatter.parens(rightExpr);
572
+ rightExpr = context.parens(rightExpr);
568
573
  }
569
- return this.formatter.format([
574
+ return context.format([
570
575
  leftExpr,
571
576
  'IS DISTINCT FROM',
572
577
  rightExpr
@@ -577,75 +582,75 @@ export class Deparser {
577
582
  let rightExpr = this.visit(rexpr, context);
578
583
  // Add parentheses for complex expressions
579
584
  if (lexpr && this.isComplexExpression(lexpr)) {
580
- leftExpr = this.formatter.parens(leftExpr);
585
+ leftExpr = context.parens(leftExpr);
581
586
  }
582
587
  if (rexpr && this.isComplexExpression(rexpr)) {
583
- rightExpr = this.formatter.parens(rightExpr);
588
+ rightExpr = context.parens(rightExpr);
584
589
  }
585
- return this.formatter.format([
590
+ return context.format([
586
591
  leftExpr,
587
592
  'IS NOT DISTINCT FROM',
588
593
  rightExpr
589
594
  ]);
590
595
  }
591
596
  case 'AEXPR_NULLIF':
592
- return this.formatter.format([
597
+ return context.format([
593
598
  'NULLIF',
594
- this.formatter.parens([
599
+ context.parens([
595
600
  this.visit(lexpr, context),
596
601
  this.visit(rexpr, context)
597
602
  ].join(', '))
598
603
  ]);
599
604
  case 'AEXPR_IN':
600
- const inOperator = this.deparseOperatorName(name);
605
+ const inOperator = this.deparseOperatorName(name, context);
601
606
  if (inOperator === '<>' || inOperator === '!=') {
602
- return this.formatter.format([
607
+ return context.format([
603
608
  this.visit(lexpr, context),
604
609
  'NOT IN',
605
- this.formatter.parens(this.visit(rexpr, context))
610
+ context.parens(this.visit(rexpr, context))
606
611
  ]);
607
612
  }
608
613
  else {
609
- return this.formatter.format([
614
+ return context.format([
610
615
  this.visit(lexpr, context),
611
616
  'IN',
612
- this.formatter.parens(this.visit(rexpr, context))
617
+ context.parens(this.visit(rexpr, context))
613
618
  ]);
614
619
  }
615
620
  case 'AEXPR_LIKE':
616
- const likeOp = this.deparseOperatorName(name);
621
+ const likeOp = this.deparseOperatorName(name, context);
617
622
  if (likeOp === '!~~') {
618
- return this.formatter.format([
623
+ return context.format([
619
624
  this.visit(lexpr, context),
620
625
  'NOT LIKE',
621
626
  this.visit(rexpr, context)
622
627
  ]);
623
628
  }
624
629
  else {
625
- return this.formatter.format([
630
+ return context.format([
626
631
  this.visit(lexpr, context),
627
632
  'LIKE',
628
633
  this.visit(rexpr, context)
629
634
  ]);
630
635
  }
631
636
  case 'AEXPR_ILIKE':
632
- const ilikeOp = this.deparseOperatorName(name);
637
+ const ilikeOp = this.deparseOperatorName(name, context);
633
638
  if (ilikeOp === '!~~*') {
634
- return this.formatter.format([
639
+ return context.format([
635
640
  this.visit(lexpr, context),
636
641
  'NOT ILIKE',
637
642
  this.visit(rexpr, context)
638
643
  ]);
639
644
  }
640
645
  else {
641
- return this.formatter.format([
646
+ return context.format([
642
647
  this.visit(lexpr, context),
643
648
  'ILIKE',
644
649
  this.visit(rexpr, context)
645
650
  ]);
646
651
  }
647
652
  case 'AEXPR_SIMILAR':
648
- const similarOp = this.deparseOperatorName(name);
653
+ const similarOp = this.deparseOperatorName(name, context);
649
654
  let rightExpr;
650
655
  if (rexpr && 'FuncCall' in rexpr &&
651
656
  rexpr.FuncCall?.funcname?.length === 2 &&
@@ -661,39 +666,39 @@ export class Deparser {
661
666
  rightExpr = this.visit(rexpr, context);
662
667
  }
663
668
  if (similarOp === '!~') {
664
- return this.formatter.format([
669
+ return context.format([
665
670
  this.visit(lexpr, context),
666
671
  'NOT SIMILAR TO',
667
672
  rightExpr
668
673
  ]);
669
674
  }
670
675
  else {
671
- return this.formatter.format([
676
+ return context.format([
672
677
  this.visit(lexpr, context),
673
678
  'SIMILAR TO',
674
679
  rightExpr
675
680
  ]);
676
681
  }
677
682
  case 'AEXPR_BETWEEN':
678
- return this.formatter.format([
683
+ return context.format([
679
684
  this.visit(lexpr, context),
680
685
  'BETWEEN',
681
686
  this.visitBetweenRange(rexpr, context)
682
687
  ]);
683
688
  case 'AEXPR_NOT_BETWEEN':
684
- return this.formatter.format([
689
+ return context.format([
685
690
  this.visit(lexpr, context),
686
691
  'NOT BETWEEN',
687
692
  this.visitBetweenRange(rexpr, context)
688
693
  ]);
689
694
  case 'AEXPR_BETWEEN_SYM':
690
- return this.formatter.format([
695
+ return context.format([
691
696
  this.visit(lexpr, context),
692
697
  'BETWEEN SYMMETRIC',
693
698
  this.visitBetweenRange(rexpr, context)
694
699
  ]);
695
700
  case 'AEXPR_NOT_BETWEEN_SYM':
696
- return this.formatter.format([
701
+ return context.format([
697
702
  this.visit(lexpr, context),
698
703
  'NOT BETWEEN SYMMETRIC',
699
704
  this.visitBetweenRange(rexpr, context)
@@ -701,7 +706,7 @@ export class Deparser {
701
706
  }
702
707
  throw new Error(`Unhandled A_Expr kind: ${kind}`);
703
708
  }
704
- deparseOperatorName(name) {
709
+ deparseOperatorName(name, context) {
705
710
  if (!name || name.length === 0) {
706
711
  return '';
707
712
  }
@@ -709,7 +714,7 @@ export class Deparser {
709
714
  if (n.String) {
710
715
  return n.String.sval || n.String.str;
711
716
  }
712
- return this.visit(n, { parentNodeTypes: [] });
717
+ return this.visit(n, context);
713
718
  });
714
719
  if (parts.length > 1) {
715
720
  return `OPERATOR(${parts.join('.')})`;
@@ -846,15 +851,15 @@ export class Deparser {
846
851
  output.push(this.RangeVar(node.relation, context));
847
852
  if (node.cols) {
848
853
  const cols = ListUtils.unwrapList(node.cols);
849
- const insertContext = { ...context, insertColumns: true };
854
+ const insertContext = context.spawn('InsertStmt', { insertColumns: true });
850
855
  const columnNames = cols.map(col => this.visit(col, insertContext));
851
- if (this.formatter.isPretty()) {
856
+ if (context.isPretty()) {
852
857
  // Always format columns in multiline parentheses for pretty printing
853
- const indentedColumns = columnNames.map(col => this.formatter.indent(col));
858
+ const indentedColumns = columnNames.map(col => context.indent(col));
854
859
  output.push('(\n' + indentedColumns.join(',\n') + '\n)');
855
860
  }
856
861
  else {
857
- output.push(this.formatter.parens(columnNames.join(', ')));
862
+ output.push(context.parens(columnNames.join(', ')));
858
863
  }
859
864
  }
860
865
  if (node.selectStmt) {
@@ -876,7 +881,7 @@ export class Deparser {
876
881
  else if (infer.indexElems) {
877
882
  const elems = ListUtils.unwrapList(infer.indexElems);
878
883
  const indexElems = elems.map(elem => this.visit(elem, context));
879
- output.push(this.formatter.parens(indexElems.join(', ')));
884
+ output.push(context.parens(indexElems.join(', ')));
880
885
  }
881
886
  // Handle WHERE clause for conflict detection
882
887
  if (infer.whereClause) {
@@ -892,12 +897,12 @@ export class Deparser {
892
897
  if (firstTarget.ResTarget?.val?.MultiAssignRef && targetList.every(target => target.ResTarget?.val?.MultiAssignRef)) {
893
898
  const sortedTargets = targetList.sort((a, b) => a.ResTarget.val.MultiAssignRef.colno - b.ResTarget.val.MultiAssignRef.colno);
894
899
  const names = sortedTargets.map(target => target.ResTarget.name);
895
- output.push(this.formatter.parens(names.join(', ')));
900
+ output.push(context.parens(names.join(', ')));
896
901
  output.push('=');
897
902
  output.push(this.visit(firstTarget.ResTarget.val.MultiAssignRef.source, context));
898
903
  }
899
904
  else {
900
- const updateContext = { ...context, update: true };
905
+ const updateContext = context.spawn('UpdateStmt', { update: true });
901
906
  const targets = targetList.map(target => this.visit(target, updateContext));
902
907
  output.push(targets.join(', '));
903
908
  }
@@ -951,12 +956,12 @@ export class Deparser {
951
956
  }
952
957
  }
953
958
  const names = relatedTargets.map(t => t.ResTarget.name);
954
- const multiAssignment = `${this.formatter.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
959
+ const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
955
960
  assignmentParts.push(multiAssignment);
956
961
  }
957
962
  else {
958
963
  // Handle regular single-column assignment
959
- assignmentParts.push(this.visit(target, { ...context, update: true }));
964
+ assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
960
965
  processedTargets.add(i);
961
966
  }
962
967
  }
@@ -1048,14 +1053,14 @@ export class Deparser {
1048
1053
  }
1049
1054
  if (node.ctes && node.ctes.length > 0) {
1050
1055
  const ctes = ListUtils.unwrapList(node.ctes);
1051
- if (this.formatter.isPretty()) {
1056
+ if (context.isPretty()) {
1052
1057
  const cteStrings = ctes.map((cte, index) => {
1053
1058
  const cteStr = this.visit(cte, context);
1054
- const prefix = index === 0 ? this.formatter.newline() : ',' + this.formatter.newline();
1059
+ const prefix = index === 0 ? context.newline() : ',' + context.newline();
1055
1060
  if (this.containsMultilineStringLiteral(cteStr)) {
1056
1061
  return prefix + cteStr;
1057
1062
  }
1058
- return prefix + this.formatter.indent(cteStr);
1063
+ return prefix + context.indent(cteStr);
1059
1064
  });
1060
1065
  output.push(cteStrings.join(''));
1061
1066
  }
@@ -1141,14 +1146,14 @@ export class Deparser {
1141
1146
  if (context.bool) {
1142
1147
  formatStr = '(%s)';
1143
1148
  }
1144
- const boolContext = { ...context, bool: true };
1149
+ const boolContext = context.spawn('BoolExpr', { bool: true });
1145
1150
  // explanation of our syntax/fix below:
1146
1151
  // return formatStr.replace('%s', andArgs); // ❌ Interprets $ as special syntax
1147
1152
  // return formatStr.replace('%s', () => andArgs); // ✅ Function callback prevents interpretation
1148
1153
  switch (boolop) {
1149
1154
  case 'AND_EXPR':
1150
- if (this.formatter.isPretty() && args.length > 1) {
1151
- const andArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' AND ');
1155
+ if (context.isPretty() && args.length > 1) {
1156
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('AND '));
1152
1157
  return formatStr.replace('%s', () => andArgs);
1153
1158
  }
1154
1159
  else {
@@ -1156,8 +1161,8 @@ export class Deparser {
1156
1161
  return formatStr.replace('%s', () => andArgs);
1157
1162
  }
1158
1163
  case 'OR_EXPR':
1159
- if (this.formatter.isPretty() && args.length > 1) {
1160
- const orArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' OR ');
1164
+ if (context.isPretty() && args.length > 1) {
1165
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('OR '));
1161
1166
  return formatStr.replace('%s', () => orArgs);
1162
1167
  }
1163
1168
  else {
@@ -1288,9 +1293,9 @@ export class Deparser {
1288
1293
  const timezone = this.visit(args[0], context);
1289
1294
  // Add parentheses around timestamp if it contains arithmetic operations
1290
1295
  if (args[1] && 'A_Expr' in args[1] && args[1].A_Expr?.kind === 'AEXPR_OP') {
1291
- const op = this.deparseOperatorName(ListUtils.unwrapList(args[1].A_Expr.name));
1296
+ const op = this.deparseOperatorName(ListUtils.unwrapList(args[1].A_Expr.name), context);
1292
1297
  if (op === '+' || op === '-' || op === '*' || op === '/') {
1293
- timestamp = this.formatter.parens(timestamp);
1298
+ timestamp = context.parens(timestamp);
1294
1299
  }
1295
1300
  }
1296
1301
  return `${timestamp} AT TIME ZONE ${timezone}`;
@@ -1360,14 +1365,14 @@ export class Deparser {
1360
1365
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1361
1366
  }
1362
1367
  // Handle window frame specifications using the dedicated formatWindowFrame method
1363
- const frameClause = this.formatWindowFrame(node.over);
1368
+ const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1364
1369
  if (frameClause) {
1365
1370
  windowParts.push(frameClause);
1366
1371
  }
1367
1372
  if (windowParts.length > 0) {
1368
- if (this.formatter.isPretty() && windowParts.length > 1) {
1369
- const formattedParts = windowParts.map(part => this.formatter.indent(part));
1370
- result += ` OVER (${this.formatter.newline()}${formattedParts.join(this.formatter.newline())}${this.formatter.newline()})`;
1373
+ if (context.isPretty() && windowParts.length > 1) {
1374
+ const formattedParts = windowParts.map(part => context.indent(part));
1375
+ result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1371
1376
  }
1372
1377
  else {
1373
1378
  result += ` OVER (${windowParts.join(' ')})`;
@@ -1732,7 +1737,7 @@ export class Deparser {
1732
1737
  }
1733
1738
  return this.quoteIfNeeded(colStr);
1734
1739
  });
1735
- output.push('AS', this.quoteIfNeeded(name) + this.formatter.parens(quotedColnames.join(', ')));
1740
+ output.push('AS', this.quoteIfNeeded(name) + context.parens(quotedColnames.join(', ')));
1736
1741
  }
1737
1742
  else {
1738
1743
  output.push('AS', this.quoteIfNeeded(name));
@@ -1744,7 +1749,7 @@ export class Deparser {
1744
1749
  // Handle ONLY keyword for inheritance control (but not for type definitions, ALTER TYPE, or CREATE FOREIGN TABLE)
1745
1750
  if (node && (!('inh' in node) || node.inh === undefined) &&
1746
1751
  !context.parentNodeTypes.includes('CompositeTypeStmt') &&
1747
- !context.parentNodeTypes.includes('AlterTypeStmt') &&
1752
+ (!context.parentNodeTypes.includes('AlterTypeStmt') && context.objtype !== 'OBJECT_TYPE') &&
1748
1753
  !context.parentNodeTypes.includes('CreateForeignTableStmt')) {
1749
1754
  output.push('ONLY');
1750
1755
  }
@@ -2004,26 +2009,26 @@ export class Deparser {
2004
2009
  output.push(this.visit(node.arg, context));
2005
2010
  }
2006
2011
  const args = ListUtils.unwrapList(node.args);
2007
- if (this.formatter.isPretty() && args.length > 0) {
2012
+ if (context.isPretty() && args.length > 0) {
2008
2013
  for (const arg of args) {
2009
2014
  const whenClause = this.visit(arg, context);
2010
2015
  if (this.containsMultilineStringLiteral(whenClause)) {
2011
- output.push(this.formatter.newline() + whenClause);
2016
+ output.push(context.newline() + whenClause);
2012
2017
  }
2013
2018
  else {
2014
- output.push(this.formatter.newline() + this.formatter.indent(whenClause));
2019
+ output.push(context.newline() + context.indent(whenClause));
2015
2020
  }
2016
2021
  }
2017
2022
  if (node.defresult) {
2018
2023
  const elseResult = this.visit(node.defresult, context);
2019
2024
  if (this.containsMultilineStringLiteral(elseResult)) {
2020
- output.push(this.formatter.newline() + 'ELSE ' + elseResult);
2025
+ output.push(context.newline() + 'ELSE ' + elseResult);
2021
2026
  }
2022
2027
  else {
2023
- output.push(this.formatter.newline() + this.formatter.indent('ELSE ' + elseResult));
2028
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
2024
2029
  }
2025
2030
  }
2026
- output.push(this.formatter.newline() + 'END');
2031
+ output.push(context.newline() + 'END');
2027
2032
  return output.join(' ');
2028
2033
  }
2029
2034
  else {
@@ -2091,7 +2096,7 @@ export class Deparser {
2091
2096
  }
2092
2097
  BooleanTest(node, context) {
2093
2098
  const output = [];
2094
- const boolContext = { ...context, bool: true };
2099
+ const boolContext = context.spawn('BooleanTest', { bool: true });
2095
2100
  output.push(this.visit(node.arg, boolContext));
2096
2101
  switch (node.booltesttype) {
2097
2102
  case 'IS_TRUE':
@@ -2242,31 +2247,31 @@ export class Deparser {
2242
2247
  const elementStrs = elements.map(el => {
2243
2248
  return this.deparse(el, context);
2244
2249
  });
2245
- output.push(this.formatter.parens(elementStrs.join(', ')));
2250
+ output.push(context.parens(elementStrs.join(', ')));
2246
2251
  }
2247
2252
  }
2248
2253
  else if (node.tableElts) {
2249
2254
  const elements = ListUtils.unwrapList(node.tableElts);
2250
2255
  const elementStrs = elements.map(el => {
2251
- return this.deparse(el, context);
2256
+ return this.deparse(el, context.spawn('CreateStmt'));
2252
2257
  });
2253
- if (this.formatter.isPretty()) {
2258
+ if (context.isPretty()) {
2254
2259
  const formattedElements = elementStrs.map(el => {
2255
2260
  const trimmedEl = el.trim();
2256
2261
  // Remove leading newlines from constraint elements to avoid extra blank lines
2257
2262
  if (trimmedEl.startsWith('\n')) {
2258
- return this.formatter.indent(trimmedEl.substring(1));
2263
+ return context.indent(trimmedEl.substring(1));
2259
2264
  }
2260
- return this.formatter.indent(trimmedEl);
2261
- }).join(',' + this.formatter.newline());
2262
- output.push('(' + this.formatter.newline() + formattedElements + this.formatter.newline() + ')');
2265
+ return context.indent(trimmedEl);
2266
+ }).join(',' + context.newline());
2267
+ output.push('(' + context.newline() + formattedElements + context.newline() + ')');
2263
2268
  }
2264
2269
  else {
2265
- output.push(this.formatter.parens(elementStrs.join(', ')));
2270
+ output.push(context.parens(elementStrs.join(', ')));
2266
2271
  }
2267
2272
  }
2268
2273
  else if (!node.partbound) {
2269
- output.push(this.formatter.parens(''));
2274
+ output.push(context.parens(''));
2270
2275
  }
2271
2276
  if (node.partbound && node.inhRelations && node.inhRelations.length > 0) {
2272
2277
  output.push('PARTITION OF');
@@ -2309,7 +2314,7 @@ export class Deparser {
2309
2314
  output.push('INHERITS');
2310
2315
  const inherits = ListUtils.unwrapList(node.inhRelations);
2311
2316
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2312
- output.push(this.formatter.parens(inheritStrs.join(', ')));
2317
+ output.push(context.parens(inheritStrs.join(', ')));
2313
2318
  }
2314
2319
  if (node.partspec) {
2315
2320
  output.push('PARTITION BY');
@@ -2347,7 +2352,7 @@ export class Deparser {
2347
2352
  }
2348
2353
  // Handle table options like WITH (fillfactor=10)
2349
2354
  if (node.options && node.options.length > 0) {
2350
- const createStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateStmt'] };
2355
+ const createStmtContext = context.spawn('CreateStmt');
2351
2356
  const optionStrs = node.options.map((option) => {
2352
2357
  return this.deparse(option, createStmtContext);
2353
2358
  });
@@ -2370,7 +2375,7 @@ export class Deparser {
2370
2375
  }
2371
2376
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2372
2377
  output.push('OPTIONS');
2373
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
2378
+ const columnContext = context.spawn('ColumnDef');
2374
2379
  const options = ListUtils.unwrapList(node.fdwoptions).map(opt => this.visit(opt, columnContext));
2375
2380
  output.push(`(${options.join(', ')})`);
2376
2381
  }
@@ -2380,7 +2385,7 @@ export class Deparser {
2380
2385
  if (node.constraints) {
2381
2386
  const constraints = ListUtils.unwrapList(node.constraints);
2382
2387
  const constraintStrs = constraints.map(constraint => {
2383
- const columnConstraintContext = { ...context, isColumnConstraint: true };
2388
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
2384
2389
  return this.visit(constraint, columnConstraintContext);
2385
2390
  });
2386
2391
  output.push(...constraintStrs);
@@ -2420,24 +2425,24 @@ export class Deparser {
2420
2425
  }
2421
2426
  break;
2422
2427
  case 'CONSTR_CHECK':
2423
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2424
- output.push('\n' + this.formatter.indent('CHECK'));
2428
+ if (context.isPretty() && !context.isColumnConstraint) {
2429
+ output.push('\n' + context.indent('CHECK'));
2425
2430
  }
2426
2431
  else {
2427
2432
  output.push('CHECK');
2428
2433
  }
2429
2434
  if (node.raw_expr) {
2430
- if (this.formatter.isPretty()) {
2435
+ if (context.isPretty()) {
2431
2436
  const checkExpr = this.visit(node.raw_expr, context);
2432
2437
  if (checkExpr.includes('\n')) {
2433
- output.push('(\n' + this.formatter.indent(checkExpr) + '\n)');
2438
+ output.push('(\n' + context.indent(checkExpr) + '\n)');
2434
2439
  }
2435
2440
  else {
2436
2441
  output.push(`(${checkExpr})`);
2437
2442
  }
2438
2443
  }
2439
2444
  else {
2440
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2445
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2441
2446
  }
2442
2447
  }
2443
2448
  // Handle NOT VALID for check constraints
@@ -2459,7 +2464,7 @@ export class Deparser {
2459
2464
  }
2460
2465
  output.push('AS');
2461
2466
  if (node.raw_expr) {
2462
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2467
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2463
2468
  }
2464
2469
  output.push('STORED');
2465
2470
  break;
@@ -2517,8 +2522,8 @@ export class Deparser {
2517
2522
  }
2518
2523
  break;
2519
2524
  case 'CONSTR_UNIQUE':
2520
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2521
- output.push('\n' + this.formatter.indent('UNIQUE'));
2525
+ if (context.isPretty() && !context.isColumnConstraint) {
2526
+ output.push('\n' + context.indent('UNIQUE'));
2522
2527
  }
2523
2528
  else {
2524
2529
  output.push('UNIQUE');
@@ -2540,15 +2545,15 @@ export class Deparser {
2540
2545
  case 'CONSTR_FOREIGN':
2541
2546
  // Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
2542
2547
  if (!context.isColumnConstraint) {
2543
- if (this.formatter.isPretty()) {
2544
- output.push('\n' + this.formatter.indent('FOREIGN KEY'));
2548
+ if (context.isPretty()) {
2549
+ output.push('\n' + context.indent('FOREIGN KEY'));
2545
2550
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2546
2551
  const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2547
2552
  .map(attr => this.visit(attr, context))
2548
2553
  .join(', ');
2549
2554
  output.push(`(${fkAttrs})`);
2550
2555
  }
2551
- output.push('\n' + this.formatter.indent('REFERENCES'));
2556
+ output.push('\n' + context.indent('REFERENCES'));
2552
2557
  }
2553
2558
  else {
2554
2559
  output.push('FOREIGN KEY');
@@ -2565,7 +2570,7 @@ export class Deparser {
2565
2570
  output.push('REFERENCES');
2566
2571
  }
2567
2572
  if (node.pktable) {
2568
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2573
+ if (context.isPretty() && !context.isColumnConstraint) {
2569
2574
  const lastIndex = output.length - 1;
2570
2575
  if (lastIndex >= 0 && output[lastIndex].includes('REFERENCES')) {
2571
2576
  output[lastIndex] += ' ' + this.RangeVar(node.pktable, context);
@@ -2582,7 +2587,7 @@ export class Deparser {
2582
2587
  const pkAttrs = ListUtils.unwrapList(node.pk_attrs)
2583
2588
  .map(attr => this.visit(attr, context))
2584
2589
  .join(', ');
2585
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2590
+ if (context.isPretty() && !context.isColumnConstraint) {
2586
2591
  const lastIndex = output.length - 1;
2587
2592
  if (lastIndex >= 0) {
2588
2593
  output[lastIndex] += ` (${pkAttrs})`;
@@ -2605,8 +2610,8 @@ export class Deparser {
2605
2610
  matchClause = 'MATCH PARTIAL';
2606
2611
  break;
2607
2612
  }
2608
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2609
- output.push('\n' + this.formatter.indent(matchClause));
2613
+ if (context.isPretty() && !context.isColumnConstraint) {
2614
+ output.push('\n' + context.indent(matchClause));
2610
2615
  }
2611
2616
  else {
2612
2617
  output.push(matchClause);
@@ -2628,8 +2633,8 @@ export class Deparser {
2628
2633
  updateClause += 'SET DEFAULT';
2629
2634
  break;
2630
2635
  }
2631
- if (this.formatter.isPretty()) {
2632
- output.push('\n' + this.formatter.indent(updateClause));
2636
+ if (context.isPretty()) {
2637
+ output.push('\n' + context.indent(updateClause));
2633
2638
  }
2634
2639
  else {
2635
2640
  output.push('ON UPDATE');
@@ -2652,8 +2657,8 @@ export class Deparser {
2652
2657
  deleteClause += 'SET DEFAULT';
2653
2658
  break;
2654
2659
  }
2655
- if (this.formatter.isPretty()) {
2656
- output.push('\n' + this.formatter.indent(deleteClause));
2660
+ if (context.isPretty()) {
2661
+ output.push('\n' + context.indent(deleteClause));
2657
2662
  }
2658
2663
  else {
2659
2664
  output.push('ON DELETE');
@@ -2662,8 +2667,8 @@ export class Deparser {
2662
2667
  }
2663
2668
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2664
2669
  if (node.skip_validation && !context.isDomainConstraint) {
2665
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2666
- output.push('\n' + this.formatter.indent('NOT VALID'));
2670
+ if (context.isPretty() && !context.isColumnConstraint) {
2671
+ output.push('\n' + context.indent('NOT VALID'));
2667
2672
  }
2668
2673
  else {
2669
2674
  output.push('NOT VALID');
@@ -2721,13 +2726,13 @@ export class Deparser {
2721
2726
  // Handle deferrable constraints for all constraint types that support it
2722
2727
  if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2723
2728
  if (node.deferrable) {
2724
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2725
- output.push('\n' + this.formatter.indent('DEFERRABLE'));
2729
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2730
+ output.push('\n' + context.indent('DEFERRABLE'));
2726
2731
  if (node.initdeferred === true) {
2727
- output.push('\n' + this.formatter.indent('INITIALLY DEFERRED'));
2732
+ output.push('\n' + context.indent('INITIALLY DEFERRED'));
2728
2733
  }
2729
2734
  else if (node.initdeferred === false) {
2730
- output.push('\n' + this.formatter.indent('INITIALLY IMMEDIATE'));
2735
+ output.push('\n' + context.indent('INITIALLY IMMEDIATE'));
2731
2736
  }
2732
2737
  }
2733
2738
  else {
@@ -2741,15 +2746,15 @@ export class Deparser {
2741
2746
  }
2742
2747
  }
2743
2748
  else if (node.deferrable === false) {
2744
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2745
- output.push('\n' + this.formatter.indent('NOT DEFERRABLE'));
2749
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2750
+ output.push('\n' + context.indent('NOT DEFERRABLE'));
2746
2751
  }
2747
2752
  else {
2748
2753
  output.push('NOT DEFERRABLE');
2749
2754
  }
2750
2755
  }
2751
2756
  }
2752
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2757
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2753
2758
  let result = '';
2754
2759
  for (let i = 0; i < output.length; i++) {
2755
2760
  if (output[i].startsWith('\n')) {
@@ -2767,12 +2772,12 @@ export class Deparser {
2767
2772
  return output.join(' ');
2768
2773
  }
2769
2774
  SubLink(node, context) {
2770
- const subselect = this.formatter.parens(this.visit(node.subselect, context));
2775
+ const subselect = context.parens(this.visit(node.subselect, context));
2771
2776
  switch (node.subLinkType) {
2772
2777
  case 'ANY_SUBLINK':
2773
2778
  if (node.testexpr && node.operName) {
2774
2779
  const testExpr = this.visit(node.testexpr, context);
2775
- const operator = this.deparseOperatorName(node.operName);
2780
+ const operator = this.deparseOperatorName(node.operName, context);
2776
2781
  return `${testExpr} ${operator} ANY ${subselect}`;
2777
2782
  }
2778
2783
  else if (node.testexpr) {
@@ -2783,7 +2788,7 @@ export class Deparser {
2783
2788
  case 'ALL_SUBLINK':
2784
2789
  if (node.testexpr && node.operName) {
2785
2790
  const testExpr = this.visit(node.testexpr, context);
2786
- const operator = this.deparseOperatorName(node.operName);
2791
+ const operator = this.deparseOperatorName(node.operName, context);
2787
2792
  return `${testExpr} ${operator} ALL ${subselect}`;
2788
2793
  }
2789
2794
  return subselect;
@@ -2825,7 +2830,7 @@ export class Deparser {
2825
2830
  }
2826
2831
  // Only add frame clause if frameOptions indicates non-default framing
2827
2832
  if (node.frameOptions && node.frameOptions !== 1058) {
2828
- const frameClause = this.formatWindowFrame(node);
2833
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2829
2834
  if (frameClause) {
2830
2835
  windowParts.push(frameClause);
2831
2836
  }
@@ -2845,7 +2850,7 @@ export class Deparser {
2845
2850
  }
2846
2851
  return output.join(' ');
2847
2852
  }
2848
- formatWindowFrame(node) {
2853
+ formatWindowFrame(node, context) {
2849
2854
  if (!node.frameOptions)
2850
2855
  return null;
2851
2856
  const frameOptions = node.frameOptions;
@@ -2854,7 +2859,7 @@ export class Deparser {
2854
2859
  if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
2855
2860
  frameParts.push('RANGE');
2856
2861
  }
2857
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2862
+ else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2858
2863
  frameParts.push('ROWS');
2859
2864
  }
2860
2865
  else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
@@ -2875,8 +2880,8 @@ export class Deparser {
2875
2880
  }
2876
2881
  else if (frameOptions === 18453) {
2877
2882
  if (node.startOffset && node.endOffset) {
2878
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2879
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2883
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2884
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2880
2885
  }
2881
2886
  }
2882
2887
  else if (frameOptions === 1557) {
@@ -2886,7 +2891,7 @@ export class Deparser {
2886
2891
  else if (frameOptions === 16917) {
2887
2892
  boundsParts.push('CURRENT ROW');
2888
2893
  if (node.endOffset) {
2889
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2894
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2890
2895
  }
2891
2896
  }
2892
2897
  else if (frameOptions === 1058) {
@@ -2896,13 +2901,13 @@ export class Deparser {
2896
2901
  // Handle start bound - prioritize explicit offset values over bit flags
2897
2902
  if (node.startOffset) {
2898
2903
  if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
2899
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2904
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2900
2905
  }
2901
2906
  else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
2902
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} FOLLOWING`);
2907
+ boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
2903
2908
  }
2904
2909
  else {
2905
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2910
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2906
2911
  }
2907
2912
  }
2908
2913
  else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
@@ -2915,13 +2920,13 @@ export class Deparser {
2915
2920
  if (node.endOffset) {
2916
2921
  if (boundsParts.length > 0) {
2917
2922
  if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
2918
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} PRECEDING`);
2923
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
2919
2924
  }
2920
2925
  else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
2921
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2926
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2922
2927
  }
2923
2928
  else {
2924
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2929
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2925
2930
  }
2926
2931
  }
2927
2932
  }
@@ -3006,7 +3011,7 @@ export class Deparser {
3006
3011
  const colnames = ListUtils.unwrapList(node.aliascolnames);
3007
3012
  const colnameStrs = colnames.map(col => this.visit(col, context));
3008
3013
  // Don't add space before column list parentheses to match original formatting
3009
- output[output.length - 1] += this.formatter.parens(colnameStrs.join(', '));
3014
+ output[output.length - 1] += context.parens(colnameStrs.join(', '));
3010
3015
  }
3011
3016
  output.push('AS');
3012
3017
  // Handle materialization clauses
@@ -3017,7 +3022,7 @@ export class Deparser {
3017
3022
  output.push('MATERIALIZED');
3018
3023
  }
3019
3024
  if (node.ctequery) {
3020
- output.push(this.formatter.parens(this.visit(node.ctequery, context)));
3025
+ output.push(context.parens(this.visit(node.ctequery, context)));
3021
3026
  }
3022
3027
  return output.join(' ');
3023
3028
  }
@@ -3093,7 +3098,7 @@ export class Deparser {
3093
3098
  DistinctExpr(node, context) {
3094
3099
  const args = ListUtils.unwrapList(node.args);
3095
3100
  if (args.length === 2) {
3096
- const literalContext = { ...context, isStringLiteral: true };
3101
+ const literalContext = context.spawn('DistinctExpr', { isStringLiteral: true });
3097
3102
  const left = this.visit(args[0], literalContext);
3098
3103
  const right = this.visit(args[1], literalContext);
3099
3104
  return `${left} IS DISTINCT FROM ${right}`;
@@ -3103,7 +3108,7 @@ export class Deparser {
3103
3108
  NullIfExpr(node, context) {
3104
3109
  const args = ListUtils.unwrapList(node.args);
3105
3110
  if (args.length === 2) {
3106
- const literalContext = { ...context, isStringLiteral: true };
3111
+ const literalContext = context.spawn('NullIfExpr', { isStringLiteral: true });
3107
3112
  const left = this.visit(args[0], literalContext);
3108
3113
  const right = this.visit(args[1], literalContext);
3109
3114
  return `NULLIF(${left}, ${right})`;
@@ -3182,7 +3187,7 @@ export class Deparser {
3182
3187
  }
3183
3188
  RelabelType(node, context) {
3184
3189
  if (node.arg) {
3185
- const literalContext = { ...context, isStringLiteral: true };
3190
+ const literalContext = context.spawn('RelabelType', { isStringLiteral: true });
3186
3191
  return this.visit(node.arg, literalContext);
3187
3192
  }
3188
3193
  return '';
@@ -3201,7 +3206,7 @@ export class Deparser {
3201
3206
  }
3202
3207
  ConvertRowtypeExpr(node, context) {
3203
3208
  if (node.arg) {
3204
- const literalContext = { ...context, isStringLiteral: true };
3209
+ const literalContext = context.spawn('ConvertRowtypeExpr', { isStringLiteral: true });
3205
3210
  return this.visit(node.arg, literalContext);
3206
3211
  }
3207
3212
  return '';
@@ -3232,10 +3237,10 @@ export class Deparser {
3232
3237
  }
3233
3238
  if (node.aliases && node.aliases.length > 0) {
3234
3239
  const aliasStrs = ListUtils.unwrapList(node.aliases).map(alias => this.visit(alias, context));
3235
- output.push(this.formatter.parens(aliasStrs.join(', ')));
3240
+ output.push(context.parens(aliasStrs.join(', ')));
3236
3241
  }
3237
3242
  if (node.options && node.options.length > 0) {
3238
- const viewContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ViewStmt'] };
3243
+ const viewContext = context.spawn('ViewStmt');
3239
3244
  const optionStrs = ListUtils.unwrapList(node.options)
3240
3245
  .map(option => this.visit(option, viewContext));
3241
3246
  output.push(`WITH (${optionStrs.join(', ')})`);
@@ -3282,22 +3287,22 @@ export class Deparser {
3282
3287
  }
3283
3288
  if (node.indexParams && node.indexParams.length > 0) {
3284
3289
  const paramStrs = ListUtils.unwrapList(node.indexParams).map(param => this.visit(param, context));
3285
- output.push(this.formatter.parens(paramStrs.join(', ')));
3290
+ output.push(context.parens(paramStrs.join(', ')));
3286
3291
  }
3287
3292
  if (node.indexIncludingParams && node.indexIncludingParams.length > 0) {
3288
3293
  const includeStrs = ListUtils.unwrapList(node.indexIncludingParams).map(param => this.visit(param, context));
3289
3294
  output.push('INCLUDE');
3290
- output.push(this.formatter.parens(includeStrs.join(', ')));
3295
+ output.push(context.parens(includeStrs.join(', ')));
3291
3296
  }
3292
3297
  if (node.whereClause) {
3293
3298
  output.push('WHERE');
3294
3299
  output.push(this.visit(node.whereClause, context));
3295
3300
  }
3296
3301
  if (node.options && node.options.length > 0) {
3297
- const indexContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'IndexStmt'] };
3302
+ const indexContext = context.spawn('IndexStmt');
3298
3303
  const optionStrs = ListUtils.unwrapList(node.options).map(option => this.visit(option, indexContext));
3299
3304
  output.push('WITH');
3300
- output.push(this.formatter.parens(optionStrs.join(', ')));
3305
+ output.push(context.parens(optionStrs.join(', ')));
3301
3306
  }
3302
3307
  if (node.nulls_not_distinct) {
3303
3308
  output.push('NULLS NOT DISTINCT');
@@ -3314,7 +3319,7 @@ export class Deparser {
3314
3319
  output.push(QuoteUtils.quote(node.name));
3315
3320
  }
3316
3321
  else if (node.expr) {
3317
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3322
+ output.push(context.parens(this.visit(node.expr, context)));
3318
3323
  }
3319
3324
  if (node.collation && node.collation.length > 0) {
3320
3325
  const collationStrs = ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3331,7 +3336,7 @@ export class Deparser {
3331
3336
  const stringData = this.getNodeData(opt.DefElem.arg);
3332
3337
  return `${opt.DefElem.defname}='${stringData.sval}'`;
3333
3338
  }
3334
- return this.visit(opt, context);
3339
+ return this.visit(opt, context.spawn('IndexElem'));
3335
3340
  });
3336
3341
  opclassStr += `(${opclassOpts.join(', ')})`;
3337
3342
  }
@@ -3365,7 +3370,7 @@ export class Deparser {
3365
3370
  output.push(QuoteUtils.quote(node.name));
3366
3371
  }
3367
3372
  else if (node.expr) {
3368
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3373
+ output.push(context.parens(this.visit(node.expr, context)));
3369
3374
  }
3370
3375
  if (node.collation && node.collation.length > 0) {
3371
3376
  const collationStrs = ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3513,16 +3518,16 @@ export class Deparser {
3513
3518
  if (node.rarg && 'JoinExpr' in node.rarg && !node.rarg.JoinExpr.alias) {
3514
3519
  rargStr = `(${rargStr})`;
3515
3520
  }
3516
- if (this.formatter.isPretty()) {
3517
- output.push(this.formatter.newline() + joinStr + ' ' + rargStr);
3521
+ if (context.isPretty()) {
3522
+ output.push(context.newline() + joinStr + ' ' + rargStr);
3518
3523
  }
3519
3524
  else {
3520
3525
  output.push(joinStr + ' ' + rargStr);
3521
3526
  }
3522
3527
  }
3523
3528
  else {
3524
- if (this.formatter.isPretty()) {
3525
- output.push(this.formatter.newline() + joinStr);
3529
+ if (context.isPretty()) {
3530
+ output.push(context.newline() + joinStr);
3526
3531
  }
3527
3532
  else {
3528
3533
  output.push(joinStr);
@@ -3531,7 +3536,7 @@ export class Deparser {
3531
3536
  if (node.usingClause && node.usingClause.length > 0) {
3532
3537
  const usingList = ListUtils.unwrapList(node.usingClause);
3533
3538
  const columnNames = usingList.map(col => this.visit(col, context));
3534
- if (this.formatter.isPretty()) {
3539
+ if (context.isPretty()) {
3535
3540
  output.push(` USING (${columnNames.join(', ')})`);
3536
3541
  }
3537
3542
  else {
@@ -3540,14 +3545,14 @@ export class Deparser {
3540
3545
  }
3541
3546
  else if (node.quals) {
3542
3547
  const qualsStr = this.visit(node.quals, context);
3543
- if (this.formatter.isPretty()) {
3548
+ if (context.isPretty()) {
3544
3549
  // For complex JOIN conditions, format with proper indentation
3545
3550
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3546
3551
  if (this.containsMultilineStringLiteral(qualsStr)) {
3547
3552
  output.push(` ON ${qualsStr}`);
3548
3553
  }
3549
3554
  else {
3550
- output.push(` ON${this.formatter.newline()}${this.formatter.indent(qualsStr)}`);
3555
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3551
3556
  }
3552
3557
  }
3553
3558
  else {
@@ -3559,7 +3564,7 @@ export class Deparser {
3559
3564
  }
3560
3565
  }
3561
3566
  let result;
3562
- if (this.formatter.isPretty()) {
3567
+ if (context.isPretty()) {
3563
3568
  result = output.join('');
3564
3569
  }
3565
3570
  else {
@@ -4162,7 +4167,7 @@ export class Deparser {
4162
4167
  }).filter((name) => name && name.trim());
4163
4168
  return items.join('.');
4164
4169
  }
4165
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DropStmt'], objtype: node.removeType };
4170
+ const objContext = context.spawn('DropStmt', { objtype: node.removeType });
4166
4171
  const objName = this.visit(objList, objContext);
4167
4172
  return objName;
4168
4173
  }).filter((name) => name && name.trim()).join(', ');
@@ -4262,7 +4267,7 @@ export class Deparser {
4262
4267
  if (node.options && node.options.length > 0) {
4263
4268
  output.push('WITH');
4264
4269
  const optionsStr = ListUtils.unwrapList(node.options)
4265
- .map(opt => this.visit(opt, context))
4270
+ .map(opt => this.visit(opt, context.spawn('CopyStmt')))
4266
4271
  .join(', ');
4267
4272
  output.push(`(${optionsStr})`);
4268
4273
  }
@@ -4308,21 +4313,19 @@ export class Deparser {
4308
4313
  if (node.missing_ok) {
4309
4314
  output.push('IF EXISTS');
4310
4315
  }
4311
- const alterContext = node.objtype === 'OBJECT_TYPE'
4312
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
4313
- : context;
4316
+ const alterContext = context.spawn('AlterTableStmt', { objtype: node.objtype });
4314
4317
  if (node.relation) {
4315
4318
  const relationStr = this.RangeVar(node.relation, alterContext);
4316
4319
  output.push(relationStr);
4317
4320
  }
4318
4321
  if (node.cmds && node.cmds.length > 0) {
4319
4322
  const commands = ListUtils.unwrapList(node.cmds);
4320
- if (this.formatter.isPretty()) {
4323
+ if (context.isPretty()) {
4321
4324
  const commandsStr = commands
4322
4325
  .map(cmd => {
4323
4326
  const cmdStr = this.visit(cmd, alterContext);
4324
4327
  if (cmdStr.startsWith('ADD CONSTRAINT') || cmdStr.startsWith('ADD ')) {
4325
- return this.formatter.newline() + this.formatter.indent(cmdStr);
4328
+ return context.newline() + context.indent(cmdStr);
4326
4329
  }
4327
4330
  return cmdStr;
4328
4331
  })
@@ -4343,7 +4346,7 @@ export class Deparser {
4343
4346
  if (node.subtype) {
4344
4347
  switch (node.subtype) {
4345
4348
  case 'AT_AddColumn':
4346
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4349
+ if (context.objtype === 'OBJECT_TYPE') {
4347
4350
  output.push('ADD ATTRIBUTE');
4348
4351
  }
4349
4352
  else {
@@ -4363,14 +4366,14 @@ export class Deparser {
4363
4366
  }
4364
4367
  if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4365
4368
  parts.push('OPTIONS');
4366
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
4369
+ const columnContext = context.spawn('ColumnDef');
4367
4370
  const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4368
4371
  parts.push(`(${options.join(', ')})`);
4369
4372
  }
4370
4373
  if (colDefData.constraints) {
4371
4374
  const constraints = ListUtils.unwrapList(colDefData.constraints);
4372
4375
  const constraintStrs = constraints.map(constraint => {
4373
- const columnConstraintContext = { ...context, isColumnConstraint: true };
4376
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4374
4377
  return this.visit(constraint, columnConstraintContext);
4375
4378
  });
4376
4379
  parts.push(...constraintStrs);
@@ -4390,7 +4393,7 @@ export class Deparser {
4390
4393
  break;
4391
4394
  case 'AT_DropColumn':
4392
4395
  if (node.missing_ok) {
4393
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4396
+ if (context.objtype === 'OBJECT_TYPE') {
4394
4397
  output.push('DROP ATTRIBUTE IF EXISTS');
4395
4398
  }
4396
4399
  else {
@@ -4398,7 +4401,7 @@ export class Deparser {
4398
4401
  }
4399
4402
  }
4400
4403
  else {
4401
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4404
+ if (context.objtype === 'OBJECT_TYPE') {
4402
4405
  output.push('DROP ATTRIBUTE');
4403
4406
  }
4404
4407
  else {
@@ -4416,7 +4419,7 @@ export class Deparser {
4416
4419
  }
4417
4420
  break;
4418
4421
  case 'AT_AlterColumnType':
4419
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4422
+ if (context.objtype === 'OBJECT_TYPE') {
4420
4423
  output.push('ALTER ATTRIBUTE');
4421
4424
  }
4422
4425
  else {
@@ -4480,7 +4483,7 @@ export class Deparser {
4480
4483
  case 'AT_SetRelOptions':
4481
4484
  output.push('SET');
4482
4485
  if (node.def) {
4483
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetRelOptions' };
4486
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetRelOptions' });
4484
4487
  const options = ListUtils.unwrapList(node.def)
4485
4488
  .map(option => this.visit(option, alterTableContext))
4486
4489
  .join(', ');
@@ -4493,7 +4496,7 @@ export class Deparser {
4493
4496
  case 'AT_ResetRelOptions':
4494
4497
  output.push('RESET');
4495
4498
  if (node.def) {
4496
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetRelOptions' };
4499
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetRelOptions' });
4497
4500
  const options = ListUtils.unwrapList(node.def)
4498
4501
  .map(option => this.visit(option, alterTableContext))
4499
4502
  .join(', ');
@@ -4588,7 +4591,7 @@ export class Deparser {
4588
4591
  }
4589
4592
  output.push('SET');
4590
4593
  if (node.def) {
4591
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetOptions' };
4594
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetOptions' });
4592
4595
  const options = ListUtils.unwrapList(node.def)
4593
4596
  .map(option => this.visit(option, alterTableContext))
4594
4597
  .join(', ');
@@ -4605,7 +4608,7 @@ export class Deparser {
4605
4608
  }
4606
4609
  output.push('RESET');
4607
4610
  if (node.def) {
4608
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetOptions' };
4611
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetOptions' });
4609
4612
  const options = ListUtils.unwrapList(node.def)
4610
4613
  .map(option => this.visit(option, alterTableContext))
4611
4614
  .join(', ');
@@ -4846,7 +4849,7 @@ export class Deparser {
4846
4849
  }
4847
4850
  output.push('OPTIONS');
4848
4851
  if (node.def) {
4849
- const alterColumnContext = { ...context, alterColumnOptions: true };
4852
+ const alterColumnContext = context.spawn('AlterTableCmd', { alterColumnOptions: true });
4850
4853
  const options = ListUtils.unwrapList(node.def)
4851
4854
  .map(option => this.visit(option, alterColumnContext))
4852
4855
  .join(', ');
@@ -4886,7 +4889,7 @@ export class Deparser {
4886
4889
  case 'AT_GenericOptions':
4887
4890
  output.push('OPTIONS');
4888
4891
  if (node.def) {
4889
- const alterTableContext = { ...context, alterTableOptions: true };
4892
+ const alterTableContext = context.spawn('AlterTableCmd', { alterTableOptions: true });
4890
4893
  const options = ListUtils.unwrapList(node.def)
4891
4894
  .map(option => this.visit(option, alterTableContext))
4892
4895
  .join(', ');
@@ -4990,7 +4993,7 @@ export class Deparser {
4990
4993
  output.push(this.TypeName(node.returnType, context));
4991
4994
  }
4992
4995
  if (node.options && node.options.length > 0) {
4993
- const funcContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFunctionStmt'] };
4996
+ const funcContext = context.spawn('CreateFunctionStmt');
4994
4997
  const options = node.options.map((opt) => this.visit(opt, funcContext));
4995
4998
  output.push(...options);
4996
4999
  }
@@ -5077,7 +5080,7 @@ export class Deparser {
5077
5080
  }
5078
5081
  output.push('AS', 'ENUM');
5079
5082
  if (node.vals && node.vals.length > 0) {
5080
- const enumContext = { ...context, isEnumValue: true };
5083
+ const enumContext = context.spawn('CreateEnumStmt', { isEnumValue: true });
5081
5084
  const values = ListUtils.unwrapList(node.vals)
5082
5085
  .map(val => this.visit(val, enumContext))
5083
5086
  .join(', ');
@@ -5132,9 +5135,8 @@ export class Deparser {
5132
5135
  output.push(roleName);
5133
5136
  }
5134
5137
  if (node.options) {
5135
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateRoleStmt'] };
5136
5138
  const options = ListUtils.unwrapList(node.options)
5137
- .map(option => this.visit(option, roleContext))
5139
+ .map(option => this.visit(option, context.spawn('CreateRoleStmt')))
5138
5140
  .join(' ');
5139
5141
  if (options) {
5140
5142
  output.push('WITH');
@@ -5165,7 +5167,7 @@ export class Deparser {
5165
5167
  const stringData = this.getNodeData(node.arg);
5166
5168
  return `${node.defname}='${stringData.sval}'`;
5167
5169
  }
5168
- return `${node.defname}=${this.visit(node.arg, { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] })}`;
5170
+ return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
5169
5171
  }
5170
5172
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
5171
5173
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -5181,13 +5183,13 @@ export class Deparser {
5181
5183
  if (!node.arg) {
5182
5184
  return `NO ${node.defname.toUpperCase()}`;
5183
5185
  }
5184
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5186
+ const defElemContext = context.spawn('DefElem');
5185
5187
  const argValue = this.visit(node.arg, defElemContext);
5186
5188
  return `${node.defname.toUpperCase()} ${argValue}`;
5187
5189
  }
5188
5190
  // Handle OPTIONS clause - use space format, not equals format
5189
5191
  if (node.arg) {
5190
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5192
+ const defElemContext = context.spawn('DefElem');
5191
5193
  const argValue = this.visit(node.arg, defElemContext);
5192
5194
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5193
5195
  const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
@@ -5241,7 +5243,7 @@ export class Deparser {
5241
5243
  if (!node.arg) {
5242
5244
  return 'PASSWORD NULL';
5243
5245
  }
5244
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5246
+ const defElemContext = context.spawn('DefElem');
5245
5247
  const argValue = this.visit(node.arg, defElemContext);
5246
5248
  const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5247
5249
  ? `'${argValue}'`
@@ -5250,7 +5252,7 @@ export class Deparser {
5250
5252
  }
5251
5253
  }
5252
5254
  if (node.arg) {
5253
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5255
+ const defElemContext = context.spawn('DefElem');
5254
5256
  const argValue = this.visit(node.arg, defElemContext);
5255
5257
  if (context.parentNodeTypes.includes('AlterOperatorStmt')) {
5256
5258
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
@@ -5326,7 +5328,7 @@ export class Deparser {
5326
5328
  if (node.defname === 'sysid') {
5327
5329
  return `SYSID ${argValue}`;
5328
5330
  }
5329
- if (argValue === 'true') {
5331
+ if (String(argValue) === 'true') {
5330
5332
  // Handle special cases where the positive form has a different name
5331
5333
  if (node.defname === 'isreplication') {
5332
5334
  return 'REPLICATION';
@@ -5336,7 +5338,7 @@ export class Deparser {
5336
5338
  }
5337
5339
  return node.defname.toUpperCase();
5338
5340
  }
5339
- else if (argValue === 'false') {
5341
+ else if (String(argValue) === 'false') {
5340
5342
  // Handle special cases where the negative form has a different name
5341
5343
  if (node.defname === 'canlogin') {
5342
5344
  return 'NOLOGIN';
@@ -5400,7 +5402,7 @@ export class Deparser {
5400
5402
  }
5401
5403
  if (context.parentNodeTypes.includes('DoStmt')) {
5402
5404
  if (node.defname === 'as') {
5403
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5405
+ const defElemContext = context.spawn('DefElem');
5404
5406
  const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
5405
5407
  if (Array.isArray(argValue)) {
5406
5408
  const bodyParts = argValue;
@@ -5691,7 +5693,7 @@ export class Deparser {
5691
5693
  }
5692
5694
  if (node.options && node.options.length > 0) {
5693
5695
  output.push('WITH');
5694
- const tsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateTableSpaceStmt'] };
5696
+ const tsContext = context.spawn('CreateTableSpaceStmt');
5695
5697
  const options = ListUtils.unwrapList(node.options)
5696
5698
  .map(option => this.visit(option, tsContext))
5697
5699
  .join(', ');
@@ -5721,7 +5723,7 @@ export class Deparser {
5721
5723
  output.push('SET');
5722
5724
  }
5723
5725
  if (node.options && node.options.length > 0) {
5724
- const tablespaceContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableSpaceOptionsStmt'] };
5726
+ const tablespaceContext = context.spawn('AlterTableSpaceOptionsStmt');
5725
5727
  const options = ListUtils.unwrapList(node.options)
5726
5728
  .map(option => this.visit(option, tablespaceContext))
5727
5729
  .join(', ');
@@ -5738,7 +5740,7 @@ export class Deparser {
5738
5740
  output.push(this.quoteIfNeeded(node.extname));
5739
5741
  }
5740
5742
  if (node.options && node.options.length > 0) {
5741
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateExtensionStmt'] };
5743
+ const extContext = context.spawn('CreateExtensionStmt');
5742
5744
  const options = ListUtils.unwrapList(node.options)
5743
5745
  .map(option => this.visit(option, extContext))
5744
5746
  .join(' ');
@@ -5752,7 +5754,7 @@ export class Deparser {
5752
5754
  output.push(this.quoteIfNeeded(node.extname));
5753
5755
  }
5754
5756
  if (node.options && node.options.length > 0) {
5755
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterExtensionStmt'] };
5757
+ const extContext = context.spawn('AlterExtensionStmt');
5756
5758
  const options = ListUtils.unwrapList(node.options)
5757
5759
  .map(option => this.visit(option, extContext))
5758
5760
  .join(' ');
@@ -5766,7 +5768,7 @@ export class Deparser {
5766
5768
  output.push(node.fdwname);
5767
5769
  }
5768
5770
  if (node.func_options && node.func_options.length > 0) {
5769
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5771
+ const fdwContext = context.spawn('CreateFdwStmt');
5770
5772
  const funcOptions = ListUtils.unwrapList(node.func_options)
5771
5773
  .map(option => this.visit(option, fdwContext))
5772
5774
  .join(' ');
@@ -5774,7 +5776,7 @@ export class Deparser {
5774
5776
  }
5775
5777
  if (node.options && node.options.length > 0) {
5776
5778
  output.push('OPTIONS');
5777
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5779
+ const fdwContext = context.spawn('CreateFdwStmt');
5778
5780
  const options = ListUtils.unwrapList(node.options)
5779
5781
  .map(option => this.visit(option, fdwContext))
5780
5782
  .join(', ');
@@ -5876,7 +5878,7 @@ export class Deparser {
5876
5878
  output.push('ADD');
5877
5879
  if (node.def) {
5878
5880
  // Pass domain context to avoid adding constraint names for domain constraints
5879
- const domainContext = { ...context, isDomainConstraint: true };
5881
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5880
5882
  output.push(this.visit(node.def, domainContext));
5881
5883
  }
5882
5884
  break;
@@ -5902,7 +5904,7 @@ export class Deparser {
5902
5904
  output.push('ADD');
5903
5905
  if (node.def) {
5904
5906
  // Pass domain context to avoid adding constraint names for domain constraints
5905
- const domainContext = { ...context, isDomainConstraint: true };
5907
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5906
5908
  output.push(this.visit(node.def, domainContext));
5907
5909
  }
5908
5910
  break;
@@ -6264,7 +6266,7 @@ export class Deparser {
6264
6266
  output.push(`${operatorName}(${args.join(', ')})`);
6265
6267
  }
6266
6268
  else {
6267
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CommentStmt'], objtype: node.objtype };
6269
+ const objContext = context.spawn('CommentStmt', { objtype: node.objtype });
6268
6270
  output.push(this.visit(node.object, objContext));
6269
6271
  }
6270
6272
  }
@@ -6315,8 +6317,8 @@ export class Deparser {
6315
6317
  output.push(initialParts.join(' '));
6316
6318
  // Add ON clause on new line in pretty mode
6317
6319
  if (node.table) {
6318
- if (this.formatter.isPretty()) {
6319
- output.push(this.formatter.newline() + this.formatter.indent(`ON ${this.RangeVar(node.table, context)}`));
6320
+ if (context.isPretty()) {
6321
+ output.push(context.newline() + context.indent(`ON ${this.RangeVar(node.table, context)}`));
6320
6322
  }
6321
6323
  else {
6322
6324
  output.push('ON');
@@ -6325,24 +6327,24 @@ export class Deparser {
6325
6327
  }
6326
6328
  // Handle AS RESTRICTIVE/PERMISSIVE clause
6327
6329
  if (node.permissive === undefined) {
6328
- if (this.formatter.isPretty()) {
6329
- output.push(this.formatter.newline() + this.formatter.indent('AS RESTRICTIVE'));
6330
+ if (context.isPretty()) {
6331
+ output.push(context.newline() + context.indent('AS RESTRICTIVE'));
6330
6332
  }
6331
6333
  else {
6332
6334
  output.push('AS', 'RESTRICTIVE');
6333
6335
  }
6334
6336
  }
6335
6337
  else if (node.permissive === true) {
6336
- if (this.formatter.isPretty()) {
6337
- output.push(this.formatter.newline() + this.formatter.indent('AS PERMISSIVE'));
6338
+ if (context.isPretty()) {
6339
+ output.push(context.newline() + context.indent('AS PERMISSIVE'));
6338
6340
  }
6339
6341
  else {
6340
6342
  output.push('AS', 'PERMISSIVE');
6341
6343
  }
6342
6344
  }
6343
6345
  if (node.cmd_name) {
6344
- if (this.formatter.isPretty()) {
6345
- output.push(this.formatter.newline() + this.formatter.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6346
+ if (context.isPretty()) {
6347
+ output.push(context.newline() + context.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6346
6348
  }
6347
6349
  else {
6348
6350
  output.push('FOR', node.cmd_name.toUpperCase());
@@ -6350,8 +6352,8 @@ export class Deparser {
6350
6352
  }
6351
6353
  if (node.roles && node.roles.length > 0) {
6352
6354
  const roles = ListUtils.unwrapList(node.roles).map(role => this.visit(role, context));
6353
- if (this.formatter.isPretty()) {
6354
- output.push(this.formatter.newline() + this.formatter.indent(`TO ${roles.join(', ')}`));
6355
+ if (context.isPretty()) {
6356
+ output.push(context.newline() + context.indent(`TO ${roles.join(', ')}`));
6355
6357
  }
6356
6358
  else {
6357
6359
  output.push('TO');
@@ -6359,11 +6361,11 @@ export class Deparser {
6359
6361
  }
6360
6362
  }
6361
6363
  if (node.qual) {
6362
- if (this.formatter.isPretty()) {
6364
+ if (context.isPretty()) {
6363
6365
  const qualExpr = this.visit(node.qual, context);
6364
- output.push(this.formatter.newline() + this.formatter.indent('USING ('));
6365
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(qualExpr)));
6366
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6366
+ output.push(context.newline() + context.indent('USING ('));
6367
+ output.push(context.newline() + context.indent(context.indent(qualExpr)));
6368
+ output.push(context.newline() + context.indent(')'));
6367
6369
  }
6368
6370
  else {
6369
6371
  output.push('USING');
@@ -6371,18 +6373,18 @@ export class Deparser {
6371
6373
  }
6372
6374
  }
6373
6375
  if (node.with_check) {
6374
- if (this.formatter.isPretty()) {
6376
+ if (context.isPretty()) {
6375
6377
  const checkExpr = this.visit(node.with_check, context);
6376
- output.push(this.formatter.newline() + this.formatter.indent('WITH CHECK ('));
6377
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(checkExpr)));
6378
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6378
+ output.push(context.newline() + context.indent('WITH CHECK ('));
6379
+ output.push(context.newline() + context.indent(context.indent(checkExpr)));
6380
+ output.push(context.newline() + context.indent(')'));
6379
6381
  }
6380
6382
  else {
6381
6383
  output.push('WITH CHECK');
6382
6384
  output.push(`(${this.visit(node.with_check, context)})`);
6383
6385
  }
6384
6386
  }
6385
- return this.formatter.isPretty() ? output.join('') : output.join(' ');
6387
+ return context.isPretty() ? output.join('') : output.join(' ');
6386
6388
  }
6387
6389
  AlterPolicyStmt(node, context) {
6388
6390
  const output = ['ALTER', 'POLICY'];
@@ -6427,7 +6429,7 @@ export class Deparser {
6427
6429
  }
6428
6430
  if (node.options && node.options.length > 0) {
6429
6431
  output.push('OPTIONS');
6430
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateUserMappingStmt'] };
6432
+ const userMappingContext = context.spawn('CreateUserMappingStmt');
6431
6433
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6432
6434
  output.push(`(${options.join(', ')})`);
6433
6435
  }
@@ -6610,7 +6612,7 @@ export class Deparser {
6610
6612
  DoStmt(node, context) {
6611
6613
  const output = ['DO'];
6612
6614
  if (node.args && node.args.length > 0) {
6613
- const doContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DoStmt'] };
6615
+ const doContext = context.spawn('DoStmt');
6614
6616
  const args = ListUtils.unwrapList(node.args);
6615
6617
  const processedArgs = [];
6616
6618
  for (const arg of args) {
@@ -6915,7 +6917,7 @@ export class Deparser {
6915
6917
  ObjectWithArgs(node, context) {
6916
6918
  let result = '';
6917
6919
  if (node.objname && node.objname.length > 0) {
6918
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ObjectWithArgs'] };
6920
+ const objContext = context.spawn('ObjectWithArgs');
6919
6921
  const names = ListUtils.unwrapList(node.objname).map(name => this.visit(name, objContext));
6920
6922
  result = names.join('.');
6921
6923
  }
@@ -6957,7 +6959,7 @@ export class Deparser {
6957
6959
  }
6958
6960
  output.push('SET');
6959
6961
  if (node.options && node.options.length > 0) {
6960
- const alterOpContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterOperatorStmt'] };
6962
+ const alterOpContext = context.spawn('AlterOperatorStmt');
6961
6963
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, alterOpContext));
6962
6964
  output.push(`(${options.join(', ')})`);
6963
6965
  }
@@ -6969,13 +6971,13 @@ export class Deparser {
6969
6971
  output.push(QuoteUtils.quote(node.fdwname));
6970
6972
  }
6971
6973
  if (node.func_options && node.func_options.length > 0) {
6972
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
6974
+ const fdwContext = context.spawn('AlterFdwStmt');
6973
6975
  const funcOptions = ListUtils.unwrapList(node.func_options).map(opt => this.visit(opt, fdwContext));
6974
6976
  output.push(funcOptions.join(' '));
6975
6977
  }
6976
6978
  if (node.options && node.options.length > 0) {
6977
6979
  output.push('OPTIONS');
6978
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
6980
+ const fdwContext = context.spawn('AlterFdwStmt');
6979
6981
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, fdwContext));
6980
6982
  output.push(`(${options.join(', ')})`);
6981
6983
  }
@@ -7001,7 +7003,7 @@ export class Deparser {
7001
7003
  if (node.options && node.options.length > 0) {
7002
7004
  output.push('OPTIONS');
7003
7005
  output.push('(');
7004
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignServerStmt'] };
7006
+ const optionsContext = context.spawn('CreateForeignServerStmt');
7005
7007
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
7006
7008
  output.push(options.join(', '));
7007
7009
  output.push(')');
@@ -7019,7 +7021,7 @@ export class Deparser {
7019
7021
  if (node.options && node.options.length > 0) {
7020
7022
  output.push('OPTIONS');
7021
7023
  output.push('(');
7022
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterForeignServerStmt'] };
7024
+ const optionsContext = context.spawn('AlterForeignServerStmt');
7023
7025
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
7024
7026
  output.push(options.join(', '));
7025
7027
  output.push(')');
@@ -7040,7 +7042,7 @@ export class Deparser {
7040
7042
  }
7041
7043
  if (node.options && node.options.length > 0) {
7042
7044
  output.push('OPTIONS');
7043
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterUserMappingStmt'] };
7045
+ const userMappingContext = context.spawn('AlterUserMappingStmt');
7044
7046
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
7045
7047
  output.push(`(${options.join(', ')})`);
7046
7048
  }
@@ -7100,7 +7102,7 @@ export class Deparser {
7100
7102
  output.push(QuoteUtils.quote(node.local_schema));
7101
7103
  }
7102
7104
  if (node.options && node.options.length > 0) {
7103
- const importSchemaContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ImportForeignSchemaStmt'] };
7105
+ const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
7104
7106
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, importSchemaContext));
7105
7107
  output.push(`OPTIONS (${options.join(', ')})`);
7106
7108
  }
@@ -7135,7 +7137,7 @@ export class Deparser {
7135
7137
  ExplainStmt(node, context) {
7136
7138
  const output = ['EXPLAIN'];
7137
7139
  if (node.options && node.options.length > 0) {
7138
- const explainContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ExplainStmt'] };
7140
+ const explainContext = context.spawn('ExplainStmt');
7139
7141
  const options = ListUtils.unwrapList(node.options).map(option => this.visit(option, explainContext));
7140
7142
  output.push(`(${options.join(', ')})`);
7141
7143
  }
@@ -7393,9 +7395,7 @@ export class Deparser {
7393
7395
  output.push(this.RangeVar(node.relation, context));
7394
7396
  }
7395
7397
  else if (node.relation) {
7396
- const rangeVarContext = node.relationType === 'OBJECT_TYPE'
7397
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
7398
- : context;
7398
+ const rangeVarContext = context.spawn('RenameStmt', { objtype: node.relationType });
7399
7399
  // Add ON keyword for policy operations
7400
7400
  if (node.renameType === 'OBJECT_POLICY') {
7401
7401
  output.push('ON');
@@ -7577,7 +7577,7 @@ export class Deparser {
7577
7577
  }
7578
7578
  }
7579
7579
  if (node.privileges && node.privileges.length > 0) {
7580
- const privilegeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'GrantStmt'] };
7580
+ const privilegeContext = context.spawn('GrantStmt');
7581
7581
  const privileges = ListUtils.unwrapList(node.privileges)
7582
7582
  .map(priv => this.visit(priv, privilegeContext))
7583
7583
  .join(', ');
@@ -7885,8 +7885,8 @@ export class Deparser {
7885
7885
  }
7886
7886
  if (node.action) {
7887
7887
  const actionStr = this.GrantStmt(node.action, context);
7888
- if (this.formatter.isPretty()) {
7889
- return output.join(' ') + this.formatter.newline() + this.formatter.indent(actionStr);
7888
+ if (context.isPretty()) {
7889
+ return output.join(' ') + context.newline() + context.indent(actionStr);
7890
7890
  }
7891
7891
  else {
7892
7892
  output.push(actionStr);
@@ -8037,7 +8037,7 @@ export class Deparser {
8037
8037
  if (node.trigname) {
8038
8038
  output.push(QuoteUtils.quote(node.trigname));
8039
8039
  }
8040
- if (this.formatter.isPretty()) {
8040
+ if (context.isPretty()) {
8041
8041
  const components = [];
8042
8042
  const timing = [];
8043
8043
  if (node.timing & 2)
@@ -8063,31 +8063,31 @@ export class Deparser {
8063
8063
  }
8064
8064
  if (node.events & 32)
8065
8065
  events.push('TRUNCATE');
8066
- components.push(this.formatter.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8066
+ components.push(context.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8067
8067
  if (node.relation) {
8068
- components.push(this.formatter.indent('ON ' + this.RangeVar(node.relation, context)));
8068
+ components.push(context.indent('ON ' + this.RangeVar(node.relation, context)));
8069
8069
  }
8070
8070
  if (node.transitionRels && node.transitionRels.length > 0) {
8071
8071
  const transitionClauses = ListUtils.unwrapList(node.transitionRels)
8072
8072
  .map(rel => this.visit(rel, context))
8073
8073
  .join(' ');
8074
- components.push(this.formatter.indent('REFERENCING ' + transitionClauses));
8074
+ components.push(context.indent('REFERENCING ' + transitionClauses));
8075
8075
  }
8076
8076
  if (node.deferrable) {
8077
- components.push(this.formatter.indent('DEFERRABLE'));
8077
+ components.push(context.indent('DEFERRABLE'));
8078
8078
  }
8079
8079
  if (node.initdeferred) {
8080
- components.push(this.formatter.indent('INITIALLY DEFERRED'));
8080
+ components.push(context.indent('INITIALLY DEFERRED'));
8081
8081
  }
8082
8082
  if (node.row) {
8083
- components.push(this.formatter.indent('FOR EACH ROW'));
8083
+ components.push(context.indent('FOR EACH ROW'));
8084
8084
  }
8085
8085
  else {
8086
- components.push(this.formatter.indent('FOR EACH STATEMENT'));
8086
+ components.push(context.indent('FOR EACH STATEMENT'));
8087
8087
  }
8088
8088
  if (node.whenClause) {
8089
8089
  const whenStr = 'WHEN (' + this.visit(node.whenClause, context) + ')';
8090
- components.push(this.formatter.indent(whenStr));
8090
+ components.push(context.indent(whenStr));
8091
8091
  }
8092
8092
  let executeStr = 'EXECUTE';
8093
8093
  if (node.funcname && node.funcname.length > 0) {
@@ -8097,7 +8097,7 @@ export class Deparser {
8097
8097
  executeStr += ' PROCEDURE ' + funcName;
8098
8098
  }
8099
8099
  if (node.args && node.args.length > 0) {
8100
- const argContext = { ...context, isStringLiteral: true };
8100
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8101
8101
  const args = ListUtils.unwrapList(node.args)
8102
8102
  .map(arg => this.visit(arg, argContext))
8103
8103
  .join(', ');
@@ -8106,8 +8106,8 @@ export class Deparser {
8106
8106
  else {
8107
8107
  executeStr += '()';
8108
8108
  }
8109
- components.push(this.formatter.indent(executeStr));
8110
- return output.join(' ') + this.formatter.newline() + components.join(this.formatter.newline());
8109
+ components.push(context.indent(executeStr));
8110
+ return output.join(' ') + context.newline() + components.join(context.newline());
8111
8111
  }
8112
8112
  else {
8113
8113
  const timing = [];
@@ -8177,7 +8177,7 @@ export class Deparser {
8177
8177
  }
8178
8178
  if (node.args && node.args.length > 0) {
8179
8179
  output.push('(');
8180
- const argContext = { ...context, isStringLiteral: true };
8180
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8181
8181
  const args = ListUtils.unwrapList(node.args)
8182
8182
  .map(arg => this.visit(arg, argContext))
8183
8183
  .join(', ');
@@ -8214,7 +8214,7 @@ export class Deparser {
8214
8214
  }
8215
8215
  if (node.whenclause && node.whenclause.length > 0) {
8216
8216
  output.push('WHEN');
8217
- const eventTriggerContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateEventTrigStmt'] };
8217
+ const eventTriggerContext = context.spawn('CreateEventTrigStmt');
8218
8218
  const conditions = ListUtils.unwrapList(node.whenclause)
8219
8219
  .map(condition => this.visit(condition, eventTriggerContext))
8220
8220
  .join(' AND ');
@@ -8403,7 +8403,7 @@ export class Deparser {
8403
8403
  output.push(sequenceName.join('.'));
8404
8404
  }
8405
8405
  if (node.options && node.options.length > 0) {
8406
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateSeqStmt'] };
8406
+ const seqContext = context.spawn('CreateSeqStmt');
8407
8407
  const optionStrs = ListUtils.unwrapList(node.options)
8408
8408
  .filter(option => option != null && this.getNodeType(option) !== 'undefined')
8409
8409
  .map(option => {
@@ -8440,7 +8440,7 @@ export class Deparser {
8440
8440
  output.push(sequenceName.join('.'));
8441
8441
  }
8442
8442
  if (node.options && node.options.length > 0) {
8443
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterSeqStmt'] };
8443
+ const seqContext = context.spawn('AlterSeqStmt');
8444
8444
  const optionStrs = ListUtils.unwrapList(node.options)
8445
8445
  .filter(option => option && option !== undefined)
8446
8446
  .map(option => {
@@ -8469,7 +8469,7 @@ export class Deparser {
8469
8469
  CompositeTypeStmt(node, context) {
8470
8470
  const output = ['CREATE', 'TYPE'];
8471
8471
  if (node.typevar) {
8472
- const typeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CompositeTypeStmt'] };
8472
+ const typeContext = context.spawn('CompositeTypeStmt');
8473
8473
  output.push(this.RangeVar(node.typevar, typeContext));
8474
8474
  }
8475
8475
  output.push('AS');
@@ -8570,7 +8570,7 @@ export class Deparser {
8570
8570
  output.push(this.RoleSpec(node.role, context));
8571
8571
  }
8572
8572
  if (node.options) {
8573
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterRoleStmt'] };
8573
+ const roleContext = context.spawn('AlterRoleStmt');
8574
8574
  // Handle GROUP operations specially based on action value
8575
8575
  if (isGroupStatement) {
8576
8576
  const roleMembersOption = ListUtils.unwrapList(node.options).find(option => option.DefElem && option.DefElem.defname === 'rolemembers');
@@ -8773,7 +8773,7 @@ export class Deparser {
8773
8773
  }
8774
8774
  if (node.cols && node.cols.length > 0) {
8775
8775
  output.push('(');
8776
- const colContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AccessPriv'] };
8776
+ const colContext = context.spawn('AccessPriv');
8777
8777
  const columns = ListUtils.unwrapList(node.cols).map(col => this.visit(col, colContext));
8778
8778
  output.push(columns.join(', '));
8779
8779
  output.push(')');
@@ -8853,7 +8853,7 @@ export class Deparser {
8853
8853
  output.push(ListUtils.unwrapList(node.defnames).map(name => this.visit(name, context)).join('.'));
8854
8854
  }
8855
8855
  if (node.definition && node.definition.length > 0) {
8856
- const defineStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefineStmt'] };
8856
+ const defineStmtContext = context.spawn('DefineStmt');
8857
8857
  const definitions = ListUtils.unwrapList(node.definition).map(def => {
8858
8858
  return this.visit(def, defineStmtContext);
8859
8859
  });
@@ -9339,7 +9339,7 @@ export class Deparser {
9339
9339
  const operatorNumber = node.number !== undefined ? node.number : 0;
9340
9340
  output.push(operatorNumber.toString());
9341
9341
  if (node.name) {
9342
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9342
+ const opClassContext = context.spawn('CreateOpClassItem');
9343
9343
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9344
9344
  }
9345
9345
  }
@@ -9349,7 +9349,7 @@ export class Deparser {
9349
9349
  const functionNumber = node.number !== undefined ? node.number : 0;
9350
9350
  output.push(functionNumber.toString());
9351
9351
  if (node.name) {
9352
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9352
+ const opClassContext = context.spawn('CreateOpClassItem');
9353
9353
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9354
9354
  }
9355
9355
  }
@@ -10047,7 +10047,7 @@ export class Deparser {
10047
10047
  output.push(this.ObjectWithArgs(node.func, context));
10048
10048
  }
10049
10049
  if (node.actions && node.actions.length > 0) {
10050
- const alterFunctionContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFunctionStmt'] };
10050
+ const alterFunctionContext = context.spawn('AlterFunctionStmt');
10051
10051
  const actionStrs = ListUtils.unwrapList(node.actions).map(action => this.visit(action, alterFunctionContext));
10052
10052
  output.push(actionStrs.join(' '));
10053
10053
  }
@@ -10291,7 +10291,7 @@ export class Deparser {
10291
10291
  CreateForeignTableStmt(node, context) {
10292
10292
  const output = ['CREATE FOREIGN TABLE'];
10293
10293
  if (node.base && node.base.relation) {
10294
- const relationContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10294
+ const relationContext = context.spawn('CreateForeignTableStmt');
10295
10295
  // Handle relation node directly as RangeVar since it contains the RangeVar properties
10296
10296
  output.push(this.RangeVar(node.base.relation, relationContext));
10297
10297
  }
@@ -10321,7 +10321,7 @@ export class Deparser {
10321
10321
  output.push(QuoteUtils.quote(node.servername));
10322
10322
  }
10323
10323
  if (node.options && node.options.length > 0) {
10324
- const foreignTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10324
+ const foreignTableContext = context.spawn('CreateForeignTableStmt');
10325
10325
  const optionStrs = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, foreignTableContext));
10326
10326
  output.push(`OPTIONS (${optionStrs.join(', ')})`);
10327
10327
  }