pgsql-deparser 17.8.2 → 17.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/deparser.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Deparser = void 0;
4
+ const base_1 = require("./visitors/base");
4
5
  const sql_formatter_1 = require("./utils/sql-formatter");
5
6
  const quote_utils_1 = require("./utils/quote-utils");
6
7
  const list_utils_1 = require("./utils/list-utils");
@@ -103,11 +104,9 @@ function isWrappedParseResult(obj) {
103
104
  * compatibility and wraps them internally for consistent processing.
104
105
  */
105
106
  class Deparser {
106
- formatter;
107
107
  tree;
108
108
  options;
109
109
  constructor(tree, opts = {}) {
110
- this.formatter = new sql_formatter_1.SqlFormatter(opts.newline, opts.tab, opts.pretty);
111
110
  // Set default options
112
111
  this.options = {
113
112
  functionDelimiter: '$$',
@@ -144,15 +143,17 @@ class Deparser {
144
143
  return new Deparser(query, opts).deparseQuery();
145
144
  }
146
145
  deparseQuery() {
146
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
147
+ const context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
147
148
  return this.tree
148
149
  .map(node => {
149
150
  // All nodes should go through the standard deparse method
150
151
  // which will route to the appropriate handler
151
- const result = this.deparse(node);
152
+ const result = this.deparse(node, context);
152
153
  return result || '';
153
154
  })
154
155
  .filter(result => result !== '')
155
- .join(this.formatter.newline() + this.formatter.newline());
156
+ .join(context.newline() + context.newline());
156
157
  }
157
158
  /**
158
159
  * Get the appropriate function delimiter based on the body content
@@ -166,10 +167,14 @@ class Deparser {
166
167
  }
167
168
  return delimiter;
168
169
  }
169
- deparse(node, context = { parentNodeTypes: [] }) {
170
+ deparse(node, context) {
170
171
  if (node == null) {
171
172
  return null;
172
173
  }
174
+ if (!context) {
175
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
176
+ context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
177
+ }
173
178
  if (typeof node === 'number' || node instanceof Number) {
174
179
  return node.toString();
175
180
  }
@@ -181,7 +186,11 @@ class Deparser {
181
186
  throw new Error(`Error deparsing ${nodeType}: ${error.message}`);
182
187
  }
183
188
  }
184
- visit(node, context = { parentNodeTypes: [] }) {
189
+ visit(node, context) {
190
+ if (!context) {
191
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
192
+ context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
193
+ }
185
194
  const nodeType = this.getNodeType(node);
186
195
  // Handle empty objects
187
196
  if (!nodeType) {
@@ -190,11 +199,7 @@ class Deparser {
190
199
  const nodeData = this.getNodeData(node);
191
200
  const methodName = nodeType;
192
201
  if (typeof this[methodName] === 'function') {
193
- const childContext = {
194
- ...context,
195
- parentNodeTypes: [...context.parentNodeTypes, nodeType]
196
- };
197
- const result = this[methodName](nodeData, childContext);
202
+ const result = this[methodName](nodeData, context);
198
203
  return result;
199
204
  }
200
205
  throw new Error(`Deparser does not handle node type: ${nodeType}`);
@@ -220,7 +225,7 @@ class Deparser {
220
225
  .filter((rawStmt) => rawStmt != null)
221
226
  .map((rawStmt) => this.RawStmt(rawStmt, context))
222
227
  .filter((result) => result !== '')
223
- .join(this.formatter.newline() + this.formatter.newline());
228
+ .join(context.newline() + context.newline());
224
229
  }
225
230
  RawStmt(node, context) {
226
231
  if (!node.stmt) {
@@ -240,7 +245,7 @@ class Deparser {
240
245
  }
241
246
  if (!node.op || node.op === 'SETOP_NONE') {
242
247
  if (node.valuesLists == null) {
243
- if (!this.formatter.isPretty() || !node.targetList) {
248
+ if (!context.isPretty() || !node.targetList) {
244
249
  output.push('SELECT');
245
250
  }
246
251
  }
@@ -260,7 +265,7 @@ class Deparser {
260
265
  node.rarg.limitOffset ||
261
266
  node.rarg.withClause);
262
267
  if (leftNeedsParens) {
263
- output.push(this.formatter.parens(leftStmt));
268
+ output.push(context.parens(leftStmt));
264
269
  }
265
270
  else {
266
271
  output.push(leftStmt);
@@ -282,7 +287,7 @@ class Deparser {
282
287
  output.push('ALL');
283
288
  }
284
289
  if (rightNeedsParens) {
285
- output.push(this.formatter.parens(rightStmt));
290
+ output.push(context.parens(rightStmt));
286
291
  }
287
292
  else {
288
293
  output.push(rightStmt);
@@ -294,20 +299,20 @@ class Deparser {
294
299
  const distinctClause = list_utils_1.ListUtils.unwrapList(node.distinctClause);
295
300
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
296
301
  const clause = distinctClause
297
- .map(e => this.visit(e, { ...context, select: true }))
302
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
298
303
  .join(', ');
299
- distinctPart = ' DISTINCT ON ' + this.formatter.parens(clause);
304
+ distinctPart = ' DISTINCT ON ' + context.parens(clause);
300
305
  }
301
306
  else {
302
307
  distinctPart = ' DISTINCT';
303
308
  }
304
- if (!this.formatter.isPretty()) {
309
+ if (!context.isPretty()) {
305
310
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
306
311
  output.push('DISTINCT ON');
307
312
  const clause = distinctClause
308
- .map(e => this.visit(e, { ...context, select: true }))
313
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
309
314
  .join(', ');
310
- output.push(this.formatter.parens(clause));
315
+ output.push(context.parens(clause));
311
316
  }
312
317
  else {
313
318
  output.push('DISTINCT');
@@ -316,10 +321,10 @@ class Deparser {
316
321
  }
317
322
  if (node.targetList) {
318
323
  const targetList = list_utils_1.ListUtils.unwrapList(node.targetList);
319
- if (this.formatter.isPretty()) {
324
+ if (context.isPretty()) {
320
325
  if (targetList.length === 1) {
321
326
  const targetNode = targetList[0];
322
- const target = this.visit(targetNode, { ...context, select: true });
327
+ const target = this.visit(targetNode, context.spawn('SelectStmt', { select: true }));
323
328
  // Check if single target is complex - if so, use multiline format
324
329
  if (this.isComplexSelectTarget(targetNode)) {
325
330
  output.push('SELECT' + distinctPart);
@@ -327,7 +332,7 @@ class Deparser {
327
332
  output.push(target);
328
333
  }
329
334
  else {
330
- output.push(this.formatter.indent(target));
335
+ output.push(context.indent(target));
331
336
  }
332
337
  }
333
338
  else {
@@ -337,20 +342,20 @@ class Deparser {
337
342
  else {
338
343
  const targetStrings = targetList
339
344
  .map(e => {
340
- const targetStr = this.visit(e, { ...context, select: true });
345
+ const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
341
346
  if (this.containsMultilineStringLiteral(targetStr)) {
342
347
  return targetStr;
343
348
  }
344
- return this.formatter.indent(targetStr);
349
+ return context.indent(targetStr);
345
350
  });
346
- const formattedTargets = targetStrings.join(',' + this.formatter.newline());
351
+ const formattedTargets = targetStrings.join(',' + context.newline());
347
352
  output.push('SELECT' + distinctPart);
348
353
  output.push(formattedTargets);
349
354
  }
350
355
  }
351
356
  else {
352
357
  const targets = targetList
353
- .map(e => this.visit(e, { ...context, select: true }))
358
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
354
359
  .join(', ');
355
360
  output.push(targets);
356
361
  }
@@ -362,22 +367,22 @@ class Deparser {
362
367
  if (node.fromClause) {
363
368
  const fromList = list_utils_1.ListUtils.unwrapList(node.fromClause);
364
369
  const fromItems = fromList
365
- .map(e => this.deparse(e, { ...context, from: true }))
370
+ .map(e => this.deparse(e, context.spawn('SelectStmt', { from: true })))
366
371
  .join(', ');
367
372
  output.push('FROM ' + fromItems.trim());
368
373
  }
369
374
  if (node.whereClause) {
370
- if (this.formatter.isPretty()) {
375
+ if (context.isPretty()) {
371
376
  output.push('WHERE');
372
377
  const whereExpr = this.visit(node.whereClause, context);
373
- const lines = whereExpr.split(this.formatter.newline());
378
+ const lines = whereExpr.split(context.newline());
374
379
  const indentedLines = lines.map((line, index) => {
375
380
  if (index === 0) {
376
- return this.formatter.indent(line);
381
+ return context.indent(line);
377
382
  }
378
383
  return line;
379
384
  });
380
- output.push(indentedLines.join(this.formatter.newline()));
385
+ output.push(indentedLines.join(context.newline()));
381
386
  }
382
387
  else {
383
388
  output.push('WHERE');
@@ -385,17 +390,17 @@ class Deparser {
385
390
  }
386
391
  }
387
392
  if (node.valuesLists) {
388
- if (this.formatter.isPretty()) {
393
+ if (context.isPretty()) {
389
394
  output.push('VALUES');
390
395
  const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
391
396
  const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
392
- return this.formatter.parens(values.join(', '));
397
+ return context.parens(values.join(', '));
393
398
  });
394
399
  const indentedTuples = lists.map(tuple => {
395
400
  if (this.containsMultilineStringLiteral(tuple)) {
396
401
  return tuple;
397
402
  }
398
- return this.formatter.indent(tuple);
403
+ return context.indent(tuple);
399
404
  });
400
405
  output.push(indentedTuples.join(',\n'));
401
406
  }
@@ -403,43 +408,43 @@ class Deparser {
403
408
  output.push('VALUES');
404
409
  const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
405
410
  const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
406
- return this.formatter.parens(values.join(', '));
411
+ return context.parens(values.join(', '));
407
412
  });
408
413
  output.push(lists.join(', '));
409
414
  }
410
415
  }
411
416
  if (node.groupClause) {
412
417
  const groupList = list_utils_1.ListUtils.unwrapList(node.groupClause);
413
- if (this.formatter.isPretty()) {
418
+ if (context.isPretty()) {
414
419
  const groupItems = groupList
415
420
  .map(e => {
416
- const groupStr = this.visit(e, { ...context, group: true });
421
+ const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
417
422
  if (this.containsMultilineStringLiteral(groupStr)) {
418
423
  return groupStr;
419
424
  }
420
- return this.formatter.indent(groupStr);
425
+ return context.indent(groupStr);
421
426
  })
422
- .join(',' + this.formatter.newline());
427
+ .join(',' + context.newline());
423
428
  output.push('GROUP BY');
424
429
  output.push(groupItems);
425
430
  }
426
431
  else {
427
432
  output.push('GROUP BY');
428
433
  const groupItems = groupList
429
- .map(e => this.visit(e, { ...context, group: true }))
434
+ .map(e => this.visit(e, context.spawn('SelectStmt', { group: true })))
430
435
  .join(', ');
431
436
  output.push(groupItems);
432
437
  }
433
438
  }
434
439
  if (node.havingClause) {
435
- if (this.formatter.isPretty()) {
440
+ if (context.isPretty()) {
436
441
  output.push('HAVING');
437
442
  const havingStr = this.visit(node.havingClause, context);
438
443
  if (this.containsMultilineStringLiteral(havingStr)) {
439
444
  output.push(havingStr);
440
445
  }
441
446
  else {
442
- output.push(this.formatter.indent(havingStr));
447
+ output.push(context.indent(havingStr));
443
448
  }
444
449
  }
445
450
  else {
@@ -457,23 +462,23 @@ class Deparser {
457
462
  }
458
463
  if (node.sortClause) {
459
464
  const sortList = list_utils_1.ListUtils.unwrapList(node.sortClause);
460
- if (this.formatter.isPretty()) {
465
+ if (context.isPretty()) {
461
466
  const sortItems = sortList
462
467
  .map(e => {
463
- const sortStr = this.visit(e, { ...context, sort: true });
468
+ const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
464
469
  if (this.containsMultilineStringLiteral(sortStr)) {
465
470
  return sortStr;
466
471
  }
467
- return this.formatter.indent(sortStr);
472
+ return context.indent(sortStr);
468
473
  })
469
- .join(',' + this.formatter.newline());
474
+ .join(',' + context.newline());
470
475
  output.push('ORDER BY');
471
476
  output.push(sortItems);
472
477
  }
473
478
  else {
474
479
  output.push('ORDER BY');
475
480
  const sortItems = sortList
476
- .map(e => this.visit(e, { ...context, sort: true }))
481
+ .map(e => this.visit(e, context.spawn('SelectStmt', { sort: true })))
477
482
  .join(', ');
478
483
  output.push(sortItems);
479
484
  }
@@ -491,9 +496,9 @@ class Deparser {
491
496
  .join(' ');
492
497
  output.push(lockingClauses);
493
498
  }
494
- if (this.formatter.isPretty()) {
499
+ if (context.isPretty()) {
495
500
  const filteredOutput = output.filter(item => item.trim() !== '');
496
- return filteredOutput.join(this.formatter.newline());
501
+ return filteredOutput.join(context.newline());
497
502
  }
498
503
  return output.join(' ');
499
504
  }
@@ -505,13 +510,13 @@ class Deparser {
505
510
  switch (kind) {
506
511
  case 'AEXPR_OP':
507
512
  if (lexpr && rexpr) {
508
- const operator = this.deparseOperatorName(name);
513
+ const operator = this.deparseOperatorName(name, context);
509
514
  let leftExpr = this.visit(lexpr, context);
510
515
  let rightExpr = this.visit(rexpr, context);
511
516
  // Check if left expression needs parentheses
512
517
  let leftNeedsParens = false;
513
518
  if (lexpr && 'A_Expr' in lexpr && lexpr.A_Expr?.kind === 'AEXPR_OP') {
514
- const leftOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(lexpr.A_Expr.name));
519
+ const leftOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(lexpr.A_Expr.name), context);
515
520
  if (this.needsParentheses(leftOp, operator, 'left')) {
516
521
  leftNeedsParens = true;
517
522
  }
@@ -520,12 +525,12 @@ class Deparser {
520
525
  leftNeedsParens = true;
521
526
  }
522
527
  if (leftNeedsParens) {
523
- leftExpr = this.formatter.parens(leftExpr);
528
+ leftExpr = context.parens(leftExpr);
524
529
  }
525
530
  // Check if right expression needs parentheses
526
531
  let rightNeedsParens = false;
527
532
  if (rexpr && 'A_Expr' in rexpr && rexpr.A_Expr?.kind === 'AEXPR_OP') {
528
- const rightOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(rexpr.A_Expr.name));
533
+ const rightOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(rexpr.A_Expr.name), context);
529
534
  if (this.needsParentheses(rightOp, operator, 'right')) {
530
535
  rightNeedsParens = true;
531
536
  }
@@ -534,42 +539,42 @@ class Deparser {
534
539
  rightNeedsParens = true;
535
540
  }
536
541
  if (rightNeedsParens) {
537
- rightExpr = this.formatter.parens(rightExpr);
542
+ rightExpr = context.parens(rightExpr);
538
543
  }
539
- return this.formatter.format([leftExpr, operator, rightExpr]);
544
+ return context.format([leftExpr, operator, rightExpr]);
540
545
  }
541
546
  else if (rexpr) {
542
- return this.formatter.format([
543
- this.deparseOperatorName(name),
547
+ return context.format([
548
+ this.deparseOperatorName(name, context),
544
549
  this.visit(rexpr, context)
545
550
  ]);
546
551
  }
547
552
  break;
548
553
  case 'AEXPR_OP_ANY':
549
- return this.formatter.format([
554
+ return context.format([
550
555
  this.visit(lexpr, context),
551
- this.deparseOperatorName(name),
556
+ this.deparseOperatorName(name, context),
552
557
  'ANY',
553
- this.formatter.parens(this.visit(rexpr, context))
558
+ context.parens(this.visit(rexpr, context))
554
559
  ]);
555
560
  case 'AEXPR_OP_ALL':
556
- return this.formatter.format([
561
+ return context.format([
557
562
  this.visit(lexpr, context),
558
- this.deparseOperatorName(name),
563
+ this.deparseOperatorName(name, context),
559
564
  'ALL',
560
- this.formatter.parens(this.visit(rexpr, context))
565
+ context.parens(this.visit(rexpr, context))
561
566
  ]);
562
567
  case 'AEXPR_DISTINCT': {
563
568
  let leftExpr = this.visit(lexpr, context);
564
569
  let rightExpr = this.visit(rexpr, context);
565
570
  // Add parentheses for complex expressions
566
571
  if (lexpr && this.isComplexExpression(lexpr)) {
567
- leftExpr = this.formatter.parens(leftExpr);
572
+ leftExpr = context.parens(leftExpr);
568
573
  }
569
574
  if (rexpr && this.isComplexExpression(rexpr)) {
570
- rightExpr = this.formatter.parens(rightExpr);
575
+ rightExpr = context.parens(rightExpr);
571
576
  }
572
- return this.formatter.format([
577
+ return context.format([
573
578
  leftExpr,
574
579
  'IS DISTINCT FROM',
575
580
  rightExpr
@@ -580,75 +585,75 @@ class Deparser {
580
585
  let rightExpr = this.visit(rexpr, context);
581
586
  // Add parentheses for complex expressions
582
587
  if (lexpr && this.isComplexExpression(lexpr)) {
583
- leftExpr = this.formatter.parens(leftExpr);
588
+ leftExpr = context.parens(leftExpr);
584
589
  }
585
590
  if (rexpr && this.isComplexExpression(rexpr)) {
586
- rightExpr = this.formatter.parens(rightExpr);
591
+ rightExpr = context.parens(rightExpr);
587
592
  }
588
- return this.formatter.format([
593
+ return context.format([
589
594
  leftExpr,
590
595
  'IS NOT DISTINCT FROM',
591
596
  rightExpr
592
597
  ]);
593
598
  }
594
599
  case 'AEXPR_NULLIF':
595
- return this.formatter.format([
600
+ return context.format([
596
601
  'NULLIF',
597
- this.formatter.parens([
602
+ context.parens([
598
603
  this.visit(lexpr, context),
599
604
  this.visit(rexpr, context)
600
605
  ].join(', '))
601
606
  ]);
602
607
  case 'AEXPR_IN':
603
- const inOperator = this.deparseOperatorName(name);
608
+ const inOperator = this.deparseOperatorName(name, context);
604
609
  if (inOperator === '<>' || inOperator === '!=') {
605
- return this.formatter.format([
610
+ return context.format([
606
611
  this.visit(lexpr, context),
607
612
  'NOT IN',
608
- this.formatter.parens(this.visit(rexpr, context))
613
+ context.parens(this.visit(rexpr, context))
609
614
  ]);
610
615
  }
611
616
  else {
612
- return this.formatter.format([
617
+ return context.format([
613
618
  this.visit(lexpr, context),
614
619
  'IN',
615
- this.formatter.parens(this.visit(rexpr, context))
620
+ context.parens(this.visit(rexpr, context))
616
621
  ]);
617
622
  }
618
623
  case 'AEXPR_LIKE':
619
- const likeOp = this.deparseOperatorName(name);
624
+ const likeOp = this.deparseOperatorName(name, context);
620
625
  if (likeOp === '!~~') {
621
- return this.formatter.format([
626
+ return context.format([
622
627
  this.visit(lexpr, context),
623
628
  'NOT LIKE',
624
629
  this.visit(rexpr, context)
625
630
  ]);
626
631
  }
627
632
  else {
628
- return this.formatter.format([
633
+ return context.format([
629
634
  this.visit(lexpr, context),
630
635
  'LIKE',
631
636
  this.visit(rexpr, context)
632
637
  ]);
633
638
  }
634
639
  case 'AEXPR_ILIKE':
635
- const ilikeOp = this.deparseOperatorName(name);
640
+ const ilikeOp = this.deparseOperatorName(name, context);
636
641
  if (ilikeOp === '!~~*') {
637
- return this.formatter.format([
642
+ return context.format([
638
643
  this.visit(lexpr, context),
639
644
  'NOT ILIKE',
640
645
  this.visit(rexpr, context)
641
646
  ]);
642
647
  }
643
648
  else {
644
- return this.formatter.format([
649
+ return context.format([
645
650
  this.visit(lexpr, context),
646
651
  'ILIKE',
647
652
  this.visit(rexpr, context)
648
653
  ]);
649
654
  }
650
655
  case 'AEXPR_SIMILAR':
651
- const similarOp = this.deparseOperatorName(name);
656
+ const similarOp = this.deparseOperatorName(name, context);
652
657
  let rightExpr;
653
658
  if (rexpr && 'FuncCall' in rexpr &&
654
659
  rexpr.FuncCall?.funcname?.length === 2 &&
@@ -664,39 +669,39 @@ class Deparser {
664
669
  rightExpr = this.visit(rexpr, context);
665
670
  }
666
671
  if (similarOp === '!~') {
667
- return this.formatter.format([
672
+ return context.format([
668
673
  this.visit(lexpr, context),
669
674
  'NOT SIMILAR TO',
670
675
  rightExpr
671
676
  ]);
672
677
  }
673
678
  else {
674
- return this.formatter.format([
679
+ return context.format([
675
680
  this.visit(lexpr, context),
676
681
  'SIMILAR TO',
677
682
  rightExpr
678
683
  ]);
679
684
  }
680
685
  case 'AEXPR_BETWEEN':
681
- return this.formatter.format([
686
+ return context.format([
682
687
  this.visit(lexpr, context),
683
688
  'BETWEEN',
684
689
  this.visitBetweenRange(rexpr, context)
685
690
  ]);
686
691
  case 'AEXPR_NOT_BETWEEN':
687
- return this.formatter.format([
692
+ return context.format([
688
693
  this.visit(lexpr, context),
689
694
  'NOT BETWEEN',
690
695
  this.visitBetweenRange(rexpr, context)
691
696
  ]);
692
697
  case 'AEXPR_BETWEEN_SYM':
693
- return this.formatter.format([
698
+ return context.format([
694
699
  this.visit(lexpr, context),
695
700
  'BETWEEN SYMMETRIC',
696
701
  this.visitBetweenRange(rexpr, context)
697
702
  ]);
698
703
  case 'AEXPR_NOT_BETWEEN_SYM':
699
- return this.formatter.format([
704
+ return context.format([
700
705
  this.visit(lexpr, context),
701
706
  'NOT BETWEEN SYMMETRIC',
702
707
  this.visitBetweenRange(rexpr, context)
@@ -704,7 +709,7 @@ class Deparser {
704
709
  }
705
710
  throw new Error(`Unhandled A_Expr kind: ${kind}`);
706
711
  }
707
- deparseOperatorName(name) {
712
+ deparseOperatorName(name, context) {
708
713
  if (!name || name.length === 0) {
709
714
  return '';
710
715
  }
@@ -712,7 +717,7 @@ class Deparser {
712
717
  if (n.String) {
713
718
  return n.String.sval || n.String.str;
714
719
  }
715
- return this.visit(n, { parentNodeTypes: [] });
720
+ return this.visit(n, context);
716
721
  });
717
722
  if (parts.length > 1) {
718
723
  return `OPERATOR(${parts.join('.')})`;
@@ -849,15 +854,15 @@ class Deparser {
849
854
  output.push(this.RangeVar(node.relation, context));
850
855
  if (node.cols) {
851
856
  const cols = list_utils_1.ListUtils.unwrapList(node.cols);
852
- const insertContext = { ...context, insertColumns: true };
857
+ const insertContext = context.spawn('InsertStmt', { insertColumns: true });
853
858
  const columnNames = cols.map(col => this.visit(col, insertContext));
854
- if (this.formatter.isPretty()) {
859
+ if (context.isPretty()) {
855
860
  // Always format columns in multiline parentheses for pretty printing
856
- const indentedColumns = columnNames.map(col => this.formatter.indent(col));
861
+ const indentedColumns = columnNames.map(col => context.indent(col));
857
862
  output.push('(\n' + indentedColumns.join(',\n') + '\n)');
858
863
  }
859
864
  else {
860
- output.push(this.formatter.parens(columnNames.join(', ')));
865
+ output.push(context.parens(columnNames.join(', ')));
861
866
  }
862
867
  }
863
868
  if (node.selectStmt) {
@@ -879,7 +884,7 @@ class Deparser {
879
884
  else if (infer.indexElems) {
880
885
  const elems = list_utils_1.ListUtils.unwrapList(infer.indexElems);
881
886
  const indexElems = elems.map(elem => this.visit(elem, context));
882
- output.push(this.formatter.parens(indexElems.join(', ')));
887
+ output.push(context.parens(indexElems.join(', ')));
883
888
  }
884
889
  // Handle WHERE clause for conflict detection
885
890
  if (infer.whereClause) {
@@ -895,12 +900,12 @@ class Deparser {
895
900
  if (firstTarget.ResTarget?.val?.MultiAssignRef && targetList.every(target => target.ResTarget?.val?.MultiAssignRef)) {
896
901
  const sortedTargets = targetList.sort((a, b) => a.ResTarget.val.MultiAssignRef.colno - b.ResTarget.val.MultiAssignRef.colno);
897
902
  const names = sortedTargets.map(target => target.ResTarget.name);
898
- output.push(this.formatter.parens(names.join(', ')));
903
+ output.push(context.parens(names.join(', ')));
899
904
  output.push('=');
900
905
  output.push(this.visit(firstTarget.ResTarget.val.MultiAssignRef.source, context));
901
906
  }
902
907
  else {
903
- const updateContext = { ...context, update: true };
908
+ const updateContext = context.spawn('UpdateStmt', { update: true });
904
909
  const targets = targetList.map(target => this.visit(target, updateContext));
905
910
  output.push(targets.join(', '));
906
911
  }
@@ -954,12 +959,12 @@ class Deparser {
954
959
  }
955
960
  }
956
961
  const names = relatedTargets.map(t => t.ResTarget.name);
957
- const multiAssignment = `${this.formatter.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
962
+ const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
958
963
  assignmentParts.push(multiAssignment);
959
964
  }
960
965
  else {
961
966
  // Handle regular single-column assignment
962
- assignmentParts.push(this.visit(target, { ...context, update: true }));
967
+ assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
963
968
  processedTargets.add(i);
964
969
  }
965
970
  }
@@ -1051,14 +1056,14 @@ class Deparser {
1051
1056
  }
1052
1057
  if (node.ctes && node.ctes.length > 0) {
1053
1058
  const ctes = list_utils_1.ListUtils.unwrapList(node.ctes);
1054
- if (this.formatter.isPretty()) {
1059
+ if (context.isPretty()) {
1055
1060
  const cteStrings = ctes.map((cte, index) => {
1056
1061
  const cteStr = this.visit(cte, context);
1057
- const prefix = index === 0 ? this.formatter.newline() : ',' + this.formatter.newline();
1062
+ const prefix = index === 0 ? context.newline() : ',' + context.newline();
1058
1063
  if (this.containsMultilineStringLiteral(cteStr)) {
1059
1064
  return prefix + cteStr;
1060
1065
  }
1061
- return prefix + this.formatter.indent(cteStr);
1066
+ return prefix + context.indent(cteStr);
1062
1067
  });
1063
1068
  output.push(cteStrings.join(''));
1064
1069
  }
@@ -1144,14 +1149,14 @@ class Deparser {
1144
1149
  if (context.bool) {
1145
1150
  formatStr = '(%s)';
1146
1151
  }
1147
- const boolContext = { ...context, bool: true };
1152
+ const boolContext = context.spawn('BoolExpr', { bool: true });
1148
1153
  // explanation of our syntax/fix below:
1149
1154
  // return formatStr.replace('%s', andArgs); // ❌ Interprets $ as special syntax
1150
1155
  // return formatStr.replace('%s', () => andArgs); // ✅ Function callback prevents interpretation
1151
1156
  switch (boolop) {
1152
1157
  case 'AND_EXPR':
1153
- if (this.formatter.isPretty() && args.length > 1) {
1154
- const andArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' AND ');
1158
+ if (context.isPretty() && args.length > 1) {
1159
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('AND '));
1155
1160
  return formatStr.replace('%s', () => andArgs);
1156
1161
  }
1157
1162
  else {
@@ -1159,8 +1164,8 @@ class Deparser {
1159
1164
  return formatStr.replace('%s', () => andArgs);
1160
1165
  }
1161
1166
  case 'OR_EXPR':
1162
- if (this.formatter.isPretty() && args.length > 1) {
1163
- const orArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' OR ');
1167
+ if (context.isPretty() && args.length > 1) {
1168
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('OR '));
1164
1169
  return formatStr.replace('%s', () => orArgs);
1165
1170
  }
1166
1171
  else {
@@ -1291,9 +1296,9 @@ class Deparser {
1291
1296
  const timezone = this.visit(args[0], context);
1292
1297
  // Add parentheses around timestamp if it contains arithmetic operations
1293
1298
  if (args[1] && 'A_Expr' in args[1] && args[1].A_Expr?.kind === 'AEXPR_OP') {
1294
- const op = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(args[1].A_Expr.name));
1299
+ const op = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(args[1].A_Expr.name), context);
1295
1300
  if (op === '+' || op === '-' || op === '*' || op === '/') {
1296
- timestamp = this.formatter.parens(timestamp);
1301
+ timestamp = context.parens(timestamp);
1297
1302
  }
1298
1303
  }
1299
1304
  return `${timestamp} AT TIME ZONE ${timezone}`;
@@ -1363,14 +1368,14 @@ class Deparser {
1363
1368
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1364
1369
  }
1365
1370
  // Handle window frame specifications using the dedicated formatWindowFrame method
1366
- const frameClause = this.formatWindowFrame(node.over);
1371
+ const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1367
1372
  if (frameClause) {
1368
1373
  windowParts.push(frameClause);
1369
1374
  }
1370
1375
  if (windowParts.length > 0) {
1371
- if (this.formatter.isPretty() && windowParts.length > 1) {
1372
- const formattedParts = windowParts.map(part => this.formatter.indent(part));
1373
- result += ` OVER (${this.formatter.newline()}${formattedParts.join(this.formatter.newline())}${this.formatter.newline()})`;
1376
+ if (context.isPretty() && windowParts.length > 1) {
1377
+ const formattedParts = windowParts.map(part => context.indent(part));
1378
+ result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1374
1379
  }
1375
1380
  else {
1376
1381
  result += ` OVER (${windowParts.join(' ')})`;
@@ -1735,7 +1740,7 @@ class Deparser {
1735
1740
  }
1736
1741
  return this.quoteIfNeeded(colStr);
1737
1742
  });
1738
- output.push('AS', this.quoteIfNeeded(name) + this.formatter.parens(quotedColnames.join(', ')));
1743
+ output.push('AS', this.quoteIfNeeded(name) + context.parens(quotedColnames.join(', ')));
1739
1744
  }
1740
1745
  else {
1741
1746
  output.push('AS', this.quoteIfNeeded(name));
@@ -1747,7 +1752,7 @@ class Deparser {
1747
1752
  // Handle ONLY keyword for inheritance control (but not for type definitions, ALTER TYPE, or CREATE FOREIGN TABLE)
1748
1753
  if (node && (!('inh' in node) || node.inh === undefined) &&
1749
1754
  !context.parentNodeTypes.includes('CompositeTypeStmt') &&
1750
- !context.parentNodeTypes.includes('AlterTypeStmt') &&
1755
+ (!context.parentNodeTypes.includes('AlterTypeStmt') && context.objtype !== 'OBJECT_TYPE') &&
1751
1756
  !context.parentNodeTypes.includes('CreateForeignTableStmt')) {
1752
1757
  output.push('ONLY');
1753
1758
  }
@@ -2007,26 +2012,26 @@ class Deparser {
2007
2012
  output.push(this.visit(node.arg, context));
2008
2013
  }
2009
2014
  const args = list_utils_1.ListUtils.unwrapList(node.args);
2010
- if (this.formatter.isPretty() && args.length > 0) {
2015
+ if (context.isPretty() && args.length > 0) {
2011
2016
  for (const arg of args) {
2012
2017
  const whenClause = this.visit(arg, context);
2013
2018
  if (this.containsMultilineStringLiteral(whenClause)) {
2014
- output.push(this.formatter.newline() + whenClause);
2019
+ output.push(context.newline() + whenClause);
2015
2020
  }
2016
2021
  else {
2017
- output.push(this.formatter.newline() + this.formatter.indent(whenClause));
2022
+ output.push(context.newline() + context.indent(whenClause));
2018
2023
  }
2019
2024
  }
2020
2025
  if (node.defresult) {
2021
2026
  const elseResult = this.visit(node.defresult, context);
2022
2027
  if (this.containsMultilineStringLiteral(elseResult)) {
2023
- output.push(this.formatter.newline() + 'ELSE ' + elseResult);
2028
+ output.push(context.newline() + 'ELSE ' + elseResult);
2024
2029
  }
2025
2030
  else {
2026
- output.push(this.formatter.newline() + this.formatter.indent('ELSE ' + elseResult));
2031
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
2027
2032
  }
2028
2033
  }
2029
- output.push(this.formatter.newline() + 'END');
2034
+ output.push(context.newline() + 'END');
2030
2035
  return output.join(' ');
2031
2036
  }
2032
2037
  else {
@@ -2094,7 +2099,7 @@ class Deparser {
2094
2099
  }
2095
2100
  BooleanTest(node, context) {
2096
2101
  const output = [];
2097
- const boolContext = { ...context, bool: true };
2102
+ const boolContext = context.spawn('BooleanTest', { bool: true });
2098
2103
  output.push(this.visit(node.arg, boolContext));
2099
2104
  switch (node.booltesttype) {
2100
2105
  case 'IS_TRUE':
@@ -2245,31 +2250,31 @@ class Deparser {
2245
2250
  const elementStrs = elements.map(el => {
2246
2251
  return this.deparse(el, context);
2247
2252
  });
2248
- output.push(this.formatter.parens(elementStrs.join(', ')));
2253
+ output.push(context.parens(elementStrs.join(', ')));
2249
2254
  }
2250
2255
  }
2251
2256
  else if (node.tableElts) {
2252
2257
  const elements = list_utils_1.ListUtils.unwrapList(node.tableElts);
2253
2258
  const elementStrs = elements.map(el => {
2254
- return this.deparse(el, context);
2259
+ return this.deparse(el, context.spawn('CreateStmt'));
2255
2260
  });
2256
- if (this.formatter.isPretty()) {
2261
+ if (context.isPretty()) {
2257
2262
  const formattedElements = elementStrs.map(el => {
2258
2263
  const trimmedEl = el.trim();
2259
2264
  // Remove leading newlines from constraint elements to avoid extra blank lines
2260
2265
  if (trimmedEl.startsWith('\n')) {
2261
- return this.formatter.indent(trimmedEl.substring(1));
2266
+ return context.indent(trimmedEl.substring(1));
2262
2267
  }
2263
- return this.formatter.indent(trimmedEl);
2264
- }).join(',' + this.formatter.newline());
2265
- output.push('(' + this.formatter.newline() + formattedElements + this.formatter.newline() + ')');
2268
+ return context.indent(trimmedEl);
2269
+ }).join(',' + context.newline());
2270
+ output.push('(' + context.newline() + formattedElements + context.newline() + ')');
2266
2271
  }
2267
2272
  else {
2268
- output.push(this.formatter.parens(elementStrs.join(', ')));
2273
+ output.push(context.parens(elementStrs.join(', ')));
2269
2274
  }
2270
2275
  }
2271
2276
  else if (!node.partbound) {
2272
- output.push(this.formatter.parens(''));
2277
+ output.push(context.parens(''));
2273
2278
  }
2274
2279
  if (node.partbound && node.inhRelations && node.inhRelations.length > 0) {
2275
2280
  output.push('PARTITION OF');
@@ -2312,7 +2317,7 @@ class Deparser {
2312
2317
  output.push('INHERITS');
2313
2318
  const inherits = list_utils_1.ListUtils.unwrapList(node.inhRelations);
2314
2319
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2315
- output.push(this.formatter.parens(inheritStrs.join(', ')));
2320
+ output.push(context.parens(inheritStrs.join(', ')));
2316
2321
  }
2317
2322
  if (node.partspec) {
2318
2323
  output.push('PARTITION BY');
@@ -2350,7 +2355,7 @@ class Deparser {
2350
2355
  }
2351
2356
  // Handle table options like WITH (fillfactor=10)
2352
2357
  if (node.options && node.options.length > 0) {
2353
- const createStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateStmt'] };
2358
+ const createStmtContext = context.spawn('CreateStmt');
2354
2359
  const optionStrs = node.options.map((option) => {
2355
2360
  return this.deparse(option, createStmtContext);
2356
2361
  });
@@ -2373,7 +2378,7 @@ class Deparser {
2373
2378
  }
2374
2379
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2375
2380
  output.push('OPTIONS');
2376
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
2381
+ const columnContext = context.spawn('ColumnDef');
2377
2382
  const options = list_utils_1.ListUtils.unwrapList(node.fdwoptions).map(opt => this.visit(opt, columnContext));
2378
2383
  output.push(`(${options.join(', ')})`);
2379
2384
  }
@@ -2383,7 +2388,7 @@ class Deparser {
2383
2388
  if (node.constraints) {
2384
2389
  const constraints = list_utils_1.ListUtils.unwrapList(node.constraints);
2385
2390
  const constraintStrs = constraints.map(constraint => {
2386
- const columnConstraintContext = { ...context, isColumnConstraint: true };
2391
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
2387
2392
  return this.visit(constraint, columnConstraintContext);
2388
2393
  });
2389
2394
  output.push(...constraintStrs);
@@ -2423,24 +2428,24 @@ class Deparser {
2423
2428
  }
2424
2429
  break;
2425
2430
  case 'CONSTR_CHECK':
2426
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2427
- output.push('\n' + this.formatter.indent('CHECK'));
2431
+ if (context.isPretty() && !context.isColumnConstraint) {
2432
+ output.push('\n' + context.indent('CHECK'));
2428
2433
  }
2429
2434
  else {
2430
2435
  output.push('CHECK');
2431
2436
  }
2432
2437
  if (node.raw_expr) {
2433
- if (this.formatter.isPretty()) {
2438
+ if (context.isPretty()) {
2434
2439
  const checkExpr = this.visit(node.raw_expr, context);
2435
2440
  if (checkExpr.includes('\n')) {
2436
- output.push('(\n' + this.formatter.indent(checkExpr) + '\n)');
2441
+ output.push('(\n' + context.indent(checkExpr) + '\n)');
2437
2442
  }
2438
2443
  else {
2439
2444
  output.push(`(${checkExpr})`);
2440
2445
  }
2441
2446
  }
2442
2447
  else {
2443
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2448
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2444
2449
  }
2445
2450
  }
2446
2451
  // Handle NOT VALID for check constraints
@@ -2462,7 +2467,7 @@ class Deparser {
2462
2467
  }
2463
2468
  output.push('AS');
2464
2469
  if (node.raw_expr) {
2465
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2470
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2466
2471
  }
2467
2472
  output.push('STORED');
2468
2473
  break;
@@ -2520,8 +2525,8 @@ class Deparser {
2520
2525
  }
2521
2526
  break;
2522
2527
  case 'CONSTR_UNIQUE':
2523
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2524
- output.push('\n' + this.formatter.indent('UNIQUE'));
2528
+ if (context.isPretty() && !context.isColumnConstraint) {
2529
+ output.push('\n' + context.indent('UNIQUE'));
2525
2530
  }
2526
2531
  else {
2527
2532
  output.push('UNIQUE');
@@ -2543,15 +2548,15 @@ class Deparser {
2543
2548
  case 'CONSTR_FOREIGN':
2544
2549
  // Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
2545
2550
  if (!context.isColumnConstraint) {
2546
- if (this.formatter.isPretty()) {
2547
- output.push('\n' + this.formatter.indent('FOREIGN KEY'));
2551
+ if (context.isPretty()) {
2552
+ output.push('\n' + context.indent('FOREIGN KEY'));
2548
2553
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2549
2554
  const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2550
2555
  .map(attr => this.visit(attr, context))
2551
2556
  .join(', ');
2552
2557
  output.push(`(${fkAttrs})`);
2553
2558
  }
2554
- output.push('\n' + this.formatter.indent('REFERENCES'));
2559
+ output.push('\n' + context.indent('REFERENCES'));
2555
2560
  }
2556
2561
  else {
2557
2562
  output.push('FOREIGN KEY');
@@ -2568,7 +2573,7 @@ class Deparser {
2568
2573
  output.push('REFERENCES');
2569
2574
  }
2570
2575
  if (node.pktable) {
2571
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2576
+ if (context.isPretty() && !context.isColumnConstraint) {
2572
2577
  const lastIndex = output.length - 1;
2573
2578
  if (lastIndex >= 0 && output[lastIndex].includes('REFERENCES')) {
2574
2579
  output[lastIndex] += ' ' + this.RangeVar(node.pktable, context);
@@ -2585,7 +2590,7 @@ class Deparser {
2585
2590
  const pkAttrs = list_utils_1.ListUtils.unwrapList(node.pk_attrs)
2586
2591
  .map(attr => this.visit(attr, context))
2587
2592
  .join(', ');
2588
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2593
+ if (context.isPretty() && !context.isColumnConstraint) {
2589
2594
  const lastIndex = output.length - 1;
2590
2595
  if (lastIndex >= 0) {
2591
2596
  output[lastIndex] += ` (${pkAttrs})`;
@@ -2608,8 +2613,8 @@ class Deparser {
2608
2613
  matchClause = 'MATCH PARTIAL';
2609
2614
  break;
2610
2615
  }
2611
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2612
- output.push('\n' + this.formatter.indent(matchClause));
2616
+ if (context.isPretty() && !context.isColumnConstraint) {
2617
+ output.push('\n' + context.indent(matchClause));
2613
2618
  }
2614
2619
  else {
2615
2620
  output.push(matchClause);
@@ -2631,8 +2636,8 @@ class Deparser {
2631
2636
  updateClause += 'SET DEFAULT';
2632
2637
  break;
2633
2638
  }
2634
- if (this.formatter.isPretty()) {
2635
- output.push('\n' + this.formatter.indent(updateClause));
2639
+ if (context.isPretty()) {
2640
+ output.push('\n' + context.indent(updateClause));
2636
2641
  }
2637
2642
  else {
2638
2643
  output.push('ON UPDATE');
@@ -2655,8 +2660,8 @@ class Deparser {
2655
2660
  deleteClause += 'SET DEFAULT';
2656
2661
  break;
2657
2662
  }
2658
- if (this.formatter.isPretty()) {
2659
- output.push('\n' + this.formatter.indent(deleteClause));
2663
+ if (context.isPretty()) {
2664
+ output.push('\n' + context.indent(deleteClause));
2660
2665
  }
2661
2666
  else {
2662
2667
  output.push('ON DELETE');
@@ -2665,8 +2670,8 @@ class Deparser {
2665
2670
  }
2666
2671
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2667
2672
  if (node.skip_validation && !context.isDomainConstraint) {
2668
- if (this.formatter.isPretty() && !context.isColumnConstraint) {
2669
- output.push('\n' + this.formatter.indent('NOT VALID'));
2673
+ if (context.isPretty() && !context.isColumnConstraint) {
2674
+ output.push('\n' + context.indent('NOT VALID'));
2670
2675
  }
2671
2676
  else {
2672
2677
  output.push('NOT VALID');
@@ -2724,13 +2729,13 @@ class Deparser {
2724
2729
  // Handle deferrable constraints for all constraint types that support it
2725
2730
  if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2726
2731
  if (node.deferrable) {
2727
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2728
- output.push('\n' + this.formatter.indent('DEFERRABLE'));
2732
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2733
+ output.push('\n' + context.indent('DEFERRABLE'));
2729
2734
  if (node.initdeferred === true) {
2730
- output.push('\n' + this.formatter.indent('INITIALLY DEFERRED'));
2735
+ output.push('\n' + context.indent('INITIALLY DEFERRED'));
2731
2736
  }
2732
2737
  else if (node.initdeferred === false) {
2733
- output.push('\n' + this.formatter.indent('INITIALLY IMMEDIATE'));
2738
+ output.push('\n' + context.indent('INITIALLY IMMEDIATE'));
2734
2739
  }
2735
2740
  }
2736
2741
  else {
@@ -2744,15 +2749,15 @@ class Deparser {
2744
2749
  }
2745
2750
  }
2746
2751
  else if (node.deferrable === false) {
2747
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2748
- output.push('\n' + this.formatter.indent('NOT DEFERRABLE'));
2752
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2753
+ output.push('\n' + context.indent('NOT DEFERRABLE'));
2749
2754
  }
2750
2755
  else {
2751
2756
  output.push('NOT DEFERRABLE');
2752
2757
  }
2753
2758
  }
2754
2759
  }
2755
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2760
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2756
2761
  let result = '';
2757
2762
  for (let i = 0; i < output.length; i++) {
2758
2763
  if (output[i].startsWith('\n')) {
@@ -2770,12 +2775,12 @@ class Deparser {
2770
2775
  return output.join(' ');
2771
2776
  }
2772
2777
  SubLink(node, context) {
2773
- const subselect = this.formatter.parens(this.visit(node.subselect, context));
2778
+ const subselect = context.parens(this.visit(node.subselect, context));
2774
2779
  switch (node.subLinkType) {
2775
2780
  case 'ANY_SUBLINK':
2776
2781
  if (node.testexpr && node.operName) {
2777
2782
  const testExpr = this.visit(node.testexpr, context);
2778
- const operator = this.deparseOperatorName(node.operName);
2783
+ const operator = this.deparseOperatorName(node.operName, context);
2779
2784
  return `${testExpr} ${operator} ANY ${subselect}`;
2780
2785
  }
2781
2786
  else if (node.testexpr) {
@@ -2786,7 +2791,7 @@ class Deparser {
2786
2791
  case 'ALL_SUBLINK':
2787
2792
  if (node.testexpr && node.operName) {
2788
2793
  const testExpr = this.visit(node.testexpr, context);
2789
- const operator = this.deparseOperatorName(node.operName);
2794
+ const operator = this.deparseOperatorName(node.operName, context);
2790
2795
  return `${testExpr} ${operator} ALL ${subselect}`;
2791
2796
  }
2792
2797
  return subselect;
@@ -2828,7 +2833,7 @@ class Deparser {
2828
2833
  }
2829
2834
  // Only add frame clause if frameOptions indicates non-default framing
2830
2835
  if (node.frameOptions && node.frameOptions !== 1058) {
2831
- const frameClause = this.formatWindowFrame(node);
2836
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2832
2837
  if (frameClause) {
2833
2838
  windowParts.push(frameClause);
2834
2839
  }
@@ -2848,7 +2853,7 @@ class Deparser {
2848
2853
  }
2849
2854
  return output.join(' ');
2850
2855
  }
2851
- formatWindowFrame(node) {
2856
+ formatWindowFrame(node, context) {
2852
2857
  if (!node.frameOptions)
2853
2858
  return null;
2854
2859
  const frameOptions = node.frameOptions;
@@ -2857,7 +2862,7 @@ class Deparser {
2857
2862
  if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
2858
2863
  frameParts.push('RANGE');
2859
2864
  }
2860
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2865
+ else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2861
2866
  frameParts.push('ROWS');
2862
2867
  }
2863
2868
  else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
@@ -2878,8 +2883,8 @@ class Deparser {
2878
2883
  }
2879
2884
  else if (frameOptions === 18453) {
2880
2885
  if (node.startOffset && node.endOffset) {
2881
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2882
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2886
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2887
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2883
2888
  }
2884
2889
  }
2885
2890
  else if (frameOptions === 1557) {
@@ -2889,7 +2894,7 @@ class Deparser {
2889
2894
  else if (frameOptions === 16917) {
2890
2895
  boundsParts.push('CURRENT ROW');
2891
2896
  if (node.endOffset) {
2892
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2897
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2893
2898
  }
2894
2899
  }
2895
2900
  else if (frameOptions === 1058) {
@@ -2899,13 +2904,13 @@ class Deparser {
2899
2904
  // Handle start bound - prioritize explicit offset values over bit flags
2900
2905
  if (node.startOffset) {
2901
2906
  if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
2902
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2907
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2903
2908
  }
2904
2909
  else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
2905
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} FOLLOWING`);
2910
+ boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
2906
2911
  }
2907
2912
  else {
2908
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2913
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2909
2914
  }
2910
2915
  }
2911
2916
  else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
@@ -2918,13 +2923,13 @@ class Deparser {
2918
2923
  if (node.endOffset) {
2919
2924
  if (boundsParts.length > 0) {
2920
2925
  if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
2921
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} PRECEDING`);
2926
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
2922
2927
  }
2923
2928
  else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
2924
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2929
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2925
2930
  }
2926
2931
  else {
2927
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2932
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2928
2933
  }
2929
2934
  }
2930
2935
  }
@@ -3009,7 +3014,7 @@ class Deparser {
3009
3014
  const colnames = list_utils_1.ListUtils.unwrapList(node.aliascolnames);
3010
3015
  const colnameStrs = colnames.map(col => this.visit(col, context));
3011
3016
  // Don't add space before column list parentheses to match original formatting
3012
- output[output.length - 1] += this.formatter.parens(colnameStrs.join(', '));
3017
+ output[output.length - 1] += context.parens(colnameStrs.join(', '));
3013
3018
  }
3014
3019
  output.push('AS');
3015
3020
  // Handle materialization clauses
@@ -3020,7 +3025,7 @@ class Deparser {
3020
3025
  output.push('MATERIALIZED');
3021
3026
  }
3022
3027
  if (node.ctequery) {
3023
- output.push(this.formatter.parens(this.visit(node.ctequery, context)));
3028
+ output.push(context.parens(this.visit(node.ctequery, context)));
3024
3029
  }
3025
3030
  return output.join(' ');
3026
3031
  }
@@ -3096,7 +3101,7 @@ class Deparser {
3096
3101
  DistinctExpr(node, context) {
3097
3102
  const args = list_utils_1.ListUtils.unwrapList(node.args);
3098
3103
  if (args.length === 2) {
3099
- const literalContext = { ...context, isStringLiteral: true };
3104
+ const literalContext = context.spawn('DistinctExpr', { isStringLiteral: true });
3100
3105
  const left = this.visit(args[0], literalContext);
3101
3106
  const right = this.visit(args[1], literalContext);
3102
3107
  return `${left} IS DISTINCT FROM ${right}`;
@@ -3106,7 +3111,7 @@ class Deparser {
3106
3111
  NullIfExpr(node, context) {
3107
3112
  const args = list_utils_1.ListUtils.unwrapList(node.args);
3108
3113
  if (args.length === 2) {
3109
- const literalContext = { ...context, isStringLiteral: true };
3114
+ const literalContext = context.spawn('NullIfExpr', { isStringLiteral: true });
3110
3115
  const left = this.visit(args[0], literalContext);
3111
3116
  const right = this.visit(args[1], literalContext);
3112
3117
  return `NULLIF(${left}, ${right})`;
@@ -3185,7 +3190,7 @@ class Deparser {
3185
3190
  }
3186
3191
  RelabelType(node, context) {
3187
3192
  if (node.arg) {
3188
- const literalContext = { ...context, isStringLiteral: true };
3193
+ const literalContext = context.spawn('RelabelType', { isStringLiteral: true });
3189
3194
  return this.visit(node.arg, literalContext);
3190
3195
  }
3191
3196
  return '';
@@ -3204,7 +3209,7 @@ class Deparser {
3204
3209
  }
3205
3210
  ConvertRowtypeExpr(node, context) {
3206
3211
  if (node.arg) {
3207
- const literalContext = { ...context, isStringLiteral: true };
3212
+ const literalContext = context.spawn('ConvertRowtypeExpr', { isStringLiteral: true });
3208
3213
  return this.visit(node.arg, literalContext);
3209
3214
  }
3210
3215
  return '';
@@ -3235,10 +3240,10 @@ class Deparser {
3235
3240
  }
3236
3241
  if (node.aliases && node.aliases.length > 0) {
3237
3242
  const aliasStrs = list_utils_1.ListUtils.unwrapList(node.aliases).map(alias => this.visit(alias, context));
3238
- output.push(this.formatter.parens(aliasStrs.join(', ')));
3243
+ output.push(context.parens(aliasStrs.join(', ')));
3239
3244
  }
3240
3245
  if (node.options && node.options.length > 0) {
3241
- const viewContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ViewStmt'] };
3246
+ const viewContext = context.spawn('ViewStmt');
3242
3247
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
3243
3248
  .map(option => this.visit(option, viewContext));
3244
3249
  output.push(`WITH (${optionStrs.join(', ')})`);
@@ -3285,22 +3290,22 @@ class Deparser {
3285
3290
  }
3286
3291
  if (node.indexParams && node.indexParams.length > 0) {
3287
3292
  const paramStrs = list_utils_1.ListUtils.unwrapList(node.indexParams).map(param => this.visit(param, context));
3288
- output.push(this.formatter.parens(paramStrs.join(', ')));
3293
+ output.push(context.parens(paramStrs.join(', ')));
3289
3294
  }
3290
3295
  if (node.indexIncludingParams && node.indexIncludingParams.length > 0) {
3291
3296
  const includeStrs = list_utils_1.ListUtils.unwrapList(node.indexIncludingParams).map(param => this.visit(param, context));
3292
3297
  output.push('INCLUDE');
3293
- output.push(this.formatter.parens(includeStrs.join(', ')));
3298
+ output.push(context.parens(includeStrs.join(', ')));
3294
3299
  }
3295
3300
  if (node.whereClause) {
3296
3301
  output.push('WHERE');
3297
3302
  output.push(this.visit(node.whereClause, context));
3298
3303
  }
3299
3304
  if (node.options && node.options.length > 0) {
3300
- const indexContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'IndexStmt'] };
3305
+ const indexContext = context.spawn('IndexStmt');
3301
3306
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options).map(option => this.visit(option, indexContext));
3302
3307
  output.push('WITH');
3303
- output.push(this.formatter.parens(optionStrs.join(', ')));
3308
+ output.push(context.parens(optionStrs.join(', ')));
3304
3309
  }
3305
3310
  if (node.nulls_not_distinct) {
3306
3311
  output.push('NULLS NOT DISTINCT');
@@ -3317,7 +3322,7 @@ class Deparser {
3317
3322
  output.push(quote_utils_1.QuoteUtils.quote(node.name));
3318
3323
  }
3319
3324
  else if (node.expr) {
3320
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3325
+ output.push(context.parens(this.visit(node.expr, context)));
3321
3326
  }
3322
3327
  if (node.collation && node.collation.length > 0) {
3323
3328
  const collationStrs = list_utils_1.ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3334,7 +3339,7 @@ class Deparser {
3334
3339
  const stringData = this.getNodeData(opt.DefElem.arg);
3335
3340
  return `${opt.DefElem.defname}='${stringData.sval}'`;
3336
3341
  }
3337
- return this.visit(opt, context);
3342
+ return this.visit(opt, context.spawn('IndexElem'));
3338
3343
  });
3339
3344
  opclassStr += `(${opclassOpts.join(', ')})`;
3340
3345
  }
@@ -3368,7 +3373,7 @@ class Deparser {
3368
3373
  output.push(quote_utils_1.QuoteUtils.quote(node.name));
3369
3374
  }
3370
3375
  else if (node.expr) {
3371
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3376
+ output.push(context.parens(this.visit(node.expr, context)));
3372
3377
  }
3373
3378
  if (node.collation && node.collation.length > 0) {
3374
3379
  const collationStrs = list_utils_1.ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3516,16 +3521,16 @@ class Deparser {
3516
3521
  if (node.rarg && 'JoinExpr' in node.rarg && !node.rarg.JoinExpr.alias) {
3517
3522
  rargStr = `(${rargStr})`;
3518
3523
  }
3519
- if (this.formatter.isPretty()) {
3520
- output.push(this.formatter.newline() + joinStr + ' ' + rargStr);
3524
+ if (context.isPretty()) {
3525
+ output.push(context.newline() + joinStr + ' ' + rargStr);
3521
3526
  }
3522
3527
  else {
3523
3528
  output.push(joinStr + ' ' + rargStr);
3524
3529
  }
3525
3530
  }
3526
3531
  else {
3527
- if (this.formatter.isPretty()) {
3528
- output.push(this.formatter.newline() + joinStr);
3532
+ if (context.isPretty()) {
3533
+ output.push(context.newline() + joinStr);
3529
3534
  }
3530
3535
  else {
3531
3536
  output.push(joinStr);
@@ -3534,7 +3539,7 @@ class Deparser {
3534
3539
  if (node.usingClause && node.usingClause.length > 0) {
3535
3540
  const usingList = list_utils_1.ListUtils.unwrapList(node.usingClause);
3536
3541
  const columnNames = usingList.map(col => this.visit(col, context));
3537
- if (this.formatter.isPretty()) {
3542
+ if (context.isPretty()) {
3538
3543
  output.push(` USING (${columnNames.join(', ')})`);
3539
3544
  }
3540
3545
  else {
@@ -3543,14 +3548,14 @@ class Deparser {
3543
3548
  }
3544
3549
  else if (node.quals) {
3545
3550
  const qualsStr = this.visit(node.quals, context);
3546
- if (this.formatter.isPretty()) {
3551
+ if (context.isPretty()) {
3547
3552
  // For complex JOIN conditions, format with proper indentation
3548
3553
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3549
3554
  if (this.containsMultilineStringLiteral(qualsStr)) {
3550
3555
  output.push(` ON ${qualsStr}`);
3551
3556
  }
3552
3557
  else {
3553
- output.push(` ON${this.formatter.newline()}${this.formatter.indent(qualsStr)}`);
3558
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3554
3559
  }
3555
3560
  }
3556
3561
  else {
@@ -3562,7 +3567,7 @@ class Deparser {
3562
3567
  }
3563
3568
  }
3564
3569
  let result;
3565
- if (this.formatter.isPretty()) {
3570
+ if (context.isPretty()) {
3566
3571
  result = output.join('');
3567
3572
  }
3568
3573
  else {
@@ -4165,7 +4170,7 @@ class Deparser {
4165
4170
  }).filter((name) => name && name.trim());
4166
4171
  return items.join('.');
4167
4172
  }
4168
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DropStmt'], objtype: node.removeType };
4173
+ const objContext = context.spawn('DropStmt', { objtype: node.removeType });
4169
4174
  const objName = this.visit(objList, objContext);
4170
4175
  return objName;
4171
4176
  }).filter((name) => name && name.trim()).join(', ');
@@ -4265,7 +4270,7 @@ class Deparser {
4265
4270
  if (node.options && node.options.length > 0) {
4266
4271
  output.push('WITH');
4267
4272
  const optionsStr = list_utils_1.ListUtils.unwrapList(node.options)
4268
- .map(opt => this.visit(opt, context))
4273
+ .map(opt => this.visit(opt, context.spawn('CopyStmt')))
4269
4274
  .join(', ');
4270
4275
  output.push(`(${optionsStr})`);
4271
4276
  }
@@ -4311,21 +4316,19 @@ class Deparser {
4311
4316
  if (node.missing_ok) {
4312
4317
  output.push('IF EXISTS');
4313
4318
  }
4314
- const alterContext = node.objtype === 'OBJECT_TYPE'
4315
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
4316
- : context;
4319
+ const alterContext = context.spawn('AlterTableStmt', { objtype: node.objtype });
4317
4320
  if (node.relation) {
4318
4321
  const relationStr = this.RangeVar(node.relation, alterContext);
4319
4322
  output.push(relationStr);
4320
4323
  }
4321
4324
  if (node.cmds && node.cmds.length > 0) {
4322
4325
  const commands = list_utils_1.ListUtils.unwrapList(node.cmds);
4323
- if (this.formatter.isPretty()) {
4326
+ if (context.isPretty()) {
4324
4327
  const commandsStr = commands
4325
4328
  .map(cmd => {
4326
4329
  const cmdStr = this.visit(cmd, alterContext);
4327
4330
  if (cmdStr.startsWith('ADD CONSTRAINT') || cmdStr.startsWith('ADD ')) {
4328
- return this.formatter.newline() + this.formatter.indent(cmdStr);
4331
+ return context.newline() + context.indent(cmdStr);
4329
4332
  }
4330
4333
  return cmdStr;
4331
4334
  })
@@ -4346,7 +4349,7 @@ class Deparser {
4346
4349
  if (node.subtype) {
4347
4350
  switch (node.subtype) {
4348
4351
  case 'AT_AddColumn':
4349
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4352
+ if (context.objtype === 'OBJECT_TYPE') {
4350
4353
  output.push('ADD ATTRIBUTE');
4351
4354
  }
4352
4355
  else {
@@ -4366,14 +4369,14 @@ class Deparser {
4366
4369
  }
4367
4370
  if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4368
4371
  parts.push('OPTIONS');
4369
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
4372
+ const columnContext = context.spawn('ColumnDef');
4370
4373
  const options = list_utils_1.ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4371
4374
  parts.push(`(${options.join(', ')})`);
4372
4375
  }
4373
4376
  if (colDefData.constraints) {
4374
4377
  const constraints = list_utils_1.ListUtils.unwrapList(colDefData.constraints);
4375
4378
  const constraintStrs = constraints.map(constraint => {
4376
- const columnConstraintContext = { ...context, isColumnConstraint: true };
4379
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4377
4380
  return this.visit(constraint, columnConstraintContext);
4378
4381
  });
4379
4382
  parts.push(...constraintStrs);
@@ -4393,7 +4396,7 @@ class Deparser {
4393
4396
  break;
4394
4397
  case 'AT_DropColumn':
4395
4398
  if (node.missing_ok) {
4396
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4399
+ if (context.objtype === 'OBJECT_TYPE') {
4397
4400
  output.push('DROP ATTRIBUTE IF EXISTS');
4398
4401
  }
4399
4402
  else {
@@ -4401,7 +4404,7 @@ class Deparser {
4401
4404
  }
4402
4405
  }
4403
4406
  else {
4404
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4407
+ if (context.objtype === 'OBJECT_TYPE') {
4405
4408
  output.push('DROP ATTRIBUTE');
4406
4409
  }
4407
4410
  else {
@@ -4419,7 +4422,7 @@ class Deparser {
4419
4422
  }
4420
4423
  break;
4421
4424
  case 'AT_AlterColumnType':
4422
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4425
+ if (context.objtype === 'OBJECT_TYPE') {
4423
4426
  output.push('ALTER ATTRIBUTE');
4424
4427
  }
4425
4428
  else {
@@ -4483,7 +4486,7 @@ class Deparser {
4483
4486
  case 'AT_SetRelOptions':
4484
4487
  output.push('SET');
4485
4488
  if (node.def) {
4486
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetRelOptions' };
4489
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetRelOptions' });
4487
4490
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4488
4491
  .map(option => this.visit(option, alterTableContext))
4489
4492
  .join(', ');
@@ -4496,7 +4499,7 @@ class Deparser {
4496
4499
  case 'AT_ResetRelOptions':
4497
4500
  output.push('RESET');
4498
4501
  if (node.def) {
4499
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetRelOptions' };
4502
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetRelOptions' });
4500
4503
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4501
4504
  .map(option => this.visit(option, alterTableContext))
4502
4505
  .join(', ');
@@ -4591,7 +4594,7 @@ class Deparser {
4591
4594
  }
4592
4595
  output.push('SET');
4593
4596
  if (node.def) {
4594
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetOptions' };
4597
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetOptions' });
4595
4598
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4596
4599
  .map(option => this.visit(option, alterTableContext))
4597
4600
  .join(', ');
@@ -4608,7 +4611,7 @@ class Deparser {
4608
4611
  }
4609
4612
  output.push('RESET');
4610
4613
  if (node.def) {
4611
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetOptions' };
4614
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetOptions' });
4612
4615
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4613
4616
  .map(option => this.visit(option, alterTableContext))
4614
4617
  .join(', ');
@@ -4849,7 +4852,7 @@ class Deparser {
4849
4852
  }
4850
4853
  output.push('OPTIONS');
4851
4854
  if (node.def) {
4852
- const alterColumnContext = { ...context, alterColumnOptions: true };
4855
+ const alterColumnContext = context.spawn('AlterTableCmd', { alterColumnOptions: true });
4853
4856
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4854
4857
  .map(option => this.visit(option, alterColumnContext))
4855
4858
  .join(', ');
@@ -4889,7 +4892,7 @@ class Deparser {
4889
4892
  case 'AT_GenericOptions':
4890
4893
  output.push('OPTIONS');
4891
4894
  if (node.def) {
4892
- const alterTableContext = { ...context, alterTableOptions: true };
4895
+ const alterTableContext = context.spawn('AlterTableCmd', { alterTableOptions: true });
4893
4896
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4894
4897
  .map(option => this.visit(option, alterTableContext))
4895
4898
  .join(', ');
@@ -4993,7 +4996,7 @@ class Deparser {
4993
4996
  output.push(this.TypeName(node.returnType, context));
4994
4997
  }
4995
4998
  if (node.options && node.options.length > 0) {
4996
- const funcContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFunctionStmt'] };
4999
+ const funcContext = context.spawn('CreateFunctionStmt');
4997
5000
  const options = node.options.map((opt) => this.visit(opt, funcContext));
4998
5001
  output.push(...options);
4999
5002
  }
@@ -5080,7 +5083,7 @@ class Deparser {
5080
5083
  }
5081
5084
  output.push('AS', 'ENUM');
5082
5085
  if (node.vals && node.vals.length > 0) {
5083
- const enumContext = { ...context, isEnumValue: true };
5086
+ const enumContext = context.spawn('CreateEnumStmt', { isEnumValue: true });
5084
5087
  const values = list_utils_1.ListUtils.unwrapList(node.vals)
5085
5088
  .map(val => this.visit(val, enumContext))
5086
5089
  .join(', ');
@@ -5135,9 +5138,8 @@ class Deparser {
5135
5138
  output.push(roleName);
5136
5139
  }
5137
5140
  if (node.options) {
5138
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateRoleStmt'] };
5139
5141
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5140
- .map(option => this.visit(option, roleContext))
5142
+ .map(option => this.visit(option, context.spawn('CreateRoleStmt')))
5141
5143
  .join(' ');
5142
5144
  if (options) {
5143
5145
  output.push('WITH');
@@ -5168,7 +5170,7 @@ class Deparser {
5168
5170
  const stringData = this.getNodeData(node.arg);
5169
5171
  return `${node.defname}='${stringData.sval}'`;
5170
5172
  }
5171
- return `${node.defname}=${this.visit(node.arg, { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] })}`;
5173
+ return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
5172
5174
  }
5173
5175
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
5174
5176
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -5184,13 +5186,13 @@ class Deparser {
5184
5186
  if (!node.arg) {
5185
5187
  return `NO ${node.defname.toUpperCase()}`;
5186
5188
  }
5187
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5189
+ const defElemContext = context.spawn('DefElem');
5188
5190
  const argValue = this.visit(node.arg, defElemContext);
5189
5191
  return `${node.defname.toUpperCase()} ${argValue}`;
5190
5192
  }
5191
5193
  // Handle OPTIONS clause - use space format, not equals format
5192
5194
  if (node.arg) {
5193
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5195
+ const defElemContext = context.spawn('DefElem');
5194
5196
  const argValue = this.visit(node.arg, defElemContext);
5195
5197
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5196
5198
  const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
@@ -5244,7 +5246,7 @@ class Deparser {
5244
5246
  if (!node.arg) {
5245
5247
  return 'PASSWORD NULL';
5246
5248
  }
5247
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5249
+ const defElemContext = context.spawn('DefElem');
5248
5250
  const argValue = this.visit(node.arg, defElemContext);
5249
5251
  const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5250
5252
  ? `'${argValue}'`
@@ -5253,7 +5255,7 @@ class Deparser {
5253
5255
  }
5254
5256
  }
5255
5257
  if (node.arg) {
5256
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5258
+ const defElemContext = context.spawn('DefElem');
5257
5259
  const argValue = this.visit(node.arg, defElemContext);
5258
5260
  if (context.parentNodeTypes.includes('AlterOperatorStmt')) {
5259
5261
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
@@ -5329,7 +5331,7 @@ class Deparser {
5329
5331
  if (node.defname === 'sysid') {
5330
5332
  return `SYSID ${argValue}`;
5331
5333
  }
5332
- if (argValue === 'true') {
5334
+ if (String(argValue) === 'true') {
5333
5335
  // Handle special cases where the positive form has a different name
5334
5336
  if (node.defname === 'isreplication') {
5335
5337
  return 'REPLICATION';
@@ -5339,7 +5341,7 @@ class Deparser {
5339
5341
  }
5340
5342
  return node.defname.toUpperCase();
5341
5343
  }
5342
- else if (argValue === 'false') {
5344
+ else if (String(argValue) === 'false') {
5343
5345
  // Handle special cases where the negative form has a different name
5344
5346
  if (node.defname === 'canlogin') {
5345
5347
  return 'NOLOGIN';
@@ -5403,7 +5405,7 @@ class Deparser {
5403
5405
  }
5404
5406
  if (context.parentNodeTypes.includes('DoStmt')) {
5405
5407
  if (node.defname === 'as') {
5406
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5408
+ const defElemContext = context.spawn('DefElem');
5407
5409
  const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
5408
5410
  if (Array.isArray(argValue)) {
5409
5411
  const bodyParts = argValue;
@@ -5694,7 +5696,7 @@ class Deparser {
5694
5696
  }
5695
5697
  if (node.options && node.options.length > 0) {
5696
5698
  output.push('WITH');
5697
- const tsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateTableSpaceStmt'] };
5699
+ const tsContext = context.spawn('CreateTableSpaceStmt');
5698
5700
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5699
5701
  .map(option => this.visit(option, tsContext))
5700
5702
  .join(', ');
@@ -5724,7 +5726,7 @@ class Deparser {
5724
5726
  output.push('SET');
5725
5727
  }
5726
5728
  if (node.options && node.options.length > 0) {
5727
- const tablespaceContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableSpaceOptionsStmt'] };
5729
+ const tablespaceContext = context.spawn('AlterTableSpaceOptionsStmt');
5728
5730
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5729
5731
  .map(option => this.visit(option, tablespaceContext))
5730
5732
  .join(', ');
@@ -5741,7 +5743,7 @@ class Deparser {
5741
5743
  output.push(this.quoteIfNeeded(node.extname));
5742
5744
  }
5743
5745
  if (node.options && node.options.length > 0) {
5744
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateExtensionStmt'] };
5746
+ const extContext = context.spawn('CreateExtensionStmt');
5745
5747
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5746
5748
  .map(option => this.visit(option, extContext))
5747
5749
  .join(' ');
@@ -5755,7 +5757,7 @@ class Deparser {
5755
5757
  output.push(this.quoteIfNeeded(node.extname));
5756
5758
  }
5757
5759
  if (node.options && node.options.length > 0) {
5758
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterExtensionStmt'] };
5760
+ const extContext = context.spawn('AlterExtensionStmt');
5759
5761
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5760
5762
  .map(option => this.visit(option, extContext))
5761
5763
  .join(' ');
@@ -5769,7 +5771,7 @@ class Deparser {
5769
5771
  output.push(node.fdwname);
5770
5772
  }
5771
5773
  if (node.func_options && node.func_options.length > 0) {
5772
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5774
+ const fdwContext = context.spawn('CreateFdwStmt');
5773
5775
  const funcOptions = list_utils_1.ListUtils.unwrapList(node.func_options)
5774
5776
  .map(option => this.visit(option, fdwContext))
5775
5777
  .join(' ');
@@ -5777,7 +5779,7 @@ class Deparser {
5777
5779
  }
5778
5780
  if (node.options && node.options.length > 0) {
5779
5781
  output.push('OPTIONS');
5780
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5782
+ const fdwContext = context.spawn('CreateFdwStmt');
5781
5783
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5782
5784
  .map(option => this.visit(option, fdwContext))
5783
5785
  .join(', ');
@@ -5879,7 +5881,7 @@ class Deparser {
5879
5881
  output.push('ADD');
5880
5882
  if (node.def) {
5881
5883
  // Pass domain context to avoid adding constraint names for domain constraints
5882
- const domainContext = { ...context, isDomainConstraint: true };
5884
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5883
5885
  output.push(this.visit(node.def, domainContext));
5884
5886
  }
5885
5887
  break;
@@ -5905,7 +5907,7 @@ class Deparser {
5905
5907
  output.push('ADD');
5906
5908
  if (node.def) {
5907
5909
  // Pass domain context to avoid adding constraint names for domain constraints
5908
- const domainContext = { ...context, isDomainConstraint: true };
5910
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5909
5911
  output.push(this.visit(node.def, domainContext));
5910
5912
  }
5911
5913
  break;
@@ -6267,7 +6269,7 @@ class Deparser {
6267
6269
  output.push(`${operatorName}(${args.join(', ')})`);
6268
6270
  }
6269
6271
  else {
6270
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CommentStmt'], objtype: node.objtype };
6272
+ const objContext = context.spawn('CommentStmt', { objtype: node.objtype });
6271
6273
  output.push(this.visit(node.object, objContext));
6272
6274
  }
6273
6275
  }
@@ -6318,8 +6320,8 @@ class Deparser {
6318
6320
  output.push(initialParts.join(' '));
6319
6321
  // Add ON clause on new line in pretty mode
6320
6322
  if (node.table) {
6321
- if (this.formatter.isPretty()) {
6322
- output.push(this.formatter.newline() + this.formatter.indent(`ON ${this.RangeVar(node.table, context)}`));
6323
+ if (context.isPretty()) {
6324
+ output.push(context.newline() + context.indent(`ON ${this.RangeVar(node.table, context)}`));
6323
6325
  }
6324
6326
  else {
6325
6327
  output.push('ON');
@@ -6328,24 +6330,24 @@ class Deparser {
6328
6330
  }
6329
6331
  // Handle AS RESTRICTIVE/PERMISSIVE clause
6330
6332
  if (node.permissive === undefined) {
6331
- if (this.formatter.isPretty()) {
6332
- output.push(this.formatter.newline() + this.formatter.indent('AS RESTRICTIVE'));
6333
+ if (context.isPretty()) {
6334
+ output.push(context.newline() + context.indent('AS RESTRICTIVE'));
6333
6335
  }
6334
6336
  else {
6335
6337
  output.push('AS', 'RESTRICTIVE');
6336
6338
  }
6337
6339
  }
6338
6340
  else if (node.permissive === true) {
6339
- if (this.formatter.isPretty()) {
6340
- output.push(this.formatter.newline() + this.formatter.indent('AS PERMISSIVE'));
6341
+ if (context.isPretty()) {
6342
+ output.push(context.newline() + context.indent('AS PERMISSIVE'));
6341
6343
  }
6342
6344
  else {
6343
6345
  output.push('AS', 'PERMISSIVE');
6344
6346
  }
6345
6347
  }
6346
6348
  if (node.cmd_name) {
6347
- if (this.formatter.isPretty()) {
6348
- output.push(this.formatter.newline() + this.formatter.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6349
+ if (context.isPretty()) {
6350
+ output.push(context.newline() + context.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6349
6351
  }
6350
6352
  else {
6351
6353
  output.push('FOR', node.cmd_name.toUpperCase());
@@ -6353,8 +6355,8 @@ class Deparser {
6353
6355
  }
6354
6356
  if (node.roles && node.roles.length > 0) {
6355
6357
  const roles = list_utils_1.ListUtils.unwrapList(node.roles).map(role => this.visit(role, context));
6356
- if (this.formatter.isPretty()) {
6357
- output.push(this.formatter.newline() + this.formatter.indent(`TO ${roles.join(', ')}`));
6358
+ if (context.isPretty()) {
6359
+ output.push(context.newline() + context.indent(`TO ${roles.join(', ')}`));
6358
6360
  }
6359
6361
  else {
6360
6362
  output.push('TO');
@@ -6362,11 +6364,11 @@ class Deparser {
6362
6364
  }
6363
6365
  }
6364
6366
  if (node.qual) {
6365
- if (this.formatter.isPretty()) {
6367
+ if (context.isPretty()) {
6366
6368
  const qualExpr = this.visit(node.qual, context);
6367
- output.push(this.formatter.newline() + this.formatter.indent('USING ('));
6368
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(qualExpr)));
6369
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6369
+ output.push(context.newline() + context.indent('USING ('));
6370
+ output.push(context.newline() + context.indent(context.indent(qualExpr)));
6371
+ output.push(context.newline() + context.indent(')'));
6370
6372
  }
6371
6373
  else {
6372
6374
  output.push('USING');
@@ -6374,18 +6376,18 @@ class Deparser {
6374
6376
  }
6375
6377
  }
6376
6378
  if (node.with_check) {
6377
- if (this.formatter.isPretty()) {
6379
+ if (context.isPretty()) {
6378
6380
  const checkExpr = this.visit(node.with_check, context);
6379
- output.push(this.formatter.newline() + this.formatter.indent('WITH CHECK ('));
6380
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(checkExpr)));
6381
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6381
+ output.push(context.newline() + context.indent('WITH CHECK ('));
6382
+ output.push(context.newline() + context.indent(context.indent(checkExpr)));
6383
+ output.push(context.newline() + context.indent(')'));
6382
6384
  }
6383
6385
  else {
6384
6386
  output.push('WITH CHECK');
6385
6387
  output.push(`(${this.visit(node.with_check, context)})`);
6386
6388
  }
6387
6389
  }
6388
- return this.formatter.isPretty() ? output.join('') : output.join(' ');
6390
+ return context.isPretty() ? output.join('') : output.join(' ');
6389
6391
  }
6390
6392
  AlterPolicyStmt(node, context) {
6391
6393
  const output = ['ALTER', 'POLICY'];
@@ -6430,7 +6432,7 @@ class Deparser {
6430
6432
  }
6431
6433
  if (node.options && node.options.length > 0) {
6432
6434
  output.push('OPTIONS');
6433
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateUserMappingStmt'] };
6435
+ const userMappingContext = context.spawn('CreateUserMappingStmt');
6434
6436
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6435
6437
  output.push(`(${options.join(', ')})`);
6436
6438
  }
@@ -6613,7 +6615,7 @@ class Deparser {
6613
6615
  DoStmt(node, context) {
6614
6616
  const output = ['DO'];
6615
6617
  if (node.args && node.args.length > 0) {
6616
- const doContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DoStmt'] };
6618
+ const doContext = context.spawn('DoStmt');
6617
6619
  const args = list_utils_1.ListUtils.unwrapList(node.args);
6618
6620
  const processedArgs = [];
6619
6621
  for (const arg of args) {
@@ -6918,7 +6920,7 @@ class Deparser {
6918
6920
  ObjectWithArgs(node, context) {
6919
6921
  let result = '';
6920
6922
  if (node.objname && node.objname.length > 0) {
6921
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ObjectWithArgs'] };
6923
+ const objContext = context.spawn('ObjectWithArgs');
6922
6924
  const names = list_utils_1.ListUtils.unwrapList(node.objname).map(name => this.visit(name, objContext));
6923
6925
  result = names.join('.');
6924
6926
  }
@@ -6960,7 +6962,7 @@ class Deparser {
6960
6962
  }
6961
6963
  output.push('SET');
6962
6964
  if (node.options && node.options.length > 0) {
6963
- const alterOpContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterOperatorStmt'] };
6965
+ const alterOpContext = context.spawn('AlterOperatorStmt');
6964
6966
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, alterOpContext));
6965
6967
  output.push(`(${options.join(', ')})`);
6966
6968
  }
@@ -6972,13 +6974,13 @@ class Deparser {
6972
6974
  output.push(quote_utils_1.QuoteUtils.quote(node.fdwname));
6973
6975
  }
6974
6976
  if (node.func_options && node.func_options.length > 0) {
6975
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
6977
+ const fdwContext = context.spawn('AlterFdwStmt');
6976
6978
  const funcOptions = list_utils_1.ListUtils.unwrapList(node.func_options).map(opt => this.visit(opt, fdwContext));
6977
6979
  output.push(funcOptions.join(' '));
6978
6980
  }
6979
6981
  if (node.options && node.options.length > 0) {
6980
6982
  output.push('OPTIONS');
6981
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
6983
+ const fdwContext = context.spawn('AlterFdwStmt');
6982
6984
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, fdwContext));
6983
6985
  output.push(`(${options.join(', ')})`);
6984
6986
  }
@@ -7004,7 +7006,7 @@ class Deparser {
7004
7006
  if (node.options && node.options.length > 0) {
7005
7007
  output.push('OPTIONS');
7006
7008
  output.push('(');
7007
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignServerStmt'] };
7009
+ const optionsContext = context.spawn('CreateForeignServerStmt');
7008
7010
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
7009
7011
  output.push(options.join(', '));
7010
7012
  output.push(')');
@@ -7022,7 +7024,7 @@ class Deparser {
7022
7024
  if (node.options && node.options.length > 0) {
7023
7025
  output.push('OPTIONS');
7024
7026
  output.push('(');
7025
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterForeignServerStmt'] };
7027
+ const optionsContext = context.spawn('AlterForeignServerStmt');
7026
7028
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
7027
7029
  output.push(options.join(', '));
7028
7030
  output.push(')');
@@ -7043,7 +7045,7 @@ class Deparser {
7043
7045
  }
7044
7046
  if (node.options && node.options.length > 0) {
7045
7047
  output.push('OPTIONS');
7046
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterUserMappingStmt'] };
7048
+ const userMappingContext = context.spawn('AlterUserMappingStmt');
7047
7049
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
7048
7050
  output.push(`(${options.join(', ')})`);
7049
7051
  }
@@ -7103,7 +7105,7 @@ class Deparser {
7103
7105
  output.push(quote_utils_1.QuoteUtils.quote(node.local_schema));
7104
7106
  }
7105
7107
  if (node.options && node.options.length > 0) {
7106
- const importSchemaContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ImportForeignSchemaStmt'] };
7108
+ const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
7107
7109
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, importSchemaContext));
7108
7110
  output.push(`OPTIONS (${options.join(', ')})`);
7109
7111
  }
@@ -7138,7 +7140,7 @@ class Deparser {
7138
7140
  ExplainStmt(node, context) {
7139
7141
  const output = ['EXPLAIN'];
7140
7142
  if (node.options && node.options.length > 0) {
7141
- const explainContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ExplainStmt'] };
7143
+ const explainContext = context.spawn('ExplainStmt');
7142
7144
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(option => this.visit(option, explainContext));
7143
7145
  output.push(`(${options.join(', ')})`);
7144
7146
  }
@@ -7396,9 +7398,7 @@ class Deparser {
7396
7398
  output.push(this.RangeVar(node.relation, context));
7397
7399
  }
7398
7400
  else if (node.relation) {
7399
- const rangeVarContext = node.relationType === 'OBJECT_TYPE'
7400
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
7401
- : context;
7401
+ const rangeVarContext = context.spawn('RenameStmt', { objtype: node.relationType });
7402
7402
  // Add ON keyword for policy operations
7403
7403
  if (node.renameType === 'OBJECT_POLICY') {
7404
7404
  output.push('ON');
@@ -7580,7 +7580,7 @@ class Deparser {
7580
7580
  }
7581
7581
  }
7582
7582
  if (node.privileges && node.privileges.length > 0) {
7583
- const privilegeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'GrantStmt'] };
7583
+ const privilegeContext = context.spawn('GrantStmt');
7584
7584
  const privileges = list_utils_1.ListUtils.unwrapList(node.privileges)
7585
7585
  .map(priv => this.visit(priv, privilegeContext))
7586
7586
  .join(', ');
@@ -7888,8 +7888,8 @@ class Deparser {
7888
7888
  }
7889
7889
  if (node.action) {
7890
7890
  const actionStr = this.GrantStmt(node.action, context);
7891
- if (this.formatter.isPretty()) {
7892
- return output.join(' ') + this.formatter.newline() + this.formatter.indent(actionStr);
7891
+ if (context.isPretty()) {
7892
+ return output.join(' ') + context.newline() + context.indent(actionStr);
7893
7893
  }
7894
7894
  else {
7895
7895
  output.push(actionStr);
@@ -8040,7 +8040,7 @@ class Deparser {
8040
8040
  if (node.trigname) {
8041
8041
  output.push(quote_utils_1.QuoteUtils.quote(node.trigname));
8042
8042
  }
8043
- if (this.formatter.isPretty()) {
8043
+ if (context.isPretty()) {
8044
8044
  const components = [];
8045
8045
  const timing = [];
8046
8046
  if (node.timing & 2)
@@ -8066,31 +8066,31 @@ class Deparser {
8066
8066
  }
8067
8067
  if (node.events & 32)
8068
8068
  events.push('TRUNCATE');
8069
- components.push(this.formatter.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8069
+ components.push(context.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8070
8070
  if (node.relation) {
8071
- components.push(this.formatter.indent('ON ' + this.RangeVar(node.relation, context)));
8071
+ components.push(context.indent('ON ' + this.RangeVar(node.relation, context)));
8072
8072
  }
8073
8073
  if (node.transitionRels && node.transitionRels.length > 0) {
8074
8074
  const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
8075
8075
  .map(rel => this.visit(rel, context))
8076
8076
  .join(' ');
8077
- components.push(this.formatter.indent('REFERENCING ' + transitionClauses));
8077
+ components.push(context.indent('REFERENCING ' + transitionClauses));
8078
8078
  }
8079
8079
  if (node.deferrable) {
8080
- components.push(this.formatter.indent('DEFERRABLE'));
8080
+ components.push(context.indent('DEFERRABLE'));
8081
8081
  }
8082
8082
  if (node.initdeferred) {
8083
- components.push(this.formatter.indent('INITIALLY DEFERRED'));
8083
+ components.push(context.indent('INITIALLY DEFERRED'));
8084
8084
  }
8085
8085
  if (node.row) {
8086
- components.push(this.formatter.indent('FOR EACH ROW'));
8086
+ components.push(context.indent('FOR EACH ROW'));
8087
8087
  }
8088
8088
  else {
8089
- components.push(this.formatter.indent('FOR EACH STATEMENT'));
8089
+ components.push(context.indent('FOR EACH STATEMENT'));
8090
8090
  }
8091
8091
  if (node.whenClause) {
8092
8092
  const whenStr = 'WHEN (' + this.visit(node.whenClause, context) + ')';
8093
- components.push(this.formatter.indent(whenStr));
8093
+ components.push(context.indent(whenStr));
8094
8094
  }
8095
8095
  let executeStr = 'EXECUTE';
8096
8096
  if (node.funcname && node.funcname.length > 0) {
@@ -8100,7 +8100,7 @@ class Deparser {
8100
8100
  executeStr += ' PROCEDURE ' + funcName;
8101
8101
  }
8102
8102
  if (node.args && node.args.length > 0) {
8103
- const argContext = { ...context, isStringLiteral: true };
8103
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8104
8104
  const args = list_utils_1.ListUtils.unwrapList(node.args)
8105
8105
  .map(arg => this.visit(arg, argContext))
8106
8106
  .join(', ');
@@ -8109,8 +8109,8 @@ class Deparser {
8109
8109
  else {
8110
8110
  executeStr += '()';
8111
8111
  }
8112
- components.push(this.formatter.indent(executeStr));
8113
- return output.join(' ') + this.formatter.newline() + components.join(this.formatter.newline());
8112
+ components.push(context.indent(executeStr));
8113
+ return output.join(' ') + context.newline() + components.join(context.newline());
8114
8114
  }
8115
8115
  else {
8116
8116
  const timing = [];
@@ -8180,7 +8180,7 @@ class Deparser {
8180
8180
  }
8181
8181
  if (node.args && node.args.length > 0) {
8182
8182
  output.push('(');
8183
- const argContext = { ...context, isStringLiteral: true };
8183
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8184
8184
  const args = list_utils_1.ListUtils.unwrapList(node.args)
8185
8185
  .map(arg => this.visit(arg, argContext))
8186
8186
  .join(', ');
@@ -8217,7 +8217,7 @@ class Deparser {
8217
8217
  }
8218
8218
  if (node.whenclause && node.whenclause.length > 0) {
8219
8219
  output.push('WHEN');
8220
- const eventTriggerContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateEventTrigStmt'] };
8220
+ const eventTriggerContext = context.spawn('CreateEventTrigStmt');
8221
8221
  const conditions = list_utils_1.ListUtils.unwrapList(node.whenclause)
8222
8222
  .map(condition => this.visit(condition, eventTriggerContext))
8223
8223
  .join(' AND ');
@@ -8406,7 +8406,7 @@ class Deparser {
8406
8406
  output.push(sequenceName.join('.'));
8407
8407
  }
8408
8408
  if (node.options && node.options.length > 0) {
8409
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateSeqStmt'] };
8409
+ const seqContext = context.spawn('CreateSeqStmt');
8410
8410
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
8411
8411
  .filter(option => option != null && this.getNodeType(option) !== 'undefined')
8412
8412
  .map(option => {
@@ -8443,7 +8443,7 @@ class Deparser {
8443
8443
  output.push(sequenceName.join('.'));
8444
8444
  }
8445
8445
  if (node.options && node.options.length > 0) {
8446
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterSeqStmt'] };
8446
+ const seqContext = context.spawn('AlterSeqStmt');
8447
8447
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
8448
8448
  .filter(option => option && option !== undefined)
8449
8449
  .map(option => {
@@ -8472,7 +8472,7 @@ class Deparser {
8472
8472
  CompositeTypeStmt(node, context) {
8473
8473
  const output = ['CREATE', 'TYPE'];
8474
8474
  if (node.typevar) {
8475
- const typeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CompositeTypeStmt'] };
8475
+ const typeContext = context.spawn('CompositeTypeStmt');
8476
8476
  output.push(this.RangeVar(node.typevar, typeContext));
8477
8477
  }
8478
8478
  output.push('AS');
@@ -8573,7 +8573,7 @@ class Deparser {
8573
8573
  output.push(this.RoleSpec(node.role, context));
8574
8574
  }
8575
8575
  if (node.options) {
8576
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterRoleStmt'] };
8576
+ const roleContext = context.spawn('AlterRoleStmt');
8577
8577
  // Handle GROUP operations specially based on action value
8578
8578
  if (isGroupStatement) {
8579
8579
  const roleMembersOption = list_utils_1.ListUtils.unwrapList(node.options).find(option => option.DefElem && option.DefElem.defname === 'rolemembers');
@@ -8776,7 +8776,7 @@ class Deparser {
8776
8776
  }
8777
8777
  if (node.cols && node.cols.length > 0) {
8778
8778
  output.push('(');
8779
- const colContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AccessPriv'] };
8779
+ const colContext = context.spawn('AccessPriv');
8780
8780
  const columns = list_utils_1.ListUtils.unwrapList(node.cols).map(col => this.visit(col, colContext));
8781
8781
  output.push(columns.join(', '));
8782
8782
  output.push(')');
@@ -8856,7 +8856,7 @@ class Deparser {
8856
8856
  output.push(list_utils_1.ListUtils.unwrapList(node.defnames).map(name => this.visit(name, context)).join('.'));
8857
8857
  }
8858
8858
  if (node.definition && node.definition.length > 0) {
8859
- const defineStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefineStmt'] };
8859
+ const defineStmtContext = context.spawn('DefineStmt');
8860
8860
  const definitions = list_utils_1.ListUtils.unwrapList(node.definition).map(def => {
8861
8861
  return this.visit(def, defineStmtContext);
8862
8862
  });
@@ -9342,7 +9342,7 @@ class Deparser {
9342
9342
  const operatorNumber = node.number !== undefined ? node.number : 0;
9343
9343
  output.push(operatorNumber.toString());
9344
9344
  if (node.name) {
9345
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9345
+ const opClassContext = context.spawn('CreateOpClassItem');
9346
9346
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9347
9347
  }
9348
9348
  }
@@ -9352,7 +9352,7 @@ class Deparser {
9352
9352
  const functionNumber = node.number !== undefined ? node.number : 0;
9353
9353
  output.push(functionNumber.toString());
9354
9354
  if (node.name) {
9355
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9355
+ const opClassContext = context.spawn('CreateOpClassItem');
9356
9356
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9357
9357
  }
9358
9358
  }
@@ -10050,7 +10050,7 @@ class Deparser {
10050
10050
  output.push(this.ObjectWithArgs(node.func, context));
10051
10051
  }
10052
10052
  if (node.actions && node.actions.length > 0) {
10053
- const alterFunctionContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFunctionStmt'] };
10053
+ const alterFunctionContext = context.spawn('AlterFunctionStmt');
10054
10054
  const actionStrs = list_utils_1.ListUtils.unwrapList(node.actions).map(action => this.visit(action, alterFunctionContext));
10055
10055
  output.push(actionStrs.join(' '));
10056
10056
  }
@@ -10294,7 +10294,7 @@ class Deparser {
10294
10294
  CreateForeignTableStmt(node, context) {
10295
10295
  const output = ['CREATE FOREIGN TABLE'];
10296
10296
  if (node.base && node.base.relation) {
10297
- const relationContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10297
+ const relationContext = context.spawn('CreateForeignTableStmt');
10298
10298
  // Handle relation node directly as RangeVar since it contains the RangeVar properties
10299
10299
  output.push(this.RangeVar(node.base.relation, relationContext));
10300
10300
  }
@@ -10324,7 +10324,7 @@ class Deparser {
10324
10324
  output.push(quote_utils_1.QuoteUtils.quote(node.servername));
10325
10325
  }
10326
10326
  if (node.options && node.options.length > 0) {
10327
- const foreignTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10327
+ const foreignTableContext = context.spawn('CreateForeignTableStmt');
10328
10328
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, foreignTableContext));
10329
10329
  output.push(`OPTIONS (${optionStrs.join(', ')})`);
10330
10330
  }