pgsql-deparser 14.0.1 → 14.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,66 @@
2
2
  * Auto-generated file with types stripped for better tree-shaking
3
3
  * DO NOT EDIT - Generated by strip-deparser-types.ts
4
4
  */
5
+ import { DeparserContext } from './visitors/base';
5
6
  import { SqlFormatter } from './utils/sql-formatter';
6
7
  import { QuoteUtils } from './utils/quote-utils';
7
8
  import { ListUtils } from './utils/list-utils';
9
+ /**
10
+ * List of real PostgreSQL built-in types as they appear in pg_catalog.pg_type.typname.
11
+ * These are stored in lowercase in PostgreSQL system catalogs.
12
+ * Use these for lookups, validations, or introspection logic.
13
+ */
14
+ const pgCatalogTypes = [
15
+ // Integers
16
+ 'int2', // smallint
17
+ 'int4', // integer
18
+ 'int8', // bigint
19
+ // Floating-point & numeric
20
+ 'float4', // real
21
+ 'float8', // double precision
22
+ 'numeric', // arbitrary precision (aka "decimal")
23
+ // Text & string
24
+ 'varchar', // variable-length string
25
+ 'char', // internal one-byte type (used in special cases)
26
+ 'bpchar', // blank-padded char(n)
27
+ 'text', // unlimited string
28
+ 'bool', // boolean
29
+ // Dates & times
30
+ 'date', // calendar date
31
+ 'time', // time without time zone
32
+ 'timetz', // time with time zone
33
+ 'timestamp', // timestamp without time zone
34
+ 'timestamptz', // timestamp with time zone
35
+ 'interval', // duration
36
+ // Binary & structured
37
+ 'bytea', // binary data
38
+ 'uuid', // universally unique identifier
39
+ // JSON & XML
40
+ 'json', // textual JSON
41
+ 'jsonb', // binary JSON
42
+ 'xml', // XML format
43
+ // Money & bitstrings
44
+ 'money', // currency value
45
+ 'bit', // fixed-length bit string
46
+ 'varbit', // variable-length bit string
47
+ // Network types
48
+ 'inet', // IPv4 or IPv6 address
49
+ 'cidr', // network address
50
+ 'macaddr', // MAC address (6 bytes)
51
+ 'macaddr8' // MAC address (8 bytes)
52
+ ];
53
+ /**
54
+ * Parser-level type aliases accepted by PostgreSQL SQL syntax,
55
+ * but not present in pg_catalog.pg_type. These are resolved to
56
+ * real types during parsing and never appear in introspection.
57
+ */
58
+ const pgCatalogTypeAliases = [
59
+ ['numeric', ['decimal', 'dec']],
60
+ ['int4', ['int', 'integer']],
61
+ ['float8', ['float']],
62
+ ['bpchar', ['character']],
63
+ ['varchar', ['character varying']]
64
+ ];
8
65
  // Type guards for better type safety
9
66
  function isParseResult(obj) {
10
67
  // A ParseResult is an object that could have stmts (but not required)
@@ -48,11 +105,9 @@ function isWrappedParseResult(obj) {
48
105
  * compatibility and wraps them internally for consistent processing.
49
106
  */
50
107
  export class Deparser {
51
- formatter;
52
108
  tree;
53
109
  options;
54
110
  constructor(tree, opts = {}) {
55
- this.formatter = new SqlFormatter(opts.newline, opts.tab, opts.pretty);
56
111
  // Set default options
57
112
  this.options = {
58
113
  functionDelimiter: '$$',
@@ -89,15 +144,17 @@ export class Deparser {
89
144
  return new Deparser(query, opts).deparseQuery();
90
145
  }
91
146
  deparseQuery() {
147
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
148
+ const context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
92
149
  return this.tree
93
150
  .map(node => {
94
151
  // All nodes should go through the standard deparse method
95
152
  // which will route to the appropriate handler
96
- const result = this.deparse(node);
153
+ const result = this.deparse(node, context);
97
154
  return result || '';
98
155
  })
99
156
  .filter(result => result !== '')
100
- .join(this.formatter.newline() + this.formatter.newline());
157
+ .join(context.newline() + context.newline());
101
158
  }
102
159
  /**
103
160
  * Get the appropriate function delimiter based on the body content
@@ -111,10 +168,14 @@ export class Deparser {
111
168
  }
112
169
  return delimiter;
113
170
  }
114
- deparse(node, context = { parentNodeTypes: [] }) {
171
+ deparse(node, context) {
115
172
  if (node == null) {
116
173
  return null;
117
174
  }
175
+ if (!context) {
176
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
177
+ context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
178
+ }
118
179
  if (typeof node === 'number' || node instanceof Number) {
119
180
  return node.toString();
120
181
  }
@@ -126,7 +187,11 @@ export class Deparser {
126
187
  throw new Error(`Error deparsing ${nodeType}: ${error.message}`);
127
188
  }
128
189
  }
129
- visit(node, context = { parentNodeTypes: [] }) {
190
+ visit(node, context) {
191
+ if (!context) {
192
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
193
+ context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
194
+ }
130
195
  const nodeType = this.getNodeType(node);
131
196
  // Handle empty objects
132
197
  if (!nodeType) {
@@ -135,11 +200,7 @@ export class Deparser {
135
200
  const nodeData = this.getNodeData(node);
136
201
  const methodName = nodeType;
137
202
  if (typeof this[methodName] === 'function') {
138
- const childContext = {
139
- ...context,
140
- parentNodeTypes: [...context.parentNodeTypes, nodeType]
141
- };
142
- const result = this[methodName](nodeData, childContext);
203
+ const result = this[methodName](nodeData, context);
143
204
  return result;
144
205
  }
145
206
  throw new Error(`Deparser does not handle node type: ${nodeType}`);
@@ -165,7 +226,7 @@ export class Deparser {
165
226
  .filter((rawStmt) => rawStmt != null)
166
227
  .map((rawStmt) => this.RawStmt(rawStmt, context))
167
228
  .filter((result) => result !== '')
168
- .join(this.formatter.newline() + this.formatter.newline());
229
+ .join(context.newline() + context.newline());
169
230
  }
170
231
  RawStmt(node, context) {
171
232
  if (!node.stmt) {
@@ -185,7 +246,7 @@ export class Deparser {
185
246
  }
186
247
  if (!node.op || node.op === 'SETOP_NONE') {
187
248
  if (node.valuesLists == null) {
188
- if (!this.formatter.isPretty() || !node.targetList) {
249
+ if (!context.isPretty() || !node.targetList) {
189
250
  output.push('SELECT');
190
251
  }
191
252
  }
@@ -205,7 +266,7 @@ export class Deparser {
205
266
  (node.rarg).limitOffset ||
206
267
  (node.rarg).withClause);
207
268
  if (leftNeedsParens) {
208
- output.push(this.formatter.parens(leftStmt));
269
+ output.push(context.parens(leftStmt));
209
270
  }
210
271
  else {
211
272
  output.push(leftStmt);
@@ -227,7 +288,7 @@ export class Deparser {
227
288
  output.push('ALL');
228
289
  }
229
290
  if (rightNeedsParens) {
230
- output.push(this.formatter.parens(rightStmt));
291
+ output.push(context.parens(rightStmt));
231
292
  }
232
293
  else {
233
294
  output.push(rightStmt);
@@ -239,20 +300,20 @@ export class Deparser {
239
300
  const distinctClause = ListUtils.unwrapList(node.distinctClause);
240
301
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
241
302
  const clause = distinctClause
242
- .map(e => this.visit(e, { ...context, select: true }))
303
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
243
304
  .join(', ');
244
- distinctPart = ' DISTINCT ON ' + this.formatter.parens(clause);
305
+ distinctPart = ' DISTINCT ON ' + context.parens(clause);
245
306
  }
246
307
  else {
247
308
  distinctPart = ' DISTINCT';
248
309
  }
249
- if (!this.formatter.isPretty()) {
310
+ if (!context.isPretty()) {
250
311
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
251
312
  output.push('DISTINCT ON');
252
313
  const clause = distinctClause
253
- .map(e => this.visit(e, { ...context, select: true }))
314
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
254
315
  .join(', ');
255
- output.push(this.formatter.parens(clause));
316
+ output.push(context.parens(clause));
256
317
  }
257
318
  else {
258
319
  output.push('DISTINCT');
@@ -261,22 +322,41 @@ export class Deparser {
261
322
  }
262
323
  if (node.targetList) {
263
324
  const targetList = ListUtils.unwrapList(node.targetList);
264
- if (this.formatter.isPretty()) {
265
- const targetStrings = targetList
266
- .map(e => {
267
- const targetStr = this.visit(e, { ...context, select: true });
268
- if (this.containsMultilineStringLiteral(targetStr)) {
269
- return targetStr;
325
+ if (context.isPretty()) {
326
+ if (targetList.length === 1) {
327
+ const targetNode = targetList[0];
328
+ const target = this.visit(targetNode, context.spawn('SelectStmt', { select: true }));
329
+ // Check if single target is complex - if so, use multiline format
330
+ if (this.isComplexSelectTarget(targetNode)) {
331
+ output.push('SELECT' + distinctPart);
332
+ if (this.containsMultilineStringLiteral(target)) {
333
+ output.push(target);
334
+ }
335
+ else {
336
+ output.push(context.indent(target));
337
+ }
270
338
  }
271
- return this.formatter.indent(targetStr);
272
- });
273
- const formattedTargets = targetStrings.join(',' + this.formatter.newline());
274
- output.push('SELECT' + distinctPart);
275
- output.push(formattedTargets);
339
+ else {
340
+ output.push('SELECT' + distinctPart + ' ' + target);
341
+ }
342
+ }
343
+ else {
344
+ const targetStrings = targetList
345
+ .map(e => {
346
+ const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
347
+ if (this.containsMultilineStringLiteral(targetStr)) {
348
+ return targetStr;
349
+ }
350
+ return context.indent(targetStr);
351
+ });
352
+ const formattedTargets = targetStrings.join(',' + context.newline());
353
+ output.push('SELECT' + distinctPart);
354
+ output.push(formattedTargets);
355
+ }
276
356
  }
277
357
  else {
278
358
  const targets = targetList
279
- .map(e => this.visit(e, { ...context, select: true }))
359
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
280
360
  .join(', ');
281
361
  output.push(targets);
282
362
  }
@@ -288,22 +368,22 @@ export class Deparser {
288
368
  if (node.fromClause) {
289
369
  const fromList = ListUtils.unwrapList(node.fromClause);
290
370
  const fromItems = fromList
291
- .map(e => this.deparse(e, { ...context, from: true }))
371
+ .map(e => this.deparse(e, context.spawn('SelectStmt', { from: true })))
292
372
  .join(', ');
293
373
  output.push('FROM ' + fromItems.trim());
294
374
  }
295
375
  if (node.whereClause) {
296
- if (this.formatter.isPretty()) {
376
+ if (context.isPretty()) {
297
377
  output.push('WHERE');
298
378
  const whereExpr = this.visit(node.whereClause, context);
299
- const lines = whereExpr.split(this.formatter.newline());
379
+ const lines = whereExpr.split(context.newline());
300
380
  const indentedLines = lines.map((line, index) => {
301
381
  if (index === 0) {
302
- return this.formatter.indent(line);
382
+ return context.indent(line);
303
383
  }
304
384
  return line;
305
385
  });
306
- output.push(indentedLines.join(this.formatter.newline()));
386
+ output.push(indentedLines.join(context.newline()));
307
387
  }
308
388
  else {
309
389
  output.push('WHERE');
@@ -311,45 +391,61 @@ export class Deparser {
311
391
  }
312
392
  }
313
393
  if (node.valuesLists) {
314
- output.push('VALUES');
315
- const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
316
- const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
317
- return this.formatter.parens(values.join(', '));
318
- });
319
- output.push(lists.join(', '));
394
+ if (context.isPretty()) {
395
+ output.push('VALUES');
396
+ const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
397
+ const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
398
+ return context.parens(values.join(', '));
399
+ });
400
+ const indentedTuples = lists.map(tuple => {
401
+ if (this.containsMultilineStringLiteral(tuple)) {
402
+ return tuple;
403
+ }
404
+ return context.indent(tuple);
405
+ });
406
+ output.push(indentedTuples.join(',\n'));
407
+ }
408
+ else {
409
+ output.push('VALUES');
410
+ const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
411
+ const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
412
+ return context.parens(values.join(', '));
413
+ });
414
+ output.push(lists.join(', '));
415
+ }
320
416
  }
321
417
  if (node.groupClause) {
322
418
  const groupList = ListUtils.unwrapList(node.groupClause);
323
- if (this.formatter.isPretty()) {
419
+ if (context.isPretty()) {
324
420
  const groupItems = groupList
325
421
  .map(e => {
326
- const groupStr = this.visit(e, { ...context, group: true });
422
+ const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
327
423
  if (this.containsMultilineStringLiteral(groupStr)) {
328
424
  return groupStr;
329
425
  }
330
- return this.formatter.indent(groupStr);
426
+ return context.indent(groupStr);
331
427
  })
332
- .join(',' + this.formatter.newline());
428
+ .join(',' + context.newline());
333
429
  output.push('GROUP BY');
334
430
  output.push(groupItems);
335
431
  }
336
432
  else {
337
433
  output.push('GROUP BY');
338
434
  const groupItems = groupList
339
- .map(e => this.visit(e, { ...context, group: true }))
435
+ .map(e => this.visit(e, context.spawn('SelectStmt', { group: true })))
340
436
  .join(', ');
341
437
  output.push(groupItems);
342
438
  }
343
439
  }
344
440
  if (node.havingClause) {
345
- if (this.formatter.isPretty()) {
441
+ if (context.isPretty()) {
346
442
  output.push('HAVING');
347
443
  const havingStr = this.visit(node.havingClause, context);
348
444
  if (this.containsMultilineStringLiteral(havingStr)) {
349
445
  output.push(havingStr);
350
446
  }
351
447
  else {
352
- output.push(this.formatter.indent(havingStr));
448
+ output.push(context.indent(havingStr));
353
449
  }
354
450
  }
355
451
  else {
@@ -367,23 +463,23 @@ export class Deparser {
367
463
  }
368
464
  if (node.sortClause) {
369
465
  const sortList = ListUtils.unwrapList(node.sortClause);
370
- if (this.formatter.isPretty()) {
466
+ if (context.isPretty()) {
371
467
  const sortItems = sortList
372
468
  .map(e => {
373
- const sortStr = this.visit(e, { ...context, sort: true });
469
+ const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
374
470
  if (this.containsMultilineStringLiteral(sortStr)) {
375
471
  return sortStr;
376
472
  }
377
- return this.formatter.indent(sortStr);
473
+ return context.indent(sortStr);
378
474
  })
379
- .join(',' + this.formatter.newline());
475
+ .join(',' + context.newline());
380
476
  output.push('ORDER BY');
381
477
  output.push(sortItems);
382
478
  }
383
479
  else {
384
480
  output.push('ORDER BY');
385
481
  const sortItems = sortList
386
- .map(e => this.visit(e, { ...context, sort: true }))
482
+ .map(e => this.visit(e, context.spawn('SelectStmt', { sort: true })))
387
483
  .join(', ');
388
484
  output.push(sortItems);
389
485
  }
@@ -401,9 +497,9 @@ export class Deparser {
401
497
  .join(' ');
402
498
  output.push(lockingClauses);
403
499
  }
404
- if (this.formatter.isPretty()) {
500
+ if (context.isPretty()) {
405
501
  const filteredOutput = output.filter(item => item.trim() !== '');
406
- return filteredOutput.join(this.formatter.newline());
502
+ return filteredOutput.join(context.newline());
407
503
  }
408
504
  return output.join(' ');
409
505
  }
@@ -415,13 +511,13 @@ export class Deparser {
415
511
  switch (kind) {
416
512
  case 'AEXPR_OP':
417
513
  if (lexpr && rexpr) {
418
- const operator = this.deparseOperatorName(name);
514
+ const operator = this.deparseOperatorName(name, context);
419
515
  let leftExpr = this.visit(lexpr, context);
420
516
  let rightExpr = this.visit(rexpr, context);
421
517
  // Check if left expression needs parentheses
422
518
  let leftNeedsParens = false;
423
519
  if (lexpr && 'A_Expr' in lexpr && lexpr.A_Expr?.kind === 'AEXPR_OP') {
424
- const leftOp = this.deparseOperatorName(ListUtils.unwrapList(lexpr.A_Expr.name));
520
+ const leftOp = this.deparseOperatorName(ListUtils.unwrapList(lexpr.A_Expr.name), context);
425
521
  if (this.needsParentheses(leftOp, operator, 'left')) {
426
522
  leftNeedsParens = true;
427
523
  }
@@ -430,12 +526,12 @@ export class Deparser {
430
526
  leftNeedsParens = true;
431
527
  }
432
528
  if (leftNeedsParens) {
433
- leftExpr = this.formatter.parens(leftExpr);
529
+ leftExpr = context.parens(leftExpr);
434
530
  }
435
531
  // Check if right expression needs parentheses
436
532
  let rightNeedsParens = false;
437
533
  if (rexpr && 'A_Expr' in rexpr && rexpr.A_Expr?.kind === 'AEXPR_OP') {
438
- const rightOp = this.deparseOperatorName(ListUtils.unwrapList(rexpr.A_Expr.name));
534
+ const rightOp = this.deparseOperatorName(ListUtils.unwrapList(rexpr.A_Expr.name), context);
439
535
  if (this.needsParentheses(rightOp, operator, 'right')) {
440
536
  rightNeedsParens = true;
441
537
  }
@@ -444,42 +540,42 @@ export class Deparser {
444
540
  rightNeedsParens = true;
445
541
  }
446
542
  if (rightNeedsParens) {
447
- rightExpr = this.formatter.parens(rightExpr);
543
+ rightExpr = context.parens(rightExpr);
448
544
  }
449
- return this.formatter.format([leftExpr, operator, rightExpr]);
545
+ return context.format([leftExpr, operator, rightExpr]);
450
546
  }
451
547
  else if (rexpr) {
452
- return this.formatter.format([
453
- this.deparseOperatorName(name),
548
+ return context.format([
549
+ this.deparseOperatorName(name, context),
454
550
  this.visit(rexpr, context)
455
551
  ]);
456
552
  }
457
553
  break;
458
554
  case 'AEXPR_OP_ANY':
459
- return this.formatter.format([
555
+ return context.format([
460
556
  this.visit(lexpr, context),
461
- this.deparseOperatorName(name),
557
+ this.deparseOperatorName(name, context),
462
558
  'ANY',
463
- this.formatter.parens(this.visit(rexpr, context))
559
+ context.parens(this.visit(rexpr, context))
464
560
  ]);
465
561
  case 'AEXPR_OP_ALL':
466
- return this.formatter.format([
562
+ return context.format([
467
563
  this.visit(lexpr, context),
468
- this.deparseOperatorName(name),
564
+ this.deparseOperatorName(name, context),
469
565
  'ALL',
470
- this.formatter.parens(this.visit(rexpr, context))
566
+ context.parens(this.visit(rexpr, context))
471
567
  ]);
472
568
  case 'AEXPR_DISTINCT': {
473
569
  let leftExpr = this.visit(lexpr, context);
474
570
  let rightExpr = this.visit(rexpr, context);
475
571
  // Add parentheses for complex expressions
476
572
  if (lexpr && this.isComplexExpression(lexpr)) {
477
- leftExpr = this.formatter.parens(leftExpr);
573
+ leftExpr = context.parens(leftExpr);
478
574
  }
479
575
  if (rexpr && this.isComplexExpression(rexpr)) {
480
- rightExpr = this.formatter.parens(rightExpr);
576
+ rightExpr = context.parens(rightExpr);
481
577
  }
482
- return this.formatter.format([
578
+ return context.format([
483
579
  leftExpr,
484
580
  'IS DISTINCT FROM',
485
581
  rightExpr
@@ -490,75 +586,75 @@ export class Deparser {
490
586
  let rightExpr = this.visit(rexpr, context);
491
587
  // Add parentheses for complex expressions
492
588
  if (lexpr && this.isComplexExpression(lexpr)) {
493
- leftExpr = this.formatter.parens(leftExpr);
589
+ leftExpr = context.parens(leftExpr);
494
590
  }
495
591
  if (rexpr && this.isComplexExpression(rexpr)) {
496
- rightExpr = this.formatter.parens(rightExpr);
592
+ rightExpr = context.parens(rightExpr);
497
593
  }
498
- return this.formatter.format([
594
+ return context.format([
499
595
  leftExpr,
500
596
  'IS NOT DISTINCT FROM',
501
597
  rightExpr
502
598
  ]);
503
599
  }
504
600
  case 'AEXPR_NULLIF':
505
- return this.formatter.format([
601
+ return context.format([
506
602
  'NULLIF',
507
- this.formatter.parens([
603
+ context.parens([
508
604
  this.visit(lexpr, context),
509
605
  this.visit(rexpr, context)
510
606
  ].join(', '))
511
607
  ]);
512
608
  case 'AEXPR_IN':
513
- const inOperator = this.deparseOperatorName(name);
609
+ const inOperator = this.deparseOperatorName(name, context);
514
610
  if (inOperator === '<>' || inOperator === '!=') {
515
- return this.formatter.format([
611
+ return context.format([
516
612
  this.visit(lexpr, context),
517
613
  'NOT IN',
518
- this.formatter.parens(this.visit(rexpr, context))
614
+ context.parens(this.visit(rexpr, context))
519
615
  ]);
520
616
  }
521
617
  else {
522
- return this.formatter.format([
618
+ return context.format([
523
619
  this.visit(lexpr, context),
524
620
  'IN',
525
- this.formatter.parens(this.visit(rexpr, context))
621
+ context.parens(this.visit(rexpr, context))
526
622
  ]);
527
623
  }
528
624
  case 'AEXPR_LIKE':
529
- const likeOp = this.deparseOperatorName(name);
625
+ const likeOp = this.deparseOperatorName(name, context);
530
626
  if (likeOp === '!~~') {
531
- return this.formatter.format([
627
+ return context.format([
532
628
  this.visit(lexpr, context),
533
629
  'NOT LIKE',
534
630
  this.visit(rexpr, context)
535
631
  ]);
536
632
  }
537
633
  else {
538
- return this.formatter.format([
634
+ return context.format([
539
635
  this.visit(lexpr, context),
540
636
  'LIKE',
541
637
  this.visit(rexpr, context)
542
638
  ]);
543
639
  }
544
640
  case 'AEXPR_ILIKE':
545
- const ilikeOp = this.deparseOperatorName(name);
641
+ const ilikeOp = this.deparseOperatorName(name, context);
546
642
  if (ilikeOp === '!~~*') {
547
- return this.formatter.format([
643
+ return context.format([
548
644
  this.visit(lexpr, context),
549
645
  'NOT ILIKE',
550
646
  this.visit(rexpr, context)
551
647
  ]);
552
648
  }
553
649
  else {
554
- return this.formatter.format([
650
+ return context.format([
555
651
  this.visit(lexpr, context),
556
652
  'ILIKE',
557
653
  this.visit(rexpr, context)
558
654
  ]);
559
655
  }
560
656
  case 'AEXPR_SIMILAR':
561
- const similarOp = this.deparseOperatorName(name);
657
+ const similarOp = this.deparseOperatorName(name, context);
562
658
  let rightExpr;
563
659
  if (rexpr && 'FuncCall' in rexpr &&
564
660
  rexpr.FuncCall?.funcname?.length === 2 &&
@@ -574,39 +670,39 @@ export class Deparser {
574
670
  rightExpr = this.visit(rexpr, context);
575
671
  }
576
672
  if (similarOp === '!~') {
577
- return this.formatter.format([
673
+ return context.format([
578
674
  this.visit(lexpr, context),
579
675
  'NOT SIMILAR TO',
580
676
  rightExpr
581
677
  ]);
582
678
  }
583
679
  else {
584
- return this.formatter.format([
680
+ return context.format([
585
681
  this.visit(lexpr, context),
586
682
  'SIMILAR TO',
587
683
  rightExpr
588
684
  ]);
589
685
  }
590
686
  case 'AEXPR_BETWEEN':
591
- return this.formatter.format([
687
+ return context.format([
592
688
  this.visit(lexpr, context),
593
689
  'BETWEEN',
594
690
  this.visitBetweenRange(rexpr, context)
595
691
  ]);
596
692
  case 'AEXPR_NOT_BETWEEN':
597
- return this.formatter.format([
693
+ return context.format([
598
694
  this.visit(lexpr, context),
599
695
  'NOT BETWEEN',
600
696
  this.visitBetweenRange(rexpr, context)
601
697
  ]);
602
698
  case 'AEXPR_BETWEEN_SYM':
603
- return this.formatter.format([
699
+ return context.format([
604
700
  this.visit(lexpr, context),
605
701
  'BETWEEN SYMMETRIC',
606
702
  this.visitBetweenRange(rexpr, context)
607
703
  ]);
608
704
  case 'AEXPR_NOT_BETWEEN_SYM':
609
- return this.formatter.format([
705
+ return context.format([
610
706
  this.visit(lexpr, context),
611
707
  'NOT BETWEEN SYMMETRIC',
612
708
  this.visitBetweenRange(rexpr, context)
@@ -614,7 +710,7 @@ export class Deparser {
614
710
  }
615
711
  throw new Error(`Unhandled A_Expr kind: ${kind}`);
616
712
  }
617
- deparseOperatorName(name) {
713
+ deparseOperatorName(name, context) {
618
714
  if (!name || name.length === 0) {
619
715
  return '';
620
716
  }
@@ -622,7 +718,7 @@ export class Deparser {
622
718
  if (n.String) {
623
719
  return n.String.sval || n.String.str;
624
720
  }
625
- return this.visit(n, { parentNodeTypes: [] });
721
+ return this.visit(n, context);
626
722
  });
627
723
  if (parts.length > 1) {
628
724
  return `OPERATOR(${parts.join('.')})`;
@@ -685,6 +781,64 @@ export class Deparser {
685
781
  node.SubLink ||
686
782
  node.A_Expr);
687
783
  }
784
+ isComplexSelectTarget(node) {
785
+ if (!node)
786
+ return false;
787
+ if (node.ResTarget?.val) {
788
+ return this.isComplexExpression(node.ResTarget.val);
789
+ }
790
+ // Always complex: CASE expressions
791
+ if (node.CaseExpr)
792
+ return true;
793
+ // Always complex: Subqueries and subselects
794
+ if (node.SubLink)
795
+ return true;
796
+ // Always complex: Boolean tests and expressions
797
+ if (node.NullTest || node.BooleanTest || node.BoolExpr)
798
+ return true;
799
+ // COALESCE and similar functions - complex if multiple arguments
800
+ if (node.CoalesceExpr) {
801
+ const args = node.CoalesceExpr.args;
802
+ if (args && Array.isArray(args) && args.length > 1)
803
+ return true;
804
+ }
805
+ // Function calls - complex if multiple args or has clauses
806
+ if (node.FuncCall) {
807
+ const funcCall = node.FuncCall;
808
+ const args = funcCall.args ? (Array.isArray(funcCall.args) ? funcCall.args : [funcCall.args]) : [];
809
+ // Complex if has window clause, filter, order by, etc.
810
+ if (funcCall.over || funcCall.agg_filter || funcCall.agg_order || funcCall.agg_distinct) {
811
+ return true;
812
+ }
813
+ // Complex if multiple arguments
814
+ if (args.length > 1)
815
+ return true;
816
+ if (args.length === 1) {
817
+ return this.isComplexSelectTarget(args[0]);
818
+ }
819
+ }
820
+ if (node.A_Expr) {
821
+ const expr = node.A_Expr;
822
+ // Check if operands are complex
823
+ if (expr.lexpr && this.isComplexSelectTarget(expr.lexpr))
824
+ return true;
825
+ if (expr.rexpr && this.isComplexSelectTarget(expr.rexpr))
826
+ return true;
827
+ return false;
828
+ }
829
+ if (node.TypeCast) {
830
+ return this.isComplexSelectTarget(node.TypeCast.arg);
831
+ }
832
+ if (node.A_ArrayExpr)
833
+ return true;
834
+ if (node.A_Indirection) {
835
+ return this.isComplexSelectTarget(node.A_Indirection.arg);
836
+ }
837
+ if (node.A_Const || node.ColumnRef || node.ParamRef || node.A_Star) {
838
+ return false;
839
+ }
840
+ return false;
841
+ }
688
842
  visitBetweenRange(rexpr, context) {
689
843
  if (rexpr && 'List' in rexpr && rexpr.List?.items) {
690
844
  const items = rexpr.List.items.map((item) => this.visit(item, context));
@@ -701,9 +855,16 @@ export class Deparser {
701
855
  output.push(this.RangeVar(node.relation, context));
702
856
  if (node.cols) {
703
857
  const cols = ListUtils.unwrapList(node.cols);
704
- const insertContext = { ...context, insertColumns: true };
858
+ const insertContext = context.spawn('InsertStmt', { insertColumns: true });
705
859
  const columnNames = cols.map(col => this.visit(col, insertContext));
706
- output.push(this.formatter.parens(columnNames.join(', ')));
860
+ if (context.isPretty()) {
861
+ // Always format columns in multiline parentheses for pretty printing
862
+ const indentedColumns = columnNames.map(col => context.indent(col));
863
+ output.push('(\n' + indentedColumns.join(',\n') + '\n)');
864
+ }
865
+ else {
866
+ output.push(context.parens(columnNames.join(', ')));
867
+ }
707
868
  }
708
869
  if (node.selectStmt) {
709
870
  output.push(this.visit(node.selectStmt, context));
@@ -724,7 +885,7 @@ export class Deparser {
724
885
  else if (infer.indexElems) {
725
886
  const elems = ListUtils.unwrapList(infer.indexElems);
726
887
  const indexElems = elems.map(elem => this.visit(elem, context));
727
- output.push(this.formatter.parens(indexElems.join(', ')));
888
+ output.push(context.parens(indexElems.join(', ')));
728
889
  }
729
890
  // Handle WHERE clause for conflict detection
730
891
  if (infer.whereClause) {
@@ -740,12 +901,12 @@ export class Deparser {
740
901
  if (firstTarget.ResTarget?.val?.MultiAssignRef && targetList.every(target => target.ResTarget?.val?.MultiAssignRef)) {
741
902
  const sortedTargets = targetList.sort((a, b) => a.ResTarget.val.MultiAssignRef.colno - b.ResTarget.val.MultiAssignRef.colno);
742
903
  const names = sortedTargets.map(target => target.ResTarget.name);
743
- output.push(this.formatter.parens(names.join(', ')));
904
+ output.push(context.parens(names.join(', ')));
744
905
  output.push('=');
745
906
  output.push(this.visit(firstTarget.ResTarget.val.MultiAssignRef.source, context));
746
907
  }
747
908
  else {
748
- const updateContext = { ...context, update: true };
909
+ const updateContext = context.spawn('UpdateStmt', { update: true });
749
910
  const targets = targetList.map(target => this.visit(target, updateContext));
750
911
  output.push(targets.join(', '));
751
912
  }
@@ -799,12 +960,12 @@ export class Deparser {
799
960
  }
800
961
  }
801
962
  const names = relatedTargets.map(t => t.ResTarget.name);
802
- const multiAssignment = `${this.formatter.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
963
+ const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
803
964
  assignmentParts.push(multiAssignment);
804
965
  }
805
966
  else {
806
967
  // Handle regular single-column assignment
807
- assignmentParts.push(this.visit(target, { ...context, update: true }));
968
+ assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
808
969
  processedTargets.add(i);
809
970
  }
810
971
  }
@@ -896,14 +1057,14 @@ export class Deparser {
896
1057
  }
897
1058
  if (node.ctes && node.ctes.length > 0) {
898
1059
  const ctes = ListUtils.unwrapList(node.ctes);
899
- if (this.formatter.isPretty()) {
1060
+ if (context.isPretty()) {
900
1061
  const cteStrings = ctes.map((cte, index) => {
901
1062
  const cteStr = this.visit(cte, context);
902
- const prefix = index === 0 ? this.formatter.newline() : ',' + this.formatter.newline();
1063
+ const prefix = index === 0 ? context.newline() : ',' + context.newline();
903
1064
  if (this.containsMultilineStringLiteral(cteStr)) {
904
1065
  return prefix + cteStr;
905
1066
  }
906
- return prefix + this.formatter.indent(cteStr);
1067
+ return prefix + context.indent(cteStr);
907
1068
  });
908
1069
  output.push(cteStrings.join(''));
909
1070
  }
@@ -989,14 +1150,14 @@ export class Deparser {
989
1150
  if (context.bool) {
990
1151
  formatStr = '(%s)';
991
1152
  }
992
- const boolContext = { ...context, bool: true };
1153
+ const boolContext = context.spawn('BoolExpr', { bool: true });
993
1154
  // explanation of our syntax/fix below:
994
1155
  // return formatStr.replace('%s', andArgs); // ❌ Interprets $ as special syntax
995
1156
  // return formatStr.replace('%s', () => andArgs); // ✅ Function callback prevents interpretation
996
1157
  switch (boolop) {
997
1158
  case 'AND_EXPR':
998
- if (this.formatter.isPretty() && args.length > 1) {
999
- const andArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' AND ');
1159
+ if (context.isPretty() && args.length > 1) {
1160
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('AND '));
1000
1161
  return formatStr.replace('%s', () => andArgs);
1001
1162
  }
1002
1163
  else {
@@ -1004,8 +1165,8 @@ export class Deparser {
1004
1165
  return formatStr.replace('%s', () => andArgs);
1005
1166
  }
1006
1167
  case 'OR_EXPR':
1007
- if (this.formatter.isPretty() && args.length > 1) {
1008
- const orArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' OR ');
1168
+ if (context.isPretty() && args.length > 1) {
1169
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('OR '));
1009
1170
  return formatStr.replace('%s', () => orArgs);
1010
1171
  }
1011
1172
  else {
@@ -1136,9 +1297,9 @@ export class Deparser {
1136
1297
  const timezone = this.visit(args[0], context);
1137
1298
  // Add parentheses around timestamp if it contains arithmetic operations
1138
1299
  if (args[1] && 'A_Expr' in args[1] && args[1].A_Expr?.kind === 'AEXPR_OP') {
1139
- const op = this.deparseOperatorName(ListUtils.unwrapList(args[1].A_Expr.name));
1300
+ const op = this.deparseOperatorName(ListUtils.unwrapList(args[1].A_Expr.name), context);
1140
1301
  if (op === '+' || op === '-' || op === '*' || op === '/') {
1141
- timestamp = this.formatter.parens(timestamp);
1302
+ timestamp = context.parens(timestamp);
1142
1303
  }
1143
1304
  }
1144
1305
  return `${timestamp} AT TIME ZONE ${timezone}`;
@@ -1208,14 +1369,14 @@ export class Deparser {
1208
1369
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1209
1370
  }
1210
1371
  // Handle window frame specifications using the dedicated formatWindowFrame method
1211
- const frameClause = this.formatWindowFrame(node.over);
1372
+ const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1212
1373
  if (frameClause) {
1213
1374
  windowParts.push(frameClause);
1214
1375
  }
1215
1376
  if (windowParts.length > 0) {
1216
- if (this.formatter.isPretty() && windowParts.length > 1) {
1217
- const formattedParts = windowParts.map(part => this.formatter.indent(part));
1218
- result += ` OVER (${this.formatter.newline()}${formattedParts.join(this.formatter.newline())}${this.formatter.newline()})`;
1377
+ if (context.isPretty() && windowParts.length > 1) {
1378
+ const formattedParts = windowParts.map(part => context.indent(part));
1379
+ result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1219
1380
  }
1220
1381
  else {
1221
1382
  result += ` OVER (${windowParts.join(' ')})`;
@@ -1495,9 +1656,6 @@ export class Deparser {
1495
1656
  return output.join(' ');
1496
1657
  }
1497
1658
  if (catalog === 'pg_catalog') {
1498
- const builtinTypes = ['int2', 'int4', 'int8', 'float4', 'float8', 'numeric', 'decimal',
1499
- 'varchar', 'char', 'bpchar', 'text', 'bool', 'date', 'time', 'timestamp',
1500
- 'timestamptz', 'interval', 'bytea', 'uuid', 'json', 'jsonb'];
1501
1659
  let typeName = `${catalog}.${type}`;
1502
1660
  if (type === 'bpchar' && args) {
1503
1661
  typeName = 'char';
@@ -1554,6 +1712,24 @@ export class Deparser {
1554
1712
  typeName = 'time with time zone';
1555
1713
  }
1556
1714
  }
1715
+ else if (type === 'timestamp') {
1716
+ if (args) {
1717
+ typeName = `timestamp(${args})`;
1718
+ args = null; // Don't apply args again in mods()
1719
+ }
1720
+ else {
1721
+ typeName = 'timestamp';
1722
+ }
1723
+ }
1724
+ else if (type === 'time') {
1725
+ if (args) {
1726
+ typeName = `time(${args})`;
1727
+ args = null; // Don't apply args again in mods()
1728
+ }
1729
+ else {
1730
+ typeName = 'time';
1731
+ }
1732
+ }
1557
1733
  let result = mods(typeName, args);
1558
1734
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1559
1735
  result += formatArrayBounds(node.arrayBounds);
@@ -1583,7 +1759,7 @@ export class Deparser {
1583
1759
  }
1584
1760
  return this.quoteIfNeeded(colStr);
1585
1761
  });
1586
- output.push('AS', this.quoteIfNeeded(name) + this.formatter.parens(quotedColnames.join(', ')));
1762
+ output.push('AS', this.quoteIfNeeded(name) + context.parens(quotedColnames.join(', ')));
1587
1763
  }
1588
1764
  else {
1589
1765
  output.push('AS', this.quoteIfNeeded(name));
@@ -1595,7 +1771,7 @@ export class Deparser {
1595
1771
  // Handle ONLY keyword for inheritance control (but not for type definitions, ALTER TYPE, or CREATE FOREIGN TABLE)
1596
1772
  if (node && (!('inh' in node) || node.inh === undefined) &&
1597
1773
  !context.parentNodeTypes.includes('CompositeTypeStmt') &&
1598
- !context.parentNodeTypes.includes('AlterTypeStmt') &&
1774
+ (!context.parentNodeTypes.includes('AlterTypeStmt') && context.objtype !== 'OBJECT_TYPE') &&
1599
1775
  !context.parentNodeTypes.includes('CreateForeignTableStmt')) {
1600
1776
  output.push('ONLY');
1601
1777
  }
@@ -1792,6 +1968,18 @@ export class Deparser {
1792
1968
  return `pg_catalog.${typeName}`;
1793
1969
  }
1794
1970
  }
1971
+ isPgCatalogType(typeName) {
1972
+ const cleanTypeName = typeName.replace(/^pg_catalog\./, '');
1973
+ if (pgCatalogTypes.includes(cleanTypeName)) {
1974
+ return true;
1975
+ }
1976
+ for (const [realType, aliases] of pgCatalogTypeAliases) {
1977
+ if (aliases.includes(cleanTypeName)) {
1978
+ return true;
1979
+ }
1980
+ }
1981
+ return false;
1982
+ }
1795
1983
  A_ArrayExpr(node, context) {
1796
1984
  const elements = ListUtils.unwrapList(node.elements);
1797
1985
  const elementStrs = elements.map(el => this.visit(el, context));
@@ -1843,26 +2031,26 @@ export class Deparser {
1843
2031
  output.push(this.visit(node.arg, context));
1844
2032
  }
1845
2033
  const args = ListUtils.unwrapList(node.args);
1846
- if (this.formatter.isPretty() && args.length > 0) {
2034
+ if (context.isPretty() && args.length > 0) {
1847
2035
  for (const arg of args) {
1848
2036
  const whenClause = this.visit(arg, context);
1849
2037
  if (this.containsMultilineStringLiteral(whenClause)) {
1850
- output.push(this.formatter.newline() + whenClause);
2038
+ output.push(context.newline() + whenClause);
1851
2039
  }
1852
2040
  else {
1853
- output.push(this.formatter.newline() + this.formatter.indent(whenClause));
2041
+ output.push(context.newline() + context.indent(whenClause));
1854
2042
  }
1855
2043
  }
1856
2044
  if (node.defresult) {
1857
2045
  const elseResult = this.visit(node.defresult, context);
1858
2046
  if (this.containsMultilineStringLiteral(elseResult)) {
1859
- output.push(this.formatter.newline() + 'ELSE ' + elseResult);
2047
+ output.push(context.newline() + 'ELSE ' + elseResult);
1860
2048
  }
1861
2049
  else {
1862
- output.push(this.formatter.newline() + this.formatter.indent('ELSE ' + elseResult));
2050
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
1863
2051
  }
1864
2052
  }
1865
- output.push(this.formatter.newline() + 'END');
2053
+ output.push(context.newline() + 'END');
1866
2054
  return output.join(' ');
1867
2055
  }
1868
2056
  else {
@@ -1885,28 +2073,29 @@ export class Deparser {
1885
2073
  TypeCast(node, context) {
1886
2074
  const arg = this.visit(node.arg, context);
1887
2075
  const typeName = this.TypeName(node.typeName, context);
1888
- // Check if this is a bpchar typecast that should use traditional char syntax
1889
- if (typeName === 'bpchar' && node.typeName && node.typeName.names) {
1890
- const names = ListUtils.unwrapList(node.typeName.names);
1891
- if (names.length === 2 &&
1892
- names[0].String?.sval === 'pg_catalog' &&
1893
- names[1].String?.sval === 'bpchar') {
1894
- return `char ${arg}`;
2076
+ // Check if this is a bpchar typecast that should preserve original syntax for AST consistency
2077
+ if (typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
2078
+ const names = node.typeName?.names;
2079
+ const isQualifiedBpchar = names && names.length === 2 &&
2080
+ names[0]?.String?.sval === 'pg_catalog' &&
2081
+ names[1]?.String?.sval === 'bpchar';
2082
+ if (isQualifiedBpchar) {
2083
+ return `CAST(${arg} AS ${typeName})`;
1895
2084
  }
1896
2085
  }
1897
- // Check if the argument is a complex expression that should preserve CAST syntax
1898
- const argType = this.getNodeType(node.arg);
1899
- const isComplexExpression = argType === 'A_Expr' || argType === 'FuncCall' || argType === 'OpExpr';
1900
- if (!isComplexExpression && (typeName.startsWith('interval') ||
1901
- typeName.startsWith('char') ||
1902
- typeName === '"char"' ||
1903
- typeName.startsWith('bpchar') ||
1904
- typeName === 'bytea' ||
1905
- typeName === 'orderedarray' ||
1906
- typeName === 'date')) {
1907
- // Remove pg_catalog prefix for :: syntax
1908
- const cleanTypeName = typeName.replace('pg_catalog.', '');
1909
- return `${arg}::${cleanTypeName}`;
2086
+ if (this.isPgCatalogType(typeName)) {
2087
+ const argType = this.getNodeType(node.arg);
2088
+ const isSimpleArgument = argType === 'A_Const' || argType === 'ColumnRef';
2089
+ const isFunctionCall = argType === 'FuncCall';
2090
+ if (isSimpleArgument || isFunctionCall) {
2091
+ // For simple arguments, avoid :: syntax if they have complex structure
2092
+ if (isSimpleArgument && (arg.includes('(') || arg.startsWith('-'))) {
2093
+ }
2094
+ else {
2095
+ const cleanTypeName = typeName.replace('pg_catalog.', '');
2096
+ return `${arg}::${cleanTypeName}`;
2097
+ }
2098
+ }
1910
2099
  }
1911
2100
  return `CAST(${arg} AS ${typeName})`;
1912
2101
  }
@@ -1929,7 +2118,7 @@ export class Deparser {
1929
2118
  }
1930
2119
  BooleanTest(node, context) {
1931
2120
  const output = [];
1932
- const boolContext = { ...context, bool: true };
2121
+ const boolContext = context.spawn('BooleanTest', { bool: true });
1933
2122
  output.push(this.visit(node.arg, boolContext));
1934
2123
  switch (node.booltesttype) {
1935
2124
  case 'IS_TRUE':
@@ -2080,24 +2269,31 @@ export class Deparser {
2080
2269
  const elementStrs = elements.map(el => {
2081
2270
  return this.deparse(el, context);
2082
2271
  });
2083
- output.push(this.formatter.parens(elementStrs.join(', ')));
2272
+ output.push(context.parens(elementStrs.join(', ')));
2084
2273
  }
2085
2274
  }
2086
2275
  else if (node.tableElts) {
2087
2276
  const elements = ListUtils.unwrapList(node.tableElts);
2088
2277
  const elementStrs = elements.map(el => {
2089
- return this.deparse(el, context);
2278
+ return this.deparse(el, context.spawn('CreateStmt'));
2090
2279
  });
2091
- if (this.formatter.isPretty()) {
2092
- const formattedElements = elementStrs.map(el => this.formatter.indent(el)).join(',' + this.formatter.newline());
2093
- output.push('(' + this.formatter.newline() + formattedElements + this.formatter.newline() + ')');
2280
+ if (context.isPretty()) {
2281
+ const formattedElements = elementStrs.map(el => {
2282
+ const trimmedEl = el.trim();
2283
+ // Remove leading newlines from constraint elements to avoid extra blank lines
2284
+ if (trimmedEl.startsWith('\n')) {
2285
+ return context.indent(trimmedEl.substring(1));
2286
+ }
2287
+ return context.indent(trimmedEl);
2288
+ }).join(',' + context.newline());
2289
+ output.push('(' + context.newline() + formattedElements + context.newline() + ')');
2094
2290
  }
2095
2291
  else {
2096
- output.push(this.formatter.parens(elementStrs.join(', ')));
2292
+ output.push(context.parens(elementStrs.join(', ')));
2097
2293
  }
2098
2294
  }
2099
2295
  else if (!node.partbound) {
2100
- output.push(this.formatter.parens(''));
2296
+ output.push(context.parens(''));
2101
2297
  }
2102
2298
  if (node.partbound && node.inhRelations && node.inhRelations.length > 0) {
2103
2299
  output.push('PARTITION OF');
@@ -2140,7 +2336,7 @@ export class Deparser {
2140
2336
  output.push('INHERITS');
2141
2337
  const inherits = ListUtils.unwrapList(node.inhRelations);
2142
2338
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2143
- output.push(this.formatter.parens(inheritStrs.join(', ')));
2339
+ output.push(context.parens(inheritStrs.join(', ')));
2144
2340
  }
2145
2341
  if (node.partspec) {
2146
2342
  output.push('PARTITION BY');
@@ -2178,7 +2374,7 @@ export class Deparser {
2178
2374
  }
2179
2375
  // Handle table options like WITH (fillfactor=10)
2180
2376
  if (node.options && node.options.length > 0) {
2181
- const createStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateStmt'] };
2377
+ const createStmtContext = context.spawn('CreateStmt');
2182
2378
  const optionStrs = node.options.map((option) => {
2183
2379
  return this.deparse(option, createStmtContext);
2184
2380
  });
@@ -2201,7 +2397,7 @@ export class Deparser {
2201
2397
  }
2202
2398
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2203
2399
  output.push('OPTIONS');
2204
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
2400
+ const columnContext = context.spawn('ColumnDef');
2205
2401
  const options = ListUtils.unwrapList(node.fdwoptions).map(opt => this.visit(opt, columnContext));
2206
2402
  output.push(`(${options.join(', ')})`);
2207
2403
  }
@@ -2211,7 +2407,7 @@ export class Deparser {
2211
2407
  if (node.constraints) {
2212
2408
  const constraints = ListUtils.unwrapList(node.constraints);
2213
2409
  const constraintStrs = constraints.map(constraint => {
2214
- const columnConstraintContext = { ...context, isColumnConstraint: true };
2410
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
2215
2411
  return this.visit(constraint, columnConstraintContext);
2216
2412
  });
2217
2413
  output.push(...constraintStrs);
@@ -2251,9 +2447,25 @@ export class Deparser {
2251
2447
  }
2252
2448
  break;
2253
2449
  case 'CONSTR_CHECK':
2254
- output.push('CHECK');
2450
+ if (context.isPretty() && !context.isColumnConstraint) {
2451
+ output.push('\n' + context.indent('CHECK'));
2452
+ }
2453
+ else {
2454
+ output.push('CHECK');
2455
+ }
2255
2456
  if (node.raw_expr) {
2256
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2457
+ if (context.isPretty()) {
2458
+ const checkExpr = this.visit(node.raw_expr, context);
2459
+ if (checkExpr.includes('\n')) {
2460
+ output.push('(\n' + context.indent(checkExpr) + '\n)');
2461
+ }
2462
+ else {
2463
+ output.push(`(${checkExpr})`);
2464
+ }
2465
+ }
2466
+ else {
2467
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2468
+ }
2257
2469
  }
2258
2470
  // Handle NOT VALID for check constraints
2259
2471
  if (node.skip_validation) {
@@ -2274,7 +2486,7 @@ export class Deparser {
2274
2486
  }
2275
2487
  output.push('AS');
2276
2488
  if (node.raw_expr) {
2277
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2489
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2278
2490
  }
2279
2491
  output.push('STORED');
2280
2492
  break;
@@ -2292,30 +2504,61 @@ export class Deparser {
2292
2504
  .map(option => {
2293
2505
  if (option.DefElem) {
2294
2506
  const defElem = option.DefElem;
2295
- const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2296
- if (defElem.defname === 'start') {
2507
+ if (defElem.defname === 'sequence_name') {
2508
+ if (defElem.arg && defElem.arg.List) {
2509
+ const nameList = ListUtils.unwrapList(defElem.arg)
2510
+ .map(item => this.visit(item, context))
2511
+ .join('.');
2512
+ return `SEQUENCE NAME ${nameList}`;
2513
+ }
2514
+ return 'SEQUENCE NAME';
2515
+ }
2516
+ else if (defElem.defname === 'start') {
2517
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2297
2518
  return `START WITH ${argValue}`;
2298
2519
  }
2299
2520
  else if (defElem.defname === 'increment') {
2521
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2300
2522
  return `INCREMENT BY ${argValue}`;
2301
2523
  }
2302
2524
  else if (defElem.defname === 'minvalue') {
2303
- return `MINVALUE ${argValue}`;
2525
+ if (defElem.arg) {
2526
+ const argValue = this.visit(defElem.arg, context);
2527
+ return `MINVALUE ${argValue}`;
2528
+ }
2529
+ else {
2530
+ return 'NO MINVALUE';
2531
+ }
2304
2532
  }
2305
2533
  else if (defElem.defname === 'maxvalue') {
2306
- return `MAXVALUE ${argValue}`;
2534
+ if (defElem.arg) {
2535
+ const argValue = this.visit(defElem.arg, context);
2536
+ return `MAXVALUE ${argValue}`;
2537
+ }
2538
+ else {
2539
+ return 'NO MAXVALUE';
2540
+ }
2307
2541
  }
2308
2542
  else if (defElem.defname === 'cache') {
2543
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2309
2544
  return `CACHE ${argValue}`;
2310
2545
  }
2311
2546
  else if (defElem.defname === 'cycle') {
2547
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2312
2548
  return argValue === 'true' ? 'CYCLE' : 'NO CYCLE';
2313
2549
  }
2550
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2314
2551
  return `${defElem.defname.toUpperCase()} ${argValue}`;
2315
2552
  }
2316
2553
  return this.visit(option, context);
2317
2554
  });
2318
- output.push(`(${optionStrs.join(' ')})`);
2555
+ if (context.isPretty()) {
2556
+ const indentedOptions = optionStrs.map(option => context.indent(option));
2557
+ output.push('(\n' + indentedOptions.join('\n') + '\n)');
2558
+ }
2559
+ else {
2560
+ output.push(`(${optionStrs.join(' ')})`);
2561
+ }
2319
2562
  }
2320
2563
  break;
2321
2564
  case 'CONSTR_PRIMARY':
@@ -2332,7 +2575,12 @@ export class Deparser {
2332
2575
  }
2333
2576
  break;
2334
2577
  case 'CONSTR_UNIQUE':
2335
- output.push('UNIQUE');
2578
+ if (context.isPretty() && !context.isColumnConstraint) {
2579
+ output.push('\n' + context.indent('UNIQUE'));
2580
+ }
2581
+ else {
2582
+ output.push('UNIQUE');
2583
+ }
2336
2584
  if (node.nulls_not_distinct) {
2337
2585
  output.push('NULLS NOT DISTINCT');
2338
2586
  }
@@ -2350,33 +2598,77 @@ export class Deparser {
2350
2598
  case 'CONSTR_FOREIGN':
2351
2599
  // Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
2352
2600
  if (!context.isColumnConstraint) {
2353
- output.push('FOREIGN KEY');
2354
- if (node.fk_attrs && node.fk_attrs.length > 0) {
2355
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2356
- .map(attr => this.visit(attr, context))
2357
- .join(', ');
2358
- output.push(`(${fkAttrs})`);
2601
+ if (context.isPretty()) {
2602
+ output.push('\n' + context.indent('FOREIGN KEY'));
2603
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2604
+ const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2605
+ .map(attr => this.visit(attr, context))
2606
+ .join(', ');
2607
+ output.push(`(${fkAttrs})`);
2608
+ }
2609
+ output.push('\n' + context.indent('REFERENCES'));
2610
+ }
2611
+ else {
2612
+ output.push('FOREIGN KEY');
2613
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2614
+ const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2615
+ .map(attr => this.visit(attr, context))
2616
+ .join(', ');
2617
+ output.push(`(${fkAttrs})`);
2618
+ }
2619
+ output.push('REFERENCES');
2359
2620
  }
2360
2621
  }
2361
- output.push('REFERENCES');
2622
+ else {
2623
+ output.push('REFERENCES');
2624
+ }
2362
2625
  if (node.pktable) {
2363
- output.push(this.RangeVar(node.pktable, context));
2626
+ if (context.isPretty() && !context.isColumnConstraint) {
2627
+ const lastIndex = output.length - 1;
2628
+ if (lastIndex >= 0 && output[lastIndex].includes('REFERENCES')) {
2629
+ output[lastIndex] += ' ' + this.RangeVar(node.pktable, context);
2630
+ }
2631
+ else {
2632
+ output.push(this.RangeVar(node.pktable, context));
2633
+ }
2634
+ }
2635
+ else {
2636
+ output.push(this.RangeVar(node.pktable, context));
2637
+ }
2364
2638
  }
2365
2639
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2366
2640
  const pkAttrs = ListUtils.unwrapList(node.pk_attrs)
2367
2641
  .map(attr => this.visit(attr, context))
2368
2642
  .join(', ');
2369
- output.push(`(${pkAttrs})`);
2643
+ if (context.isPretty() && !context.isColumnConstraint) {
2644
+ const lastIndex = output.length - 1;
2645
+ if (lastIndex >= 0) {
2646
+ output[lastIndex] += ` (${pkAttrs})`;
2647
+ }
2648
+ else {
2649
+ output.push(`(${pkAttrs})`);
2650
+ }
2651
+ }
2652
+ else {
2653
+ output.push(`(${pkAttrs})`);
2654
+ }
2370
2655
  }
2371
2656
  if (node.fk_matchtype && node.fk_matchtype !== 's') {
2657
+ let matchClause = '';
2372
2658
  switch (node.fk_matchtype) {
2373
2659
  case 'f':
2374
- output.push('MATCH FULL');
2660
+ matchClause = 'MATCH FULL';
2375
2661
  break;
2376
2662
  case 'p':
2377
- output.push('MATCH PARTIAL');
2663
+ matchClause = 'MATCH PARTIAL';
2378
2664
  break;
2379
2665
  }
2666
+ if (context.isPretty() && !context.isColumnConstraint) {
2667
+ output.push('\n' + context.indent(matchClause));
2668
+ }
2669
+ else {
2670
+ output.push(matchClause);
2671
+ }
2380
2672
  }
2381
2673
  if (node.fk_upd_action && node.fk_upd_action !== 'a') {
2382
2674
  let updateClause = 'ON UPDATE ';
@@ -2394,8 +2686,8 @@ export class Deparser {
2394
2686
  updateClause += 'SET DEFAULT';
2395
2687
  break;
2396
2688
  }
2397
- if (this.formatter.isPretty()) {
2398
- output.push('\n' + this.formatter.indent(updateClause));
2689
+ if (context.isPretty()) {
2690
+ output.push('\n' + context.indent(updateClause));
2399
2691
  }
2400
2692
  else {
2401
2693
  output.push('ON UPDATE');
@@ -2418,8 +2710,8 @@ export class Deparser {
2418
2710
  deleteClause += 'SET DEFAULT';
2419
2711
  break;
2420
2712
  }
2421
- if (this.formatter.isPretty()) {
2422
- output.push('\n' + this.formatter.indent(deleteClause));
2713
+ if (context.isPretty()) {
2714
+ output.push('\n' + context.indent(deleteClause));
2423
2715
  }
2424
2716
  else {
2425
2717
  output.push('ON DELETE');
@@ -2428,7 +2720,12 @@ export class Deparser {
2428
2720
  }
2429
2721
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2430
2722
  if (node.skip_validation && !context.isDomainConstraint) {
2431
- output.push('NOT VALID');
2723
+ if (context.isPretty() && !context.isColumnConstraint) {
2724
+ output.push('\n' + context.indent('NOT VALID'));
2725
+ }
2726
+ else {
2727
+ output.push('NOT VALID');
2728
+ }
2432
2729
  }
2433
2730
  break;
2434
2731
  case 'CONSTR_ATTR_DEFERRABLE':
@@ -2482,13 +2779,13 @@ export class Deparser {
2482
2779
  // Handle deferrable constraints for all constraint types that support it
2483
2780
  if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2484
2781
  if (node.deferrable) {
2485
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2486
- output.push('\n' + this.formatter.indent('DEFERRABLE'));
2782
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2783
+ output.push('\n' + context.indent('DEFERRABLE'));
2487
2784
  if (node.initdeferred === true) {
2488
- output.push('\n' + this.formatter.indent('INITIALLY DEFERRED'));
2785
+ output.push('\n' + context.indent('INITIALLY DEFERRED'));
2489
2786
  }
2490
2787
  else if (node.initdeferred === false) {
2491
- output.push('\n' + this.formatter.indent('INITIALLY IMMEDIATE'));
2788
+ output.push('\n' + context.indent('INITIALLY IMMEDIATE'));
2492
2789
  }
2493
2790
  }
2494
2791
  else {
@@ -2502,15 +2799,15 @@ export class Deparser {
2502
2799
  }
2503
2800
  }
2504
2801
  else if (node.deferrable === false) {
2505
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2506
- output.push('\n' + this.formatter.indent('NOT DEFERRABLE'));
2802
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2803
+ output.push('\n' + context.indent('NOT DEFERRABLE'));
2507
2804
  }
2508
2805
  else {
2509
2806
  output.push('NOT DEFERRABLE');
2510
2807
  }
2511
2808
  }
2512
2809
  }
2513
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2810
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2514
2811
  let result = '';
2515
2812
  for (let i = 0; i < output.length; i++) {
2516
2813
  if (output[i].startsWith('\n')) {
@@ -2528,12 +2825,12 @@ export class Deparser {
2528
2825
  return output.join(' ');
2529
2826
  }
2530
2827
  SubLink(node, context) {
2531
- const subselect = this.formatter.parens(this.visit(node.subselect, context));
2828
+ const subselect = context.parens(this.visit(node.subselect, context));
2532
2829
  switch (node.subLinkType) {
2533
2830
  case 'ANY_SUBLINK':
2534
2831
  if (node.testexpr && node.operName) {
2535
2832
  const testExpr = this.visit(node.testexpr, context);
2536
- const operator = this.deparseOperatorName(node.operName);
2833
+ const operator = this.deparseOperatorName(node.operName, context);
2537
2834
  return `${testExpr} ${operator} ANY ${subselect}`;
2538
2835
  }
2539
2836
  else if (node.testexpr) {
@@ -2544,7 +2841,7 @@ export class Deparser {
2544
2841
  case 'ALL_SUBLINK':
2545
2842
  if (node.testexpr && node.operName) {
2546
2843
  const testExpr = this.visit(node.testexpr, context);
2547
- const operator = this.deparseOperatorName(node.operName);
2844
+ const operator = this.deparseOperatorName(node.operName, context);
2548
2845
  return `${testExpr} ${operator} ALL ${subselect}`;
2549
2846
  }
2550
2847
  return subselect;
@@ -2586,7 +2883,7 @@ export class Deparser {
2586
2883
  }
2587
2884
  // Only add frame clause if frameOptions indicates non-default framing
2588
2885
  if (node.frameOptions && node.frameOptions !== 1058) {
2589
- const frameClause = this.formatWindowFrame(node);
2886
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2590
2887
  if (frameClause) {
2591
2888
  windowParts.push(frameClause);
2592
2889
  }
@@ -2606,7 +2903,7 @@ export class Deparser {
2606
2903
  }
2607
2904
  return output.join(' ');
2608
2905
  }
2609
- formatWindowFrame(node) {
2906
+ formatWindowFrame(node, context) {
2610
2907
  if (!node.frameOptions)
2611
2908
  return null;
2612
2909
  const frameOptions = node.frameOptions;
@@ -2615,7 +2912,7 @@ export class Deparser {
2615
2912
  if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
2616
2913
  frameParts.push('RANGE');
2617
2914
  }
2618
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2915
+ else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2619
2916
  frameParts.push('ROWS');
2620
2917
  }
2621
2918
  else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
@@ -2636,8 +2933,8 @@ export class Deparser {
2636
2933
  }
2637
2934
  else if (frameOptions === 18453) {
2638
2935
  if (node.startOffset && node.endOffset) {
2639
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2640
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2936
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2937
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2641
2938
  }
2642
2939
  }
2643
2940
  else if (frameOptions === 1557) {
@@ -2647,7 +2944,7 @@ export class Deparser {
2647
2944
  else if (frameOptions === 16917) {
2648
2945
  boundsParts.push('CURRENT ROW');
2649
2946
  if (node.endOffset) {
2650
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2947
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2651
2948
  }
2652
2949
  }
2653
2950
  else if (frameOptions === 1058) {
@@ -2657,13 +2954,13 @@ export class Deparser {
2657
2954
  // Handle start bound - prioritize explicit offset values over bit flags
2658
2955
  if (node.startOffset) {
2659
2956
  if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
2660
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2957
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2661
2958
  }
2662
2959
  else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
2663
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} FOLLOWING`);
2960
+ boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
2664
2961
  }
2665
2962
  else {
2666
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2963
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2667
2964
  }
2668
2965
  }
2669
2966
  else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
@@ -2676,13 +2973,13 @@ export class Deparser {
2676
2973
  if (node.endOffset) {
2677
2974
  if (boundsParts.length > 0) {
2678
2975
  if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
2679
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} PRECEDING`);
2976
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
2680
2977
  }
2681
2978
  else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
2682
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2979
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2683
2980
  }
2684
2981
  else {
2685
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
2982
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2686
2983
  }
2687
2984
  }
2688
2985
  }
@@ -2767,7 +3064,7 @@ export class Deparser {
2767
3064
  const colnames = ListUtils.unwrapList(node.aliascolnames);
2768
3065
  const colnameStrs = colnames.map(col => this.visit(col, context));
2769
3066
  // Don't add space before column list parentheses to match original formatting
2770
- output[output.length - 1] += this.formatter.parens(colnameStrs.join(', '));
3067
+ output[output.length - 1] += context.parens(colnameStrs.join(', '));
2771
3068
  }
2772
3069
  output.push('AS');
2773
3070
  // Handle materialization clauses
@@ -2778,7 +3075,7 @@ export class Deparser {
2778
3075
  output.push('MATERIALIZED');
2779
3076
  }
2780
3077
  if (node.ctequery) {
2781
- output.push(this.formatter.parens(this.visit(node.ctequery, context)));
3078
+ output.push(context.parens(this.visit(node.ctequery, context)));
2782
3079
  }
2783
3080
  return output.join(' ');
2784
3081
  }
@@ -2854,7 +3151,7 @@ export class Deparser {
2854
3151
  DistinctExpr(node, context) {
2855
3152
  const args = ListUtils.unwrapList(node.args);
2856
3153
  if (args.length === 2) {
2857
- const literalContext = { ...context, isStringLiteral: true };
3154
+ const literalContext = context.spawn('DistinctExpr', { isStringLiteral: true });
2858
3155
  const left = this.visit(args[0], literalContext);
2859
3156
  const right = this.visit(args[1], literalContext);
2860
3157
  return `${left} IS DISTINCT FROM ${right}`;
@@ -2864,7 +3161,7 @@ export class Deparser {
2864
3161
  NullIfExpr(node, context) {
2865
3162
  const args = ListUtils.unwrapList(node.args);
2866
3163
  if (args.length === 2) {
2867
- const literalContext = { ...context, isStringLiteral: true };
3164
+ const literalContext = context.spawn('NullIfExpr', { isStringLiteral: true });
2868
3165
  const left = this.visit(args[0], literalContext);
2869
3166
  const right = this.visit(args[1], literalContext);
2870
3167
  return `NULLIF(${left}, ${right})`;
@@ -2943,7 +3240,7 @@ export class Deparser {
2943
3240
  }
2944
3241
  RelabelType(node, context) {
2945
3242
  if (node.arg) {
2946
- const literalContext = { ...context, isStringLiteral: true };
3243
+ const literalContext = context.spawn('RelabelType', { isStringLiteral: true });
2947
3244
  return this.visit(node.arg, literalContext);
2948
3245
  }
2949
3246
  return '';
@@ -2962,7 +3259,7 @@ export class Deparser {
2962
3259
  }
2963
3260
  ConvertRowtypeExpr(node, context) {
2964
3261
  if (node.arg) {
2965
- const literalContext = { ...context, isStringLiteral: true };
3262
+ const literalContext = context.spawn('ConvertRowtypeExpr', { isStringLiteral: true });
2966
3263
  return this.visit(node.arg, literalContext);
2967
3264
  }
2968
3265
  return '';
@@ -2993,10 +3290,10 @@ export class Deparser {
2993
3290
  }
2994
3291
  if (node.aliases && node.aliases.length > 0) {
2995
3292
  const aliasStrs = ListUtils.unwrapList(node.aliases).map(alias => this.visit(alias, context));
2996
- output.push(this.formatter.parens(aliasStrs.join(', ')));
3293
+ output.push(context.parens(aliasStrs.join(', ')));
2997
3294
  }
2998
3295
  if (node.options && node.options.length > 0) {
2999
- const viewContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ViewStmt'] };
3296
+ const viewContext = context.spawn('ViewStmt');
3000
3297
  const optionStrs = ListUtils.unwrapList(node.options)
3001
3298
  .map(option => this.visit(option, viewContext));
3002
3299
  output.push(`WITH (${optionStrs.join(', ')})`);
@@ -3043,22 +3340,22 @@ export class Deparser {
3043
3340
  }
3044
3341
  if (node.indexParams && node.indexParams.length > 0) {
3045
3342
  const paramStrs = ListUtils.unwrapList(node.indexParams).map(param => this.visit(param, context));
3046
- output.push(this.formatter.parens(paramStrs.join(', ')));
3343
+ output.push(context.parens(paramStrs.join(', ')));
3047
3344
  }
3048
3345
  if (node.indexIncludingParams && node.indexIncludingParams.length > 0) {
3049
3346
  const includeStrs = ListUtils.unwrapList(node.indexIncludingParams).map(param => this.visit(param, context));
3050
3347
  output.push('INCLUDE');
3051
- output.push(this.formatter.parens(includeStrs.join(', ')));
3348
+ output.push(context.parens(includeStrs.join(', ')));
3052
3349
  }
3053
3350
  if (node.whereClause) {
3054
3351
  output.push('WHERE');
3055
3352
  output.push(this.visit(node.whereClause, context));
3056
3353
  }
3057
3354
  if (node.options && node.options.length > 0) {
3058
- const indexContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'IndexStmt'] };
3355
+ const indexContext = context.spawn('IndexStmt');
3059
3356
  const optionStrs = ListUtils.unwrapList(node.options).map(option => this.visit(option, indexContext));
3060
3357
  output.push('WITH');
3061
- output.push(this.formatter.parens(optionStrs.join(', ')));
3358
+ output.push(context.parens(optionStrs.join(', ')));
3062
3359
  }
3063
3360
  if (node.nulls_not_distinct) {
3064
3361
  output.push('NULLS NOT DISTINCT');
@@ -3075,7 +3372,7 @@ export class Deparser {
3075
3372
  output.push(QuoteUtils.quote(node.name));
3076
3373
  }
3077
3374
  else if (node.expr) {
3078
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3375
+ output.push(context.parens(this.visit(node.expr, context)));
3079
3376
  }
3080
3377
  if (node.collation && node.collation.length > 0) {
3081
3378
  const collationStrs = ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3092,7 +3389,7 @@ export class Deparser {
3092
3389
  const stringData = this.getNodeData(opt.DefElem.arg);
3093
3390
  return `${opt.DefElem.defname}='${stringData.sval}'`;
3094
3391
  }
3095
- return this.visit(opt, context);
3392
+ return this.visit(opt, context.spawn('IndexElem'));
3096
3393
  });
3097
3394
  opclassStr += `(${opclassOpts.join(', ')})`;
3098
3395
  }
@@ -3126,7 +3423,7 @@ export class Deparser {
3126
3423
  output.push(QuoteUtils.quote(node.name));
3127
3424
  }
3128
3425
  else if (node.expr) {
3129
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3426
+ output.push(context.parens(this.visit(node.expr, context)));
3130
3427
  }
3131
3428
  if (node.collation && node.collation.length > 0) {
3132
3429
  const collationStrs = ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3274,16 +3571,16 @@ export class Deparser {
3274
3571
  if (node.rarg && 'JoinExpr' in node.rarg && !node.rarg.JoinExpr.alias) {
3275
3572
  rargStr = `(${rargStr})`;
3276
3573
  }
3277
- if (this.formatter.isPretty()) {
3278
- output.push(this.formatter.newline() + joinStr + ' ' + rargStr);
3574
+ if (context.isPretty()) {
3575
+ output.push(context.newline() + joinStr + ' ' + rargStr);
3279
3576
  }
3280
3577
  else {
3281
3578
  output.push(joinStr + ' ' + rargStr);
3282
3579
  }
3283
3580
  }
3284
3581
  else {
3285
- if (this.formatter.isPretty()) {
3286
- output.push(this.formatter.newline() + joinStr);
3582
+ if (context.isPretty()) {
3583
+ output.push(context.newline() + joinStr);
3287
3584
  }
3288
3585
  else {
3289
3586
  output.push(joinStr);
@@ -3292,7 +3589,7 @@ export class Deparser {
3292
3589
  if (node.usingClause && node.usingClause.length > 0) {
3293
3590
  const usingList = ListUtils.unwrapList(node.usingClause);
3294
3591
  const columnNames = usingList.map(col => this.visit(col, context));
3295
- if (this.formatter.isPretty()) {
3592
+ if (context.isPretty()) {
3296
3593
  output.push(` USING (${columnNames.join(', ')})`);
3297
3594
  }
3298
3595
  else {
@@ -3301,14 +3598,14 @@ export class Deparser {
3301
3598
  }
3302
3599
  else if (node.quals) {
3303
3600
  const qualsStr = this.visit(node.quals, context);
3304
- if (this.formatter.isPretty()) {
3601
+ if (context.isPretty()) {
3305
3602
  // For complex JOIN conditions, format with proper indentation
3306
3603
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3307
3604
  if (this.containsMultilineStringLiteral(qualsStr)) {
3308
3605
  output.push(` ON ${qualsStr}`);
3309
3606
  }
3310
3607
  else {
3311
- output.push(` ON${this.formatter.newline()}${this.formatter.indent(qualsStr)}`);
3608
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3312
3609
  }
3313
3610
  }
3314
3611
  else {
@@ -3320,7 +3617,7 @@ export class Deparser {
3320
3617
  }
3321
3618
  }
3322
3619
  let result;
3323
- if (this.formatter.isPretty()) {
3620
+ if (context.isPretty()) {
3324
3621
  result = output.join('');
3325
3622
  }
3326
3623
  else {
@@ -3427,8 +3724,8 @@ export class Deparser {
3427
3724
  else if (nodeData.sval !== undefined) {
3428
3725
  // Handle nested sval structure: { sval: { sval: "value" } }
3429
3726
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3430
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3431
- boolValue = stringValue === 'on' || stringValue === 'true';
3727
+ const stringValue = svalValue.replace(/'/g, '');
3728
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3432
3729
  }
3433
3730
  }
3434
3731
  return boolValue ? 'READ ONLY' : 'READ WRITE';
@@ -3451,8 +3748,8 @@ export class Deparser {
3451
3748
  else if (nodeData.sval !== undefined) {
3452
3749
  // Handle nested sval structure: { sval: { sval: "value" } }
3453
3750
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3454
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3455
- boolValue = stringValue === 'on' || stringValue === 'true';
3751
+ const stringValue = svalValue.replace(/'/g, '');
3752
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3456
3753
  }
3457
3754
  }
3458
3755
  return boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE';
@@ -3519,8 +3816,8 @@ export class Deparser {
3519
3816
  else if (nodeData.sval !== undefined) {
3520
3817
  // Handle nested sval structure: { sval: { sval: "value" } }
3521
3818
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3522
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3523
- boolValue = stringValue === 'on' || stringValue === 'true';
3819
+ const stringValue = svalValue.replace(/'/g, '');
3820
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3524
3821
  }
3525
3822
  }
3526
3823
  transactionOptions.push(boolValue ? 'READ ONLY' : 'READ WRITE');
@@ -3538,8 +3835,8 @@ export class Deparser {
3538
3835
  else if (nodeData.sval !== undefined) {
3539
3836
  // Handle nested sval structure: { sval: { sval: "value" } }
3540
3837
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3541
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3542
- boolValue = stringValue === 'on' || stringValue === 'true';
3838
+ const stringValue = svalValue.replace(/'/g, '');
3839
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3543
3840
  }
3544
3841
  }
3545
3842
  transactionOptions.push(boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE');
@@ -3605,7 +3902,7 @@ export class Deparser {
3605
3902
  }
3606
3903
  switch (node.roletype) {
3607
3904
  case 'ROLESPEC_PUBLIC':
3608
- return 'public';
3905
+ return 'PUBLIC';
3609
3906
  case 'ROLESPEC_CURRENT_USER':
3610
3907
  return 'CURRENT_USER';
3611
3908
  case 'ROLESPEC_SESSION_USER':
@@ -3613,7 +3910,7 @@ export class Deparser {
3613
3910
  case 'ROLESPEC_CURRENT_ROLE':
3614
3911
  return 'CURRENT_ROLE';
3615
3912
  default:
3616
- return 'public';
3913
+ return 'PUBLIC';
3617
3914
  }
3618
3915
  }
3619
3916
  roletype(node, context) {
@@ -3923,7 +4220,7 @@ export class Deparser {
3923
4220
  }).filter((name) => name && name.trim());
3924
4221
  return items.join('.');
3925
4222
  }
3926
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DropStmt'], objtype: node.removeType };
4223
+ const objContext = context.spawn('DropStmt', { objtype: node.removeType });
3927
4224
  const objName = this.visit(objList, objContext);
3928
4225
  return objName;
3929
4226
  }).filter((name) => name && name.trim()).join(', ');
@@ -4023,7 +4320,7 @@ export class Deparser {
4023
4320
  if (node.options && node.options.length > 0) {
4024
4321
  output.push('WITH');
4025
4322
  const optionsStr = ListUtils.unwrapList(node.options)
4026
- .map(opt => this.visit(opt, context))
4323
+ .map(opt => this.visit(opt, context.spawn('CopyStmt')))
4027
4324
  .join(', ');
4028
4325
  output.push(`(${optionsStr})`);
4029
4326
  }
@@ -4069,18 +4366,28 @@ export class Deparser {
4069
4366
  if (node.missing_ok) {
4070
4367
  output.push('IF EXISTS');
4071
4368
  }
4072
- const alterContext = node.objtype === 'OBJECT_TYPE'
4073
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
4074
- : context;
4369
+ const alterContext = context.spawn('AlterTableStmt', { objtype: node.objtype });
4075
4370
  if (node.relation) {
4076
4371
  const relationStr = this.RangeVar(node.relation, alterContext);
4077
4372
  output.push(relationStr);
4078
4373
  }
4079
4374
  if (node.cmds && node.cmds.length > 0) {
4080
- const commandsStr = ListUtils.unwrapList(node.cmds)
4081
- .map(cmd => this.visit(cmd, alterContext))
4082
- .join(', ');
4083
- output.push(commandsStr);
4375
+ const commands = ListUtils.unwrapList(node.cmds);
4376
+ if (context.isPretty()) {
4377
+ const commandsStr = commands
4378
+ .map(cmd => {
4379
+ const cmdStr = this.visit(cmd, alterContext);
4380
+ return context.newline() + context.indent(cmdStr);
4381
+ })
4382
+ .join(',');
4383
+ output.push(commandsStr);
4384
+ }
4385
+ else {
4386
+ const commandsStr = commands
4387
+ .map(cmd => this.visit(cmd, alterContext))
4388
+ .join(', ');
4389
+ output.push(commandsStr);
4390
+ }
4084
4391
  }
4085
4392
  return output.join(' ');
4086
4393
  }
@@ -4089,7 +4396,7 @@ export class Deparser {
4089
4396
  if (node.subtype) {
4090
4397
  switch (node.subtype) {
4091
4398
  case 'AT_AddColumn':
4092
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4399
+ if (context.objtype === 'OBJECT_TYPE') {
4093
4400
  output.push('ADD ATTRIBUTE');
4094
4401
  }
4095
4402
  else {
@@ -4100,35 +4407,99 @@ export class Deparser {
4100
4407
  }
4101
4408
  if (node.def) {
4102
4409
  const colDefData = this.getNodeData(node.def);
4103
- const parts = [];
4104
- if (colDefData.colname) {
4105
- parts.push(QuoteUtils.quote(colDefData.colname));
4106
- }
4107
- if (colDefData.typeName) {
4108
- parts.push(this.TypeName(colDefData.typeName, context));
4109
- }
4110
- if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4111
- parts.push('OPTIONS');
4112
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
4113
- const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4114
- parts.push(`(${options.join(', ')})`);
4115
- }
4116
- if (colDefData.constraints) {
4117
- const constraints = ListUtils.unwrapList(colDefData.constraints);
4118
- const constraintStrs = constraints.map(constraint => {
4119
- const columnConstraintContext = { ...context, isColumnConstraint: true };
4120
- return this.visit(constraint, columnConstraintContext);
4121
- });
4122
- parts.push(...constraintStrs);
4123
- }
4124
- if (colDefData.raw_default) {
4125
- parts.push('DEFAULT');
4126
- parts.push(this.visit(colDefData.raw_default, context));
4410
+ if (context.isPretty()) {
4411
+ const parts = [];
4412
+ const indentedParts = [];
4413
+ if (colDefData.colname) {
4414
+ parts.push(QuoteUtils.quote(colDefData.colname));
4415
+ }
4416
+ if (colDefData.typeName) {
4417
+ parts.push(this.TypeName(colDefData.typeName, context));
4418
+ }
4419
+ if (colDefData.is_not_null) {
4420
+ indentedParts.push('NOT NULL');
4421
+ }
4422
+ if (colDefData.collClause) {
4423
+ indentedParts.push(this.CollateClause(colDefData.collClause, context));
4424
+ }
4425
+ if (colDefData.constraints) {
4426
+ const constraints = ListUtils.unwrapList(colDefData.constraints);
4427
+ constraints.forEach(constraint => {
4428
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4429
+ const constraintStr = this.visit(constraint, columnConstraintContext);
4430
+ if (constraintStr.includes('REFERENCES') && constraintStr.includes('ON DELETE')) {
4431
+ const refMatch = constraintStr.match(/^(.*REFERENCES[^)]*\([^)]*\))\s*(ON\s+DELETE\s+CASCADE.*)$/);
4432
+ if (refMatch) {
4433
+ indentedParts.push(refMatch[1]);
4434
+ indentedParts.push(refMatch[2]);
4435
+ }
4436
+ else {
4437
+ indentedParts.push(constraintStr);
4438
+ }
4439
+ }
4440
+ else if (constraintStr === 'UNIQUE' && colDefData.raw_default) {
4441
+ const defaultStr = 'DEFAULT ' + this.visit(colDefData.raw_default, context);
4442
+ indentedParts.push('UNIQUE ' + defaultStr);
4443
+ }
4444
+ else {
4445
+ indentedParts.push(constraintStr);
4446
+ }
4447
+ });
4448
+ }
4449
+ if (colDefData.raw_default && !colDefData.constraints?.some((c) => {
4450
+ const constraintStr = this.visit(c, context.spawn('ColumnDef', { isColumnConstraint: true }));
4451
+ return constraintStr === 'UNIQUE';
4452
+ })) {
4453
+ const defaultStr = 'DEFAULT ' + this.visit(colDefData.raw_default, context);
4454
+ indentedParts.push(defaultStr);
4455
+ }
4456
+ if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4457
+ indentedParts.push('OPTIONS');
4458
+ const columnContext = context.spawn('ColumnDef');
4459
+ const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4460
+ indentedParts.push(`(${options.join(', ')})`);
4461
+ }
4462
+ let result = parts.join(' ');
4463
+ if (indentedParts.length > 0) {
4464
+ const indentedStr = indentedParts.map(part => context.indent(part)).join(context.newline());
4465
+ result += context.newline() + indentedStr;
4466
+ }
4467
+ output.push(result);
4127
4468
  }
4128
- if (colDefData.is_not_null) {
4129
- parts.push('NOT NULL');
4469
+ else {
4470
+ const parts = [];
4471
+ if (colDefData.colname) {
4472
+ parts.push(QuoteUtils.quote(colDefData.colname));
4473
+ }
4474
+ if (colDefData.typeName) {
4475
+ parts.push(this.TypeName(colDefData.typeName, context));
4476
+ }
4477
+ if (colDefData.collClause) {
4478
+ parts.push(this.CollateClause(colDefData.collClause, context));
4479
+ }
4480
+ if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4481
+ parts.push('OPTIONS');
4482
+ const columnContext = context.spawn('ColumnDef');
4483
+ const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4484
+ parts.push(`(${options.join(', ')})`);
4485
+ }
4486
+ if (colDefData.constraints) {
4487
+ const constraints = ListUtils.unwrapList(colDefData.constraints);
4488
+ const constraintStrs = constraints.map(constraint => {
4489
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4490
+ return this.visit(constraint, columnConstraintContext);
4491
+ });
4492
+ parts.push(...constraintStrs);
4493
+ }
4494
+ if (colDefData.raw_default) {
4495
+ parts.push('DEFAULT');
4496
+ parts.push(this.visit(colDefData.raw_default, context));
4497
+ }
4498
+ if (colDefData.is_not_null) {
4499
+ parts.push('NOT NULL');
4500
+ }
4501
+ output.push(parts.join(' '));
4130
4502
  }
4131
- output.push(parts.join(' '));
4132
4503
  }
4133
4504
  if (node.behavior === 'DROP_CASCADE') {
4134
4505
  output.push('CASCADE');
@@ -4136,7 +4507,7 @@ export class Deparser {
4136
4507
  break;
4137
4508
  case 'AT_DropColumn':
4138
4509
  if (node.missing_ok) {
4139
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4510
+ if (context.objtype === 'OBJECT_TYPE') {
4140
4511
  output.push('DROP ATTRIBUTE IF EXISTS');
4141
4512
  }
4142
4513
  else {
@@ -4144,7 +4515,7 @@ export class Deparser {
4144
4515
  }
4145
4516
  }
4146
4517
  else {
4147
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4518
+ if (context.objtype === 'OBJECT_TYPE') {
4148
4519
  output.push('DROP ATTRIBUTE');
4149
4520
  }
4150
4521
  else {
@@ -4162,7 +4533,7 @@ export class Deparser {
4162
4533
  }
4163
4534
  break;
4164
4535
  case 'AT_AlterColumnType':
4165
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4536
+ if (context.objtype === 'OBJECT_TYPE') {
4166
4537
  output.push('ALTER ATTRIBUTE');
4167
4538
  }
4168
4539
  else {
@@ -4226,7 +4597,7 @@ export class Deparser {
4226
4597
  case 'AT_SetRelOptions':
4227
4598
  output.push('SET');
4228
4599
  if (node.def) {
4229
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetRelOptions' };
4600
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetRelOptions' });
4230
4601
  const options = ListUtils.unwrapList(node.def)
4231
4602
  .map(option => this.visit(option, alterTableContext))
4232
4603
  .join(', ');
@@ -4239,7 +4610,7 @@ export class Deparser {
4239
4610
  case 'AT_ResetRelOptions':
4240
4611
  output.push('RESET');
4241
4612
  if (node.def) {
4242
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetRelOptions' };
4613
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetRelOptions' });
4243
4614
  const options = ListUtils.unwrapList(node.def)
4244
4615
  .map(option => this.visit(option, alterTableContext))
4245
4616
  .join(', ');
@@ -4334,7 +4705,7 @@ export class Deparser {
4334
4705
  }
4335
4706
  output.push('SET');
4336
4707
  if (node.def) {
4337
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetOptions' };
4708
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetOptions' });
4338
4709
  const options = ListUtils.unwrapList(node.def)
4339
4710
  .map(option => this.visit(option, alterTableContext))
4340
4711
  .join(', ');
@@ -4351,7 +4722,7 @@ export class Deparser {
4351
4722
  }
4352
4723
  output.push('RESET');
4353
4724
  if (node.def) {
4354
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetOptions' };
4725
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetOptions' });
4355
4726
  const options = ListUtils.unwrapList(node.def)
4356
4727
  .map(option => this.visit(option, alterTableContext))
4357
4728
  .join(', ');
@@ -4592,7 +4963,7 @@ export class Deparser {
4592
4963
  }
4593
4964
  output.push('OPTIONS');
4594
4965
  if (node.def) {
4595
- const alterColumnContext = { ...context, alterColumnOptions: true };
4966
+ const alterColumnContext = context.spawn('AlterTableCmd', { alterColumnOptions: true });
4596
4967
  const options = ListUtils.unwrapList(node.def)
4597
4968
  .map(option => this.visit(option, alterColumnContext))
4598
4969
  .join(', ');
@@ -4632,7 +5003,7 @@ export class Deparser {
4632
5003
  case 'AT_GenericOptions':
4633
5004
  output.push('OPTIONS');
4634
5005
  if (node.def) {
4635
- const alterTableContext = { ...context, alterTableOptions: true };
5006
+ const alterTableContext = context.spawn('AlterTableCmd', { alterTableOptions: true });
4636
5007
  const options = ListUtils.unwrapList(node.def)
4637
5008
  .map(option => this.visit(option, alterTableContext))
4638
5009
  .join(', ');
@@ -4644,11 +5015,10 @@ export class Deparser {
4644
5015
  if (node.name) {
4645
5016
  output.push(QuoteUtils.quote(node.name));
4646
5017
  }
4647
- output.push('ADD GENERATED');
5018
+ output.push('ADD');
4648
5019
  if (node.def) {
4649
5020
  output.push(this.visit(node.def, context));
4650
5021
  }
4651
- output.push('AS IDENTITY');
4652
5022
  break;
4653
5023
  case 'AT_SetIdentity':
4654
5024
  output.push('ALTER COLUMN');
@@ -4736,7 +5106,7 @@ export class Deparser {
4736
5106
  output.push(this.TypeName(node.returnType, context));
4737
5107
  }
4738
5108
  if (node.options && node.options.length > 0) {
4739
- const funcContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFunctionStmt'] };
5109
+ const funcContext = context.spawn('CreateFunctionStmt');
4740
5110
  const options = node.options.map((opt) => this.visit(opt, funcContext));
4741
5111
  output.push(...options);
4742
5112
  }
@@ -4823,7 +5193,7 @@ export class Deparser {
4823
5193
  }
4824
5194
  output.push('AS', 'ENUM');
4825
5195
  if (node.vals && node.vals.length > 0) {
4826
- const enumContext = { ...context, isEnumValue: true };
5196
+ const enumContext = context.spawn('CreateEnumStmt', { isEnumValue: true });
4827
5197
  const values = ListUtils.unwrapList(node.vals)
4828
5198
  .map(val => this.visit(val, enumContext))
4829
5199
  .join(', ');
@@ -4878,9 +5248,8 @@ export class Deparser {
4878
5248
  output.push(roleName);
4879
5249
  }
4880
5250
  if (node.options) {
4881
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateRoleStmt'] };
4882
5251
  const options = ListUtils.unwrapList(node.options)
4883
- .map(option => this.visit(option, roleContext))
5252
+ .map(option => this.visit(option, context.spawn('CreateRoleStmt')))
4884
5253
  .join(' ');
4885
5254
  if (options) {
4886
5255
  output.push('WITH');
@@ -4911,7 +5280,7 @@ export class Deparser {
4911
5280
  const stringData = this.getNodeData(node.arg);
4912
5281
  return `${node.defname}='${stringData.sval}'`;
4913
5282
  }
4914
- return `${node.defname}=${this.visit(node.arg, { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] })}`;
5283
+ return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
4915
5284
  }
4916
5285
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
4917
5286
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -4927,13 +5296,13 @@ export class Deparser {
4927
5296
  if (!node.arg) {
4928
5297
  return `NO ${node.defname.toUpperCase()}`;
4929
5298
  }
4930
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5299
+ const defElemContext = context.spawn('DefElem');
4931
5300
  const argValue = this.visit(node.arg, defElemContext);
4932
5301
  return `${node.defname.toUpperCase()} ${argValue}`;
4933
5302
  }
4934
5303
  // Handle OPTIONS clause - use space format, not equals format
4935
5304
  if (node.arg) {
4936
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5305
+ const defElemContext = context.spawn('DefElem');
4937
5306
  const argValue = this.visit(node.arg, defElemContext);
4938
5307
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
4939
5308
  const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
@@ -4987,7 +5356,7 @@ export class Deparser {
4987
5356
  if (!node.arg) {
4988
5357
  return 'PASSWORD NULL';
4989
5358
  }
4990
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5359
+ const defElemContext = context.spawn('DefElem');
4991
5360
  const argValue = this.visit(node.arg, defElemContext);
4992
5361
  const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
4993
5362
  ? `'${argValue}'`
@@ -4996,7 +5365,7 @@ export class Deparser {
4996
5365
  }
4997
5366
  }
4998
5367
  if (node.arg) {
4999
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5368
+ const defElemContext = context.spawn('DefElem');
5000
5369
  const argValue = this.visit(node.arg, defElemContext);
5001
5370
  if (context.parentNodeTypes.includes('AlterOperatorStmt')) {
5002
5371
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
@@ -5072,7 +5441,7 @@ export class Deparser {
5072
5441
  if (node.defname === 'sysid') {
5073
5442
  return `SYSID ${argValue}`;
5074
5443
  }
5075
- if (argValue === 'true') {
5444
+ if (String(argValue) === 'true') {
5076
5445
  // Handle special cases where the positive form has a different name
5077
5446
  if (node.defname === 'isreplication') {
5078
5447
  return 'REPLICATION';
@@ -5082,7 +5451,7 @@ export class Deparser {
5082
5451
  }
5083
5452
  return node.defname.toUpperCase();
5084
5453
  }
5085
- else if (argValue === 'false') {
5454
+ else if (String(argValue) === 'false') {
5086
5455
  // Handle special cases where the negative form has a different name
5087
5456
  if (node.defname === 'canlogin') {
5088
5457
  return 'NOLOGIN';
@@ -5146,7 +5515,7 @@ export class Deparser {
5146
5515
  }
5147
5516
  if (context.parentNodeTypes.includes('DoStmt')) {
5148
5517
  if (node.defname === 'as') {
5149
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5518
+ const defElemContext = context.spawn('DefElem');
5150
5519
  const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
5151
5520
  if (Array.isArray(argValue)) {
5152
5521
  const bodyParts = argValue;
@@ -5437,7 +5806,7 @@ export class Deparser {
5437
5806
  }
5438
5807
  if (node.options && node.options.length > 0) {
5439
5808
  output.push('WITH');
5440
- const tsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateTableSpaceStmt'] };
5809
+ const tsContext = context.spawn('CreateTableSpaceStmt');
5441
5810
  const options = ListUtils.unwrapList(node.options)
5442
5811
  .map(option => this.visit(option, tsContext))
5443
5812
  .join(', ');
@@ -5467,7 +5836,7 @@ export class Deparser {
5467
5836
  output.push('SET');
5468
5837
  }
5469
5838
  if (node.options && node.options.length > 0) {
5470
- const tablespaceContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableSpaceOptionsStmt'] };
5839
+ const tablespaceContext = context.spawn('AlterTableSpaceOptionsStmt');
5471
5840
  const options = ListUtils.unwrapList(node.options)
5472
5841
  .map(option => this.visit(option, tablespaceContext))
5473
5842
  .join(', ');
@@ -5484,7 +5853,7 @@ export class Deparser {
5484
5853
  output.push(this.quoteIfNeeded(node.extname));
5485
5854
  }
5486
5855
  if (node.options && node.options.length > 0) {
5487
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateExtensionStmt'] };
5856
+ const extContext = context.spawn('CreateExtensionStmt');
5488
5857
  const options = ListUtils.unwrapList(node.options)
5489
5858
  .map(option => this.visit(option, extContext))
5490
5859
  .join(' ');
@@ -5498,7 +5867,7 @@ export class Deparser {
5498
5867
  output.push(this.quoteIfNeeded(node.extname));
5499
5868
  }
5500
5869
  if (node.options && node.options.length > 0) {
5501
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterExtensionStmt'] };
5870
+ const extContext = context.spawn('AlterExtensionStmt');
5502
5871
  const options = ListUtils.unwrapList(node.options)
5503
5872
  .map(option => this.visit(option, extContext))
5504
5873
  .join(' ');
@@ -5512,7 +5881,7 @@ export class Deparser {
5512
5881
  output.push(node.fdwname);
5513
5882
  }
5514
5883
  if (node.func_options && node.func_options.length > 0) {
5515
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5884
+ const fdwContext = context.spawn('CreateFdwStmt');
5516
5885
  const funcOptions = ListUtils.unwrapList(node.func_options)
5517
5886
  .map(option => this.visit(option, fdwContext))
5518
5887
  .join(' ');
@@ -5520,7 +5889,7 @@ export class Deparser {
5520
5889
  }
5521
5890
  if (node.options && node.options.length > 0) {
5522
5891
  output.push('OPTIONS');
5523
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
5892
+ const fdwContext = context.spawn('CreateFdwStmt');
5524
5893
  const options = ListUtils.unwrapList(node.options)
5525
5894
  .map(option => this.visit(option, fdwContext))
5526
5895
  .join(', ');
@@ -5622,7 +5991,7 @@ export class Deparser {
5622
5991
  output.push('ADD');
5623
5992
  if (node.def) {
5624
5993
  // Pass domain context to avoid adding constraint names for domain constraints
5625
- const domainContext = { ...context, isDomainConstraint: true };
5994
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5626
5995
  output.push(this.visit(node.def, domainContext));
5627
5996
  }
5628
5997
  break;
@@ -5648,7 +6017,7 @@ export class Deparser {
5648
6017
  output.push('ADD');
5649
6018
  if (node.def) {
5650
6019
  // Pass domain context to avoid adding constraint names for domain constraints
5651
- const domainContext = { ...context, isDomainConstraint: true };
6020
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5652
6021
  output.push(this.visit(node.def, domainContext));
5653
6022
  }
5654
6023
  break;
@@ -6010,7 +6379,7 @@ export class Deparser {
6010
6379
  output.push(`${operatorName}(${args.join(', ')})`);
6011
6380
  }
6012
6381
  else {
6013
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CommentStmt'], objtype: node.objtype };
6382
+ const objContext = context.spawn('CommentStmt', { objtype: node.objtype });
6014
6383
  output.push(this.visit(node.object, objContext));
6015
6384
  }
6016
6385
  }
@@ -6056,13 +6425,13 @@ export class Deparser {
6056
6425
  const output = [];
6057
6426
  const initialParts = ['CREATE', 'POLICY'];
6058
6427
  if (node.policy_name) {
6059
- initialParts.push(`"${node.policy_name}"`);
6428
+ initialParts.push(QuoteUtils.quote(node.policy_name));
6060
6429
  }
6061
6430
  output.push(initialParts.join(' '));
6062
6431
  // Add ON clause on new line in pretty mode
6063
6432
  if (node.table) {
6064
- if (this.formatter.isPretty()) {
6065
- output.push(this.formatter.newline() + this.formatter.indent(`ON ${this.RangeVar(node.table, context)}`));
6433
+ if (context.isPretty()) {
6434
+ output.push(context.newline() + context.indent(`ON ${this.RangeVar(node.table, context)}`));
6066
6435
  }
6067
6436
  else {
6068
6437
  output.push('ON');
@@ -6071,24 +6440,24 @@ export class Deparser {
6071
6440
  }
6072
6441
  // Handle AS RESTRICTIVE/PERMISSIVE clause
6073
6442
  if (node.permissive === undefined) {
6074
- if (this.formatter.isPretty()) {
6075
- output.push(this.formatter.newline() + this.formatter.indent('AS RESTRICTIVE'));
6443
+ if (context.isPretty()) {
6444
+ output.push(context.newline() + context.indent('AS RESTRICTIVE'));
6076
6445
  }
6077
6446
  else {
6078
6447
  output.push('AS', 'RESTRICTIVE');
6079
6448
  }
6080
6449
  }
6081
6450
  else if (node.permissive === true) {
6082
- if (this.formatter.isPretty()) {
6083
- output.push(this.formatter.newline() + this.formatter.indent('AS PERMISSIVE'));
6451
+ if (context.isPretty()) {
6452
+ output.push(context.newline() + context.indent('AS PERMISSIVE'));
6084
6453
  }
6085
6454
  else {
6086
6455
  output.push('AS', 'PERMISSIVE');
6087
6456
  }
6088
6457
  }
6089
6458
  if (node.cmd_name) {
6090
- if (this.formatter.isPretty()) {
6091
- output.push(this.formatter.newline() + this.formatter.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6459
+ if (context.isPretty()) {
6460
+ output.push(context.newline() + context.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6092
6461
  }
6093
6462
  else {
6094
6463
  output.push('FOR', node.cmd_name.toUpperCase());
@@ -6096,8 +6465,8 @@ export class Deparser {
6096
6465
  }
6097
6466
  if (node.roles && node.roles.length > 0) {
6098
6467
  const roles = ListUtils.unwrapList(node.roles).map(role => this.visit(role, context));
6099
- if (this.formatter.isPretty()) {
6100
- output.push(this.formatter.newline() + this.formatter.indent(`TO ${roles.join(', ')}`));
6468
+ if (context.isPretty()) {
6469
+ output.push(context.newline() + context.indent(`TO ${roles.join(', ')}`));
6101
6470
  }
6102
6471
  else {
6103
6472
  output.push('TO');
@@ -6105,11 +6474,11 @@ export class Deparser {
6105
6474
  }
6106
6475
  }
6107
6476
  if (node.qual) {
6108
- if (this.formatter.isPretty()) {
6477
+ if (context.isPretty()) {
6109
6478
  const qualExpr = this.visit(node.qual, context);
6110
- output.push(this.formatter.newline() + this.formatter.indent('USING ('));
6111
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(qualExpr)));
6112
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6479
+ output.push(context.newline() + context.indent('USING ('));
6480
+ output.push(context.newline() + context.indent(context.indent(qualExpr)));
6481
+ output.push(context.newline() + context.indent(')'));
6113
6482
  }
6114
6483
  else {
6115
6484
  output.push('USING');
@@ -6117,23 +6486,23 @@ export class Deparser {
6117
6486
  }
6118
6487
  }
6119
6488
  if (node.with_check) {
6120
- if (this.formatter.isPretty()) {
6489
+ if (context.isPretty()) {
6121
6490
  const checkExpr = this.visit(node.with_check, context);
6122
- output.push(this.formatter.newline() + this.formatter.indent('WITH CHECK ('));
6123
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(checkExpr)));
6124
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6491
+ output.push(context.newline() + context.indent('WITH CHECK ('));
6492
+ output.push(context.newline() + context.indent(context.indent(checkExpr)));
6493
+ output.push(context.newline() + context.indent(')'));
6125
6494
  }
6126
6495
  else {
6127
6496
  output.push('WITH CHECK');
6128
6497
  output.push(`(${this.visit(node.with_check, context)})`);
6129
6498
  }
6130
6499
  }
6131
- return this.formatter.isPretty() ? output.join('') : output.join(' ');
6500
+ return context.isPretty() ? output.join('') : output.join(' ');
6132
6501
  }
6133
6502
  AlterPolicyStmt(node, context) {
6134
6503
  const output = ['ALTER', 'POLICY'];
6135
6504
  if (node.policy_name) {
6136
- output.push(`"${node.policy_name}"`);
6505
+ output.push(QuoteUtils.quote(node.policy_name));
6137
6506
  }
6138
6507
  if (node.table) {
6139
6508
  output.push('ON');
@@ -6173,7 +6542,7 @@ export class Deparser {
6173
6542
  }
6174
6543
  if (node.options && node.options.length > 0) {
6175
6544
  output.push('OPTIONS');
6176
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateUserMappingStmt'] };
6545
+ const userMappingContext = context.spawn('CreateUserMappingStmt');
6177
6546
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6178
6547
  output.push(`(${options.join(', ')})`);
6179
6548
  }
@@ -6356,7 +6725,7 @@ export class Deparser {
6356
6725
  DoStmt(node, context) {
6357
6726
  const output = ['DO'];
6358
6727
  if (node.args && node.args.length > 0) {
6359
- const doContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DoStmt'] };
6728
+ const doContext = context.spawn('DoStmt');
6360
6729
  const args = ListUtils.unwrapList(node.args);
6361
6730
  const processedArgs = [];
6362
6731
  for (const arg of args) {
@@ -6661,7 +7030,7 @@ export class Deparser {
6661
7030
  ObjectWithArgs(node, context) {
6662
7031
  let result = '';
6663
7032
  if (node.objname && node.objname.length > 0) {
6664
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ObjectWithArgs'] };
7033
+ const objContext = context.spawn('ObjectWithArgs');
6665
7034
  const names = ListUtils.unwrapList(node.objname).map(name => this.visit(name, objContext));
6666
7035
  result = names.join('.');
6667
7036
  }
@@ -6703,7 +7072,7 @@ export class Deparser {
6703
7072
  }
6704
7073
  output.push('SET');
6705
7074
  if (node.options && node.options.length > 0) {
6706
- const alterOpContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterOperatorStmt'] };
7075
+ const alterOpContext = context.spawn('AlterOperatorStmt');
6707
7076
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, alterOpContext));
6708
7077
  output.push(`(${options.join(', ')})`);
6709
7078
  }
@@ -6715,13 +7084,13 @@ export class Deparser {
6715
7084
  output.push(QuoteUtils.quote(node.fdwname));
6716
7085
  }
6717
7086
  if (node.func_options && node.func_options.length > 0) {
6718
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
7087
+ const fdwContext = context.spawn('AlterFdwStmt');
6719
7088
  const funcOptions = ListUtils.unwrapList(node.func_options).map(opt => this.visit(opt, fdwContext));
6720
7089
  output.push(funcOptions.join(' '));
6721
7090
  }
6722
7091
  if (node.options && node.options.length > 0) {
6723
7092
  output.push('OPTIONS');
6724
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
7093
+ const fdwContext = context.spawn('AlterFdwStmt');
6725
7094
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, fdwContext));
6726
7095
  output.push(`(${options.join(', ')})`);
6727
7096
  }
@@ -6747,7 +7116,7 @@ export class Deparser {
6747
7116
  if (node.options && node.options.length > 0) {
6748
7117
  output.push('OPTIONS');
6749
7118
  output.push('(');
6750
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignServerStmt'] };
7119
+ const optionsContext = context.spawn('CreateForeignServerStmt');
6751
7120
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6752
7121
  output.push(options.join(', '));
6753
7122
  output.push(')');
@@ -6765,7 +7134,7 @@ export class Deparser {
6765
7134
  if (node.options && node.options.length > 0) {
6766
7135
  output.push('OPTIONS');
6767
7136
  output.push('(');
6768
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterForeignServerStmt'] };
7137
+ const optionsContext = context.spawn('AlterForeignServerStmt');
6769
7138
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6770
7139
  output.push(options.join(', '));
6771
7140
  output.push(')');
@@ -6786,7 +7155,7 @@ export class Deparser {
6786
7155
  }
6787
7156
  if (node.options && node.options.length > 0) {
6788
7157
  output.push('OPTIONS');
6789
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterUserMappingStmt'] };
7158
+ const userMappingContext = context.spawn('AlterUserMappingStmt');
6790
7159
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6791
7160
  output.push(`(${options.join(', ')})`);
6792
7161
  }
@@ -6846,7 +7215,7 @@ export class Deparser {
6846
7215
  output.push(QuoteUtils.quote(node.local_schema));
6847
7216
  }
6848
7217
  if (node.options && node.options.length > 0) {
6849
- const importSchemaContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ImportForeignSchemaStmt'] };
7218
+ const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
6850
7219
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, importSchemaContext));
6851
7220
  output.push(`OPTIONS (${options.join(', ')})`);
6852
7221
  }
@@ -6881,7 +7250,7 @@ export class Deparser {
6881
7250
  ExplainStmt(node, context) {
6882
7251
  const output = ['EXPLAIN'];
6883
7252
  if (node.options && node.options.length > 0) {
6884
- const explainContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ExplainStmt'] };
7253
+ const explainContext = context.spawn('ExplainStmt');
6885
7254
  const options = ListUtils.unwrapList(node.options).map(option => this.visit(option, explainContext));
6886
7255
  output.push(`(${options.join(', ')})`);
6887
7256
  }
@@ -7139,9 +7508,7 @@ export class Deparser {
7139
7508
  output.push(this.RangeVar(node.relation, context));
7140
7509
  }
7141
7510
  else if (node.relation) {
7142
- const rangeVarContext = node.relationType === 'OBJECT_TYPE'
7143
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
7144
- : context;
7511
+ const rangeVarContext = context.spawn('RenameStmt', { objtype: node.relationType });
7145
7512
  // Add ON keyword for policy operations
7146
7513
  if (node.renameType === 'OBJECT_POLICY') {
7147
7514
  output.push('ON');
@@ -7323,7 +7690,7 @@ export class Deparser {
7323
7690
  }
7324
7691
  }
7325
7692
  if (node.privileges && node.privileges.length > 0) {
7326
- const privilegeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'GrantStmt'] };
7693
+ const privilegeContext = context.spawn('GrantStmt');
7327
7694
  const privileges = ListUtils.unwrapList(node.privileges)
7328
7695
  .map(priv => this.visit(priv, privilegeContext))
7329
7696
  .join(', ');
@@ -7631,7 +7998,12 @@ export class Deparser {
7631
7998
  }
7632
7999
  if (node.action) {
7633
8000
  const actionStr = this.GrantStmt(node.action, context);
7634
- output.push(actionStr);
8001
+ if (context.isPretty()) {
8002
+ return output.join(' ') + context.newline() + context.indent(actionStr);
8003
+ }
8004
+ else {
8005
+ output.push(actionStr);
8006
+ }
7635
8007
  }
7636
8008
  return output.join(' ');
7637
8009
  }
@@ -7778,84 +8150,158 @@ export class Deparser {
7778
8150
  if (node.trigname) {
7779
8151
  output.push(QuoteUtils.quote(node.trigname));
7780
8152
  }
7781
- const timing = [];
7782
- if (node.timing & 2)
7783
- timing.push('BEFORE');
7784
- else if (node.timing & 64)
7785
- timing.push('INSTEAD OF');
7786
- else
7787
- timing.push('AFTER'); // Default timing when no specific timing is set
7788
- output.push(timing.join(' '));
7789
- const events = [];
7790
- if (node.events & 4)
7791
- events.push('INSERT');
7792
- if (node.events & 8)
7793
- events.push('DELETE');
7794
- if (node.events & 16)
7795
- events.push('UPDATE');
7796
- if (node.events & 32)
7797
- events.push('TRUNCATE');
7798
- output.push(events.join(' OR '));
7799
- if (node.columns && node.columns.length > 0) {
7800
- output.push('OF');
7801
- const columnNames = ListUtils.unwrapList(node.columns)
7802
- .map(col => this.visit(col, context))
7803
- .join(', ');
7804
- output.push(columnNames);
7805
- }
7806
- output.push('ON');
7807
- if (node.relation) {
7808
- output.push(this.RangeVar(node.relation, context));
7809
- }
7810
- if (node.constrrel) {
7811
- output.push('FROM');
7812
- output.push(this.RangeVar(node.constrrel, context));
7813
- }
7814
- if (node.deferrable) {
7815
- output.push('DEFERRABLE');
7816
- }
7817
- if (node.initdeferred) {
7818
- output.push('INITIALLY DEFERRED');
7819
- }
7820
- // Handle REFERENCING clauses
7821
- if (node.transitionRels && node.transitionRels.length > 0) {
7822
- output.push('REFERENCING');
7823
- const transitionClauses = ListUtils.unwrapList(node.transitionRels)
7824
- .map(rel => this.visit(rel, context))
7825
- .join(' ');
7826
- output.push(transitionClauses);
7827
- }
7828
- if (node.row) {
7829
- output.push('FOR EACH ROW');
7830
- }
7831
- else {
7832
- output.push('FOR EACH STATEMENT');
7833
- }
7834
- if (node.whenClause) {
7835
- output.push('WHEN');
7836
- output.push('(');
7837
- output.push(this.visit(node.whenClause, context));
7838
- output.push(')');
7839
- }
7840
- output.push('EXECUTE');
7841
- if (node.funcname && node.funcname.length > 0) {
7842
- const funcName = ListUtils.unwrapList(node.funcname)
7843
- .map(name => this.visit(name, context))
7844
- .join('.');
7845
- output.push('FUNCTION', funcName);
7846
- }
7847
- if (node.args && node.args.length > 0) {
7848
- output.push('(');
7849
- const args = ListUtils.unwrapList(node.args)
7850
- .map(arg => this.visit(arg, context))
7851
- .join(', ');
7852
- output.push(args);
7853
- output.push(')');
8153
+ if (context.isPretty()) {
8154
+ const components = [];
8155
+ const timing = [];
8156
+ if (node.timing & 2)
8157
+ timing.push('BEFORE');
8158
+ else if (node.timing & 64)
8159
+ timing.push('INSTEAD OF');
8160
+ else
8161
+ timing.push('AFTER');
8162
+ const events = [];
8163
+ if (node.events & 4)
8164
+ events.push('INSERT');
8165
+ if (node.events & 8)
8166
+ events.push('DELETE');
8167
+ if (node.events & 16) {
8168
+ let updateStr = 'UPDATE';
8169
+ if (node.columns && node.columns.length > 0) {
8170
+ const columnNames = ListUtils.unwrapList(node.columns)
8171
+ .map(col => this.visit(col, context))
8172
+ .join(', ');
8173
+ updateStr += ' OF ' + columnNames;
8174
+ }
8175
+ events.push(updateStr);
8176
+ }
8177
+ if (node.events & 32)
8178
+ events.push('TRUNCATE');
8179
+ components.push(context.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8180
+ if (node.relation) {
8181
+ components.push(context.indent('ON ' + this.RangeVar(node.relation, context)));
8182
+ }
8183
+ if (node.transitionRels && node.transitionRels.length > 0) {
8184
+ const transitionClauses = ListUtils.unwrapList(node.transitionRels)
8185
+ .map(rel => this.visit(rel, context))
8186
+ .join(' ');
8187
+ components.push(context.indent('REFERENCING ' + transitionClauses));
8188
+ }
8189
+ if (node.deferrable) {
8190
+ components.push(context.indent('DEFERRABLE'));
8191
+ }
8192
+ if (node.initdeferred) {
8193
+ components.push(context.indent('INITIALLY DEFERRED'));
8194
+ }
8195
+ if (node.row) {
8196
+ components.push(context.indent('FOR EACH ROW'));
8197
+ }
8198
+ else {
8199
+ components.push(context.indent('FOR EACH STATEMENT'));
8200
+ }
8201
+ if (node.whenClause) {
8202
+ const whenStr = 'WHEN (' + this.visit(node.whenClause, context) + ')';
8203
+ components.push(context.indent(whenStr));
8204
+ }
8205
+ let executeStr = 'EXECUTE';
8206
+ if (node.funcname && node.funcname.length > 0) {
8207
+ const funcName = ListUtils.unwrapList(node.funcname)
8208
+ .map(name => this.visit(name, context))
8209
+ .join('.');
8210
+ executeStr += ' PROCEDURE ' + funcName;
8211
+ }
8212
+ if (node.args && node.args.length > 0) {
8213
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8214
+ const args = ListUtils.unwrapList(node.args)
8215
+ .map(arg => this.visit(arg, argContext))
8216
+ .join(', ');
8217
+ executeStr += '(' + args + ')';
8218
+ }
8219
+ else {
8220
+ executeStr += '()';
8221
+ }
8222
+ components.push(context.indent(executeStr));
8223
+ return output.join(' ') + context.newline() + components.join(context.newline());
7854
8224
  }
7855
8225
  else {
7856
- output.push('()');
8226
+ const timing = [];
8227
+ if (node.timing & 2)
8228
+ timing.push('BEFORE');
8229
+ else if (node.timing & 64)
8230
+ timing.push('INSTEAD OF');
8231
+ else
8232
+ timing.push('AFTER');
8233
+ output.push(timing.join(' '));
8234
+ const events = [];
8235
+ if (node.events & 4)
8236
+ events.push('INSERT');
8237
+ if (node.events & 8)
8238
+ events.push('DELETE');
8239
+ if (node.events & 16)
8240
+ events.push('UPDATE');
8241
+ if (node.events & 32)
8242
+ events.push('TRUNCATE');
8243
+ output.push(events.join(' OR '));
8244
+ if (node.columns && node.columns.length > 0) {
8245
+ output.push('OF');
8246
+ const columnNames = ListUtils.unwrapList(node.columns)
8247
+ .map(col => this.visit(col, context))
8248
+ .join(', ');
8249
+ output.push(columnNames);
8250
+ }
8251
+ output.push('ON');
8252
+ if (node.relation) {
8253
+ output.push(this.RangeVar(node.relation, context));
8254
+ }
8255
+ if (node.constrrel) {
8256
+ output.push('FROM');
8257
+ output.push(this.RangeVar(node.constrrel, context));
8258
+ }
8259
+ if (node.deferrable) {
8260
+ output.push('DEFERRABLE');
8261
+ }
8262
+ if (node.initdeferred) {
8263
+ output.push('INITIALLY DEFERRED');
8264
+ }
8265
+ if (node.transitionRels && node.transitionRels.length > 0) {
8266
+ output.push('REFERENCING');
8267
+ const transitionClauses = ListUtils.unwrapList(node.transitionRels)
8268
+ .map(rel => this.visit(rel, context))
8269
+ .join(' ');
8270
+ output.push(transitionClauses);
8271
+ }
8272
+ if (node.row) {
8273
+ output.push('FOR EACH ROW');
8274
+ }
8275
+ else {
8276
+ output.push('FOR EACH STATEMENT');
8277
+ }
8278
+ if (node.whenClause) {
8279
+ output.push('WHEN');
8280
+ output.push('(');
8281
+ output.push(this.visit(node.whenClause, context));
8282
+ output.push(')');
8283
+ }
8284
+ output.push('EXECUTE');
8285
+ if (node.funcname && node.funcname.length > 0) {
8286
+ const funcName = ListUtils.unwrapList(node.funcname)
8287
+ .map(name => this.visit(name, context))
8288
+ .join('.');
8289
+ output.push('FUNCTION', funcName);
8290
+ }
8291
+ if (node.args && node.args.length > 0) {
8292
+ output.push('(');
8293
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8294
+ const args = ListUtils.unwrapList(node.args)
8295
+ .map(arg => this.visit(arg, argContext))
8296
+ .join(', ');
8297
+ output.push(args);
8298
+ output.push(')');
8299
+ }
8300
+ else {
8301
+ output.push('()');
8302
+ }
8303
+ return output.join(' ');
7857
8304
  }
7858
- return output.join(' ');
7859
8305
  }
7860
8306
  TriggerTransition(node, context) {
7861
8307
  const output = [];
@@ -7881,7 +8327,7 @@ export class Deparser {
7881
8327
  }
7882
8328
  if (node.whenclause && node.whenclause.length > 0) {
7883
8329
  output.push('WHEN');
7884
- const eventTriggerContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateEventTrigStmt'] };
8330
+ const eventTriggerContext = context.spawn('CreateEventTrigStmt');
7885
8331
  const conditions = ListUtils.unwrapList(node.whenclause)
7886
8332
  .map(condition => this.visit(condition, eventTriggerContext))
7887
8333
  .join(' AND ');
@@ -8070,7 +8516,7 @@ export class Deparser {
8070
8516
  output.push(sequenceName.join('.'));
8071
8517
  }
8072
8518
  if (node.options && node.options.length > 0) {
8073
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateSeqStmt'] };
8519
+ const seqContext = context.spawn('CreateSeqStmt');
8074
8520
  const optionStrs = ListUtils.unwrapList(node.options)
8075
8521
  .filter(option => option != null && this.getNodeType(option) !== 'undefined')
8076
8522
  .map(option => {
@@ -8107,7 +8553,7 @@ export class Deparser {
8107
8553
  output.push(sequenceName.join('.'));
8108
8554
  }
8109
8555
  if (node.options && node.options.length > 0) {
8110
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterSeqStmt'] };
8556
+ const seqContext = context.spawn('AlterSeqStmt');
8111
8557
  const optionStrs = ListUtils.unwrapList(node.options)
8112
8558
  .filter(option => option && option !== undefined)
8113
8559
  .map(option => {
@@ -8136,7 +8582,7 @@ export class Deparser {
8136
8582
  CompositeTypeStmt(node, context) {
8137
8583
  const output = ['CREATE', 'TYPE'];
8138
8584
  if (node.typevar) {
8139
- const typeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CompositeTypeStmt'] };
8585
+ const typeContext = context.spawn('CompositeTypeStmt');
8140
8586
  output.push(this.RangeVar(node.typevar, typeContext));
8141
8587
  }
8142
8588
  output.push('AS');
@@ -8237,7 +8683,7 @@ export class Deparser {
8237
8683
  output.push(this.RoleSpec(node.role, context));
8238
8684
  }
8239
8685
  if (node.options) {
8240
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterRoleStmt'] };
8686
+ const roleContext = context.spawn('AlterRoleStmt');
8241
8687
  // Handle GROUP operations specially based on action value
8242
8688
  if (isGroupStatement) {
8243
8689
  const roleMembersOption = ListUtils.unwrapList(node.options).find(option => option.DefElem && option.DefElem.defname === 'rolemembers');
@@ -8433,14 +8879,14 @@ export class Deparser {
8433
8879
  AccessPriv(node, context) {
8434
8880
  const output = [];
8435
8881
  if (node.priv_name) {
8436
- output.push(node.priv_name);
8882
+ output.push(node.priv_name.toUpperCase());
8437
8883
  }
8438
8884
  else {
8439
8885
  output.push('ALL');
8440
8886
  }
8441
8887
  if (node.cols && node.cols.length > 0) {
8442
8888
  output.push('(');
8443
- const colContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AccessPriv'] };
8889
+ const colContext = context.spawn('AccessPriv');
8444
8890
  const columns = ListUtils.unwrapList(node.cols).map(col => this.visit(col, colContext));
8445
8891
  output.push(columns.join(', '));
8446
8892
  output.push(')');
@@ -8520,7 +8966,7 @@ export class Deparser {
8520
8966
  output.push(ListUtils.unwrapList(node.defnames).map(name => this.visit(name, context)).join('.'));
8521
8967
  }
8522
8968
  if (node.definition && node.definition.length > 0) {
8523
- const defineStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefineStmt'] };
8969
+ const defineStmtContext = context.spawn('DefineStmt');
8524
8970
  const definitions = ListUtils.unwrapList(node.definition).map(def => {
8525
8971
  return this.visit(def, defineStmtContext);
8526
8972
  });
@@ -9006,7 +9452,7 @@ export class Deparser {
9006
9452
  const operatorNumber = node.number !== undefined ? node.number : 0;
9007
9453
  output.push(operatorNumber.toString());
9008
9454
  if (node.name) {
9009
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9455
+ const opClassContext = context.spawn('CreateOpClassItem');
9010
9456
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9011
9457
  }
9012
9458
  }
@@ -9016,7 +9462,7 @@ export class Deparser {
9016
9462
  const functionNumber = node.number !== undefined ? node.number : 0;
9017
9463
  output.push(functionNumber.toString());
9018
9464
  if (node.name) {
9019
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9465
+ const opClassContext = context.spawn('CreateOpClassItem');
9020
9466
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9021
9467
  }
9022
9468
  }
@@ -9714,7 +10160,7 @@ export class Deparser {
9714
10160
  output.push(this.ObjectWithArgs(node.func, context));
9715
10161
  }
9716
10162
  if (node.actions && node.actions.length > 0) {
9717
- const alterFunctionContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFunctionStmt'] };
10163
+ const alterFunctionContext = context.spawn('AlterFunctionStmt');
9718
10164
  const actionStrs = ListUtils.unwrapList(node.actions).map(action => this.visit(action, alterFunctionContext));
9719
10165
  output.push(actionStrs.join(' '));
9720
10166
  }
@@ -9958,7 +10404,7 @@ export class Deparser {
9958
10404
  CreateForeignTableStmt(node, context) {
9959
10405
  const output = ['CREATE FOREIGN TABLE'];
9960
10406
  if (node.base && node.base.relation) {
9961
- const relationContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10407
+ const relationContext = context.spawn('CreateForeignTableStmt');
9962
10408
  // Handle relation node directly as RangeVar since it contains the RangeVar properties
9963
10409
  output.push(this.RangeVar(node.base.relation, relationContext));
9964
10410
  }
@@ -9988,7 +10434,7 @@ export class Deparser {
9988
10434
  output.push(QuoteUtils.quote(node.servername));
9989
10435
  }
9990
10436
  if (node.options && node.options.length > 0) {
9991
- const foreignTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10437
+ const foreignTableContext = context.spawn('CreateForeignTableStmt');
9992
10438
  const optionStrs = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, foreignTableContext));
9993
10439
  output.push(`OPTIONS (${optionStrs.join(', ')})`);
9994
10440
  }