pgsql-deparser 17.8.1 → 17.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/deparser.js CHANGED
@@ -1,6 +1,63 @@
1
+ import { DeparserContext } from './visitors/base';
1
2
  import { SqlFormatter } from './utils/sql-formatter';
2
3
  import { QuoteUtils } from './utils/quote-utils';
3
4
  import { ListUtils } from './utils/list-utils';
5
+ /**
6
+ * List of real PostgreSQL built-in types as they appear in pg_catalog.pg_type.typname.
7
+ * These are stored in lowercase in PostgreSQL system catalogs.
8
+ * Use these for lookups, validations, or introspection logic.
9
+ */
10
+ const pgCatalogTypes = [
11
+ // Integers
12
+ 'int2', // smallint
13
+ 'int4', // integer
14
+ 'int8', // bigint
15
+ // Floating-point & numeric
16
+ 'float4', // real
17
+ 'float8', // double precision
18
+ 'numeric', // arbitrary precision (aka "decimal")
19
+ // Text & string
20
+ 'varchar', // variable-length string
21
+ 'char', // internal one-byte type (used in special cases)
22
+ 'bpchar', // blank-padded char(n)
23
+ 'text', // unlimited string
24
+ 'bool', // boolean
25
+ // Dates & times
26
+ 'date', // calendar date
27
+ 'time', // time without time zone
28
+ 'timetz', // time with time zone
29
+ 'timestamp', // timestamp without time zone
30
+ 'timestamptz', // timestamp with time zone
31
+ 'interval', // duration
32
+ // Binary & structured
33
+ 'bytea', // binary data
34
+ 'uuid', // universally unique identifier
35
+ // JSON & XML
36
+ 'json', // textual JSON
37
+ 'jsonb', // binary JSON
38
+ 'xml', // XML format
39
+ // Money & bitstrings
40
+ 'money', // currency value
41
+ 'bit', // fixed-length bit string
42
+ 'varbit', // variable-length bit string
43
+ // Network types
44
+ 'inet', // IPv4 or IPv6 address
45
+ 'cidr', // network address
46
+ 'macaddr', // MAC address (6 bytes)
47
+ 'macaddr8' // MAC address (8 bytes)
48
+ ];
49
+ /**
50
+ * Parser-level type aliases accepted by PostgreSQL SQL syntax,
51
+ * but not present in pg_catalog.pg_type. These are resolved to
52
+ * real types during parsing and never appear in introspection.
53
+ */
54
+ const pgCatalogTypeAliases = [
55
+ ['numeric', ['decimal', 'dec']],
56
+ ['int4', ['int', 'integer']],
57
+ ['float8', ['float']],
58
+ ['bpchar', ['character']],
59
+ ['varchar', ['character varying']]
60
+ ];
4
61
  // Type guards for better type safety
5
62
  function isParseResult(obj) {
6
63
  // A ParseResult is an object that could have stmts (but not required)
@@ -44,11 +101,9 @@ function isWrappedParseResult(obj) {
44
101
  * compatibility and wraps them internally for consistent processing.
45
102
  */
46
103
  export class Deparser {
47
- formatter;
48
104
  tree;
49
105
  options;
50
106
  constructor(tree, opts = {}) {
51
- this.formatter = new SqlFormatter(opts.newline, opts.tab, opts.pretty);
52
107
  // Set default options
53
108
  this.options = {
54
109
  functionDelimiter: '$$',
@@ -85,15 +140,17 @@ export class Deparser {
85
140
  return new Deparser(query, opts).deparseQuery();
86
141
  }
87
142
  deparseQuery() {
143
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
144
+ const context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
88
145
  return this.tree
89
146
  .map(node => {
90
147
  // All nodes should go through the standard deparse method
91
148
  // which will route to the appropriate handler
92
- const result = this.deparse(node);
149
+ const result = this.deparse(node, context);
93
150
  return result || '';
94
151
  })
95
152
  .filter(result => result !== '')
96
- .join(this.formatter.newline() + this.formatter.newline());
153
+ .join(context.newline() + context.newline());
97
154
  }
98
155
  /**
99
156
  * Get the appropriate function delimiter based on the body content
@@ -107,10 +164,14 @@ export class Deparser {
107
164
  }
108
165
  return delimiter;
109
166
  }
110
- deparse(node, context = { parentNodeTypes: [] }) {
167
+ deparse(node, context) {
111
168
  if (node == null) {
112
169
  return null;
113
170
  }
171
+ if (!context) {
172
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
173
+ context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
174
+ }
114
175
  if (typeof node === 'number' || node instanceof Number) {
115
176
  return node.toString();
116
177
  }
@@ -122,7 +183,11 @@ export class Deparser {
122
183
  throw new Error(`Error deparsing ${nodeType}: ${error.message}`);
123
184
  }
124
185
  }
125
- visit(node, context = { parentNodeTypes: [] }) {
186
+ visit(node, context) {
187
+ if (!context) {
188
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
189
+ context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
190
+ }
126
191
  const nodeType = this.getNodeType(node);
127
192
  // Handle empty objects
128
193
  if (!nodeType) {
@@ -131,11 +196,7 @@ export class Deparser {
131
196
  const nodeData = this.getNodeData(node);
132
197
  const methodName = nodeType;
133
198
  if (typeof this[methodName] === 'function') {
134
- const childContext = {
135
- ...context,
136
- parentNodeTypes: [...context.parentNodeTypes, nodeType]
137
- };
138
- const result = this[methodName](nodeData, childContext);
199
+ const result = this[methodName](nodeData, context);
139
200
  return result;
140
201
  }
141
202
  throw new Error(`Deparser does not handle node type: ${nodeType}`);
@@ -161,7 +222,7 @@ export class Deparser {
161
222
  .filter((rawStmt) => rawStmt != null)
162
223
  .map((rawStmt) => this.RawStmt(rawStmt, context))
163
224
  .filter((result) => result !== '')
164
- .join(this.formatter.newline() + this.formatter.newline());
225
+ .join(context.newline() + context.newline());
165
226
  }
166
227
  RawStmt(node, context) {
167
228
  if (!node.stmt) {
@@ -181,7 +242,7 @@ export class Deparser {
181
242
  }
182
243
  if (!node.op || node.op === 'SETOP_NONE') {
183
244
  if (node.valuesLists == null) {
184
- if (!this.formatter.isPretty() || !node.targetList) {
245
+ if (!context.isPretty() || !node.targetList) {
185
246
  output.push('SELECT');
186
247
  }
187
248
  }
@@ -201,7 +262,7 @@ export class Deparser {
201
262
  node.rarg.limitOffset ||
202
263
  node.rarg.withClause);
203
264
  if (leftNeedsParens) {
204
- output.push(this.formatter.parens(leftStmt));
265
+ output.push(context.parens(leftStmt));
205
266
  }
206
267
  else {
207
268
  output.push(leftStmt);
@@ -223,7 +284,7 @@ export class Deparser {
223
284
  output.push('ALL');
224
285
  }
225
286
  if (rightNeedsParens) {
226
- output.push(this.formatter.parens(rightStmt));
287
+ output.push(context.parens(rightStmt));
227
288
  }
228
289
  else {
229
290
  output.push(rightStmt);
@@ -235,20 +296,20 @@ export class Deparser {
235
296
  const distinctClause = ListUtils.unwrapList(node.distinctClause);
236
297
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
237
298
  const clause = distinctClause
238
- .map(e => this.visit(e, { ...context, select: true }))
299
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
239
300
  .join(', ');
240
- distinctPart = ' DISTINCT ON ' + this.formatter.parens(clause);
301
+ distinctPart = ' DISTINCT ON ' + context.parens(clause);
241
302
  }
242
303
  else {
243
304
  distinctPart = ' DISTINCT';
244
305
  }
245
- if (!this.formatter.isPretty()) {
306
+ if (!context.isPretty()) {
246
307
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
247
308
  output.push('DISTINCT ON');
248
309
  const clause = distinctClause
249
- .map(e => this.visit(e, { ...context, select: true }))
310
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
250
311
  .join(', ');
251
- output.push(this.formatter.parens(clause));
312
+ output.push(context.parens(clause));
252
313
  }
253
314
  else {
254
315
  output.push('DISTINCT');
@@ -257,22 +318,41 @@ export class Deparser {
257
318
  }
258
319
  if (node.targetList) {
259
320
  const targetList = ListUtils.unwrapList(node.targetList);
260
- if (this.formatter.isPretty()) {
261
- const targetStrings = targetList
262
- .map(e => {
263
- const targetStr = this.visit(e, { ...context, select: true });
264
- if (this.containsMultilineStringLiteral(targetStr)) {
265
- return targetStr;
321
+ if (context.isPretty()) {
322
+ if (targetList.length === 1) {
323
+ const targetNode = targetList[0];
324
+ const target = this.visit(targetNode, context.spawn('SelectStmt', { select: true }));
325
+ // Check if single target is complex - if so, use multiline format
326
+ if (this.isComplexSelectTarget(targetNode)) {
327
+ output.push('SELECT' + distinctPart);
328
+ if (this.containsMultilineStringLiteral(target)) {
329
+ output.push(target);
330
+ }
331
+ else {
332
+ output.push(context.indent(target));
333
+ }
266
334
  }
267
- return this.formatter.indent(targetStr);
268
- });
269
- const formattedTargets = targetStrings.join(',' + this.formatter.newline());
270
- output.push('SELECT' + distinctPart);
271
- output.push(formattedTargets);
335
+ else {
336
+ output.push('SELECT' + distinctPart + ' ' + target);
337
+ }
338
+ }
339
+ else {
340
+ const targetStrings = targetList
341
+ .map(e => {
342
+ const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
343
+ if (this.containsMultilineStringLiteral(targetStr)) {
344
+ return targetStr;
345
+ }
346
+ return context.indent(targetStr);
347
+ });
348
+ const formattedTargets = targetStrings.join(',' + context.newline());
349
+ output.push('SELECT' + distinctPart);
350
+ output.push(formattedTargets);
351
+ }
272
352
  }
273
353
  else {
274
354
  const targets = targetList
275
- .map(e => this.visit(e, { ...context, select: true }))
355
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
276
356
  .join(', ');
277
357
  output.push(targets);
278
358
  }
@@ -284,22 +364,22 @@ export class Deparser {
284
364
  if (node.fromClause) {
285
365
  const fromList = ListUtils.unwrapList(node.fromClause);
286
366
  const fromItems = fromList
287
- .map(e => this.deparse(e, { ...context, from: true }))
367
+ .map(e => this.deparse(e, context.spawn('SelectStmt', { from: true })))
288
368
  .join(', ');
289
369
  output.push('FROM ' + fromItems.trim());
290
370
  }
291
371
  if (node.whereClause) {
292
- if (this.formatter.isPretty()) {
372
+ if (context.isPretty()) {
293
373
  output.push('WHERE');
294
374
  const whereExpr = this.visit(node.whereClause, context);
295
- const lines = whereExpr.split(this.formatter.newline());
375
+ const lines = whereExpr.split(context.newline());
296
376
  const indentedLines = lines.map((line, index) => {
297
377
  if (index === 0) {
298
- return this.formatter.indent(line);
378
+ return context.indent(line);
299
379
  }
300
380
  return line;
301
381
  });
302
- output.push(indentedLines.join(this.formatter.newline()));
382
+ output.push(indentedLines.join(context.newline()));
303
383
  }
304
384
  else {
305
385
  output.push('WHERE');
@@ -307,45 +387,61 @@ export class Deparser {
307
387
  }
308
388
  }
309
389
  if (node.valuesLists) {
310
- output.push('VALUES');
311
- const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
312
- const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
313
- return this.formatter.parens(values.join(', '));
314
- });
315
- output.push(lists.join(', '));
390
+ if (context.isPretty()) {
391
+ output.push('VALUES');
392
+ const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
393
+ const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
394
+ return context.parens(values.join(', '));
395
+ });
396
+ const indentedTuples = lists.map(tuple => {
397
+ if (this.containsMultilineStringLiteral(tuple)) {
398
+ return tuple;
399
+ }
400
+ return context.indent(tuple);
401
+ });
402
+ output.push(indentedTuples.join(',\n'));
403
+ }
404
+ else {
405
+ output.push('VALUES');
406
+ const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
407
+ const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
408
+ return context.parens(values.join(', '));
409
+ });
410
+ output.push(lists.join(', '));
411
+ }
316
412
  }
317
413
  if (node.groupClause) {
318
414
  const groupList = ListUtils.unwrapList(node.groupClause);
319
- if (this.formatter.isPretty()) {
415
+ if (context.isPretty()) {
320
416
  const groupItems = groupList
321
417
  .map(e => {
322
- const groupStr = this.visit(e, { ...context, group: true });
418
+ const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
323
419
  if (this.containsMultilineStringLiteral(groupStr)) {
324
420
  return groupStr;
325
421
  }
326
- return this.formatter.indent(groupStr);
422
+ return context.indent(groupStr);
327
423
  })
328
- .join(',' + this.formatter.newline());
424
+ .join(',' + context.newline());
329
425
  output.push('GROUP BY');
330
426
  output.push(groupItems);
331
427
  }
332
428
  else {
333
429
  output.push('GROUP BY');
334
430
  const groupItems = groupList
335
- .map(e => this.visit(e, { ...context, group: true }))
431
+ .map(e => this.visit(e, context.spawn('SelectStmt', { group: true })))
336
432
  .join(', ');
337
433
  output.push(groupItems);
338
434
  }
339
435
  }
340
436
  if (node.havingClause) {
341
- if (this.formatter.isPretty()) {
437
+ if (context.isPretty()) {
342
438
  output.push('HAVING');
343
439
  const havingStr = this.visit(node.havingClause, context);
344
440
  if (this.containsMultilineStringLiteral(havingStr)) {
345
441
  output.push(havingStr);
346
442
  }
347
443
  else {
348
- output.push(this.formatter.indent(havingStr));
444
+ output.push(context.indent(havingStr));
349
445
  }
350
446
  }
351
447
  else {
@@ -363,23 +459,23 @@ export class Deparser {
363
459
  }
364
460
  if (node.sortClause) {
365
461
  const sortList = ListUtils.unwrapList(node.sortClause);
366
- if (this.formatter.isPretty()) {
462
+ if (context.isPretty()) {
367
463
  const sortItems = sortList
368
464
  .map(e => {
369
- const sortStr = this.visit(e, { ...context, sort: true });
465
+ const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
370
466
  if (this.containsMultilineStringLiteral(sortStr)) {
371
467
  return sortStr;
372
468
  }
373
- return this.formatter.indent(sortStr);
469
+ return context.indent(sortStr);
374
470
  })
375
- .join(',' + this.formatter.newline());
471
+ .join(',' + context.newline());
376
472
  output.push('ORDER BY');
377
473
  output.push(sortItems);
378
474
  }
379
475
  else {
380
476
  output.push('ORDER BY');
381
477
  const sortItems = sortList
382
- .map(e => this.visit(e, { ...context, sort: true }))
478
+ .map(e => this.visit(e, context.spawn('SelectStmt', { sort: true })))
383
479
  .join(', ');
384
480
  output.push(sortItems);
385
481
  }
@@ -397,9 +493,9 @@ export class Deparser {
397
493
  .join(' ');
398
494
  output.push(lockingClauses);
399
495
  }
400
- if (this.formatter.isPretty()) {
496
+ if (context.isPretty()) {
401
497
  const filteredOutput = output.filter(item => item.trim() !== '');
402
- return filteredOutput.join(this.formatter.newline());
498
+ return filteredOutput.join(context.newline());
403
499
  }
404
500
  return output.join(' ');
405
501
  }
@@ -411,13 +507,13 @@ export class Deparser {
411
507
  switch (kind) {
412
508
  case 'AEXPR_OP':
413
509
  if (lexpr && rexpr) {
414
- const operator = this.deparseOperatorName(name);
510
+ const operator = this.deparseOperatorName(name, context);
415
511
  let leftExpr = this.visit(lexpr, context);
416
512
  let rightExpr = this.visit(rexpr, context);
417
513
  // Check if left expression needs parentheses
418
514
  let leftNeedsParens = false;
419
515
  if (lexpr && 'A_Expr' in lexpr && lexpr.A_Expr?.kind === 'AEXPR_OP') {
420
- const leftOp = this.deparseOperatorName(ListUtils.unwrapList(lexpr.A_Expr.name));
516
+ const leftOp = this.deparseOperatorName(ListUtils.unwrapList(lexpr.A_Expr.name), context);
421
517
  if (this.needsParentheses(leftOp, operator, 'left')) {
422
518
  leftNeedsParens = true;
423
519
  }
@@ -426,12 +522,12 @@ export class Deparser {
426
522
  leftNeedsParens = true;
427
523
  }
428
524
  if (leftNeedsParens) {
429
- leftExpr = this.formatter.parens(leftExpr);
525
+ leftExpr = context.parens(leftExpr);
430
526
  }
431
527
  // Check if right expression needs parentheses
432
528
  let rightNeedsParens = false;
433
529
  if (rexpr && 'A_Expr' in rexpr && rexpr.A_Expr?.kind === 'AEXPR_OP') {
434
- const rightOp = this.deparseOperatorName(ListUtils.unwrapList(rexpr.A_Expr.name));
530
+ const rightOp = this.deparseOperatorName(ListUtils.unwrapList(rexpr.A_Expr.name), context);
435
531
  if (this.needsParentheses(rightOp, operator, 'right')) {
436
532
  rightNeedsParens = true;
437
533
  }
@@ -440,42 +536,42 @@ export class Deparser {
440
536
  rightNeedsParens = true;
441
537
  }
442
538
  if (rightNeedsParens) {
443
- rightExpr = this.formatter.parens(rightExpr);
539
+ rightExpr = context.parens(rightExpr);
444
540
  }
445
- return this.formatter.format([leftExpr, operator, rightExpr]);
541
+ return context.format([leftExpr, operator, rightExpr]);
446
542
  }
447
543
  else if (rexpr) {
448
- return this.formatter.format([
449
- this.deparseOperatorName(name),
544
+ return context.format([
545
+ this.deparseOperatorName(name, context),
450
546
  this.visit(rexpr, context)
451
547
  ]);
452
548
  }
453
549
  break;
454
550
  case 'AEXPR_OP_ANY':
455
- return this.formatter.format([
551
+ return context.format([
456
552
  this.visit(lexpr, context),
457
- this.deparseOperatorName(name),
553
+ this.deparseOperatorName(name, context),
458
554
  'ANY',
459
- this.formatter.parens(this.visit(rexpr, context))
555
+ context.parens(this.visit(rexpr, context))
460
556
  ]);
461
557
  case 'AEXPR_OP_ALL':
462
- return this.formatter.format([
558
+ return context.format([
463
559
  this.visit(lexpr, context),
464
- this.deparseOperatorName(name),
560
+ this.deparseOperatorName(name, context),
465
561
  'ALL',
466
- this.formatter.parens(this.visit(rexpr, context))
562
+ context.parens(this.visit(rexpr, context))
467
563
  ]);
468
564
  case 'AEXPR_DISTINCT': {
469
565
  let leftExpr = this.visit(lexpr, context);
470
566
  let rightExpr = this.visit(rexpr, context);
471
567
  // Add parentheses for complex expressions
472
568
  if (lexpr && this.isComplexExpression(lexpr)) {
473
- leftExpr = this.formatter.parens(leftExpr);
569
+ leftExpr = context.parens(leftExpr);
474
570
  }
475
571
  if (rexpr && this.isComplexExpression(rexpr)) {
476
- rightExpr = this.formatter.parens(rightExpr);
572
+ rightExpr = context.parens(rightExpr);
477
573
  }
478
- return this.formatter.format([
574
+ return context.format([
479
575
  leftExpr,
480
576
  'IS DISTINCT FROM',
481
577
  rightExpr
@@ -486,75 +582,75 @@ export class Deparser {
486
582
  let rightExpr = this.visit(rexpr, context);
487
583
  // Add parentheses for complex expressions
488
584
  if (lexpr && this.isComplexExpression(lexpr)) {
489
- leftExpr = this.formatter.parens(leftExpr);
585
+ leftExpr = context.parens(leftExpr);
490
586
  }
491
587
  if (rexpr && this.isComplexExpression(rexpr)) {
492
- rightExpr = this.formatter.parens(rightExpr);
588
+ rightExpr = context.parens(rightExpr);
493
589
  }
494
- return this.formatter.format([
590
+ return context.format([
495
591
  leftExpr,
496
592
  'IS NOT DISTINCT FROM',
497
593
  rightExpr
498
594
  ]);
499
595
  }
500
596
  case 'AEXPR_NULLIF':
501
- return this.formatter.format([
597
+ return context.format([
502
598
  'NULLIF',
503
- this.formatter.parens([
599
+ context.parens([
504
600
  this.visit(lexpr, context),
505
601
  this.visit(rexpr, context)
506
602
  ].join(', '))
507
603
  ]);
508
604
  case 'AEXPR_IN':
509
- const inOperator = this.deparseOperatorName(name);
605
+ const inOperator = this.deparseOperatorName(name, context);
510
606
  if (inOperator === '<>' || inOperator === '!=') {
511
- return this.formatter.format([
607
+ return context.format([
512
608
  this.visit(lexpr, context),
513
609
  'NOT IN',
514
- this.formatter.parens(this.visit(rexpr, context))
610
+ context.parens(this.visit(rexpr, context))
515
611
  ]);
516
612
  }
517
613
  else {
518
- return this.formatter.format([
614
+ return context.format([
519
615
  this.visit(lexpr, context),
520
616
  'IN',
521
- this.formatter.parens(this.visit(rexpr, context))
617
+ context.parens(this.visit(rexpr, context))
522
618
  ]);
523
619
  }
524
620
  case 'AEXPR_LIKE':
525
- const likeOp = this.deparseOperatorName(name);
621
+ const likeOp = this.deparseOperatorName(name, context);
526
622
  if (likeOp === '!~~') {
527
- return this.formatter.format([
623
+ return context.format([
528
624
  this.visit(lexpr, context),
529
625
  'NOT LIKE',
530
626
  this.visit(rexpr, context)
531
627
  ]);
532
628
  }
533
629
  else {
534
- return this.formatter.format([
630
+ return context.format([
535
631
  this.visit(lexpr, context),
536
632
  'LIKE',
537
633
  this.visit(rexpr, context)
538
634
  ]);
539
635
  }
540
636
  case 'AEXPR_ILIKE':
541
- const ilikeOp = this.deparseOperatorName(name);
637
+ const ilikeOp = this.deparseOperatorName(name, context);
542
638
  if (ilikeOp === '!~~*') {
543
- return this.formatter.format([
639
+ return context.format([
544
640
  this.visit(lexpr, context),
545
641
  'NOT ILIKE',
546
642
  this.visit(rexpr, context)
547
643
  ]);
548
644
  }
549
645
  else {
550
- return this.formatter.format([
646
+ return context.format([
551
647
  this.visit(lexpr, context),
552
648
  'ILIKE',
553
649
  this.visit(rexpr, context)
554
650
  ]);
555
651
  }
556
652
  case 'AEXPR_SIMILAR':
557
- const similarOp = this.deparseOperatorName(name);
653
+ const similarOp = this.deparseOperatorName(name, context);
558
654
  let rightExpr;
559
655
  if (rexpr && 'FuncCall' in rexpr &&
560
656
  rexpr.FuncCall?.funcname?.length === 2 &&
@@ -570,39 +666,39 @@ export class Deparser {
570
666
  rightExpr = this.visit(rexpr, context);
571
667
  }
572
668
  if (similarOp === '!~') {
573
- return this.formatter.format([
669
+ return context.format([
574
670
  this.visit(lexpr, context),
575
671
  'NOT SIMILAR TO',
576
672
  rightExpr
577
673
  ]);
578
674
  }
579
675
  else {
580
- return this.formatter.format([
676
+ return context.format([
581
677
  this.visit(lexpr, context),
582
678
  'SIMILAR TO',
583
679
  rightExpr
584
680
  ]);
585
681
  }
586
682
  case 'AEXPR_BETWEEN':
587
- return this.formatter.format([
683
+ return context.format([
588
684
  this.visit(lexpr, context),
589
685
  'BETWEEN',
590
686
  this.visitBetweenRange(rexpr, context)
591
687
  ]);
592
688
  case 'AEXPR_NOT_BETWEEN':
593
- return this.formatter.format([
689
+ return context.format([
594
690
  this.visit(lexpr, context),
595
691
  'NOT BETWEEN',
596
692
  this.visitBetweenRange(rexpr, context)
597
693
  ]);
598
694
  case 'AEXPR_BETWEEN_SYM':
599
- return this.formatter.format([
695
+ return context.format([
600
696
  this.visit(lexpr, context),
601
697
  'BETWEEN SYMMETRIC',
602
698
  this.visitBetweenRange(rexpr, context)
603
699
  ]);
604
700
  case 'AEXPR_NOT_BETWEEN_SYM':
605
- return this.formatter.format([
701
+ return context.format([
606
702
  this.visit(lexpr, context),
607
703
  'NOT BETWEEN SYMMETRIC',
608
704
  this.visitBetweenRange(rexpr, context)
@@ -610,7 +706,7 @@ export class Deparser {
610
706
  }
611
707
  throw new Error(`Unhandled A_Expr kind: ${kind}`);
612
708
  }
613
- deparseOperatorName(name) {
709
+ deparseOperatorName(name, context) {
614
710
  if (!name || name.length === 0) {
615
711
  return '';
616
712
  }
@@ -618,7 +714,7 @@ export class Deparser {
618
714
  if (n.String) {
619
715
  return n.String.sval || n.String.str;
620
716
  }
621
- return this.visit(n, { parentNodeTypes: [] });
717
+ return this.visit(n, context);
622
718
  });
623
719
  if (parts.length > 1) {
624
720
  return `OPERATOR(${parts.join('.')})`;
@@ -681,6 +777,64 @@ export class Deparser {
681
777
  node.SubLink ||
682
778
  node.A_Expr);
683
779
  }
780
+ isComplexSelectTarget(node) {
781
+ if (!node)
782
+ return false;
783
+ if (node.ResTarget?.val) {
784
+ return this.isComplexExpression(node.ResTarget.val);
785
+ }
786
+ // Always complex: CASE expressions
787
+ if (node.CaseExpr)
788
+ return true;
789
+ // Always complex: Subqueries and subselects
790
+ if (node.SubLink)
791
+ return true;
792
+ // Always complex: Boolean tests and expressions
793
+ if (node.NullTest || node.BooleanTest || node.BoolExpr)
794
+ return true;
795
+ // COALESCE and similar functions - complex if multiple arguments
796
+ if (node.CoalesceExpr) {
797
+ const args = node.CoalesceExpr.args;
798
+ if (args && Array.isArray(args) && args.length > 1)
799
+ return true;
800
+ }
801
+ // Function calls - complex if multiple args or has clauses
802
+ if (node.FuncCall) {
803
+ const funcCall = node.FuncCall;
804
+ const args = funcCall.args ? (Array.isArray(funcCall.args) ? funcCall.args : [funcCall.args]) : [];
805
+ // Complex if has window clause, filter, order by, etc.
806
+ if (funcCall.over || funcCall.agg_filter || funcCall.agg_order || funcCall.agg_distinct) {
807
+ return true;
808
+ }
809
+ // Complex if multiple arguments
810
+ if (args.length > 1)
811
+ return true;
812
+ if (args.length === 1) {
813
+ return this.isComplexSelectTarget(args[0]);
814
+ }
815
+ }
816
+ if (node.A_Expr) {
817
+ const expr = node.A_Expr;
818
+ // Check if operands are complex
819
+ if (expr.lexpr && this.isComplexSelectTarget(expr.lexpr))
820
+ return true;
821
+ if (expr.rexpr && this.isComplexSelectTarget(expr.rexpr))
822
+ return true;
823
+ return false;
824
+ }
825
+ if (node.TypeCast) {
826
+ return this.isComplexSelectTarget(node.TypeCast.arg);
827
+ }
828
+ if (node.A_ArrayExpr)
829
+ return true;
830
+ if (node.A_Indirection) {
831
+ return this.isComplexSelectTarget(node.A_Indirection.arg);
832
+ }
833
+ if (node.A_Const || node.ColumnRef || node.ParamRef || node.A_Star) {
834
+ return false;
835
+ }
836
+ return false;
837
+ }
684
838
  visitBetweenRange(rexpr, context) {
685
839
  if (rexpr && 'List' in rexpr && rexpr.List?.items) {
686
840
  const items = rexpr.List.items.map((item) => this.visit(item, context));
@@ -697,9 +851,16 @@ export class Deparser {
697
851
  output.push(this.RangeVar(node.relation, context));
698
852
  if (node.cols) {
699
853
  const cols = ListUtils.unwrapList(node.cols);
700
- const insertContext = { ...context, insertColumns: true };
854
+ const insertContext = context.spawn('InsertStmt', { insertColumns: true });
701
855
  const columnNames = cols.map(col => this.visit(col, insertContext));
702
- output.push(this.formatter.parens(columnNames.join(', ')));
856
+ if (context.isPretty()) {
857
+ // Always format columns in multiline parentheses for pretty printing
858
+ const indentedColumns = columnNames.map(col => context.indent(col));
859
+ output.push('(\n' + indentedColumns.join(',\n') + '\n)');
860
+ }
861
+ else {
862
+ output.push(context.parens(columnNames.join(', ')));
863
+ }
703
864
  }
704
865
  if (node.selectStmt) {
705
866
  output.push(this.visit(node.selectStmt, context));
@@ -720,7 +881,7 @@ export class Deparser {
720
881
  else if (infer.indexElems) {
721
882
  const elems = ListUtils.unwrapList(infer.indexElems);
722
883
  const indexElems = elems.map(elem => this.visit(elem, context));
723
- output.push(this.formatter.parens(indexElems.join(', ')));
884
+ output.push(context.parens(indexElems.join(', ')));
724
885
  }
725
886
  // Handle WHERE clause for conflict detection
726
887
  if (infer.whereClause) {
@@ -736,12 +897,12 @@ export class Deparser {
736
897
  if (firstTarget.ResTarget?.val?.MultiAssignRef && targetList.every(target => target.ResTarget?.val?.MultiAssignRef)) {
737
898
  const sortedTargets = targetList.sort((a, b) => a.ResTarget.val.MultiAssignRef.colno - b.ResTarget.val.MultiAssignRef.colno);
738
899
  const names = sortedTargets.map(target => target.ResTarget.name);
739
- output.push(this.formatter.parens(names.join(', ')));
900
+ output.push(context.parens(names.join(', ')));
740
901
  output.push('=');
741
902
  output.push(this.visit(firstTarget.ResTarget.val.MultiAssignRef.source, context));
742
903
  }
743
904
  else {
744
- const updateContext = { ...context, update: true };
905
+ const updateContext = context.spawn('UpdateStmt', { update: true });
745
906
  const targets = targetList.map(target => this.visit(target, updateContext));
746
907
  output.push(targets.join(', '));
747
908
  }
@@ -795,12 +956,12 @@ export class Deparser {
795
956
  }
796
957
  }
797
958
  const names = relatedTargets.map(t => t.ResTarget.name);
798
- const multiAssignment = `${this.formatter.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
959
+ const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
799
960
  assignmentParts.push(multiAssignment);
800
961
  }
801
962
  else {
802
963
  // Handle regular single-column assignment
803
- assignmentParts.push(this.visit(target, { ...context, update: true }));
964
+ assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
804
965
  processedTargets.add(i);
805
966
  }
806
967
  }
@@ -892,14 +1053,14 @@ export class Deparser {
892
1053
  }
893
1054
  if (node.ctes && node.ctes.length > 0) {
894
1055
  const ctes = ListUtils.unwrapList(node.ctes);
895
- if (this.formatter.isPretty()) {
1056
+ if (context.isPretty()) {
896
1057
  const cteStrings = ctes.map((cte, index) => {
897
1058
  const cteStr = this.visit(cte, context);
898
- const prefix = index === 0 ? this.formatter.newline() : ',' + this.formatter.newline();
1059
+ const prefix = index === 0 ? context.newline() : ',' + context.newline();
899
1060
  if (this.containsMultilineStringLiteral(cteStr)) {
900
1061
  return prefix + cteStr;
901
1062
  }
902
- return prefix + this.formatter.indent(cteStr);
1063
+ return prefix + context.indent(cteStr);
903
1064
  });
904
1065
  output.push(cteStrings.join(''));
905
1066
  }
@@ -985,14 +1146,14 @@ export class Deparser {
985
1146
  if (context.bool) {
986
1147
  formatStr = '(%s)';
987
1148
  }
988
- const boolContext = { ...context, bool: true };
1149
+ const boolContext = context.spawn('BoolExpr', { bool: true });
989
1150
  // explanation of our syntax/fix below:
990
1151
  // return formatStr.replace('%s', andArgs); // ❌ Interprets $ as special syntax
991
1152
  // return formatStr.replace('%s', () => andArgs); // ✅ Function callback prevents interpretation
992
1153
  switch (boolop) {
993
1154
  case 'AND_EXPR':
994
- if (this.formatter.isPretty() && args.length > 1) {
995
- const andArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' AND ');
1155
+ if (context.isPretty() && args.length > 1) {
1156
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('AND '));
996
1157
  return formatStr.replace('%s', () => andArgs);
997
1158
  }
998
1159
  else {
@@ -1000,8 +1161,8 @@ export class Deparser {
1000
1161
  return formatStr.replace('%s', () => andArgs);
1001
1162
  }
1002
1163
  case 'OR_EXPR':
1003
- if (this.formatter.isPretty() && args.length > 1) {
1004
- const orArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' OR ');
1164
+ if (context.isPretty() && args.length > 1) {
1165
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('OR '));
1005
1166
  return formatStr.replace('%s', () => orArgs);
1006
1167
  }
1007
1168
  else {
@@ -1132,9 +1293,9 @@ export class Deparser {
1132
1293
  const timezone = this.visit(args[0], context);
1133
1294
  // Add parentheses around timestamp if it contains arithmetic operations
1134
1295
  if (args[1] && 'A_Expr' in args[1] && args[1].A_Expr?.kind === 'AEXPR_OP') {
1135
- const op = this.deparseOperatorName(ListUtils.unwrapList(args[1].A_Expr.name));
1296
+ const op = this.deparseOperatorName(ListUtils.unwrapList(args[1].A_Expr.name), context);
1136
1297
  if (op === '+' || op === '-' || op === '*' || op === '/') {
1137
- timestamp = this.formatter.parens(timestamp);
1298
+ timestamp = context.parens(timestamp);
1138
1299
  }
1139
1300
  }
1140
1301
  return `${timestamp} AT TIME ZONE ${timezone}`;
@@ -1204,14 +1365,14 @@ export class Deparser {
1204
1365
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1205
1366
  }
1206
1367
  // Handle window frame specifications using the dedicated formatWindowFrame method
1207
- const frameClause = this.formatWindowFrame(node.over);
1368
+ const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1208
1369
  if (frameClause) {
1209
1370
  windowParts.push(frameClause);
1210
1371
  }
1211
1372
  if (windowParts.length > 0) {
1212
- if (this.formatter.isPretty() && windowParts.length > 1) {
1213
- const formattedParts = windowParts.map(part => this.formatter.indent(part));
1214
- result += ` OVER (${this.formatter.newline()}${formattedParts.join(this.formatter.newline())}${this.formatter.newline()})`;
1373
+ if (context.isPretty() && windowParts.length > 1) {
1374
+ const formattedParts = windowParts.map(part => context.indent(part));
1375
+ result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1215
1376
  }
1216
1377
  else {
1217
1378
  result += ` OVER (${windowParts.join(' ')})`;
@@ -1491,9 +1652,6 @@ export class Deparser {
1491
1652
  return output.join(' ');
1492
1653
  }
1493
1654
  if (catalog === 'pg_catalog') {
1494
- const builtinTypes = ['int2', 'int4', 'int8', 'float4', 'float8', 'numeric', 'decimal',
1495
- 'varchar', 'char', 'bpchar', 'text', 'bool', 'date', 'time', 'timestamp',
1496
- 'timestamptz', 'interval', 'bytea', 'uuid', 'json', 'jsonb'];
1497
1655
  let typeName = `${catalog}.${type}`;
1498
1656
  if (type === 'bpchar' && args) {
1499
1657
  typeName = 'char';
@@ -1579,7 +1737,7 @@ export class Deparser {
1579
1737
  }
1580
1738
  return this.quoteIfNeeded(colStr);
1581
1739
  });
1582
- output.push('AS', this.quoteIfNeeded(name) + this.formatter.parens(quotedColnames.join(', ')));
1740
+ output.push('AS', this.quoteIfNeeded(name) + context.parens(quotedColnames.join(', ')));
1583
1741
  }
1584
1742
  else {
1585
1743
  output.push('AS', this.quoteIfNeeded(name));
@@ -1591,7 +1749,7 @@ export class Deparser {
1591
1749
  // Handle ONLY keyword for inheritance control (but not for type definitions, ALTER TYPE, or CREATE FOREIGN TABLE)
1592
1750
  if (node && (!('inh' in node) || node.inh === undefined) &&
1593
1751
  !context.parentNodeTypes.includes('CompositeTypeStmt') &&
1594
- !context.parentNodeTypes.includes('AlterTypeStmt') &&
1752
+ (!context.parentNodeTypes.includes('AlterTypeStmt') && context.objtype !== 'OBJECT_TYPE') &&
1595
1753
  !context.parentNodeTypes.includes('CreateForeignTableStmt')) {
1596
1754
  output.push('ONLY');
1597
1755
  }
@@ -1788,6 +1946,18 @@ export class Deparser {
1788
1946
  return `pg_catalog.${typeName}`;
1789
1947
  }
1790
1948
  }
1949
+ isPgCatalogType(typeName) {
1950
+ const cleanTypeName = typeName.replace(/^pg_catalog\./, '');
1951
+ if (pgCatalogTypes.includes(cleanTypeName)) {
1952
+ return true;
1953
+ }
1954
+ for (const [realType, aliases] of pgCatalogTypeAliases) {
1955
+ if (aliases.includes(cleanTypeName)) {
1956
+ return true;
1957
+ }
1958
+ }
1959
+ return false;
1960
+ }
1791
1961
  A_ArrayExpr(node, context) {
1792
1962
  const elements = ListUtils.unwrapList(node.elements);
1793
1963
  const elementStrs = elements.map(el => this.visit(el, context));
@@ -1839,26 +2009,26 @@ export class Deparser {
1839
2009
  output.push(this.visit(node.arg, context));
1840
2010
  }
1841
2011
  const args = ListUtils.unwrapList(node.args);
1842
- if (this.formatter.isPretty() && args.length > 0) {
2012
+ if (context.isPretty() && args.length > 0) {
1843
2013
  for (const arg of args) {
1844
2014
  const whenClause = this.visit(arg, context);
1845
2015
  if (this.containsMultilineStringLiteral(whenClause)) {
1846
- output.push(this.formatter.newline() + whenClause);
2016
+ output.push(context.newline() + whenClause);
1847
2017
  }
1848
2018
  else {
1849
- output.push(this.formatter.newline() + this.formatter.indent(whenClause));
2019
+ output.push(context.newline() + context.indent(whenClause));
1850
2020
  }
1851
2021
  }
1852
2022
  if (node.defresult) {
1853
2023
  const elseResult = this.visit(node.defresult, context);
1854
2024
  if (this.containsMultilineStringLiteral(elseResult)) {
1855
- output.push(this.formatter.newline() + 'ELSE ' + elseResult);
2025
+ output.push(context.newline() + 'ELSE ' + elseResult);
1856
2026
  }
1857
2027
  else {
1858
- output.push(this.formatter.newline() + this.formatter.indent('ELSE ' + elseResult));
2028
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
1859
2029
  }
1860
2030
  }
1861
- output.push(this.formatter.newline() + 'END');
2031
+ output.push(context.newline() + 'END');
1862
2032
  return output.join(' ');
1863
2033
  }
1864
2034
  else {
@@ -1881,28 +2051,29 @@ export class Deparser {
1881
2051
  TypeCast(node, context) {
1882
2052
  const arg = this.visit(node.arg, context);
1883
2053
  const typeName = this.TypeName(node.typeName, context);
1884
- // Check if this is a bpchar typecast that should use traditional char syntax
1885
- if (typeName === 'bpchar' && node.typeName && node.typeName.names) {
1886
- const names = ListUtils.unwrapList(node.typeName.names);
1887
- if (names.length === 2 &&
1888
- names[0].String?.sval === 'pg_catalog' &&
1889
- names[1].String?.sval === 'bpchar') {
1890
- return `char ${arg}`;
2054
+ // Check if this is a bpchar typecast that should preserve original syntax for AST consistency
2055
+ if (typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
2056
+ const names = node.typeName?.names;
2057
+ const isQualifiedBpchar = names && names.length === 2 &&
2058
+ names[0]?.String?.sval === 'pg_catalog' &&
2059
+ names[1]?.String?.sval === 'bpchar';
2060
+ if (isQualifiedBpchar) {
2061
+ return `CAST(${arg} AS ${typeName})`;
1891
2062
  }
1892
2063
  }
1893
- // Check if the argument is a complex expression that should preserve CAST syntax
1894
- const argType = this.getNodeType(node.arg);
1895
- const isComplexExpression = argType === 'A_Expr' || argType === 'FuncCall' || argType === 'OpExpr';
1896
- if (!isComplexExpression && (typeName.startsWith('interval') ||
1897
- typeName.startsWith('char') ||
1898
- typeName === '"char"' ||
1899
- typeName.startsWith('bpchar') ||
1900
- typeName === 'bytea' ||
1901
- typeName === 'orderedarray' ||
1902
- typeName === 'date')) {
1903
- // Remove pg_catalog prefix for :: syntax
1904
- const cleanTypeName = typeName.replace('pg_catalog.', '');
1905
- return `${arg}::${cleanTypeName}`;
2064
+ if (this.isPgCatalogType(typeName)) {
2065
+ const argType = this.getNodeType(node.arg);
2066
+ const isSimpleArgument = argType === 'A_Const' || argType === 'ColumnRef';
2067
+ const isFunctionCall = argType === 'FuncCall';
2068
+ if (isSimpleArgument || isFunctionCall) {
2069
+ // For simple arguments, avoid :: syntax if they have complex structure
2070
+ if (isSimpleArgument && (arg.includes('(') || arg.startsWith('-'))) {
2071
+ }
2072
+ else {
2073
+ const cleanTypeName = typeName.replace('pg_catalog.', '');
2074
+ return `${arg}::${cleanTypeName}`;
2075
+ }
2076
+ }
1906
2077
  }
1907
2078
  return `CAST(${arg} AS ${typeName})`;
1908
2079
  }
@@ -1925,7 +2096,7 @@ export class Deparser {
1925
2096
  }
1926
2097
  BooleanTest(node, context) {
1927
2098
  const output = [];
1928
- const boolContext = { ...context, bool: true };
2099
+ const boolContext = context.spawn('BooleanTest', { bool: true });
1929
2100
  output.push(this.visit(node.arg, boolContext));
1930
2101
  switch (node.booltesttype) {
1931
2102
  case 'IS_TRUE':
@@ -2076,24 +2247,31 @@ export class Deparser {
2076
2247
  const elementStrs = elements.map(el => {
2077
2248
  return this.deparse(el, context);
2078
2249
  });
2079
- output.push(this.formatter.parens(elementStrs.join(', ')));
2250
+ output.push(context.parens(elementStrs.join(', ')));
2080
2251
  }
2081
2252
  }
2082
2253
  else if (node.tableElts) {
2083
2254
  const elements = ListUtils.unwrapList(node.tableElts);
2084
2255
  const elementStrs = elements.map(el => {
2085
- return this.deparse(el, context);
2256
+ return this.deparse(el, context.spawn('CreateStmt'));
2086
2257
  });
2087
- if (this.formatter.isPretty()) {
2088
- const formattedElements = elementStrs.map(el => this.formatter.indent(el)).join(',' + this.formatter.newline());
2089
- output.push('(' + this.formatter.newline() + formattedElements + this.formatter.newline() + ')');
2258
+ if (context.isPretty()) {
2259
+ const formattedElements = elementStrs.map(el => {
2260
+ const trimmedEl = el.trim();
2261
+ // Remove leading newlines from constraint elements to avoid extra blank lines
2262
+ if (trimmedEl.startsWith('\n')) {
2263
+ return context.indent(trimmedEl.substring(1));
2264
+ }
2265
+ return context.indent(trimmedEl);
2266
+ }).join(',' + context.newline());
2267
+ output.push('(' + context.newline() + formattedElements + context.newline() + ')');
2090
2268
  }
2091
2269
  else {
2092
- output.push(this.formatter.parens(elementStrs.join(', ')));
2270
+ output.push(context.parens(elementStrs.join(', ')));
2093
2271
  }
2094
2272
  }
2095
2273
  else if (!node.partbound) {
2096
- output.push(this.formatter.parens(''));
2274
+ output.push(context.parens(''));
2097
2275
  }
2098
2276
  if (node.partbound && node.inhRelations && node.inhRelations.length > 0) {
2099
2277
  output.push('PARTITION OF');
@@ -2136,7 +2314,7 @@ export class Deparser {
2136
2314
  output.push('INHERITS');
2137
2315
  const inherits = ListUtils.unwrapList(node.inhRelations);
2138
2316
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2139
- output.push(this.formatter.parens(inheritStrs.join(', ')));
2317
+ output.push(context.parens(inheritStrs.join(', ')));
2140
2318
  }
2141
2319
  if (node.partspec) {
2142
2320
  output.push('PARTITION BY');
@@ -2174,7 +2352,7 @@ export class Deparser {
2174
2352
  }
2175
2353
  // Handle table options like WITH (fillfactor=10)
2176
2354
  if (node.options && node.options.length > 0) {
2177
- const createStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateStmt'] };
2355
+ const createStmtContext = context.spawn('CreateStmt');
2178
2356
  const optionStrs = node.options.map((option) => {
2179
2357
  return this.deparse(option, createStmtContext);
2180
2358
  });
@@ -2197,7 +2375,7 @@ export class Deparser {
2197
2375
  }
2198
2376
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2199
2377
  output.push('OPTIONS');
2200
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
2378
+ const columnContext = context.spawn('ColumnDef');
2201
2379
  const options = ListUtils.unwrapList(node.fdwoptions).map(opt => this.visit(opt, columnContext));
2202
2380
  output.push(`(${options.join(', ')})`);
2203
2381
  }
@@ -2207,7 +2385,7 @@ export class Deparser {
2207
2385
  if (node.constraints) {
2208
2386
  const constraints = ListUtils.unwrapList(node.constraints);
2209
2387
  const constraintStrs = constraints.map(constraint => {
2210
- const columnConstraintContext = { ...context, isColumnConstraint: true };
2388
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
2211
2389
  return this.visit(constraint, columnConstraintContext);
2212
2390
  });
2213
2391
  output.push(...constraintStrs);
@@ -2247,9 +2425,25 @@ export class Deparser {
2247
2425
  }
2248
2426
  break;
2249
2427
  case 'CONSTR_CHECK':
2250
- output.push('CHECK');
2428
+ if (context.isPretty() && !context.isColumnConstraint) {
2429
+ output.push('\n' + context.indent('CHECK'));
2430
+ }
2431
+ else {
2432
+ output.push('CHECK');
2433
+ }
2251
2434
  if (node.raw_expr) {
2252
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2435
+ if (context.isPretty()) {
2436
+ const checkExpr = this.visit(node.raw_expr, context);
2437
+ if (checkExpr.includes('\n')) {
2438
+ output.push('(\n' + context.indent(checkExpr) + '\n)');
2439
+ }
2440
+ else {
2441
+ output.push(`(${checkExpr})`);
2442
+ }
2443
+ }
2444
+ else {
2445
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2446
+ }
2253
2447
  }
2254
2448
  // Handle NOT VALID for check constraints
2255
2449
  if (node.skip_validation) {
@@ -2270,7 +2464,7 @@ export class Deparser {
2270
2464
  }
2271
2465
  output.push('AS');
2272
2466
  if (node.raw_expr) {
2273
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2467
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2274
2468
  }
2275
2469
  output.push('STORED');
2276
2470
  break;
@@ -2328,7 +2522,12 @@ export class Deparser {
2328
2522
  }
2329
2523
  break;
2330
2524
  case 'CONSTR_UNIQUE':
2331
- output.push('UNIQUE');
2525
+ if (context.isPretty() && !context.isColumnConstraint) {
2526
+ output.push('\n' + context.indent('UNIQUE'));
2527
+ }
2528
+ else {
2529
+ output.push('UNIQUE');
2530
+ }
2332
2531
  if (node.nulls_not_distinct) {
2333
2532
  output.push('NULLS NOT DISTINCT');
2334
2533
  }
@@ -2346,33 +2545,77 @@ export class Deparser {
2346
2545
  case 'CONSTR_FOREIGN':
2347
2546
  // Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
2348
2547
  if (!context.isColumnConstraint) {
2349
- output.push('FOREIGN KEY');
2350
- if (node.fk_attrs && node.fk_attrs.length > 0) {
2351
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2352
- .map(attr => this.visit(attr, context))
2353
- .join(', ');
2354
- output.push(`(${fkAttrs})`);
2548
+ if (context.isPretty()) {
2549
+ output.push('\n' + context.indent('FOREIGN KEY'));
2550
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2551
+ const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2552
+ .map(attr => this.visit(attr, context))
2553
+ .join(', ');
2554
+ output.push(`(${fkAttrs})`);
2555
+ }
2556
+ output.push('\n' + context.indent('REFERENCES'));
2355
2557
  }
2558
+ else {
2559
+ output.push('FOREIGN KEY');
2560
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2561
+ const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2562
+ .map(attr => this.visit(attr, context))
2563
+ .join(', ');
2564
+ output.push(`(${fkAttrs})`);
2565
+ }
2566
+ output.push('REFERENCES');
2567
+ }
2568
+ }
2569
+ else {
2570
+ output.push('REFERENCES');
2356
2571
  }
2357
- output.push('REFERENCES');
2358
2572
  if (node.pktable) {
2359
- output.push(this.RangeVar(node.pktable, context));
2573
+ if (context.isPretty() && !context.isColumnConstraint) {
2574
+ const lastIndex = output.length - 1;
2575
+ if (lastIndex >= 0 && output[lastIndex].includes('REFERENCES')) {
2576
+ output[lastIndex] += ' ' + this.RangeVar(node.pktable, context);
2577
+ }
2578
+ else {
2579
+ output.push(this.RangeVar(node.pktable, context));
2580
+ }
2581
+ }
2582
+ else {
2583
+ output.push(this.RangeVar(node.pktable, context));
2584
+ }
2360
2585
  }
2361
2586
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2362
2587
  const pkAttrs = ListUtils.unwrapList(node.pk_attrs)
2363
2588
  .map(attr => this.visit(attr, context))
2364
2589
  .join(', ');
2365
- output.push(`(${pkAttrs})`);
2590
+ if (context.isPretty() && !context.isColumnConstraint) {
2591
+ const lastIndex = output.length - 1;
2592
+ if (lastIndex >= 0) {
2593
+ output[lastIndex] += ` (${pkAttrs})`;
2594
+ }
2595
+ else {
2596
+ output.push(`(${pkAttrs})`);
2597
+ }
2598
+ }
2599
+ else {
2600
+ output.push(`(${pkAttrs})`);
2601
+ }
2366
2602
  }
2367
2603
  if (node.fk_matchtype && node.fk_matchtype !== 's') {
2604
+ let matchClause = '';
2368
2605
  switch (node.fk_matchtype) {
2369
2606
  case 'f':
2370
- output.push('MATCH FULL');
2607
+ matchClause = 'MATCH FULL';
2371
2608
  break;
2372
2609
  case 'p':
2373
- output.push('MATCH PARTIAL');
2610
+ matchClause = 'MATCH PARTIAL';
2374
2611
  break;
2375
2612
  }
2613
+ if (context.isPretty() && !context.isColumnConstraint) {
2614
+ output.push('\n' + context.indent(matchClause));
2615
+ }
2616
+ else {
2617
+ output.push(matchClause);
2618
+ }
2376
2619
  }
2377
2620
  if (node.fk_upd_action && node.fk_upd_action !== 'a') {
2378
2621
  let updateClause = 'ON UPDATE ';
@@ -2390,8 +2633,8 @@ export class Deparser {
2390
2633
  updateClause += 'SET DEFAULT';
2391
2634
  break;
2392
2635
  }
2393
- if (this.formatter.isPretty()) {
2394
- output.push('\n' + this.formatter.indent(updateClause));
2636
+ if (context.isPretty()) {
2637
+ output.push('\n' + context.indent(updateClause));
2395
2638
  }
2396
2639
  else {
2397
2640
  output.push('ON UPDATE');
@@ -2414,8 +2657,8 @@ export class Deparser {
2414
2657
  deleteClause += 'SET DEFAULT';
2415
2658
  break;
2416
2659
  }
2417
- if (this.formatter.isPretty()) {
2418
- output.push('\n' + this.formatter.indent(deleteClause));
2660
+ if (context.isPretty()) {
2661
+ output.push('\n' + context.indent(deleteClause));
2419
2662
  }
2420
2663
  else {
2421
2664
  output.push('ON DELETE');
@@ -2424,7 +2667,12 @@ export class Deparser {
2424
2667
  }
2425
2668
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2426
2669
  if (node.skip_validation && !context.isDomainConstraint) {
2427
- output.push('NOT VALID');
2670
+ if (context.isPretty() && !context.isColumnConstraint) {
2671
+ output.push('\n' + context.indent('NOT VALID'));
2672
+ }
2673
+ else {
2674
+ output.push('NOT VALID');
2675
+ }
2428
2676
  }
2429
2677
  break;
2430
2678
  case 'CONSTR_ATTR_DEFERRABLE':
@@ -2478,13 +2726,13 @@ export class Deparser {
2478
2726
  // Handle deferrable constraints for all constraint types that support it
2479
2727
  if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2480
2728
  if (node.deferrable) {
2481
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2482
- output.push('\n' + this.formatter.indent('DEFERRABLE'));
2729
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2730
+ output.push('\n' + context.indent('DEFERRABLE'));
2483
2731
  if (node.initdeferred === true) {
2484
- output.push('\n' + this.formatter.indent('INITIALLY DEFERRED'));
2732
+ output.push('\n' + context.indent('INITIALLY DEFERRED'));
2485
2733
  }
2486
2734
  else if (node.initdeferred === false) {
2487
- output.push('\n' + this.formatter.indent('INITIALLY IMMEDIATE'));
2735
+ output.push('\n' + context.indent('INITIALLY IMMEDIATE'));
2488
2736
  }
2489
2737
  }
2490
2738
  else {
@@ -2498,15 +2746,15 @@ export class Deparser {
2498
2746
  }
2499
2747
  }
2500
2748
  else if (node.deferrable === false) {
2501
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2502
- output.push('\n' + this.formatter.indent('NOT DEFERRABLE'));
2749
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2750
+ output.push('\n' + context.indent('NOT DEFERRABLE'));
2503
2751
  }
2504
2752
  else {
2505
2753
  output.push('NOT DEFERRABLE');
2506
2754
  }
2507
2755
  }
2508
2756
  }
2509
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2757
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2510
2758
  let result = '';
2511
2759
  for (let i = 0; i < output.length; i++) {
2512
2760
  if (output[i].startsWith('\n')) {
@@ -2524,12 +2772,12 @@ export class Deparser {
2524
2772
  return output.join(' ');
2525
2773
  }
2526
2774
  SubLink(node, context) {
2527
- const subselect = this.formatter.parens(this.visit(node.subselect, context));
2775
+ const subselect = context.parens(this.visit(node.subselect, context));
2528
2776
  switch (node.subLinkType) {
2529
2777
  case 'ANY_SUBLINK':
2530
2778
  if (node.testexpr && node.operName) {
2531
2779
  const testExpr = this.visit(node.testexpr, context);
2532
- const operator = this.deparseOperatorName(node.operName);
2780
+ const operator = this.deparseOperatorName(node.operName, context);
2533
2781
  return `${testExpr} ${operator} ANY ${subselect}`;
2534
2782
  }
2535
2783
  else if (node.testexpr) {
@@ -2540,7 +2788,7 @@ export class Deparser {
2540
2788
  case 'ALL_SUBLINK':
2541
2789
  if (node.testexpr && node.operName) {
2542
2790
  const testExpr = this.visit(node.testexpr, context);
2543
- const operator = this.deparseOperatorName(node.operName);
2791
+ const operator = this.deparseOperatorName(node.operName, context);
2544
2792
  return `${testExpr} ${operator} ALL ${subselect}`;
2545
2793
  }
2546
2794
  return subselect;
@@ -2582,7 +2830,7 @@ export class Deparser {
2582
2830
  }
2583
2831
  // Only add frame clause if frameOptions indicates non-default framing
2584
2832
  if (node.frameOptions && node.frameOptions !== 1058) {
2585
- const frameClause = this.formatWindowFrame(node);
2833
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2586
2834
  if (frameClause) {
2587
2835
  windowParts.push(frameClause);
2588
2836
  }
@@ -2602,7 +2850,7 @@ export class Deparser {
2602
2850
  }
2603
2851
  return output.join(' ');
2604
2852
  }
2605
- formatWindowFrame(node) {
2853
+ formatWindowFrame(node, context) {
2606
2854
  if (!node.frameOptions)
2607
2855
  return null;
2608
2856
  const frameOptions = node.frameOptions;
@@ -2611,7 +2859,7 @@ export class Deparser {
2611
2859
  if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
2612
2860
  frameParts.push('RANGE');
2613
2861
  }
2614
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2862
+ else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2615
2863
  frameParts.push('ROWS');
2616
2864
  }
2617
2865
  else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
@@ -2632,8 +2880,8 @@ export class Deparser {
2632
2880
  }
2633
2881
  else if (frameOptions === 18453) {
2634
2882
  if (node.startOffset && node.endOffset) {
2635
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2636
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2883
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2884
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2637
2885
  }
2638
2886
  }
2639
2887
  else if (frameOptions === 1557) {
@@ -2643,7 +2891,7 @@ export class Deparser {
2643
2891
  else if (frameOptions === 16917) {
2644
2892
  boundsParts.push('CURRENT ROW');
2645
2893
  if (node.endOffset) {
2646
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2894
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2647
2895
  }
2648
2896
  }
2649
2897
  else if (frameOptions === 1058) {
@@ -2653,13 +2901,13 @@ export class Deparser {
2653
2901
  // Handle start bound - prioritize explicit offset values over bit flags
2654
2902
  if (node.startOffset) {
2655
2903
  if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
2656
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2904
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2657
2905
  }
2658
2906
  else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
2659
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} FOLLOWING`);
2907
+ boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
2660
2908
  }
2661
2909
  else {
2662
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2910
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2663
2911
  }
2664
2912
  }
2665
2913
  else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
@@ -2672,13 +2920,13 @@ export class Deparser {
2672
2920
  if (node.endOffset) {
2673
2921
  if (boundsParts.length > 0) {
2674
2922
  if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
2675
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} PRECEDING`);
2923
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
2676
2924
  }
2677
2925
  else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
2678
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2926
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2679
2927
  }
2680
2928
  else {
2681
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2929
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2682
2930
  }
2683
2931
  }
2684
2932
  }
@@ -2763,7 +3011,7 @@ export class Deparser {
2763
3011
  const colnames = ListUtils.unwrapList(node.aliascolnames);
2764
3012
  const colnameStrs = colnames.map(col => this.visit(col, context));
2765
3013
  // Don't add space before column list parentheses to match original formatting
2766
- output[output.length - 1] += this.formatter.parens(colnameStrs.join(', '));
3014
+ output[output.length - 1] += context.parens(colnameStrs.join(', '));
2767
3015
  }
2768
3016
  output.push('AS');
2769
3017
  // Handle materialization clauses
@@ -2774,7 +3022,7 @@ export class Deparser {
2774
3022
  output.push('MATERIALIZED');
2775
3023
  }
2776
3024
  if (node.ctequery) {
2777
- output.push(this.formatter.parens(this.visit(node.ctequery, context)));
3025
+ output.push(context.parens(this.visit(node.ctequery, context)));
2778
3026
  }
2779
3027
  return output.join(' ');
2780
3028
  }
@@ -2850,7 +3098,7 @@ export class Deparser {
2850
3098
  DistinctExpr(node, context) {
2851
3099
  const args = ListUtils.unwrapList(node.args);
2852
3100
  if (args.length === 2) {
2853
- const literalContext = { ...context, isStringLiteral: true };
3101
+ const literalContext = context.spawn('DistinctExpr', { isStringLiteral: true });
2854
3102
  const left = this.visit(args[0], literalContext);
2855
3103
  const right = this.visit(args[1], literalContext);
2856
3104
  return `${left} IS DISTINCT FROM ${right}`;
@@ -2860,7 +3108,7 @@ export class Deparser {
2860
3108
  NullIfExpr(node, context) {
2861
3109
  const args = ListUtils.unwrapList(node.args);
2862
3110
  if (args.length === 2) {
2863
- const literalContext = { ...context, isStringLiteral: true };
3111
+ const literalContext = context.spawn('NullIfExpr', { isStringLiteral: true });
2864
3112
  const left = this.visit(args[0], literalContext);
2865
3113
  const right = this.visit(args[1], literalContext);
2866
3114
  return `NULLIF(${left}, ${right})`;
@@ -2939,7 +3187,7 @@ export class Deparser {
2939
3187
  }
2940
3188
  RelabelType(node, context) {
2941
3189
  if (node.arg) {
2942
- const literalContext = { ...context, isStringLiteral: true };
3190
+ const literalContext = context.spawn('RelabelType', { isStringLiteral: true });
2943
3191
  return this.visit(node.arg, literalContext);
2944
3192
  }
2945
3193
  return '';
@@ -2958,7 +3206,7 @@ export class Deparser {
2958
3206
  }
2959
3207
  ConvertRowtypeExpr(node, context) {
2960
3208
  if (node.arg) {
2961
- const literalContext = { ...context, isStringLiteral: true };
3209
+ const literalContext = context.spawn('ConvertRowtypeExpr', { isStringLiteral: true });
2962
3210
  return this.visit(node.arg, literalContext);
2963
3211
  }
2964
3212
  return '';
@@ -2989,10 +3237,10 @@ export class Deparser {
2989
3237
  }
2990
3238
  if (node.aliases && node.aliases.length > 0) {
2991
3239
  const aliasStrs = ListUtils.unwrapList(node.aliases).map(alias => this.visit(alias, context));
2992
- output.push(this.formatter.parens(aliasStrs.join(', ')));
3240
+ output.push(context.parens(aliasStrs.join(', ')));
2993
3241
  }
2994
3242
  if (node.options && node.options.length > 0) {
2995
- const viewContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ViewStmt'] };
3243
+ const viewContext = context.spawn('ViewStmt');
2996
3244
  const optionStrs = ListUtils.unwrapList(node.options)
2997
3245
  .map(option => this.visit(option, viewContext));
2998
3246
  output.push(`WITH (${optionStrs.join(', ')})`);
@@ -3039,22 +3287,22 @@ export class Deparser {
3039
3287
  }
3040
3288
  if (node.indexParams && node.indexParams.length > 0) {
3041
3289
  const paramStrs = ListUtils.unwrapList(node.indexParams).map(param => this.visit(param, context));
3042
- output.push(this.formatter.parens(paramStrs.join(', ')));
3290
+ output.push(context.parens(paramStrs.join(', ')));
3043
3291
  }
3044
3292
  if (node.indexIncludingParams && node.indexIncludingParams.length > 0) {
3045
3293
  const includeStrs = ListUtils.unwrapList(node.indexIncludingParams).map(param => this.visit(param, context));
3046
3294
  output.push('INCLUDE');
3047
- output.push(this.formatter.parens(includeStrs.join(', ')));
3295
+ output.push(context.parens(includeStrs.join(', ')));
3048
3296
  }
3049
3297
  if (node.whereClause) {
3050
3298
  output.push('WHERE');
3051
3299
  output.push(this.visit(node.whereClause, context));
3052
3300
  }
3053
3301
  if (node.options && node.options.length > 0) {
3054
- const indexContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'IndexStmt'] };
3302
+ const indexContext = context.spawn('IndexStmt');
3055
3303
  const optionStrs = ListUtils.unwrapList(node.options).map(option => this.visit(option, indexContext));
3056
3304
  output.push('WITH');
3057
- output.push(this.formatter.parens(optionStrs.join(', ')));
3305
+ output.push(context.parens(optionStrs.join(', ')));
3058
3306
  }
3059
3307
  if (node.nulls_not_distinct) {
3060
3308
  output.push('NULLS NOT DISTINCT');
@@ -3071,7 +3319,7 @@ export class Deparser {
3071
3319
  output.push(QuoteUtils.quote(node.name));
3072
3320
  }
3073
3321
  else if (node.expr) {
3074
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3322
+ output.push(context.parens(this.visit(node.expr, context)));
3075
3323
  }
3076
3324
  if (node.collation && node.collation.length > 0) {
3077
3325
  const collationStrs = ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3088,7 +3336,7 @@ export class Deparser {
3088
3336
  const stringData = this.getNodeData(opt.DefElem.arg);
3089
3337
  return `${opt.DefElem.defname}='${stringData.sval}'`;
3090
3338
  }
3091
- return this.visit(opt, context);
3339
+ return this.visit(opt, context.spawn('IndexElem'));
3092
3340
  });
3093
3341
  opclassStr += `(${opclassOpts.join(', ')})`;
3094
3342
  }
@@ -3122,7 +3370,7 @@ export class Deparser {
3122
3370
  output.push(QuoteUtils.quote(node.name));
3123
3371
  }
3124
3372
  else if (node.expr) {
3125
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3373
+ output.push(context.parens(this.visit(node.expr, context)));
3126
3374
  }
3127
3375
  if (node.collation && node.collation.length > 0) {
3128
3376
  const collationStrs = ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3270,16 +3518,16 @@ export class Deparser {
3270
3518
  if (node.rarg && 'JoinExpr' in node.rarg && !node.rarg.JoinExpr.alias) {
3271
3519
  rargStr = `(${rargStr})`;
3272
3520
  }
3273
- if (this.formatter.isPretty()) {
3274
- output.push(this.formatter.newline() + joinStr + ' ' + rargStr);
3521
+ if (context.isPretty()) {
3522
+ output.push(context.newline() + joinStr + ' ' + rargStr);
3275
3523
  }
3276
3524
  else {
3277
3525
  output.push(joinStr + ' ' + rargStr);
3278
3526
  }
3279
3527
  }
3280
3528
  else {
3281
- if (this.formatter.isPretty()) {
3282
- output.push(this.formatter.newline() + joinStr);
3529
+ if (context.isPretty()) {
3530
+ output.push(context.newline() + joinStr);
3283
3531
  }
3284
3532
  else {
3285
3533
  output.push(joinStr);
@@ -3288,7 +3536,7 @@ export class Deparser {
3288
3536
  if (node.usingClause && node.usingClause.length > 0) {
3289
3537
  const usingList = ListUtils.unwrapList(node.usingClause);
3290
3538
  const columnNames = usingList.map(col => this.visit(col, context));
3291
- if (this.formatter.isPretty()) {
3539
+ if (context.isPretty()) {
3292
3540
  output.push(` USING (${columnNames.join(', ')})`);
3293
3541
  }
3294
3542
  else {
@@ -3297,14 +3545,14 @@ export class Deparser {
3297
3545
  }
3298
3546
  else if (node.quals) {
3299
3547
  const qualsStr = this.visit(node.quals, context);
3300
- if (this.formatter.isPretty()) {
3548
+ if (context.isPretty()) {
3301
3549
  // For complex JOIN conditions, format with proper indentation
3302
3550
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3303
3551
  if (this.containsMultilineStringLiteral(qualsStr)) {
3304
3552
  output.push(` ON ${qualsStr}`);
3305
3553
  }
3306
3554
  else {
3307
- output.push(` ON${this.formatter.newline()}${this.formatter.indent(qualsStr)}`);
3555
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3308
3556
  }
3309
3557
  }
3310
3558
  else {
@@ -3316,7 +3564,7 @@ export class Deparser {
3316
3564
  }
3317
3565
  }
3318
3566
  let result;
3319
- if (this.formatter.isPretty()) {
3567
+ if (context.isPretty()) {
3320
3568
  result = output.join('');
3321
3569
  }
3322
3570
  else {
@@ -3423,8 +3671,8 @@ export class Deparser {
3423
3671
  else if (nodeData.sval !== undefined) {
3424
3672
  // Handle nested sval structure: { sval: { sval: "value" } }
3425
3673
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3426
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3427
- boolValue = stringValue === 'on' || stringValue === 'true';
3674
+ const stringValue = svalValue.replace(/'/g, '');
3675
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3428
3676
  }
3429
3677
  }
3430
3678
  return boolValue ? 'READ ONLY' : 'READ WRITE';
@@ -3447,8 +3695,8 @@ export class Deparser {
3447
3695
  else if (nodeData.sval !== undefined) {
3448
3696
  // Handle nested sval structure: { sval: { sval: "value" } }
3449
3697
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3450
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3451
- boolValue = stringValue === 'on' || stringValue === 'true';
3698
+ const stringValue = svalValue.replace(/'/g, '');
3699
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3452
3700
  }
3453
3701
  }
3454
3702
  return boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE';
@@ -3515,8 +3763,8 @@ export class Deparser {
3515
3763
  else if (nodeData.sval !== undefined) {
3516
3764
  // Handle nested sval structure: { sval: { sval: "value" } }
3517
3765
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3518
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3519
- boolValue = stringValue === 'on' || stringValue === 'true';
3766
+ const stringValue = svalValue.replace(/'/g, '');
3767
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3520
3768
  }
3521
3769
  }
3522
3770
  transactionOptions.push(boolValue ? 'READ ONLY' : 'READ WRITE');
@@ -3534,8 +3782,8 @@ export class Deparser {
3534
3782
  else if (nodeData.sval !== undefined) {
3535
3783
  // Handle nested sval structure: { sval: { sval: "value" } }
3536
3784
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3537
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3538
- boolValue = stringValue === 'on' || stringValue === 'true';
3785
+ const stringValue = svalValue.replace(/'/g, '');
3786
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3539
3787
  }
3540
3788
  }
3541
3789
  transactionOptions.push(boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE');
@@ -3601,7 +3849,7 @@ export class Deparser {
3601
3849
  }
3602
3850
  switch (node.roletype) {
3603
3851
  case 'ROLESPEC_PUBLIC':
3604
- return 'public';
3852
+ return 'PUBLIC';
3605
3853
  case 'ROLESPEC_CURRENT_USER':
3606
3854
  return 'CURRENT_USER';
3607
3855
  case 'ROLESPEC_SESSION_USER':
@@ -3609,7 +3857,7 @@ export class Deparser {
3609
3857
  case 'ROLESPEC_CURRENT_ROLE':
3610
3858
  return 'CURRENT_ROLE';
3611
3859
  default:
3612
- return 'public';
3860
+ return 'PUBLIC';
3613
3861
  }
3614
3862
  }
3615
3863
  roletype(node, context) {
@@ -3919,7 +4167,7 @@ export class Deparser {
3919
4167
  }).filter((name) => name && name.trim());
3920
4168
  return items.join('.');
3921
4169
  }
3922
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DropStmt'], objtype: node.removeType };
4170
+ const objContext = context.spawn('DropStmt', { objtype: node.removeType });
3923
4171
  const objName = this.visit(objList, objContext);
3924
4172
  return objName;
3925
4173
  }).filter((name) => name && name.trim()).join(', ');
@@ -4019,7 +4267,7 @@ export class Deparser {
4019
4267
  if (node.options && node.options.length > 0) {
4020
4268
  output.push('WITH');
4021
4269
  const optionsStr = ListUtils.unwrapList(node.options)
4022
- .map(opt => this.visit(opt, context))
4270
+ .map(opt => this.visit(opt, context.spawn('CopyStmt')))
4023
4271
  .join(', ');
4024
4272
  output.push(`(${optionsStr})`);
4025
4273
  }
@@ -4065,18 +4313,31 @@ export class Deparser {
4065
4313
  if (node.missing_ok) {
4066
4314
  output.push('IF EXISTS');
4067
4315
  }
4068
- const alterContext = node.objtype === 'OBJECT_TYPE'
4069
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
4070
- : context;
4316
+ const alterContext = context.spawn('AlterTableStmt', { objtype: node.objtype });
4071
4317
  if (node.relation) {
4072
4318
  const relationStr = this.RangeVar(node.relation, alterContext);
4073
4319
  output.push(relationStr);
4074
4320
  }
4075
4321
  if (node.cmds && node.cmds.length > 0) {
4076
- const commandsStr = ListUtils.unwrapList(node.cmds)
4077
- .map(cmd => this.visit(cmd, alterContext))
4078
- .join(', ');
4079
- output.push(commandsStr);
4322
+ const commands = ListUtils.unwrapList(node.cmds);
4323
+ if (context.isPretty()) {
4324
+ const commandsStr = commands
4325
+ .map(cmd => {
4326
+ const cmdStr = this.visit(cmd, alterContext);
4327
+ if (cmdStr.startsWith('ADD CONSTRAINT') || cmdStr.startsWith('ADD ')) {
4328
+ return context.newline() + context.indent(cmdStr);
4329
+ }
4330
+ return cmdStr;
4331
+ })
4332
+ .join(',');
4333
+ output.push(commandsStr);
4334
+ }
4335
+ else {
4336
+ const commandsStr = commands
4337
+ .map(cmd => this.visit(cmd, alterContext))
4338
+ .join(', ');
4339
+ output.push(commandsStr);
4340
+ }
4080
4341
  }
4081
4342
  return output.join(' ');
4082
4343
  }
@@ -4085,7 +4346,7 @@ export class Deparser {
4085
4346
  if (node.subtype) {
4086
4347
  switch (node.subtype) {
4087
4348
  case 'AT_AddColumn':
4088
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4349
+ if (context.objtype === 'OBJECT_TYPE') {
4089
4350
  output.push('ADD ATTRIBUTE');
4090
4351
  }
4091
4352
  else {
@@ -4105,14 +4366,14 @@ export class Deparser {
4105
4366
  }
4106
4367
  if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4107
4368
  parts.push('OPTIONS');
4108
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
4369
+ const columnContext = context.spawn('ColumnDef');
4109
4370
  const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4110
4371
  parts.push(`(${options.join(', ')})`);
4111
4372
  }
4112
4373
  if (colDefData.constraints) {
4113
4374
  const constraints = ListUtils.unwrapList(colDefData.constraints);
4114
4375
  const constraintStrs = constraints.map(constraint => {
4115
- const columnConstraintContext = { ...context, isColumnConstraint: true };
4376
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4116
4377
  return this.visit(constraint, columnConstraintContext);
4117
4378
  });
4118
4379
  parts.push(...constraintStrs);
@@ -4132,7 +4393,7 @@ export class Deparser {
4132
4393
  break;
4133
4394
  case 'AT_DropColumn':
4134
4395
  if (node.missing_ok) {
4135
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4396
+ if (context.objtype === 'OBJECT_TYPE') {
4136
4397
  output.push('DROP ATTRIBUTE IF EXISTS');
4137
4398
  }
4138
4399
  else {
@@ -4140,7 +4401,7 @@ export class Deparser {
4140
4401
  }
4141
4402
  }
4142
4403
  else {
4143
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4404
+ if (context.objtype === 'OBJECT_TYPE') {
4144
4405
  output.push('DROP ATTRIBUTE');
4145
4406
  }
4146
4407
  else {
@@ -4158,7 +4419,7 @@ export class Deparser {
4158
4419
  }
4159
4420
  break;
4160
4421
  case 'AT_AlterColumnType':
4161
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4422
+ if (context.objtype === 'OBJECT_TYPE') {
4162
4423
  output.push('ALTER ATTRIBUTE');
4163
4424
  }
4164
4425
  else {
@@ -4222,7 +4483,7 @@ export class Deparser {
4222
4483
  case 'AT_SetRelOptions':
4223
4484
  output.push('SET');
4224
4485
  if (node.def) {
4225
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetRelOptions' };
4486
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetRelOptions' });
4226
4487
  const options = ListUtils.unwrapList(node.def)
4227
4488
  .map(option => this.visit(option, alterTableContext))
4228
4489
  .join(', ');
@@ -4235,7 +4496,7 @@ export class Deparser {
4235
4496
  case 'AT_ResetRelOptions':
4236
4497
  output.push('RESET');
4237
4498
  if (node.def) {
4238
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetRelOptions' };
4499
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetRelOptions' });
4239
4500
  const options = ListUtils.unwrapList(node.def)
4240
4501
  .map(option => this.visit(option, alterTableContext))
4241
4502
  .join(', ');
@@ -4330,7 +4591,7 @@ export class Deparser {
4330
4591
  }
4331
4592
  output.push('SET');
4332
4593
  if (node.def) {
4333
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetOptions' };
4594
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetOptions' });
4334
4595
  const options = ListUtils.unwrapList(node.def)
4335
4596
  .map(option => this.visit(option, alterTableContext))
4336
4597
  .join(', ');
@@ -4347,7 +4608,7 @@ export class Deparser {
4347
4608
  }
4348
4609
  output.push('RESET');
4349
4610
  if (node.def) {
4350
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetOptions' };
4611
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetOptions' });
4351
4612
  const options = ListUtils.unwrapList(node.def)
4352
4613
  .map(option => this.visit(option, alterTableContext))
4353
4614
  .join(', ');
@@ -4588,7 +4849,7 @@ export class Deparser {
4588
4849
  }
4589
4850
  output.push('OPTIONS');
4590
4851
  if (node.def) {
4591
- const alterColumnContext = { ...context, alterColumnOptions: true };
4852
+ const alterColumnContext = context.spawn('AlterTableCmd', { alterColumnOptions: true });
4592
4853
  const options = ListUtils.unwrapList(node.def)
4593
4854
  .map(option => this.visit(option, alterColumnContext))
4594
4855
  .join(', ');
@@ -4628,7 +4889,7 @@ export class Deparser {
4628
4889
  case 'AT_GenericOptions':
4629
4890
  output.push('OPTIONS');
4630
4891
  if (node.def) {
4631
- const alterTableContext = { ...context, alterTableOptions: true };
4892
+ const alterTableContext = context.spawn('AlterTableCmd', { alterTableOptions: true });
4632
4893
  const options = ListUtils.unwrapList(node.def)
4633
4894
  .map(option => this.visit(option, alterTableContext))
4634
4895
  .join(', ');
@@ -4732,7 +4993,7 @@ export class Deparser {
4732
4993
  output.push(this.TypeName(node.returnType, context));
4733
4994
  }
4734
4995
  if (node.options && node.options.length > 0) {
4735
- const funcContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFunctionStmt'] };
4996
+ const funcContext = context.spawn('CreateFunctionStmt');
4736
4997
  const options = node.options.map((opt) => this.visit(opt, funcContext));
4737
4998
  output.push(...options);
4738
4999
  }
@@ -4819,7 +5080,7 @@ export class Deparser {
4819
5080
  }
4820
5081
  output.push('AS', 'ENUM');
4821
5082
  if (node.vals && node.vals.length > 0) {
4822
- const enumContext = { ...context, isEnumValue: true };
5083
+ const enumContext = context.spawn('CreateEnumStmt', { isEnumValue: true });
4823
5084
  const values = ListUtils.unwrapList(node.vals)
4824
5085
  .map(val => this.visit(val, enumContext))
4825
5086
  .join(', ');
@@ -4874,9 +5135,8 @@ export class Deparser {
4874
5135
  output.push(roleName);
4875
5136
  }
4876
5137
  if (node.options) {
4877
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateRoleStmt'] };
4878
5138
  const options = ListUtils.unwrapList(node.options)
4879
- .map(option => this.visit(option, roleContext))
5139
+ .map(option => this.visit(option, context.spawn('CreateRoleStmt')))
4880
5140
  .join(' ');
4881
5141
  if (options) {
4882
5142
  output.push('WITH');
@@ -4907,7 +5167,7 @@ export class Deparser {
4907
5167
  const stringData = this.getNodeData(node.arg);
4908
5168
  return `${node.defname}='${stringData.sval}'`;
4909
5169
  }
4910
- return `${node.defname}=${this.visit(node.arg, { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] })}`;
5170
+ return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
4911
5171
  }
4912
5172
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
4913
5173
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -4923,13 +5183,13 @@ export class Deparser {
4923
5183
  if (!node.arg) {
4924
5184
  return `NO ${node.defname.toUpperCase()}`;
4925
5185
  }
4926
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5186
+ const defElemContext = context.spawn('DefElem');
4927
5187
  const argValue = this.visit(node.arg, defElemContext);
4928
5188
  return `${node.defname.toUpperCase()} ${argValue}`;
4929
5189
  }
4930
5190
  // Handle OPTIONS clause - use space format, not equals format
4931
5191
  if (node.arg) {
4932
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5192
+ const defElemContext = context.spawn('DefElem');
4933
5193
  const argValue = this.visit(node.arg, defElemContext);
4934
5194
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
4935
5195
  const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
@@ -4983,7 +5243,7 @@ export class Deparser {
4983
5243
  if (!node.arg) {
4984
5244
  return 'PASSWORD NULL';
4985
5245
  }
4986
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5246
+ const defElemContext = context.spawn('DefElem');
4987
5247
  const argValue = this.visit(node.arg, defElemContext);
4988
5248
  const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
4989
5249
  ? `'${argValue}'`
@@ -4992,7 +5252,7 @@ export class Deparser {
4992
5252
  }
4993
5253
  }
4994
5254
  if (node.arg) {
4995
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5255
+ const defElemContext = context.spawn('DefElem');
4996
5256
  const argValue = this.visit(node.arg, defElemContext);
4997
5257
  if (context.parentNodeTypes.includes('AlterOperatorStmt')) {
4998
5258
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
@@ -5068,7 +5328,7 @@ export class Deparser {
5068
5328
  if (node.defname === 'sysid') {
5069
5329
  return `SYSID ${argValue}`;
5070
5330
  }
5071
- if (argValue === 'true') {
5331
+ if (String(argValue) === 'true') {
5072
5332
  // Handle special cases where the positive form has a different name
5073
5333
  if (node.defname === 'isreplication') {
5074
5334
  return 'REPLICATION';
@@ -5078,7 +5338,7 @@ export class Deparser {
5078
5338
  }
5079
5339
  return node.defname.toUpperCase();
5080
5340
  }
5081
- else if (argValue === 'false') {
5341
+ else if (String(argValue) === 'false') {
5082
5342
  // Handle special cases where the negative form has a different name
5083
5343
  if (node.defname === 'canlogin') {
5084
5344
  return 'NOLOGIN';
@@ -5142,7 +5402,7 @@ export class Deparser {
5142
5402
  }
5143
5403
  if (context.parentNodeTypes.includes('DoStmt')) {
5144
5404
  if (node.defname === 'as') {
5145
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5405
+ const defElemContext = context.spawn('DefElem');
5146
5406
  const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
5147
5407
  if (Array.isArray(argValue)) {
5148
5408
  const bodyParts = argValue;
@@ -5433,7 +5693,7 @@ export class Deparser {
5433
5693
  }
5434
5694
  if (node.options && node.options.length > 0) {
5435
5695
  output.push('WITH');
5436
- const tsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateTableSpaceStmt'] };
5696
+ const tsContext = context.spawn('CreateTableSpaceStmt');
5437
5697
  const options = ListUtils.unwrapList(node.options)
5438
5698
  .map(option => this.visit(option, tsContext))
5439
5699
  .join(', ');
@@ -5463,7 +5723,7 @@ export class Deparser {
5463
5723
  output.push('SET');
5464
5724
  }
5465
5725
  if (node.options && node.options.length > 0) {
5466
- const tablespaceContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableSpaceOptionsStmt'] };
5726
+ const tablespaceContext = context.spawn('AlterTableSpaceOptionsStmt');
5467
5727
  const options = ListUtils.unwrapList(node.options)
5468
5728
  .map(option => this.visit(option, tablespaceContext))
5469
5729
  .join(', ');
@@ -5480,7 +5740,7 @@ export class Deparser {
5480
5740
  output.push(this.quoteIfNeeded(node.extname));
5481
5741
  }
5482
5742
  if (node.options && node.options.length > 0) {
5483
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateExtensionStmt'] };
5743
+ const extContext = context.spawn('CreateExtensionStmt');
5484
5744
  const options = ListUtils.unwrapList(node.options)
5485
5745
  .map(option => this.visit(option, extContext))
5486
5746
  .join(' ');
@@ -5494,7 +5754,7 @@ export class Deparser {
5494
5754
  output.push(this.quoteIfNeeded(node.extname));
5495
5755
  }
5496
5756
  if (node.options && node.options.length > 0) {
5497
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterExtensionStmt'] };
5757
+ const extContext = context.spawn('AlterExtensionStmt');
5498
5758
  const options = ListUtils.unwrapList(node.options)
5499
5759
  .map(option => this.visit(option, extContext))
5500
5760
  .join(' ');
@@ -5508,7 +5768,7 @@ export class Deparser {
5508
5768
  output.push(node.fdwname);
5509
5769
  }
5510
5770
  if (node.func_options && node.func_options.length > 0) {
5511
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5771
+ const fdwContext = context.spawn('CreateFdwStmt');
5512
5772
  const funcOptions = ListUtils.unwrapList(node.func_options)
5513
5773
  .map(option => this.visit(option, fdwContext))
5514
5774
  .join(' ');
@@ -5516,7 +5776,7 @@ export class Deparser {
5516
5776
  }
5517
5777
  if (node.options && node.options.length > 0) {
5518
5778
  output.push('OPTIONS');
5519
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5779
+ const fdwContext = context.spawn('CreateFdwStmt');
5520
5780
  const options = ListUtils.unwrapList(node.options)
5521
5781
  .map(option => this.visit(option, fdwContext))
5522
5782
  .join(', ');
@@ -5618,7 +5878,7 @@ export class Deparser {
5618
5878
  output.push('ADD');
5619
5879
  if (node.def) {
5620
5880
  // Pass domain context to avoid adding constraint names for domain constraints
5621
- const domainContext = { ...context, isDomainConstraint: true };
5881
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5622
5882
  output.push(this.visit(node.def, domainContext));
5623
5883
  }
5624
5884
  break;
@@ -5644,7 +5904,7 @@ export class Deparser {
5644
5904
  output.push('ADD');
5645
5905
  if (node.def) {
5646
5906
  // Pass domain context to avoid adding constraint names for domain constraints
5647
- const domainContext = { ...context, isDomainConstraint: true };
5907
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5648
5908
  output.push(this.visit(node.def, domainContext));
5649
5909
  }
5650
5910
  break;
@@ -6006,7 +6266,7 @@ export class Deparser {
6006
6266
  output.push(`${operatorName}(${args.join(', ')})`);
6007
6267
  }
6008
6268
  else {
6009
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CommentStmt'], objtype: node.objtype };
6269
+ const objContext = context.spawn('CommentStmt', { objtype: node.objtype });
6010
6270
  output.push(this.visit(node.object, objContext));
6011
6271
  }
6012
6272
  }
@@ -6052,13 +6312,13 @@ export class Deparser {
6052
6312
  const output = [];
6053
6313
  const initialParts = ['CREATE', 'POLICY'];
6054
6314
  if (node.policy_name) {
6055
- initialParts.push(`"${node.policy_name}"`);
6315
+ initialParts.push(QuoteUtils.quote(node.policy_name));
6056
6316
  }
6057
6317
  output.push(initialParts.join(' '));
6058
6318
  // Add ON clause on new line in pretty mode
6059
6319
  if (node.table) {
6060
- if (this.formatter.isPretty()) {
6061
- output.push(this.formatter.newline() + this.formatter.indent(`ON ${this.RangeVar(node.table, context)}`));
6320
+ if (context.isPretty()) {
6321
+ output.push(context.newline() + context.indent(`ON ${this.RangeVar(node.table, context)}`));
6062
6322
  }
6063
6323
  else {
6064
6324
  output.push('ON');
@@ -6067,24 +6327,24 @@ export class Deparser {
6067
6327
  }
6068
6328
  // Handle AS RESTRICTIVE/PERMISSIVE clause
6069
6329
  if (node.permissive === undefined) {
6070
- if (this.formatter.isPretty()) {
6071
- output.push(this.formatter.newline() + this.formatter.indent('AS RESTRICTIVE'));
6330
+ if (context.isPretty()) {
6331
+ output.push(context.newline() + context.indent('AS RESTRICTIVE'));
6072
6332
  }
6073
6333
  else {
6074
6334
  output.push('AS', 'RESTRICTIVE');
6075
6335
  }
6076
6336
  }
6077
6337
  else if (node.permissive === true) {
6078
- if (this.formatter.isPretty()) {
6079
- output.push(this.formatter.newline() + this.formatter.indent('AS PERMISSIVE'));
6338
+ if (context.isPretty()) {
6339
+ output.push(context.newline() + context.indent('AS PERMISSIVE'));
6080
6340
  }
6081
6341
  else {
6082
6342
  output.push('AS', 'PERMISSIVE');
6083
6343
  }
6084
6344
  }
6085
6345
  if (node.cmd_name) {
6086
- if (this.formatter.isPretty()) {
6087
- output.push(this.formatter.newline() + this.formatter.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6346
+ if (context.isPretty()) {
6347
+ output.push(context.newline() + context.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6088
6348
  }
6089
6349
  else {
6090
6350
  output.push('FOR', node.cmd_name.toUpperCase());
@@ -6092,8 +6352,8 @@ export class Deparser {
6092
6352
  }
6093
6353
  if (node.roles && node.roles.length > 0) {
6094
6354
  const roles = ListUtils.unwrapList(node.roles).map(role => this.visit(role, context));
6095
- if (this.formatter.isPretty()) {
6096
- output.push(this.formatter.newline() + this.formatter.indent(`TO ${roles.join(', ')}`));
6355
+ if (context.isPretty()) {
6356
+ output.push(context.newline() + context.indent(`TO ${roles.join(', ')}`));
6097
6357
  }
6098
6358
  else {
6099
6359
  output.push('TO');
@@ -6101,11 +6361,11 @@ export class Deparser {
6101
6361
  }
6102
6362
  }
6103
6363
  if (node.qual) {
6104
- if (this.formatter.isPretty()) {
6364
+ if (context.isPretty()) {
6105
6365
  const qualExpr = this.visit(node.qual, context);
6106
- output.push(this.formatter.newline() + this.formatter.indent('USING ('));
6107
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(qualExpr)));
6108
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6366
+ output.push(context.newline() + context.indent('USING ('));
6367
+ output.push(context.newline() + context.indent(context.indent(qualExpr)));
6368
+ output.push(context.newline() + context.indent(')'));
6109
6369
  }
6110
6370
  else {
6111
6371
  output.push('USING');
@@ -6113,23 +6373,23 @@ export class Deparser {
6113
6373
  }
6114
6374
  }
6115
6375
  if (node.with_check) {
6116
- if (this.formatter.isPretty()) {
6376
+ if (context.isPretty()) {
6117
6377
  const checkExpr = this.visit(node.with_check, context);
6118
- output.push(this.formatter.newline() + this.formatter.indent('WITH CHECK ('));
6119
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(checkExpr)));
6120
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6378
+ output.push(context.newline() + context.indent('WITH CHECK ('));
6379
+ output.push(context.newline() + context.indent(context.indent(checkExpr)));
6380
+ output.push(context.newline() + context.indent(')'));
6121
6381
  }
6122
6382
  else {
6123
6383
  output.push('WITH CHECK');
6124
6384
  output.push(`(${this.visit(node.with_check, context)})`);
6125
6385
  }
6126
6386
  }
6127
- return this.formatter.isPretty() ? output.join('') : output.join(' ');
6387
+ return context.isPretty() ? output.join('') : output.join(' ');
6128
6388
  }
6129
6389
  AlterPolicyStmt(node, context) {
6130
6390
  const output = ['ALTER', 'POLICY'];
6131
6391
  if (node.policy_name) {
6132
- output.push(`"${node.policy_name}"`);
6392
+ output.push(QuoteUtils.quote(node.policy_name));
6133
6393
  }
6134
6394
  if (node.table) {
6135
6395
  output.push('ON');
@@ -6169,7 +6429,7 @@ export class Deparser {
6169
6429
  }
6170
6430
  if (node.options && node.options.length > 0) {
6171
6431
  output.push('OPTIONS');
6172
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateUserMappingStmt'] };
6432
+ const userMappingContext = context.spawn('CreateUserMappingStmt');
6173
6433
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6174
6434
  output.push(`(${options.join(', ')})`);
6175
6435
  }
@@ -6352,7 +6612,7 @@ export class Deparser {
6352
6612
  DoStmt(node, context) {
6353
6613
  const output = ['DO'];
6354
6614
  if (node.args && node.args.length > 0) {
6355
- const doContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DoStmt'] };
6615
+ const doContext = context.spawn('DoStmt');
6356
6616
  const args = ListUtils.unwrapList(node.args);
6357
6617
  const processedArgs = [];
6358
6618
  for (const arg of args) {
@@ -6657,7 +6917,7 @@ export class Deparser {
6657
6917
  ObjectWithArgs(node, context) {
6658
6918
  let result = '';
6659
6919
  if (node.objname && node.objname.length > 0) {
6660
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ObjectWithArgs'] };
6920
+ const objContext = context.spawn('ObjectWithArgs');
6661
6921
  const names = ListUtils.unwrapList(node.objname).map(name => this.visit(name, objContext));
6662
6922
  result = names.join('.');
6663
6923
  }
@@ -6699,7 +6959,7 @@ export class Deparser {
6699
6959
  }
6700
6960
  output.push('SET');
6701
6961
  if (node.options && node.options.length > 0) {
6702
- const alterOpContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterOperatorStmt'] };
6962
+ const alterOpContext = context.spawn('AlterOperatorStmt');
6703
6963
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, alterOpContext));
6704
6964
  output.push(`(${options.join(', ')})`);
6705
6965
  }
@@ -6711,13 +6971,13 @@ export class Deparser {
6711
6971
  output.push(QuoteUtils.quote(node.fdwname));
6712
6972
  }
6713
6973
  if (node.func_options && node.func_options.length > 0) {
6714
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
6974
+ const fdwContext = context.spawn('AlterFdwStmt');
6715
6975
  const funcOptions = ListUtils.unwrapList(node.func_options).map(opt => this.visit(opt, fdwContext));
6716
6976
  output.push(funcOptions.join(' '));
6717
6977
  }
6718
6978
  if (node.options && node.options.length > 0) {
6719
6979
  output.push('OPTIONS');
6720
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
6980
+ const fdwContext = context.spawn('AlterFdwStmt');
6721
6981
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, fdwContext));
6722
6982
  output.push(`(${options.join(', ')})`);
6723
6983
  }
@@ -6743,7 +7003,7 @@ export class Deparser {
6743
7003
  if (node.options && node.options.length > 0) {
6744
7004
  output.push('OPTIONS');
6745
7005
  output.push('(');
6746
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignServerStmt'] };
7006
+ const optionsContext = context.spawn('CreateForeignServerStmt');
6747
7007
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6748
7008
  output.push(options.join(', '));
6749
7009
  output.push(')');
@@ -6761,7 +7021,7 @@ export class Deparser {
6761
7021
  if (node.options && node.options.length > 0) {
6762
7022
  output.push('OPTIONS');
6763
7023
  output.push('(');
6764
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterForeignServerStmt'] };
7024
+ const optionsContext = context.spawn('AlterForeignServerStmt');
6765
7025
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6766
7026
  output.push(options.join(', '));
6767
7027
  output.push(')');
@@ -6782,7 +7042,7 @@ export class Deparser {
6782
7042
  }
6783
7043
  if (node.options && node.options.length > 0) {
6784
7044
  output.push('OPTIONS');
6785
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterUserMappingStmt'] };
7045
+ const userMappingContext = context.spawn('AlterUserMappingStmt');
6786
7046
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6787
7047
  output.push(`(${options.join(', ')})`);
6788
7048
  }
@@ -6842,7 +7102,7 @@ export class Deparser {
6842
7102
  output.push(QuoteUtils.quote(node.local_schema));
6843
7103
  }
6844
7104
  if (node.options && node.options.length > 0) {
6845
- const importSchemaContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ImportForeignSchemaStmt'] };
7105
+ const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
6846
7106
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, importSchemaContext));
6847
7107
  output.push(`OPTIONS (${options.join(', ')})`);
6848
7108
  }
@@ -6877,7 +7137,7 @@ export class Deparser {
6877
7137
  ExplainStmt(node, context) {
6878
7138
  const output = ['EXPLAIN'];
6879
7139
  if (node.options && node.options.length > 0) {
6880
- const explainContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ExplainStmt'] };
7140
+ const explainContext = context.spawn('ExplainStmt');
6881
7141
  const options = ListUtils.unwrapList(node.options).map(option => this.visit(option, explainContext));
6882
7142
  output.push(`(${options.join(', ')})`);
6883
7143
  }
@@ -7135,9 +7395,7 @@ export class Deparser {
7135
7395
  output.push(this.RangeVar(node.relation, context));
7136
7396
  }
7137
7397
  else if (node.relation) {
7138
- const rangeVarContext = node.relationType === 'OBJECT_TYPE'
7139
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
7140
- : context;
7398
+ const rangeVarContext = context.spawn('RenameStmt', { objtype: node.relationType });
7141
7399
  // Add ON keyword for policy operations
7142
7400
  if (node.renameType === 'OBJECT_POLICY') {
7143
7401
  output.push('ON');
@@ -7319,7 +7577,7 @@ export class Deparser {
7319
7577
  }
7320
7578
  }
7321
7579
  if (node.privileges && node.privileges.length > 0) {
7322
- const privilegeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'GrantStmt'] };
7580
+ const privilegeContext = context.spawn('GrantStmt');
7323
7581
  const privileges = ListUtils.unwrapList(node.privileges)
7324
7582
  .map(priv => this.visit(priv, privilegeContext))
7325
7583
  .join(', ');
@@ -7627,7 +7885,12 @@ export class Deparser {
7627
7885
  }
7628
7886
  if (node.action) {
7629
7887
  const actionStr = this.GrantStmt(node.action, context);
7630
- output.push(actionStr);
7888
+ if (context.isPretty()) {
7889
+ return output.join(' ') + context.newline() + context.indent(actionStr);
7890
+ }
7891
+ else {
7892
+ output.push(actionStr);
7893
+ }
7631
7894
  }
7632
7895
  return output.join(' ');
7633
7896
  }
@@ -7774,84 +8037,158 @@ export class Deparser {
7774
8037
  if (node.trigname) {
7775
8038
  output.push(QuoteUtils.quote(node.trigname));
7776
8039
  }
7777
- const timing = [];
7778
- if (node.timing & 2)
7779
- timing.push('BEFORE');
7780
- else if (node.timing & 64)
7781
- timing.push('INSTEAD OF');
7782
- else
7783
- timing.push('AFTER'); // Default timing when no specific timing is set
7784
- output.push(timing.join(' '));
7785
- const events = [];
7786
- if (node.events & 4)
7787
- events.push('INSERT');
7788
- if (node.events & 8)
7789
- events.push('DELETE');
7790
- if (node.events & 16)
7791
- events.push('UPDATE');
7792
- if (node.events & 32)
7793
- events.push('TRUNCATE');
7794
- output.push(events.join(' OR '));
7795
- if (node.columns && node.columns.length > 0) {
7796
- output.push('OF');
7797
- const columnNames = ListUtils.unwrapList(node.columns)
7798
- .map(col => this.visit(col, context))
7799
- .join(', ');
7800
- output.push(columnNames);
7801
- }
7802
- output.push('ON');
7803
- if (node.relation) {
7804
- output.push(this.RangeVar(node.relation, context));
7805
- }
7806
- if (node.constrrel) {
7807
- output.push('FROM');
7808
- output.push(this.RangeVar(node.constrrel, context));
7809
- }
7810
- if (node.deferrable) {
7811
- output.push('DEFERRABLE');
7812
- }
7813
- if (node.initdeferred) {
7814
- output.push('INITIALLY DEFERRED');
7815
- }
7816
- // Handle REFERENCING clauses
7817
- if (node.transitionRels && node.transitionRels.length > 0) {
7818
- output.push('REFERENCING');
7819
- const transitionClauses = ListUtils.unwrapList(node.transitionRels)
7820
- .map(rel => this.visit(rel, context))
7821
- .join(' ');
7822
- output.push(transitionClauses);
7823
- }
7824
- if (node.row) {
7825
- output.push('FOR EACH ROW');
7826
- }
7827
- else {
7828
- output.push('FOR EACH STATEMENT');
7829
- }
7830
- if (node.whenClause) {
7831
- output.push('WHEN');
7832
- output.push('(');
7833
- output.push(this.visit(node.whenClause, context));
7834
- output.push(')');
7835
- }
7836
- output.push('EXECUTE');
7837
- if (node.funcname && node.funcname.length > 0) {
7838
- const funcName = ListUtils.unwrapList(node.funcname)
7839
- .map(name => this.visit(name, context))
7840
- .join('.');
7841
- output.push('FUNCTION', funcName);
7842
- }
7843
- if (node.args && node.args.length > 0) {
7844
- output.push('(');
7845
- const args = ListUtils.unwrapList(node.args)
7846
- .map(arg => this.visit(arg, context))
7847
- .join(', ');
7848
- output.push(args);
7849
- output.push(')');
8040
+ if (context.isPretty()) {
8041
+ const components = [];
8042
+ const timing = [];
8043
+ if (node.timing & 2)
8044
+ timing.push('BEFORE');
8045
+ else if (node.timing & 64)
8046
+ timing.push('INSTEAD OF');
8047
+ else
8048
+ timing.push('AFTER');
8049
+ const events = [];
8050
+ if (node.events & 4)
8051
+ events.push('INSERT');
8052
+ if (node.events & 8)
8053
+ events.push('DELETE');
8054
+ if (node.events & 16) {
8055
+ let updateStr = 'UPDATE';
8056
+ if (node.columns && node.columns.length > 0) {
8057
+ const columnNames = ListUtils.unwrapList(node.columns)
8058
+ .map(col => this.visit(col, context))
8059
+ .join(', ');
8060
+ updateStr += ' OF ' + columnNames;
8061
+ }
8062
+ events.push(updateStr);
8063
+ }
8064
+ if (node.events & 32)
8065
+ events.push('TRUNCATE');
8066
+ components.push(context.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8067
+ if (node.relation) {
8068
+ components.push(context.indent('ON ' + this.RangeVar(node.relation, context)));
8069
+ }
8070
+ if (node.transitionRels && node.transitionRels.length > 0) {
8071
+ const transitionClauses = ListUtils.unwrapList(node.transitionRels)
8072
+ .map(rel => this.visit(rel, context))
8073
+ .join(' ');
8074
+ components.push(context.indent('REFERENCING ' + transitionClauses));
8075
+ }
8076
+ if (node.deferrable) {
8077
+ components.push(context.indent('DEFERRABLE'));
8078
+ }
8079
+ if (node.initdeferred) {
8080
+ components.push(context.indent('INITIALLY DEFERRED'));
8081
+ }
8082
+ if (node.row) {
8083
+ components.push(context.indent('FOR EACH ROW'));
8084
+ }
8085
+ else {
8086
+ components.push(context.indent('FOR EACH STATEMENT'));
8087
+ }
8088
+ if (node.whenClause) {
8089
+ const whenStr = 'WHEN (' + this.visit(node.whenClause, context) + ')';
8090
+ components.push(context.indent(whenStr));
8091
+ }
8092
+ let executeStr = 'EXECUTE';
8093
+ if (node.funcname && node.funcname.length > 0) {
8094
+ const funcName = ListUtils.unwrapList(node.funcname)
8095
+ .map(name => this.visit(name, context))
8096
+ .join('.');
8097
+ executeStr += ' PROCEDURE ' + funcName;
8098
+ }
8099
+ if (node.args && node.args.length > 0) {
8100
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8101
+ const args = ListUtils.unwrapList(node.args)
8102
+ .map(arg => this.visit(arg, argContext))
8103
+ .join(', ');
8104
+ executeStr += '(' + args + ')';
8105
+ }
8106
+ else {
8107
+ executeStr += '()';
8108
+ }
8109
+ components.push(context.indent(executeStr));
8110
+ return output.join(' ') + context.newline() + components.join(context.newline());
7850
8111
  }
7851
8112
  else {
7852
- output.push('()');
8113
+ const timing = [];
8114
+ if (node.timing & 2)
8115
+ timing.push('BEFORE');
8116
+ else if (node.timing & 64)
8117
+ timing.push('INSTEAD OF');
8118
+ else
8119
+ timing.push('AFTER');
8120
+ output.push(timing.join(' '));
8121
+ const events = [];
8122
+ if (node.events & 4)
8123
+ events.push('INSERT');
8124
+ if (node.events & 8)
8125
+ events.push('DELETE');
8126
+ if (node.events & 16)
8127
+ events.push('UPDATE');
8128
+ if (node.events & 32)
8129
+ events.push('TRUNCATE');
8130
+ output.push(events.join(' OR '));
8131
+ if (node.columns && node.columns.length > 0) {
8132
+ output.push('OF');
8133
+ const columnNames = ListUtils.unwrapList(node.columns)
8134
+ .map(col => this.visit(col, context))
8135
+ .join(', ');
8136
+ output.push(columnNames);
8137
+ }
8138
+ output.push('ON');
8139
+ if (node.relation) {
8140
+ output.push(this.RangeVar(node.relation, context));
8141
+ }
8142
+ if (node.constrrel) {
8143
+ output.push('FROM');
8144
+ output.push(this.RangeVar(node.constrrel, context));
8145
+ }
8146
+ if (node.deferrable) {
8147
+ output.push('DEFERRABLE');
8148
+ }
8149
+ if (node.initdeferred) {
8150
+ output.push('INITIALLY DEFERRED');
8151
+ }
8152
+ if (node.transitionRels && node.transitionRels.length > 0) {
8153
+ output.push('REFERENCING');
8154
+ const transitionClauses = ListUtils.unwrapList(node.transitionRels)
8155
+ .map(rel => this.visit(rel, context))
8156
+ .join(' ');
8157
+ output.push(transitionClauses);
8158
+ }
8159
+ if (node.row) {
8160
+ output.push('FOR EACH ROW');
8161
+ }
8162
+ else {
8163
+ output.push('FOR EACH STATEMENT');
8164
+ }
8165
+ if (node.whenClause) {
8166
+ output.push('WHEN');
8167
+ output.push('(');
8168
+ output.push(this.visit(node.whenClause, context));
8169
+ output.push(')');
8170
+ }
8171
+ output.push('EXECUTE');
8172
+ if (node.funcname && node.funcname.length > 0) {
8173
+ const funcName = ListUtils.unwrapList(node.funcname)
8174
+ .map(name => this.visit(name, context))
8175
+ .join('.');
8176
+ output.push('FUNCTION', funcName);
8177
+ }
8178
+ if (node.args && node.args.length > 0) {
8179
+ output.push('(');
8180
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8181
+ const args = ListUtils.unwrapList(node.args)
8182
+ .map(arg => this.visit(arg, argContext))
8183
+ .join(', ');
8184
+ output.push(args);
8185
+ output.push(')');
8186
+ }
8187
+ else {
8188
+ output.push('()');
8189
+ }
8190
+ return output.join(' ');
7853
8191
  }
7854
- return output.join(' ');
7855
8192
  }
7856
8193
  TriggerTransition(node, context) {
7857
8194
  const output = [];
@@ -7877,7 +8214,7 @@ export class Deparser {
7877
8214
  }
7878
8215
  if (node.whenclause && node.whenclause.length > 0) {
7879
8216
  output.push('WHEN');
7880
- const eventTriggerContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateEventTrigStmt'] };
8217
+ const eventTriggerContext = context.spawn('CreateEventTrigStmt');
7881
8218
  const conditions = ListUtils.unwrapList(node.whenclause)
7882
8219
  .map(condition => this.visit(condition, eventTriggerContext))
7883
8220
  .join(' AND ');
@@ -8066,7 +8403,7 @@ export class Deparser {
8066
8403
  output.push(sequenceName.join('.'));
8067
8404
  }
8068
8405
  if (node.options && node.options.length > 0) {
8069
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateSeqStmt'] };
8406
+ const seqContext = context.spawn('CreateSeqStmt');
8070
8407
  const optionStrs = ListUtils.unwrapList(node.options)
8071
8408
  .filter(option => option != null && this.getNodeType(option) !== 'undefined')
8072
8409
  .map(option => {
@@ -8103,7 +8440,7 @@ export class Deparser {
8103
8440
  output.push(sequenceName.join('.'));
8104
8441
  }
8105
8442
  if (node.options && node.options.length > 0) {
8106
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterSeqStmt'] };
8443
+ const seqContext = context.spawn('AlterSeqStmt');
8107
8444
  const optionStrs = ListUtils.unwrapList(node.options)
8108
8445
  .filter(option => option && option !== undefined)
8109
8446
  .map(option => {
@@ -8132,7 +8469,7 @@ export class Deparser {
8132
8469
  CompositeTypeStmt(node, context) {
8133
8470
  const output = ['CREATE', 'TYPE'];
8134
8471
  if (node.typevar) {
8135
- const typeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CompositeTypeStmt'] };
8472
+ const typeContext = context.spawn('CompositeTypeStmt');
8136
8473
  output.push(this.RangeVar(node.typevar, typeContext));
8137
8474
  }
8138
8475
  output.push('AS');
@@ -8233,7 +8570,7 @@ export class Deparser {
8233
8570
  output.push(this.RoleSpec(node.role, context));
8234
8571
  }
8235
8572
  if (node.options) {
8236
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterRoleStmt'] };
8573
+ const roleContext = context.spawn('AlterRoleStmt');
8237
8574
  // Handle GROUP operations specially based on action value
8238
8575
  if (isGroupStatement) {
8239
8576
  const roleMembersOption = ListUtils.unwrapList(node.options).find(option => option.DefElem && option.DefElem.defname === 'rolemembers');
@@ -8429,14 +8766,14 @@ export class Deparser {
8429
8766
  AccessPriv(node, context) {
8430
8767
  const output = [];
8431
8768
  if (node.priv_name) {
8432
- output.push(node.priv_name);
8769
+ output.push(node.priv_name.toUpperCase());
8433
8770
  }
8434
8771
  else {
8435
8772
  output.push('ALL');
8436
8773
  }
8437
8774
  if (node.cols && node.cols.length > 0) {
8438
8775
  output.push('(');
8439
- const colContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AccessPriv'] };
8776
+ const colContext = context.spawn('AccessPriv');
8440
8777
  const columns = ListUtils.unwrapList(node.cols).map(col => this.visit(col, colContext));
8441
8778
  output.push(columns.join(', '));
8442
8779
  output.push(')');
@@ -8516,7 +8853,7 @@ export class Deparser {
8516
8853
  output.push(ListUtils.unwrapList(node.defnames).map(name => this.visit(name, context)).join('.'));
8517
8854
  }
8518
8855
  if (node.definition && node.definition.length > 0) {
8519
- const defineStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefineStmt'] };
8856
+ const defineStmtContext = context.spawn('DefineStmt');
8520
8857
  const definitions = ListUtils.unwrapList(node.definition).map(def => {
8521
8858
  return this.visit(def, defineStmtContext);
8522
8859
  });
@@ -9002,7 +9339,7 @@ export class Deparser {
9002
9339
  const operatorNumber = node.number !== undefined ? node.number : 0;
9003
9340
  output.push(operatorNumber.toString());
9004
9341
  if (node.name) {
9005
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9342
+ const opClassContext = context.spawn('CreateOpClassItem');
9006
9343
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9007
9344
  }
9008
9345
  }
@@ -9012,7 +9349,7 @@ export class Deparser {
9012
9349
  const functionNumber = node.number !== undefined ? node.number : 0;
9013
9350
  output.push(functionNumber.toString());
9014
9351
  if (node.name) {
9015
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9352
+ const opClassContext = context.spawn('CreateOpClassItem');
9016
9353
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9017
9354
  }
9018
9355
  }
@@ -9710,7 +10047,7 @@ export class Deparser {
9710
10047
  output.push(this.ObjectWithArgs(node.func, context));
9711
10048
  }
9712
10049
  if (node.actions && node.actions.length > 0) {
9713
- const alterFunctionContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFunctionStmt'] };
10050
+ const alterFunctionContext = context.spawn('AlterFunctionStmt');
9714
10051
  const actionStrs = ListUtils.unwrapList(node.actions).map(action => this.visit(action, alterFunctionContext));
9715
10052
  output.push(actionStrs.join(' '));
9716
10053
  }
@@ -9954,7 +10291,7 @@ export class Deparser {
9954
10291
  CreateForeignTableStmt(node, context) {
9955
10292
  const output = ['CREATE FOREIGN TABLE'];
9956
10293
  if (node.base && node.base.relation) {
9957
- const relationContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10294
+ const relationContext = context.spawn('CreateForeignTableStmt');
9958
10295
  // Handle relation node directly as RangeVar since it contains the RangeVar properties
9959
10296
  output.push(this.RangeVar(node.base.relation, relationContext));
9960
10297
  }
@@ -9984,7 +10321,7 @@ export class Deparser {
9984
10321
  output.push(QuoteUtils.quote(node.servername));
9985
10322
  }
9986
10323
  if (node.options && node.options.length > 0) {
9987
- const foreignTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10324
+ const foreignTableContext = context.spawn('CreateForeignTableStmt');
9988
10325
  const optionStrs = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, foreignTableContext));
9989
10326
  output.push(`OPTIONS (${optionStrs.join(', ')})`);
9990
10327
  }