pgsql-deparser 16.0.0 → 16.1.1

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,128 @@ export class Deparser {
111
168
  }
112
169
  return delimiter;
113
170
  }
114
- deparse(node, context = { parentNodeTypes: [] }) {
171
+ /**
172
+ * Maps ObjectType enum values to their corresponding SQL keywords
173
+ * Used by AlterOwnerStmt, AlterObjectSchemaStmt, and other statements that need object type keywords
174
+ */
175
+ getObjectTypeKeyword(objectType) {
176
+ switch (objectType) {
177
+ case 'OBJECT_TABLE':
178
+ return 'TABLE';
179
+ case 'OBJECT_VIEW':
180
+ return 'VIEW';
181
+ case 'OBJECT_INDEX':
182
+ return 'INDEX';
183
+ case 'OBJECT_SEQUENCE':
184
+ return 'SEQUENCE';
185
+ case 'OBJECT_FUNCTION':
186
+ return 'FUNCTION';
187
+ case 'OBJECT_PROCEDURE':
188
+ return 'PROCEDURE';
189
+ case 'OBJECT_SCHEMA':
190
+ return 'SCHEMA';
191
+ case 'OBJECT_DATABASE':
192
+ return 'DATABASE';
193
+ case 'OBJECT_DOMAIN':
194
+ return 'DOMAIN';
195
+ case 'OBJECT_AGGREGATE':
196
+ return 'AGGREGATE';
197
+ case 'OBJECT_CONVERSION':
198
+ return 'CONVERSION';
199
+ case 'OBJECT_LANGUAGE':
200
+ return 'LANGUAGE';
201
+ case 'OBJECT_OPERATOR':
202
+ return 'OPERATOR';
203
+ case 'OBJECT_OPFAMILY':
204
+ return 'OPERATOR FAMILY';
205
+ case 'OBJECT_OPCLASS':
206
+ return 'OPERATOR CLASS';
207
+ case 'OBJECT_TSDICTIONARY':
208
+ return 'TEXT SEARCH DICTIONARY';
209
+ case 'OBJECT_TSCONFIGURATION':
210
+ return 'TEXT SEARCH CONFIGURATION';
211
+ case 'OBJECT_EVENT_TRIGGER':
212
+ return 'EVENT TRIGGER';
213
+ case 'OBJECT_FDW':
214
+ return 'FOREIGN DATA WRAPPER';
215
+ case 'OBJECT_FOREIGN_SERVER':
216
+ return 'SERVER';
217
+ case 'OBJECT_TYPE':
218
+ return 'TYPE';
219
+ case 'OBJECT_COLLATION':
220
+ return 'COLLATION';
221
+ case 'OBJECT_PUBLICATION':
222
+ return 'PUBLICATION';
223
+ case 'OBJECT_ACCESS_METHOD':
224
+ return 'ACCESS METHOD';
225
+ case 'OBJECT_AMOP':
226
+ return 'OPERATOR CLASS';
227
+ case 'OBJECT_AMPROC':
228
+ return 'OPERATOR CLASS';
229
+ case 'OBJECT_ATTRIBUTE':
230
+ return 'ATTRIBUTE';
231
+ case 'OBJECT_CAST':
232
+ return 'CAST';
233
+ case 'OBJECT_COLUMN':
234
+ return 'COLUMN';
235
+ case 'OBJECT_DEFAULT':
236
+ return 'DEFAULT';
237
+ case 'OBJECT_DEFACL':
238
+ return 'DEFAULT PRIVILEGES';
239
+ case 'OBJECT_DOMCONSTRAINT':
240
+ return 'DOMAIN';
241
+ case 'OBJECT_EXTENSION':
242
+ return 'EXTENSION';
243
+ case 'OBJECT_FOREIGN_TABLE':
244
+ return 'FOREIGN TABLE';
245
+ case 'OBJECT_LARGEOBJECT':
246
+ return 'LARGE OBJECT';
247
+ case 'OBJECT_MATVIEW':
248
+ return 'MATERIALIZED VIEW';
249
+ case 'OBJECT_PARAMETER_ACL':
250
+ return 'PARAMETER';
251
+ case 'OBJECT_POLICY':
252
+ return 'POLICY';
253
+ case 'OBJECT_PUBLICATION_NAMESPACE':
254
+ return 'PUBLICATION';
255
+ case 'OBJECT_PUBLICATION_REL':
256
+ return 'PUBLICATION';
257
+ case 'OBJECT_ROLE':
258
+ return 'ROLE';
259
+ case 'OBJECT_ROUTINE':
260
+ return 'ROUTINE';
261
+ case 'OBJECT_RULE':
262
+ return 'RULE';
263
+ case 'OBJECT_STATISTIC_EXT':
264
+ return 'STATISTICS';
265
+ case 'OBJECT_SUBSCRIPTION':
266
+ return 'SUBSCRIPTION';
267
+ case 'OBJECT_TABCONSTRAINT':
268
+ return 'CONSTRAINT';
269
+ case 'OBJECT_TABLESPACE':
270
+ return 'TABLESPACE';
271
+ case 'OBJECT_TRANSFORM':
272
+ return 'TRANSFORM';
273
+ case 'OBJECT_TRIGGER':
274
+ return 'TRIGGER';
275
+ case 'OBJECT_TSPARSER':
276
+ return 'TEXT SEARCH PARSER';
277
+ case 'OBJECT_TSTEMPLATE':
278
+ return 'TEXT SEARCH TEMPLATE';
279
+ case 'OBJECT_USER_MAPPING':
280
+ return 'USER MAPPING';
281
+ default:
282
+ throw new Error(`Unsupported objectType: ${objectType}`);
283
+ }
284
+ }
285
+ deparse(node, context) {
115
286
  if (node == null) {
116
287
  return null;
117
288
  }
289
+ if (!context) {
290
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
291
+ context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
292
+ }
118
293
  if (typeof node === 'number' || node instanceof Number) {
119
294
  return node.toString();
120
295
  }
@@ -126,7 +301,11 @@ export class Deparser {
126
301
  throw new Error(`Error deparsing ${nodeType}: ${error.message}`);
127
302
  }
128
303
  }
129
- visit(node, context = { parentNodeTypes: [] }) {
304
+ visit(node, context) {
305
+ if (!context) {
306
+ const formatter = new SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
307
+ context = new DeparserContext({ formatter, prettyMode: this.options.pretty });
308
+ }
130
309
  const nodeType = this.getNodeType(node);
131
310
  // Handle empty objects
132
311
  if (!nodeType) {
@@ -135,11 +314,7 @@ export class Deparser {
135
314
  const nodeData = this.getNodeData(node);
136
315
  const methodName = nodeType;
137
316
  if (typeof this[methodName] === 'function') {
138
- const childContext = {
139
- ...context,
140
- parentNodeTypes: [...context.parentNodeTypes, nodeType]
141
- };
142
- const result = this[methodName](nodeData, childContext);
317
+ const result = this[methodName](nodeData, context);
143
318
  return result;
144
319
  }
145
320
  throw new Error(`Deparser does not handle node type: ${nodeType}`);
@@ -165,7 +340,7 @@ export class Deparser {
165
340
  .filter((rawStmt) => rawStmt != null)
166
341
  .map((rawStmt) => this.RawStmt(rawStmt, context))
167
342
  .filter((result) => result !== '')
168
- .join(this.formatter.newline() + this.formatter.newline());
343
+ .join(context.newline() + context.newline());
169
344
  }
170
345
  RawStmt(node, context) {
171
346
  if (!node.stmt) {
@@ -185,7 +360,7 @@ export class Deparser {
185
360
  }
186
361
  if (!node.op || node.op === 'SETOP_NONE') {
187
362
  if (node.valuesLists == null) {
188
- if (!this.formatter.isPretty() || !node.targetList) {
363
+ if (!context.isPretty() || !node.targetList) {
189
364
  output.push('SELECT');
190
365
  }
191
366
  }
@@ -205,7 +380,7 @@ export class Deparser {
205
380
  (node.rarg).limitOffset ||
206
381
  (node.rarg).withClause);
207
382
  if (leftNeedsParens) {
208
- output.push(this.formatter.parens(leftStmt));
383
+ output.push(context.parens(leftStmt));
209
384
  }
210
385
  else {
211
386
  output.push(leftStmt);
@@ -227,7 +402,7 @@ export class Deparser {
227
402
  output.push('ALL');
228
403
  }
229
404
  if (rightNeedsParens) {
230
- output.push(this.formatter.parens(rightStmt));
405
+ output.push(context.parens(rightStmt));
231
406
  }
232
407
  else {
233
408
  output.push(rightStmt);
@@ -239,20 +414,20 @@ export class Deparser {
239
414
  const distinctClause = ListUtils.unwrapList(node.distinctClause);
240
415
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
241
416
  const clause = distinctClause
242
- .map(e => this.visit(e, { ...context, select: true }))
417
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
243
418
  .join(', ');
244
- distinctPart = ' DISTINCT ON ' + this.formatter.parens(clause);
419
+ distinctPart = ' DISTINCT ON ' + context.parens(clause);
245
420
  }
246
421
  else {
247
422
  distinctPart = ' DISTINCT';
248
423
  }
249
- if (!this.formatter.isPretty()) {
424
+ if (!context.isPretty()) {
250
425
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
251
426
  output.push('DISTINCT ON');
252
427
  const clause = distinctClause
253
- .map(e => this.visit(e, { ...context, select: true }))
428
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
254
429
  .join(', ');
255
- output.push(this.formatter.parens(clause));
430
+ output.push(context.parens(clause));
256
431
  }
257
432
  else {
258
433
  output.push('DISTINCT');
@@ -261,22 +436,41 @@ export class Deparser {
261
436
  }
262
437
  if (node.targetList) {
263
438
  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;
439
+ if (context.isPretty()) {
440
+ if (targetList.length === 1) {
441
+ const targetNode = targetList[0];
442
+ const target = this.visit(targetNode, context.spawn('SelectStmt', { select: true }));
443
+ // Check if single target is complex - if so, use multiline format
444
+ if (this.isComplexSelectTarget(targetNode)) {
445
+ output.push('SELECT' + distinctPart);
446
+ if (this.containsMultilineStringLiteral(target)) {
447
+ output.push(target);
448
+ }
449
+ else {
450
+ output.push(context.indent(target));
451
+ }
270
452
  }
271
- return this.formatter.indent(targetStr);
272
- });
273
- const formattedTargets = targetStrings.join(',' + this.formatter.newline());
274
- output.push('SELECT' + distinctPart);
275
- output.push(formattedTargets);
453
+ else {
454
+ output.push('SELECT' + distinctPart + ' ' + target);
455
+ }
456
+ }
457
+ else {
458
+ const targetStrings = targetList
459
+ .map(e => {
460
+ const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
461
+ if (this.containsMultilineStringLiteral(targetStr)) {
462
+ return targetStr;
463
+ }
464
+ return context.indent(targetStr);
465
+ });
466
+ const formattedTargets = targetStrings.join(',' + context.newline());
467
+ output.push('SELECT' + distinctPart);
468
+ output.push(formattedTargets);
469
+ }
276
470
  }
277
471
  else {
278
472
  const targets = targetList
279
- .map(e => this.visit(e, { ...context, select: true }))
473
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
280
474
  .join(', ');
281
475
  output.push(targets);
282
476
  }
@@ -288,22 +482,22 @@ export class Deparser {
288
482
  if (node.fromClause) {
289
483
  const fromList = ListUtils.unwrapList(node.fromClause);
290
484
  const fromItems = fromList
291
- .map(e => this.deparse(e, { ...context, from: true }))
485
+ .map(e => this.deparse(e, context.spawn('SelectStmt', { from: true })))
292
486
  .join(', ');
293
487
  output.push('FROM ' + fromItems.trim());
294
488
  }
295
489
  if (node.whereClause) {
296
- if (this.formatter.isPretty()) {
490
+ if (context.isPretty()) {
297
491
  output.push('WHERE');
298
492
  const whereExpr = this.visit(node.whereClause, context);
299
- const lines = whereExpr.split(this.formatter.newline());
493
+ const lines = whereExpr.split(context.newline());
300
494
  const indentedLines = lines.map((line, index) => {
301
495
  if (index === 0) {
302
- return this.formatter.indent(line);
496
+ return context.indent(line);
303
497
  }
304
498
  return line;
305
499
  });
306
- output.push(indentedLines.join(this.formatter.newline()));
500
+ output.push(indentedLines.join(context.newline()));
307
501
  }
308
502
  else {
309
503
  output.push('WHERE');
@@ -311,45 +505,61 @@ export class Deparser {
311
505
  }
312
506
  }
313
507
  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(', '));
508
+ if (context.isPretty()) {
509
+ output.push('VALUES');
510
+ const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
511
+ const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
512
+ return context.parens(values.join(', '));
513
+ });
514
+ const indentedTuples = lists.map(tuple => {
515
+ if (this.containsMultilineStringLiteral(tuple)) {
516
+ return tuple;
517
+ }
518
+ return context.indent(tuple);
519
+ });
520
+ output.push(indentedTuples.join(',\n'));
521
+ }
522
+ else {
523
+ output.push('VALUES');
524
+ const lists = ListUtils.unwrapList(node.valuesLists).map(list => {
525
+ const values = ListUtils.unwrapList(list).map(val => this.visit(val, context));
526
+ return context.parens(values.join(', '));
527
+ });
528
+ output.push(lists.join(', '));
529
+ }
320
530
  }
321
531
  if (node.groupClause) {
322
532
  const groupList = ListUtils.unwrapList(node.groupClause);
323
- if (this.formatter.isPretty()) {
533
+ if (context.isPretty()) {
324
534
  const groupItems = groupList
325
535
  .map(e => {
326
- const groupStr = this.visit(e, { ...context, group: true });
536
+ const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
327
537
  if (this.containsMultilineStringLiteral(groupStr)) {
328
538
  return groupStr;
329
539
  }
330
- return this.formatter.indent(groupStr);
540
+ return context.indent(groupStr);
331
541
  })
332
- .join(',' + this.formatter.newline());
542
+ .join(',' + context.newline());
333
543
  output.push('GROUP BY');
334
544
  output.push(groupItems);
335
545
  }
336
546
  else {
337
547
  output.push('GROUP BY');
338
548
  const groupItems = groupList
339
- .map(e => this.visit(e, { ...context, group: true }))
549
+ .map(e => this.visit(e, context.spawn('SelectStmt', { group: true })))
340
550
  .join(', ');
341
551
  output.push(groupItems);
342
552
  }
343
553
  }
344
554
  if (node.havingClause) {
345
- if (this.formatter.isPretty()) {
555
+ if (context.isPretty()) {
346
556
  output.push('HAVING');
347
557
  const havingStr = this.visit(node.havingClause, context);
348
558
  if (this.containsMultilineStringLiteral(havingStr)) {
349
559
  output.push(havingStr);
350
560
  }
351
561
  else {
352
- output.push(this.formatter.indent(havingStr));
562
+ output.push(context.indent(havingStr));
353
563
  }
354
564
  }
355
565
  else {
@@ -367,23 +577,23 @@ export class Deparser {
367
577
  }
368
578
  if (node.sortClause) {
369
579
  const sortList = ListUtils.unwrapList(node.sortClause);
370
- if (this.formatter.isPretty()) {
580
+ if (context.isPretty()) {
371
581
  const sortItems = sortList
372
582
  .map(e => {
373
- const sortStr = this.visit(e, { ...context, sort: true });
583
+ const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
374
584
  if (this.containsMultilineStringLiteral(sortStr)) {
375
585
  return sortStr;
376
586
  }
377
- return this.formatter.indent(sortStr);
587
+ return context.indent(sortStr);
378
588
  })
379
- .join(',' + this.formatter.newline());
589
+ .join(',' + context.newline());
380
590
  output.push('ORDER BY');
381
591
  output.push(sortItems);
382
592
  }
383
593
  else {
384
594
  output.push('ORDER BY');
385
595
  const sortItems = sortList
386
- .map(e => this.visit(e, { ...context, sort: true }))
596
+ .map(e => this.visit(e, context.spawn('SelectStmt', { sort: true })))
387
597
  .join(', ');
388
598
  output.push(sortItems);
389
599
  }
@@ -401,9 +611,9 @@ export class Deparser {
401
611
  .join(' ');
402
612
  output.push(lockingClauses);
403
613
  }
404
- if (this.formatter.isPretty()) {
614
+ if (context.isPretty()) {
405
615
  const filteredOutput = output.filter(item => item.trim() !== '');
406
- return filteredOutput.join(this.formatter.newline());
616
+ return filteredOutput.join(context.newline());
407
617
  }
408
618
  return output.join(' ');
409
619
  }
@@ -415,13 +625,13 @@ export class Deparser {
415
625
  switch (kind) {
416
626
  case 'AEXPR_OP':
417
627
  if (lexpr && rexpr) {
418
- const operator = this.deparseOperatorName(name);
628
+ const operator = this.deparseOperatorName(name, context);
419
629
  let leftExpr = this.visit(lexpr, context);
420
630
  let rightExpr = this.visit(rexpr, context);
421
631
  // Check if left expression needs parentheses
422
632
  let leftNeedsParens = false;
423
633
  if (lexpr && 'A_Expr' in lexpr && lexpr.A_Expr?.kind === 'AEXPR_OP') {
424
- const leftOp = this.deparseOperatorName(ListUtils.unwrapList(lexpr.A_Expr.name));
634
+ const leftOp = this.deparseOperatorName(ListUtils.unwrapList(lexpr.A_Expr.name), context);
425
635
  if (this.needsParentheses(leftOp, operator, 'left')) {
426
636
  leftNeedsParens = true;
427
637
  }
@@ -430,12 +640,12 @@ export class Deparser {
430
640
  leftNeedsParens = true;
431
641
  }
432
642
  if (leftNeedsParens) {
433
- leftExpr = this.formatter.parens(leftExpr);
643
+ leftExpr = context.parens(leftExpr);
434
644
  }
435
645
  // Check if right expression needs parentheses
436
646
  let rightNeedsParens = false;
437
647
  if (rexpr && 'A_Expr' in rexpr && rexpr.A_Expr?.kind === 'AEXPR_OP') {
438
- const rightOp = this.deparseOperatorName(ListUtils.unwrapList(rexpr.A_Expr.name));
648
+ const rightOp = this.deparseOperatorName(ListUtils.unwrapList(rexpr.A_Expr.name), context);
439
649
  if (this.needsParentheses(rightOp, operator, 'right')) {
440
650
  rightNeedsParens = true;
441
651
  }
@@ -444,42 +654,42 @@ export class Deparser {
444
654
  rightNeedsParens = true;
445
655
  }
446
656
  if (rightNeedsParens) {
447
- rightExpr = this.formatter.parens(rightExpr);
657
+ rightExpr = context.parens(rightExpr);
448
658
  }
449
- return this.formatter.format([leftExpr, operator, rightExpr]);
659
+ return context.format([leftExpr, operator, rightExpr]);
450
660
  }
451
661
  else if (rexpr) {
452
- return this.formatter.format([
453
- this.deparseOperatorName(name),
662
+ return context.format([
663
+ this.deparseOperatorName(name, context),
454
664
  this.visit(rexpr, context)
455
665
  ]);
456
666
  }
457
667
  break;
458
668
  case 'AEXPR_OP_ANY':
459
- return this.formatter.format([
669
+ return context.format([
460
670
  this.visit(lexpr, context),
461
- this.deparseOperatorName(name),
671
+ this.deparseOperatorName(name, context),
462
672
  'ANY',
463
- this.formatter.parens(this.visit(rexpr, context))
673
+ context.parens(this.visit(rexpr, context))
464
674
  ]);
465
675
  case 'AEXPR_OP_ALL':
466
- return this.formatter.format([
676
+ return context.format([
467
677
  this.visit(lexpr, context),
468
- this.deparseOperatorName(name),
678
+ this.deparseOperatorName(name, context),
469
679
  'ALL',
470
- this.formatter.parens(this.visit(rexpr, context))
680
+ context.parens(this.visit(rexpr, context))
471
681
  ]);
472
682
  case 'AEXPR_DISTINCT': {
473
683
  let leftExpr = this.visit(lexpr, context);
474
684
  let rightExpr = this.visit(rexpr, context);
475
685
  // Add parentheses for complex expressions
476
686
  if (lexpr && this.isComplexExpression(lexpr)) {
477
- leftExpr = this.formatter.parens(leftExpr);
687
+ leftExpr = context.parens(leftExpr);
478
688
  }
479
689
  if (rexpr && this.isComplexExpression(rexpr)) {
480
- rightExpr = this.formatter.parens(rightExpr);
690
+ rightExpr = context.parens(rightExpr);
481
691
  }
482
- return this.formatter.format([
692
+ return context.format([
483
693
  leftExpr,
484
694
  'IS DISTINCT FROM',
485
695
  rightExpr
@@ -490,75 +700,75 @@ export class Deparser {
490
700
  let rightExpr = this.visit(rexpr, context);
491
701
  // Add parentheses for complex expressions
492
702
  if (lexpr && this.isComplexExpression(lexpr)) {
493
- leftExpr = this.formatter.parens(leftExpr);
703
+ leftExpr = context.parens(leftExpr);
494
704
  }
495
705
  if (rexpr && this.isComplexExpression(rexpr)) {
496
- rightExpr = this.formatter.parens(rightExpr);
706
+ rightExpr = context.parens(rightExpr);
497
707
  }
498
- return this.formatter.format([
708
+ return context.format([
499
709
  leftExpr,
500
710
  'IS NOT DISTINCT FROM',
501
711
  rightExpr
502
712
  ]);
503
713
  }
504
714
  case 'AEXPR_NULLIF':
505
- return this.formatter.format([
715
+ return context.format([
506
716
  'NULLIF',
507
- this.formatter.parens([
717
+ context.parens([
508
718
  this.visit(lexpr, context),
509
719
  this.visit(rexpr, context)
510
720
  ].join(', '))
511
721
  ]);
512
722
  case 'AEXPR_IN':
513
- const inOperator = this.deparseOperatorName(name);
723
+ const inOperator = this.deparseOperatorName(name, context);
514
724
  if (inOperator === '<>' || inOperator === '!=') {
515
- return this.formatter.format([
725
+ return context.format([
516
726
  this.visit(lexpr, context),
517
727
  'NOT IN',
518
- this.formatter.parens(this.visit(rexpr, context))
728
+ context.parens(this.visit(rexpr, context))
519
729
  ]);
520
730
  }
521
731
  else {
522
- return this.formatter.format([
732
+ return context.format([
523
733
  this.visit(lexpr, context),
524
734
  'IN',
525
- this.formatter.parens(this.visit(rexpr, context))
735
+ context.parens(this.visit(rexpr, context))
526
736
  ]);
527
737
  }
528
738
  case 'AEXPR_LIKE':
529
- const likeOp = this.deparseOperatorName(name);
739
+ const likeOp = this.deparseOperatorName(name, context);
530
740
  if (likeOp === '!~~') {
531
- return this.formatter.format([
741
+ return context.format([
532
742
  this.visit(lexpr, context),
533
743
  'NOT LIKE',
534
744
  this.visit(rexpr, context)
535
745
  ]);
536
746
  }
537
747
  else {
538
- return this.formatter.format([
748
+ return context.format([
539
749
  this.visit(lexpr, context),
540
750
  'LIKE',
541
751
  this.visit(rexpr, context)
542
752
  ]);
543
753
  }
544
754
  case 'AEXPR_ILIKE':
545
- const ilikeOp = this.deparseOperatorName(name);
755
+ const ilikeOp = this.deparseOperatorName(name, context);
546
756
  if (ilikeOp === '!~~*') {
547
- return this.formatter.format([
757
+ return context.format([
548
758
  this.visit(lexpr, context),
549
759
  'NOT ILIKE',
550
760
  this.visit(rexpr, context)
551
761
  ]);
552
762
  }
553
763
  else {
554
- return this.formatter.format([
764
+ return context.format([
555
765
  this.visit(lexpr, context),
556
766
  'ILIKE',
557
767
  this.visit(rexpr, context)
558
768
  ]);
559
769
  }
560
770
  case 'AEXPR_SIMILAR':
561
- const similarOp = this.deparseOperatorName(name);
771
+ const similarOp = this.deparseOperatorName(name, context);
562
772
  let rightExpr;
563
773
  if (rexpr && 'FuncCall' in rexpr &&
564
774
  rexpr.FuncCall?.funcname?.length === 2 &&
@@ -574,39 +784,39 @@ export class Deparser {
574
784
  rightExpr = this.visit(rexpr, context);
575
785
  }
576
786
  if (similarOp === '!~') {
577
- return this.formatter.format([
787
+ return context.format([
578
788
  this.visit(lexpr, context),
579
789
  'NOT SIMILAR TO',
580
790
  rightExpr
581
791
  ]);
582
792
  }
583
793
  else {
584
- return this.formatter.format([
794
+ return context.format([
585
795
  this.visit(lexpr, context),
586
796
  'SIMILAR TO',
587
797
  rightExpr
588
798
  ]);
589
799
  }
590
800
  case 'AEXPR_BETWEEN':
591
- return this.formatter.format([
801
+ return context.format([
592
802
  this.visit(lexpr, context),
593
803
  'BETWEEN',
594
804
  this.visitBetweenRange(rexpr, context)
595
805
  ]);
596
806
  case 'AEXPR_NOT_BETWEEN':
597
- return this.formatter.format([
807
+ return context.format([
598
808
  this.visit(lexpr, context),
599
809
  'NOT BETWEEN',
600
810
  this.visitBetweenRange(rexpr, context)
601
811
  ]);
602
812
  case 'AEXPR_BETWEEN_SYM':
603
- return this.formatter.format([
813
+ return context.format([
604
814
  this.visit(lexpr, context),
605
815
  'BETWEEN SYMMETRIC',
606
816
  this.visitBetweenRange(rexpr, context)
607
817
  ]);
608
818
  case 'AEXPR_NOT_BETWEEN_SYM':
609
- return this.formatter.format([
819
+ return context.format([
610
820
  this.visit(lexpr, context),
611
821
  'NOT BETWEEN SYMMETRIC',
612
822
  this.visitBetweenRange(rexpr, context)
@@ -614,7 +824,7 @@ export class Deparser {
614
824
  }
615
825
  throw new Error(`Unhandled A_Expr kind: ${kind}`);
616
826
  }
617
- deparseOperatorName(name) {
827
+ deparseOperatorName(name, context) {
618
828
  if (!name || name.length === 0) {
619
829
  return '';
620
830
  }
@@ -622,7 +832,7 @@ export class Deparser {
622
832
  if (n.String) {
623
833
  return n.String.sval || n.String.str;
624
834
  }
625
- return this.visit(n, { parentNodeTypes: [] });
835
+ return this.visit(n, context);
626
836
  });
627
837
  if (parts.length > 1) {
628
838
  return `OPERATOR(${parts.join('.')})`;
@@ -685,6 +895,64 @@ export class Deparser {
685
895
  node.SubLink ||
686
896
  node.A_Expr);
687
897
  }
898
+ isComplexSelectTarget(node) {
899
+ if (!node)
900
+ return false;
901
+ if (node.ResTarget?.val) {
902
+ return this.isComplexExpression(node.ResTarget.val);
903
+ }
904
+ // Always complex: CASE expressions
905
+ if (node.CaseExpr)
906
+ return true;
907
+ // Always complex: Subqueries and subselects
908
+ if (node.SubLink)
909
+ return true;
910
+ // Always complex: Boolean tests and expressions
911
+ if (node.NullTest || node.BooleanTest || node.BoolExpr)
912
+ return true;
913
+ // COALESCE and similar functions - complex if multiple arguments
914
+ if (node.CoalesceExpr) {
915
+ const args = node.CoalesceExpr.args;
916
+ if (args && Array.isArray(args) && args.length > 1)
917
+ return true;
918
+ }
919
+ // Function calls - complex if multiple args or has clauses
920
+ if (node.FuncCall) {
921
+ const funcCall = node.FuncCall;
922
+ const args = funcCall.args ? (Array.isArray(funcCall.args) ? funcCall.args : [funcCall.args]) : [];
923
+ // Complex if has window clause, filter, order by, etc.
924
+ if (funcCall.over || funcCall.agg_filter || funcCall.agg_order || funcCall.agg_distinct) {
925
+ return true;
926
+ }
927
+ // Complex if multiple arguments
928
+ if (args.length > 1)
929
+ return true;
930
+ if (args.length === 1) {
931
+ return this.isComplexSelectTarget(args[0]);
932
+ }
933
+ }
934
+ if (node.A_Expr) {
935
+ const expr = node.A_Expr;
936
+ // Check if operands are complex
937
+ if (expr.lexpr && this.isComplexSelectTarget(expr.lexpr))
938
+ return true;
939
+ if (expr.rexpr && this.isComplexSelectTarget(expr.rexpr))
940
+ return true;
941
+ return false;
942
+ }
943
+ if (node.TypeCast) {
944
+ return this.isComplexSelectTarget(node.TypeCast.arg);
945
+ }
946
+ if (node.A_ArrayExpr)
947
+ return true;
948
+ if (node.A_Indirection) {
949
+ return this.isComplexSelectTarget(node.A_Indirection.arg);
950
+ }
951
+ if (node.A_Const || node.ColumnRef || node.ParamRef || node.A_Star) {
952
+ return false;
953
+ }
954
+ return false;
955
+ }
688
956
  visitBetweenRange(rexpr, context) {
689
957
  if (rexpr && 'List' in rexpr && rexpr.List?.items) {
690
958
  const items = rexpr.List.items.map((item) => this.visit(item, context));
@@ -701,9 +969,16 @@ export class Deparser {
701
969
  output.push(this.RangeVar(node.relation, context));
702
970
  if (node.cols) {
703
971
  const cols = ListUtils.unwrapList(node.cols);
704
- const insertContext = { ...context, insertColumns: true };
972
+ const insertContext = context.spawn('InsertStmt', { insertColumns: true });
705
973
  const columnNames = cols.map(col => this.visit(col, insertContext));
706
- output.push(this.formatter.parens(columnNames.join(', ')));
974
+ if (context.isPretty()) {
975
+ // Always format columns in multiline parentheses for pretty printing
976
+ const indentedColumns = columnNames.map(col => context.indent(col));
977
+ output.push('(\n' + indentedColumns.join(',\n') + '\n)');
978
+ }
979
+ else {
980
+ output.push(context.parens(columnNames.join(', ')));
981
+ }
707
982
  }
708
983
  if (node.selectStmt) {
709
984
  output.push(this.visit(node.selectStmt, context));
@@ -724,7 +999,7 @@ export class Deparser {
724
999
  else if (infer.indexElems) {
725
1000
  const elems = ListUtils.unwrapList(infer.indexElems);
726
1001
  const indexElems = elems.map(elem => this.visit(elem, context));
727
- output.push(this.formatter.parens(indexElems.join(', ')));
1002
+ output.push(context.parens(indexElems.join(', ')));
728
1003
  }
729
1004
  // Handle WHERE clause for conflict detection
730
1005
  if (infer.whereClause) {
@@ -740,12 +1015,12 @@ export class Deparser {
740
1015
  if (firstTarget.ResTarget?.val?.MultiAssignRef && targetList.every(target => target.ResTarget?.val?.MultiAssignRef)) {
741
1016
  const sortedTargets = targetList.sort((a, b) => a.ResTarget.val.MultiAssignRef.colno - b.ResTarget.val.MultiAssignRef.colno);
742
1017
  const names = sortedTargets.map(target => target.ResTarget.name);
743
- output.push(this.formatter.parens(names.join(', ')));
1018
+ output.push(context.parens(names.join(', ')));
744
1019
  output.push('=');
745
1020
  output.push(this.visit(firstTarget.ResTarget.val.MultiAssignRef.source, context));
746
1021
  }
747
1022
  else {
748
- const updateContext = { ...context, update: true };
1023
+ const updateContext = context.spawn('UpdateStmt', { update: true });
749
1024
  const targets = targetList.map(target => this.visit(target, updateContext));
750
1025
  output.push(targets.join(', '));
751
1026
  }
@@ -799,12 +1074,12 @@ export class Deparser {
799
1074
  }
800
1075
  }
801
1076
  const names = relatedTargets.map(t => t.ResTarget.name);
802
- const multiAssignment = `${this.formatter.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
1077
+ const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
803
1078
  assignmentParts.push(multiAssignment);
804
1079
  }
805
1080
  else {
806
1081
  // Handle regular single-column assignment
807
- assignmentParts.push(this.visit(target, { ...context, update: true }));
1082
+ assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
808
1083
  processedTargets.add(i);
809
1084
  }
810
1085
  }
@@ -896,14 +1171,14 @@ export class Deparser {
896
1171
  }
897
1172
  if (node.ctes && node.ctes.length > 0) {
898
1173
  const ctes = ListUtils.unwrapList(node.ctes);
899
- if (this.formatter.isPretty()) {
1174
+ if (context.isPretty()) {
900
1175
  const cteStrings = ctes.map((cte, index) => {
901
1176
  const cteStr = this.visit(cte, context);
902
- const prefix = index === 0 ? this.formatter.newline() : ',' + this.formatter.newline();
1177
+ const prefix = index === 0 ? context.newline() : ',' + context.newline();
903
1178
  if (this.containsMultilineStringLiteral(cteStr)) {
904
1179
  return prefix + cteStr;
905
1180
  }
906
- return prefix + this.formatter.indent(cteStr);
1181
+ return prefix + context.indent(cteStr);
907
1182
  });
908
1183
  output.push(cteStrings.join(''));
909
1184
  }
@@ -989,14 +1264,14 @@ export class Deparser {
989
1264
  if (context.bool) {
990
1265
  formatStr = '(%s)';
991
1266
  }
992
- const boolContext = { ...context, bool: true };
1267
+ const boolContext = context.spawn('BoolExpr', { bool: true });
993
1268
  // explanation of our syntax/fix below:
994
1269
  // return formatStr.replace('%s', andArgs); // ❌ Interprets $ as special syntax
995
1270
  // return formatStr.replace('%s', () => andArgs); // ✅ Function callback prevents interpretation
996
1271
  switch (boolop) {
997
1272
  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 ');
1273
+ if (context.isPretty() && args.length > 1) {
1274
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('AND '));
1000
1275
  return formatStr.replace('%s', () => andArgs);
1001
1276
  }
1002
1277
  else {
@@ -1004,8 +1279,8 @@ export class Deparser {
1004
1279
  return formatStr.replace('%s', () => andArgs);
1005
1280
  }
1006
1281
  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 ');
1282
+ if (context.isPretty() && args.length > 1) {
1283
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('OR '));
1009
1284
  return formatStr.replace('%s', () => orArgs);
1010
1285
  }
1011
1286
  else {
@@ -1136,9 +1411,9 @@ export class Deparser {
1136
1411
  const timezone = this.visit(args[0], context);
1137
1412
  // Add parentheses around timestamp if it contains arithmetic operations
1138
1413
  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));
1414
+ const op = this.deparseOperatorName(ListUtils.unwrapList(args[1].A_Expr.name), context);
1140
1415
  if (op === '+' || op === '-' || op === '*' || op === '/') {
1141
- timestamp = this.formatter.parens(timestamp);
1416
+ timestamp = context.parens(timestamp);
1142
1417
  }
1143
1418
  }
1144
1419
  return `${timestamp} AT TIME ZONE ${timezone}`;
@@ -1208,14 +1483,14 @@ export class Deparser {
1208
1483
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1209
1484
  }
1210
1485
  // Handle window frame specifications using the dedicated formatWindowFrame method
1211
- const frameClause = this.formatWindowFrame(node.over);
1486
+ const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1212
1487
  if (frameClause) {
1213
1488
  windowParts.push(frameClause);
1214
1489
  }
1215
1490
  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()})`;
1491
+ if (context.isPretty() && windowParts.length > 1) {
1492
+ const formattedParts = windowParts.map(part => context.indent(part));
1493
+ result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1219
1494
  }
1220
1495
  else {
1221
1496
  result += ` OVER (${windowParts.join(' ')})`;
@@ -1495,9 +1770,6 @@ export class Deparser {
1495
1770
  return output.join(' ');
1496
1771
  }
1497
1772
  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
1773
  let typeName = `${catalog}.${type}`;
1502
1774
  if (type === 'bpchar' && args) {
1503
1775
  typeName = 'char';
@@ -1554,6 +1826,24 @@ export class Deparser {
1554
1826
  typeName = 'time with time zone';
1555
1827
  }
1556
1828
  }
1829
+ else if (type === 'timestamp') {
1830
+ if (args) {
1831
+ typeName = `timestamp(${args})`;
1832
+ args = null; // Don't apply args again in mods()
1833
+ }
1834
+ else {
1835
+ typeName = 'timestamp';
1836
+ }
1837
+ }
1838
+ else if (type === 'time') {
1839
+ if (args) {
1840
+ typeName = `time(${args})`;
1841
+ args = null; // Don't apply args again in mods()
1842
+ }
1843
+ else {
1844
+ typeName = 'time';
1845
+ }
1846
+ }
1557
1847
  let result = mods(typeName, args);
1558
1848
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1559
1849
  result += formatArrayBounds(node.arrayBounds);
@@ -1583,7 +1873,7 @@ export class Deparser {
1583
1873
  }
1584
1874
  return this.quoteIfNeeded(colStr);
1585
1875
  });
1586
- output.push('AS', this.quoteIfNeeded(name) + this.formatter.parens(quotedColnames.join(', ')));
1876
+ output.push('AS', this.quoteIfNeeded(name) + context.parens(quotedColnames.join(', ')));
1587
1877
  }
1588
1878
  else {
1589
1879
  output.push('AS', this.quoteIfNeeded(name));
@@ -1595,7 +1885,7 @@ export class Deparser {
1595
1885
  // Handle ONLY keyword for inheritance control (but not for type definitions, ALTER TYPE, or CREATE FOREIGN TABLE)
1596
1886
  if (node && (!('inh' in node) || node.inh === undefined) &&
1597
1887
  !context.parentNodeTypes.includes('CompositeTypeStmt') &&
1598
- !context.parentNodeTypes.includes('AlterTypeStmt') &&
1888
+ (!context.parentNodeTypes.includes('AlterTypeStmt') && context.objtype !== 'OBJECT_TYPE') &&
1599
1889
  !context.parentNodeTypes.includes('CreateForeignTableStmt')) {
1600
1890
  output.push('ONLY');
1601
1891
  }
@@ -1792,6 +2082,18 @@ export class Deparser {
1792
2082
  return `pg_catalog.${typeName}`;
1793
2083
  }
1794
2084
  }
2085
+ isPgCatalogType(typeName) {
2086
+ const cleanTypeName = typeName.replace(/^pg_catalog\./, '');
2087
+ if (pgCatalogTypes.includes(cleanTypeName)) {
2088
+ return true;
2089
+ }
2090
+ for (const [realType, aliases] of pgCatalogTypeAliases) {
2091
+ if (aliases.includes(cleanTypeName)) {
2092
+ return true;
2093
+ }
2094
+ }
2095
+ return false;
2096
+ }
1795
2097
  A_ArrayExpr(node, context) {
1796
2098
  const elements = ListUtils.unwrapList(node.elements);
1797
2099
  const elementStrs = elements.map(el => this.visit(el, context));
@@ -1843,26 +2145,26 @@ export class Deparser {
1843
2145
  output.push(this.visit(node.arg, context));
1844
2146
  }
1845
2147
  const args = ListUtils.unwrapList(node.args);
1846
- if (this.formatter.isPretty() && args.length > 0) {
2148
+ if (context.isPretty() && args.length > 0) {
1847
2149
  for (const arg of args) {
1848
2150
  const whenClause = this.visit(arg, context);
1849
2151
  if (this.containsMultilineStringLiteral(whenClause)) {
1850
- output.push(this.formatter.newline() + whenClause);
2152
+ output.push(context.newline() + whenClause);
1851
2153
  }
1852
2154
  else {
1853
- output.push(this.formatter.newline() + this.formatter.indent(whenClause));
2155
+ output.push(context.newline() + context.indent(whenClause));
1854
2156
  }
1855
2157
  }
1856
2158
  if (node.defresult) {
1857
2159
  const elseResult = this.visit(node.defresult, context);
1858
2160
  if (this.containsMultilineStringLiteral(elseResult)) {
1859
- output.push(this.formatter.newline() + 'ELSE ' + elseResult);
2161
+ output.push(context.newline() + 'ELSE ' + elseResult);
1860
2162
  }
1861
2163
  else {
1862
- output.push(this.formatter.newline() + this.formatter.indent('ELSE ' + elseResult));
2164
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
1863
2165
  }
1864
2166
  }
1865
- output.push(this.formatter.newline() + 'END');
2167
+ output.push(context.newline() + 'END');
1866
2168
  return output.join(' ');
1867
2169
  }
1868
2170
  else {
@@ -1885,28 +2187,32 @@ export class Deparser {
1885
2187
  TypeCast(node, context) {
1886
2188
  const arg = this.visit(node.arg, context);
1887
2189
  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}`;
2190
+ // Check if this is a bpchar typecast that should preserve original syntax for AST consistency
2191
+ if (typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
2192
+ const names = node.typeName?.names;
2193
+ const isQualifiedBpchar = names && names.length === 2 &&
2194
+ names[0]?.String?.sval === 'pg_catalog' &&
2195
+ names[1]?.String?.sval === 'bpchar';
2196
+ if (isQualifiedBpchar) {
2197
+ return `CAST(${arg} AS ${typeName})`;
1895
2198
  }
1896
2199
  }
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}`;
2200
+ if (this.isPgCatalogType(typeName)) {
2201
+ const argType = this.getNodeType(node.arg);
2202
+ const isSimpleArgument = argType === 'A_Const' || argType === 'ColumnRef';
2203
+ const isFunctionCall = argType === 'FuncCall';
2204
+ if (isSimpleArgument || isFunctionCall) {
2205
+ // For simple arguments, avoid :: syntax if they have complex structure
2206
+ const shouldUseCastSyntax = isSimpleArgument && (arg.includes('(') || arg.startsWith('-'));
2207
+ if (!shouldUseCastSyntax) {
2208
+ const cleanTypeName = typeName.replace('pg_catalog.', '');
2209
+ // Wrap FuncCall arguments in parentheses to prevent operator precedence issues
2210
+ if (isFunctionCall) {
2211
+ return `${context.parens(arg)}::${cleanTypeName}`;
2212
+ }
2213
+ return `${arg}::${cleanTypeName}`;
2214
+ }
2215
+ }
1910
2216
  }
1911
2217
  return `CAST(${arg} AS ${typeName})`;
1912
2218
  }
@@ -1929,7 +2235,7 @@ export class Deparser {
1929
2235
  }
1930
2236
  BooleanTest(node, context) {
1931
2237
  const output = [];
1932
- const boolContext = { ...context, bool: true };
2238
+ const boolContext = context.spawn('BooleanTest', { bool: true });
1933
2239
  output.push(this.visit(node.arg, boolContext));
1934
2240
  switch (node.booltesttype) {
1935
2241
  case 'IS_TRUE':
@@ -2080,24 +2386,31 @@ export class Deparser {
2080
2386
  const elementStrs = elements.map(el => {
2081
2387
  return this.deparse(el, context);
2082
2388
  });
2083
- output.push(this.formatter.parens(elementStrs.join(', ')));
2389
+ output.push(context.parens(elementStrs.join(', ')));
2084
2390
  }
2085
2391
  }
2086
2392
  else if (node.tableElts) {
2087
2393
  const elements = ListUtils.unwrapList(node.tableElts);
2088
2394
  const elementStrs = elements.map(el => {
2089
- return this.deparse(el, context);
2395
+ return this.deparse(el, context.spawn('CreateStmt'));
2090
2396
  });
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() + ')');
2397
+ if (context.isPretty()) {
2398
+ const formattedElements = elementStrs.map(el => {
2399
+ const trimmedEl = el.trim();
2400
+ // Remove leading newlines from constraint elements to avoid extra blank lines
2401
+ if (trimmedEl.startsWith('\n')) {
2402
+ return context.indent(trimmedEl.substring(1));
2403
+ }
2404
+ return context.indent(trimmedEl);
2405
+ }).join(',' + context.newline());
2406
+ output.push('(' + context.newline() + formattedElements + context.newline() + ')');
2094
2407
  }
2095
2408
  else {
2096
- output.push(this.formatter.parens(elementStrs.join(', ')));
2409
+ output.push(context.parens(elementStrs.join(', ')));
2097
2410
  }
2098
2411
  }
2099
2412
  else if (!node.partbound) {
2100
- output.push(this.formatter.parens(''));
2413
+ output.push(context.parens(''));
2101
2414
  }
2102
2415
  if (node.partbound && node.inhRelations && node.inhRelations.length > 0) {
2103
2416
  output.push('PARTITION OF');
@@ -2140,7 +2453,7 @@ export class Deparser {
2140
2453
  output.push('INHERITS');
2141
2454
  const inherits = ListUtils.unwrapList(node.inhRelations);
2142
2455
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2143
- output.push(this.formatter.parens(inheritStrs.join(', ')));
2456
+ output.push(context.parens(inheritStrs.join(', ')));
2144
2457
  }
2145
2458
  if (node.partspec) {
2146
2459
  output.push('PARTITION BY');
@@ -2178,7 +2491,7 @@ export class Deparser {
2178
2491
  }
2179
2492
  // Handle table options like WITH (fillfactor=10)
2180
2493
  if (node.options && node.options.length > 0) {
2181
- const createStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateStmt'] };
2494
+ const createStmtContext = context.spawn('CreateStmt');
2182
2495
  const optionStrs = node.options.map((option) => {
2183
2496
  return this.deparse(option, createStmtContext);
2184
2497
  });
@@ -2201,7 +2514,7 @@ export class Deparser {
2201
2514
  }
2202
2515
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2203
2516
  output.push('OPTIONS');
2204
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
2517
+ const columnContext = context.spawn('ColumnDef');
2205
2518
  const options = ListUtils.unwrapList(node.fdwoptions).map(opt => this.visit(opt, columnContext));
2206
2519
  output.push(`(${options.join(', ')})`);
2207
2520
  }
@@ -2211,7 +2524,7 @@ export class Deparser {
2211
2524
  if (node.constraints) {
2212
2525
  const constraints = ListUtils.unwrapList(node.constraints);
2213
2526
  const constraintStrs = constraints.map(constraint => {
2214
- const columnConstraintContext = { ...context, isColumnConstraint: true };
2527
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
2215
2528
  return this.visit(constraint, columnConstraintContext);
2216
2529
  });
2217
2530
  output.push(...constraintStrs);
@@ -2251,9 +2564,25 @@ export class Deparser {
2251
2564
  }
2252
2565
  break;
2253
2566
  case 'CONSTR_CHECK':
2254
- output.push('CHECK');
2567
+ if (context.isPretty() && !context.isColumnConstraint) {
2568
+ output.push('\n' + context.indent('CHECK'));
2569
+ }
2570
+ else {
2571
+ output.push('CHECK');
2572
+ }
2255
2573
  if (node.raw_expr) {
2256
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2574
+ if (context.isPretty()) {
2575
+ const checkExpr = this.visit(node.raw_expr, context);
2576
+ if (checkExpr.includes('\n')) {
2577
+ output.push('(\n' + context.indent(checkExpr) + '\n)');
2578
+ }
2579
+ else {
2580
+ output.push(`(${checkExpr})`);
2581
+ }
2582
+ }
2583
+ else {
2584
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2585
+ }
2257
2586
  }
2258
2587
  // Handle NOT VALID for check constraints
2259
2588
  if (node.skip_validation) {
@@ -2274,7 +2603,7 @@ export class Deparser {
2274
2603
  }
2275
2604
  output.push('AS');
2276
2605
  if (node.raw_expr) {
2277
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2606
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2278
2607
  }
2279
2608
  output.push('STORED');
2280
2609
  break;
@@ -2292,30 +2621,61 @@ export class Deparser {
2292
2621
  .map(option => {
2293
2622
  if (option.DefElem) {
2294
2623
  const defElem = option.DefElem;
2295
- const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2296
- if (defElem.defname === 'start') {
2624
+ if (defElem.defname === 'sequence_name') {
2625
+ if (defElem.arg && defElem.arg.List) {
2626
+ const nameList = ListUtils.unwrapList(defElem.arg)
2627
+ .map(item => this.visit(item, context))
2628
+ .join('.');
2629
+ return `SEQUENCE NAME ${nameList}`;
2630
+ }
2631
+ return 'SEQUENCE NAME';
2632
+ }
2633
+ else if (defElem.defname === 'start') {
2634
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2297
2635
  return `START WITH ${argValue}`;
2298
2636
  }
2299
2637
  else if (defElem.defname === 'increment') {
2638
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2300
2639
  return `INCREMENT BY ${argValue}`;
2301
2640
  }
2302
2641
  else if (defElem.defname === 'minvalue') {
2303
- return `MINVALUE ${argValue}`;
2642
+ if (defElem.arg) {
2643
+ const argValue = this.visit(defElem.arg, context);
2644
+ return `MINVALUE ${argValue}`;
2645
+ }
2646
+ else {
2647
+ return 'NO MINVALUE';
2648
+ }
2304
2649
  }
2305
2650
  else if (defElem.defname === 'maxvalue') {
2306
- return `MAXVALUE ${argValue}`;
2651
+ if (defElem.arg) {
2652
+ const argValue = this.visit(defElem.arg, context);
2653
+ return `MAXVALUE ${argValue}`;
2654
+ }
2655
+ else {
2656
+ return 'NO MAXVALUE';
2657
+ }
2307
2658
  }
2308
2659
  else if (defElem.defname === 'cache') {
2660
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2309
2661
  return `CACHE ${argValue}`;
2310
2662
  }
2311
2663
  else if (defElem.defname === 'cycle') {
2664
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2312
2665
  return argValue === 'true' ? 'CYCLE' : 'NO CYCLE';
2313
2666
  }
2667
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2314
2668
  return `${defElem.defname.toUpperCase()} ${argValue}`;
2315
2669
  }
2316
2670
  return this.visit(option, context);
2317
2671
  });
2318
- output.push(`(${optionStrs.join(' ')})`);
2672
+ if (context.isPretty()) {
2673
+ const indentedOptions = optionStrs.map(option => context.indent(option));
2674
+ output.push('(\n' + indentedOptions.join('\n') + '\n)');
2675
+ }
2676
+ else {
2677
+ output.push(`(${optionStrs.join(' ')})`);
2678
+ }
2319
2679
  }
2320
2680
  break;
2321
2681
  case 'CONSTR_PRIMARY':
@@ -2332,7 +2692,12 @@ export class Deparser {
2332
2692
  }
2333
2693
  break;
2334
2694
  case 'CONSTR_UNIQUE':
2335
- output.push('UNIQUE');
2695
+ if (context.isPretty() && !context.isColumnConstraint) {
2696
+ output.push('\n' + context.indent('UNIQUE'));
2697
+ }
2698
+ else {
2699
+ output.push('UNIQUE');
2700
+ }
2336
2701
  if (node.nulls_not_distinct) {
2337
2702
  output.push('NULLS NOT DISTINCT');
2338
2703
  }
@@ -2350,33 +2715,77 @@ export class Deparser {
2350
2715
  case 'CONSTR_FOREIGN':
2351
2716
  // Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
2352
2717
  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})`);
2718
+ if (context.isPretty()) {
2719
+ output.push('\n' + context.indent('FOREIGN KEY'));
2720
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2721
+ const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2722
+ .map(attr => this.visit(attr, context))
2723
+ .join(', ');
2724
+ output.push(`(${fkAttrs})`);
2725
+ }
2726
+ output.push('\n' + context.indent('REFERENCES'));
2359
2727
  }
2728
+ else {
2729
+ output.push('FOREIGN KEY');
2730
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2731
+ const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2732
+ .map(attr => this.visit(attr, context))
2733
+ .join(', ');
2734
+ output.push(`(${fkAttrs})`);
2735
+ }
2736
+ output.push('REFERENCES');
2737
+ }
2738
+ }
2739
+ else {
2740
+ output.push('REFERENCES');
2360
2741
  }
2361
- output.push('REFERENCES');
2362
2742
  if (node.pktable) {
2363
- output.push(this.RangeVar(node.pktable, context));
2743
+ if (context.isPretty() && !context.isColumnConstraint) {
2744
+ const lastIndex = output.length - 1;
2745
+ if (lastIndex >= 0 && output[lastIndex].includes('REFERENCES')) {
2746
+ output[lastIndex] += ' ' + this.RangeVar(node.pktable, context);
2747
+ }
2748
+ else {
2749
+ output.push(this.RangeVar(node.pktable, context));
2750
+ }
2751
+ }
2752
+ else {
2753
+ output.push(this.RangeVar(node.pktable, context));
2754
+ }
2364
2755
  }
2365
2756
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2366
2757
  const pkAttrs = ListUtils.unwrapList(node.pk_attrs)
2367
2758
  .map(attr => this.visit(attr, context))
2368
2759
  .join(', ');
2369
- output.push(`(${pkAttrs})`);
2760
+ if (context.isPretty() && !context.isColumnConstraint) {
2761
+ const lastIndex = output.length - 1;
2762
+ if (lastIndex >= 0) {
2763
+ output[lastIndex] += ` (${pkAttrs})`;
2764
+ }
2765
+ else {
2766
+ output.push(`(${pkAttrs})`);
2767
+ }
2768
+ }
2769
+ else {
2770
+ output.push(`(${pkAttrs})`);
2771
+ }
2370
2772
  }
2371
2773
  if (node.fk_matchtype && node.fk_matchtype !== 's') {
2774
+ let matchClause = '';
2372
2775
  switch (node.fk_matchtype) {
2373
2776
  case 'f':
2374
- output.push('MATCH FULL');
2777
+ matchClause = 'MATCH FULL';
2375
2778
  break;
2376
2779
  case 'p':
2377
- output.push('MATCH PARTIAL');
2780
+ matchClause = 'MATCH PARTIAL';
2378
2781
  break;
2379
2782
  }
2783
+ if (context.isPretty() && !context.isColumnConstraint) {
2784
+ output.push('\n' + context.indent(matchClause));
2785
+ }
2786
+ else {
2787
+ output.push(matchClause);
2788
+ }
2380
2789
  }
2381
2790
  if (node.fk_upd_action && node.fk_upd_action !== 'a') {
2382
2791
  let updateClause = 'ON UPDATE ';
@@ -2394,8 +2803,8 @@ export class Deparser {
2394
2803
  updateClause += 'SET DEFAULT';
2395
2804
  break;
2396
2805
  }
2397
- if (this.formatter.isPretty()) {
2398
- output.push('\n' + this.formatter.indent(updateClause));
2806
+ if (context.isPretty()) {
2807
+ output.push('\n' + context.indent(updateClause));
2399
2808
  }
2400
2809
  else {
2401
2810
  output.push('ON UPDATE');
@@ -2418,8 +2827,8 @@ export class Deparser {
2418
2827
  deleteClause += 'SET DEFAULT';
2419
2828
  break;
2420
2829
  }
2421
- if (this.formatter.isPretty()) {
2422
- output.push('\n' + this.formatter.indent(deleteClause));
2830
+ if (context.isPretty()) {
2831
+ output.push('\n' + context.indent(deleteClause));
2423
2832
  }
2424
2833
  else {
2425
2834
  output.push('ON DELETE');
@@ -2428,7 +2837,12 @@ export class Deparser {
2428
2837
  }
2429
2838
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2430
2839
  if (node.skip_validation && !context.isDomainConstraint) {
2431
- output.push('NOT VALID');
2840
+ if (context.isPretty() && !context.isColumnConstraint) {
2841
+ output.push('\n' + context.indent('NOT VALID'));
2842
+ }
2843
+ else {
2844
+ output.push('NOT VALID');
2845
+ }
2432
2846
  }
2433
2847
  break;
2434
2848
  case 'CONSTR_ATTR_DEFERRABLE':
@@ -2482,13 +2896,13 @@ export class Deparser {
2482
2896
  // Handle deferrable constraints for all constraint types that support it
2483
2897
  if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2484
2898
  if (node.deferrable) {
2485
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2486
- output.push('\n' + this.formatter.indent('DEFERRABLE'));
2899
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2900
+ output.push('\n' + context.indent('DEFERRABLE'));
2487
2901
  if (node.initdeferred === true) {
2488
- output.push('\n' + this.formatter.indent('INITIALLY DEFERRED'));
2902
+ output.push('\n' + context.indent('INITIALLY DEFERRED'));
2489
2903
  }
2490
2904
  else if (node.initdeferred === false) {
2491
- output.push('\n' + this.formatter.indent('INITIALLY IMMEDIATE'));
2905
+ output.push('\n' + context.indent('INITIALLY IMMEDIATE'));
2492
2906
  }
2493
2907
  }
2494
2908
  else {
@@ -2502,15 +2916,15 @@ export class Deparser {
2502
2916
  }
2503
2917
  }
2504
2918
  else if (node.deferrable === false) {
2505
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2506
- output.push('\n' + this.formatter.indent('NOT DEFERRABLE'));
2919
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2920
+ output.push('\n' + context.indent('NOT DEFERRABLE'));
2507
2921
  }
2508
2922
  else {
2509
2923
  output.push('NOT DEFERRABLE');
2510
2924
  }
2511
2925
  }
2512
2926
  }
2513
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2927
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2514
2928
  let result = '';
2515
2929
  for (let i = 0; i < output.length; i++) {
2516
2930
  if (output[i].startsWith('\n')) {
@@ -2528,12 +2942,12 @@ export class Deparser {
2528
2942
  return output.join(' ');
2529
2943
  }
2530
2944
  SubLink(node, context) {
2531
- const subselect = this.formatter.parens(this.visit(node.subselect, context));
2945
+ const subselect = context.parens(this.visit(node.subselect, context));
2532
2946
  switch (node.subLinkType) {
2533
2947
  case 'ANY_SUBLINK':
2534
2948
  if (node.testexpr && node.operName) {
2535
2949
  const testExpr = this.visit(node.testexpr, context);
2536
- const operator = this.deparseOperatorName(node.operName);
2950
+ const operator = this.deparseOperatorName(node.operName, context);
2537
2951
  return `${testExpr} ${operator} ANY ${subselect}`;
2538
2952
  }
2539
2953
  else if (node.testexpr) {
@@ -2544,7 +2958,7 @@ export class Deparser {
2544
2958
  case 'ALL_SUBLINK':
2545
2959
  if (node.testexpr && node.operName) {
2546
2960
  const testExpr = this.visit(node.testexpr, context);
2547
- const operator = this.deparseOperatorName(node.operName);
2961
+ const operator = this.deparseOperatorName(node.operName, context);
2548
2962
  return `${testExpr} ${operator} ALL ${subselect}`;
2549
2963
  }
2550
2964
  return subselect;
@@ -2586,7 +3000,7 @@ export class Deparser {
2586
3000
  }
2587
3001
  // Only add frame clause if frameOptions indicates non-default framing
2588
3002
  if (node.frameOptions && node.frameOptions !== 1058) {
2589
- const frameClause = this.formatWindowFrame(node);
3003
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2590
3004
  if (frameClause) {
2591
3005
  windowParts.push(frameClause);
2592
3006
  }
@@ -2606,7 +3020,7 @@ export class Deparser {
2606
3020
  }
2607
3021
  return output.join(' ');
2608
3022
  }
2609
- formatWindowFrame(node) {
3023
+ formatWindowFrame(node, context) {
2610
3024
  if (!node.frameOptions)
2611
3025
  return null;
2612
3026
  const frameOptions = node.frameOptions;
@@ -2615,7 +3029,7 @@ export class Deparser {
2615
3029
  if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
2616
3030
  frameParts.push('RANGE');
2617
3031
  }
2618
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
3032
+ else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2619
3033
  frameParts.push('ROWS');
2620
3034
  }
2621
3035
  else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
@@ -2636,8 +3050,8 @@ export class Deparser {
2636
3050
  }
2637
3051
  else if (frameOptions === 18453) {
2638
3052
  if (node.startOffset && node.endOffset) {
2639
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2640
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
3053
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
3054
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2641
3055
  }
2642
3056
  }
2643
3057
  else if (frameOptions === 1557) {
@@ -2647,7 +3061,7 @@ export class Deparser {
2647
3061
  else if (frameOptions === 16917) {
2648
3062
  boundsParts.push('CURRENT ROW');
2649
3063
  if (node.endOffset) {
2650
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
3064
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2651
3065
  }
2652
3066
  }
2653
3067
  else if (frameOptions === 1058) {
@@ -2657,13 +3071,13 @@ export class Deparser {
2657
3071
  // Handle start bound - prioritize explicit offset values over bit flags
2658
3072
  if (node.startOffset) {
2659
3073
  if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
2660
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
3074
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2661
3075
  }
2662
3076
  else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
2663
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} FOLLOWING`);
3077
+ boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
2664
3078
  }
2665
3079
  else {
2666
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
3080
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2667
3081
  }
2668
3082
  }
2669
3083
  else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
@@ -2676,13 +3090,13 @@ export class Deparser {
2676
3090
  if (node.endOffset) {
2677
3091
  if (boundsParts.length > 0) {
2678
3092
  if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
2679
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} PRECEDING`);
3093
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
2680
3094
  }
2681
3095
  else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
2682
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
3096
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2683
3097
  }
2684
3098
  else {
2685
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
3099
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2686
3100
  }
2687
3101
  }
2688
3102
  }
@@ -2767,7 +3181,7 @@ export class Deparser {
2767
3181
  const colnames = ListUtils.unwrapList(node.aliascolnames);
2768
3182
  const colnameStrs = colnames.map(col => this.visit(col, context));
2769
3183
  // Don't add space before column list parentheses to match original formatting
2770
- output[output.length - 1] += this.formatter.parens(colnameStrs.join(', '));
3184
+ output[output.length - 1] += context.parens(colnameStrs.join(', '));
2771
3185
  }
2772
3186
  output.push('AS');
2773
3187
  // Handle materialization clauses
@@ -2778,7 +3192,7 @@ export class Deparser {
2778
3192
  output.push('MATERIALIZED');
2779
3193
  }
2780
3194
  if (node.ctequery) {
2781
- output.push(this.formatter.parens(this.visit(node.ctequery, context)));
3195
+ output.push(context.parens(this.visit(node.ctequery, context)));
2782
3196
  }
2783
3197
  return output.join(' ');
2784
3198
  }
@@ -2854,7 +3268,7 @@ export class Deparser {
2854
3268
  DistinctExpr(node, context) {
2855
3269
  const args = ListUtils.unwrapList(node.args);
2856
3270
  if (args.length === 2) {
2857
- const literalContext = { ...context, isStringLiteral: true };
3271
+ const literalContext = context.spawn('DistinctExpr', { isStringLiteral: true });
2858
3272
  const left = this.visit(args[0], literalContext);
2859
3273
  const right = this.visit(args[1], literalContext);
2860
3274
  return `${left} IS DISTINCT FROM ${right}`;
@@ -2864,7 +3278,7 @@ export class Deparser {
2864
3278
  NullIfExpr(node, context) {
2865
3279
  const args = ListUtils.unwrapList(node.args);
2866
3280
  if (args.length === 2) {
2867
- const literalContext = { ...context, isStringLiteral: true };
3281
+ const literalContext = context.spawn('NullIfExpr', { isStringLiteral: true });
2868
3282
  const left = this.visit(args[0], literalContext);
2869
3283
  const right = this.visit(args[1], literalContext);
2870
3284
  return `NULLIF(${left}, ${right})`;
@@ -2943,7 +3357,7 @@ export class Deparser {
2943
3357
  }
2944
3358
  RelabelType(node, context) {
2945
3359
  if (node.arg) {
2946
- const literalContext = { ...context, isStringLiteral: true };
3360
+ const literalContext = context.spawn('RelabelType', { isStringLiteral: true });
2947
3361
  return this.visit(node.arg, literalContext);
2948
3362
  }
2949
3363
  return '';
@@ -2962,7 +3376,7 @@ export class Deparser {
2962
3376
  }
2963
3377
  ConvertRowtypeExpr(node, context) {
2964
3378
  if (node.arg) {
2965
- const literalContext = { ...context, isStringLiteral: true };
3379
+ const literalContext = context.spawn('ConvertRowtypeExpr', { isStringLiteral: true });
2966
3380
  return this.visit(node.arg, literalContext);
2967
3381
  }
2968
3382
  return '';
@@ -2993,10 +3407,10 @@ export class Deparser {
2993
3407
  }
2994
3408
  if (node.aliases && node.aliases.length > 0) {
2995
3409
  const aliasStrs = ListUtils.unwrapList(node.aliases).map(alias => this.visit(alias, context));
2996
- output.push(this.formatter.parens(aliasStrs.join(', ')));
3410
+ output.push(context.parens(aliasStrs.join(', ')));
2997
3411
  }
2998
3412
  if (node.options && node.options.length > 0) {
2999
- const viewContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ViewStmt'] };
3413
+ const viewContext = context.spawn('ViewStmt');
3000
3414
  const optionStrs = ListUtils.unwrapList(node.options)
3001
3415
  .map(option => this.visit(option, viewContext));
3002
3416
  output.push(`WITH (${optionStrs.join(', ')})`);
@@ -3043,22 +3457,22 @@ export class Deparser {
3043
3457
  }
3044
3458
  if (node.indexParams && node.indexParams.length > 0) {
3045
3459
  const paramStrs = ListUtils.unwrapList(node.indexParams).map(param => this.visit(param, context));
3046
- output.push(this.formatter.parens(paramStrs.join(', ')));
3460
+ output.push(context.parens(paramStrs.join(', ')));
3047
3461
  }
3048
3462
  if (node.indexIncludingParams && node.indexIncludingParams.length > 0) {
3049
3463
  const includeStrs = ListUtils.unwrapList(node.indexIncludingParams).map(param => this.visit(param, context));
3050
3464
  output.push('INCLUDE');
3051
- output.push(this.formatter.parens(includeStrs.join(', ')));
3465
+ output.push(context.parens(includeStrs.join(', ')));
3052
3466
  }
3053
3467
  if (node.whereClause) {
3054
3468
  output.push('WHERE');
3055
3469
  output.push(this.visit(node.whereClause, context));
3056
3470
  }
3057
3471
  if (node.options && node.options.length > 0) {
3058
- const indexContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'IndexStmt'] };
3472
+ const indexContext = context.spawn('IndexStmt');
3059
3473
  const optionStrs = ListUtils.unwrapList(node.options).map(option => this.visit(option, indexContext));
3060
3474
  output.push('WITH');
3061
- output.push(this.formatter.parens(optionStrs.join(', ')));
3475
+ output.push(context.parens(optionStrs.join(', ')));
3062
3476
  }
3063
3477
  if (node.nulls_not_distinct) {
3064
3478
  output.push('NULLS NOT DISTINCT');
@@ -3075,7 +3489,7 @@ export class Deparser {
3075
3489
  output.push(QuoteUtils.quote(node.name));
3076
3490
  }
3077
3491
  else if (node.expr) {
3078
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3492
+ output.push(context.parens(this.visit(node.expr, context)));
3079
3493
  }
3080
3494
  if (node.collation && node.collation.length > 0) {
3081
3495
  const collationStrs = ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3092,7 +3506,7 @@ export class Deparser {
3092
3506
  const stringData = this.getNodeData(opt.DefElem.arg);
3093
3507
  return `${opt.DefElem.defname}='${stringData.sval}'`;
3094
3508
  }
3095
- return this.visit(opt, context);
3509
+ return this.visit(opt, context.spawn('IndexElem'));
3096
3510
  });
3097
3511
  opclassStr += `(${opclassOpts.join(', ')})`;
3098
3512
  }
@@ -3126,7 +3540,7 @@ export class Deparser {
3126
3540
  output.push(QuoteUtils.quote(node.name));
3127
3541
  }
3128
3542
  else if (node.expr) {
3129
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3543
+ output.push(context.parens(this.visit(node.expr, context)));
3130
3544
  }
3131
3545
  if (node.collation && node.collation.length > 0) {
3132
3546
  const collationStrs = ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3274,16 +3688,16 @@ export class Deparser {
3274
3688
  if (node.rarg && 'JoinExpr' in node.rarg && !node.rarg.JoinExpr.alias) {
3275
3689
  rargStr = `(${rargStr})`;
3276
3690
  }
3277
- if (this.formatter.isPretty()) {
3278
- output.push(this.formatter.newline() + joinStr + ' ' + rargStr);
3691
+ if (context.isPretty()) {
3692
+ output.push(context.newline() + joinStr + ' ' + rargStr);
3279
3693
  }
3280
3694
  else {
3281
3695
  output.push(joinStr + ' ' + rargStr);
3282
3696
  }
3283
3697
  }
3284
3698
  else {
3285
- if (this.formatter.isPretty()) {
3286
- output.push(this.formatter.newline() + joinStr);
3699
+ if (context.isPretty()) {
3700
+ output.push(context.newline() + joinStr);
3287
3701
  }
3288
3702
  else {
3289
3703
  output.push(joinStr);
@@ -3292,7 +3706,7 @@ export class Deparser {
3292
3706
  if (node.usingClause && node.usingClause.length > 0) {
3293
3707
  const usingList = ListUtils.unwrapList(node.usingClause);
3294
3708
  const columnNames = usingList.map(col => this.visit(col, context));
3295
- if (this.formatter.isPretty()) {
3709
+ if (context.isPretty()) {
3296
3710
  output.push(` USING (${columnNames.join(', ')})`);
3297
3711
  }
3298
3712
  else {
@@ -3301,14 +3715,14 @@ export class Deparser {
3301
3715
  }
3302
3716
  else if (node.quals) {
3303
3717
  const qualsStr = this.visit(node.quals, context);
3304
- if (this.formatter.isPretty()) {
3718
+ if (context.isPretty()) {
3305
3719
  // For complex JOIN conditions, format with proper indentation
3306
3720
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3307
3721
  if (this.containsMultilineStringLiteral(qualsStr)) {
3308
3722
  output.push(` ON ${qualsStr}`);
3309
3723
  }
3310
3724
  else {
3311
- output.push(` ON${this.formatter.newline()}${this.formatter.indent(qualsStr)}`);
3725
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3312
3726
  }
3313
3727
  }
3314
3728
  else {
@@ -3320,7 +3734,7 @@ export class Deparser {
3320
3734
  }
3321
3735
  }
3322
3736
  let result;
3323
- if (this.formatter.isPretty()) {
3737
+ if (context.isPretty()) {
3324
3738
  result = output.join('');
3325
3739
  }
3326
3740
  else {
@@ -3427,8 +3841,8 @@ export class Deparser {
3427
3841
  else if (nodeData.sval !== undefined) {
3428
3842
  // Handle nested sval structure: { sval: { sval: "value" } }
3429
3843
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3430
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3431
- boolValue = stringValue === 'on' || stringValue === 'true';
3844
+ const stringValue = svalValue.replace(/'/g, '');
3845
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3432
3846
  }
3433
3847
  }
3434
3848
  return boolValue ? 'READ ONLY' : 'READ WRITE';
@@ -3451,8 +3865,8 @@ export class Deparser {
3451
3865
  else if (nodeData.sval !== undefined) {
3452
3866
  // Handle nested sval structure: { sval: { sval: "value" } }
3453
3867
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3454
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3455
- boolValue = stringValue === 'on' || stringValue === 'true';
3868
+ const stringValue = svalValue.replace(/'/g, '');
3869
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3456
3870
  }
3457
3871
  }
3458
3872
  return boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE';
@@ -3519,8 +3933,8 @@ export class Deparser {
3519
3933
  else if (nodeData.sval !== undefined) {
3520
3934
  // Handle nested sval structure: { sval: { sval: "value" } }
3521
3935
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3522
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3523
- boolValue = stringValue === 'on' || stringValue === 'true';
3936
+ const stringValue = svalValue.replace(/'/g, '');
3937
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3524
3938
  }
3525
3939
  }
3526
3940
  transactionOptions.push(boolValue ? 'READ ONLY' : 'READ WRITE');
@@ -3538,8 +3952,8 @@ export class Deparser {
3538
3952
  else if (nodeData.sval !== undefined) {
3539
3953
  // Handle nested sval structure: { sval: { sval: "value" } }
3540
3954
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3541
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3542
- boolValue = stringValue === 'on' || stringValue === 'true';
3955
+ const stringValue = svalValue.replace(/'/g, '');
3956
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3543
3957
  }
3544
3958
  }
3545
3959
  transactionOptions.push(boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE');
@@ -3605,7 +4019,7 @@ export class Deparser {
3605
4019
  }
3606
4020
  switch (node.roletype) {
3607
4021
  case 'ROLESPEC_PUBLIC':
3608
- return 'public';
4022
+ return 'PUBLIC';
3609
4023
  case 'ROLESPEC_CURRENT_USER':
3610
4024
  return 'CURRENT_USER';
3611
4025
  case 'ROLESPEC_SESSION_USER':
@@ -3613,7 +4027,7 @@ export class Deparser {
3613
4027
  case 'ROLESPEC_CURRENT_ROLE':
3614
4028
  return 'CURRENT_ROLE';
3615
4029
  default:
3616
- return 'public';
4030
+ return 'PUBLIC';
3617
4031
  }
3618
4032
  }
3619
4033
  roletype(node, context) {
@@ -3923,7 +4337,7 @@ export class Deparser {
3923
4337
  }).filter((name) => name && name.trim());
3924
4338
  return items.join('.');
3925
4339
  }
3926
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DropStmt'], objtype: node.removeType };
4340
+ const objContext = context.spawn('DropStmt', { objtype: node.removeType });
3927
4341
  const objName = this.visit(objList, objContext);
3928
4342
  return objName;
3929
4343
  }).filter((name) => name && name.trim()).join(', ');
@@ -4023,7 +4437,7 @@ export class Deparser {
4023
4437
  if (node.options && node.options.length > 0) {
4024
4438
  output.push('WITH');
4025
4439
  const optionsStr = ListUtils.unwrapList(node.options)
4026
- .map(opt => this.visit(opt, context))
4440
+ .map(opt => this.visit(opt, context.spawn('CopyStmt')))
4027
4441
  .join(', ');
4028
4442
  output.push(`(${optionsStr})`);
4029
4443
  }
@@ -4069,18 +4483,28 @@ export class Deparser {
4069
4483
  if (node.missing_ok) {
4070
4484
  output.push('IF EXISTS');
4071
4485
  }
4072
- const alterContext = node.objtype === 'OBJECT_TYPE'
4073
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
4074
- : context;
4486
+ const alterContext = context.spawn('AlterTableStmt', { objtype: node.objtype });
4075
4487
  if (node.relation) {
4076
4488
  const relationStr = this.RangeVar(node.relation, alterContext);
4077
4489
  output.push(relationStr);
4078
4490
  }
4079
4491
  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);
4492
+ const commands = ListUtils.unwrapList(node.cmds);
4493
+ if (context.isPretty()) {
4494
+ const commandsStr = commands
4495
+ .map(cmd => {
4496
+ const cmdStr = this.visit(cmd, alterContext);
4497
+ return context.newline() + context.indent(cmdStr);
4498
+ })
4499
+ .join(',');
4500
+ output.push(commandsStr);
4501
+ }
4502
+ else {
4503
+ const commandsStr = commands
4504
+ .map(cmd => this.visit(cmd, alterContext))
4505
+ .join(', ');
4506
+ output.push(commandsStr);
4507
+ }
4084
4508
  }
4085
4509
  return output.join(' ');
4086
4510
  }
@@ -4089,7 +4513,7 @@ export class Deparser {
4089
4513
  if (node.subtype) {
4090
4514
  switch (node.subtype) {
4091
4515
  case 'AT_AddColumn':
4092
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4516
+ if (context.objtype === 'OBJECT_TYPE') {
4093
4517
  output.push('ADD ATTRIBUTE');
4094
4518
  }
4095
4519
  else {
@@ -4100,35 +4524,99 @@ export class Deparser {
4100
4524
  }
4101
4525
  if (node.def) {
4102
4526
  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));
4527
+ if (context.isPretty()) {
4528
+ const parts = [];
4529
+ const indentedParts = [];
4530
+ if (colDefData.colname) {
4531
+ parts.push(QuoteUtils.quote(colDefData.colname));
4532
+ }
4533
+ if (colDefData.typeName) {
4534
+ parts.push(this.TypeName(colDefData.typeName, context));
4535
+ }
4536
+ if (colDefData.is_not_null) {
4537
+ indentedParts.push('NOT NULL');
4538
+ }
4539
+ if (colDefData.collClause) {
4540
+ indentedParts.push(this.CollateClause(colDefData.collClause, context));
4541
+ }
4542
+ if (colDefData.constraints) {
4543
+ const constraints = ListUtils.unwrapList(colDefData.constraints);
4544
+ constraints.forEach(constraint => {
4545
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4546
+ const constraintStr = this.visit(constraint, columnConstraintContext);
4547
+ if (constraintStr.includes('REFERENCES') && constraintStr.includes('ON DELETE')) {
4548
+ const refMatch = constraintStr.match(/^(.*REFERENCES[^)]*\([^)]*\))\s*(ON\s+DELETE\s+CASCADE.*)$/);
4549
+ if (refMatch) {
4550
+ indentedParts.push(refMatch[1]);
4551
+ indentedParts.push(refMatch[2]);
4552
+ }
4553
+ else {
4554
+ indentedParts.push(constraintStr);
4555
+ }
4556
+ }
4557
+ else if (constraintStr === 'UNIQUE' && colDefData.raw_default) {
4558
+ const defaultStr = 'DEFAULT ' + this.visit(colDefData.raw_default, context);
4559
+ indentedParts.push('UNIQUE ' + defaultStr);
4560
+ }
4561
+ else {
4562
+ indentedParts.push(constraintStr);
4563
+ }
4564
+ });
4565
+ }
4566
+ if (colDefData.raw_default && !colDefData.constraints?.some((c) => {
4567
+ const constraintStr = this.visit(c, context.spawn('ColumnDef', { isColumnConstraint: true }));
4568
+ return constraintStr === 'UNIQUE';
4569
+ })) {
4570
+ const defaultStr = 'DEFAULT ' + this.visit(colDefData.raw_default, context);
4571
+ indentedParts.push(defaultStr);
4572
+ }
4573
+ if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4574
+ indentedParts.push('OPTIONS');
4575
+ const columnContext = context.spawn('ColumnDef');
4576
+ const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4577
+ indentedParts.push(`(${options.join(', ')})`);
4578
+ }
4579
+ let result = parts.join(' ');
4580
+ if (indentedParts.length > 0) {
4581
+ const indentedStr = indentedParts.map(part => context.indent(part)).join(context.newline());
4582
+ result += context.newline() + indentedStr;
4583
+ }
4584
+ output.push(result);
4127
4585
  }
4128
- if (colDefData.is_not_null) {
4129
- parts.push('NOT NULL');
4586
+ else {
4587
+ const parts = [];
4588
+ if (colDefData.colname) {
4589
+ parts.push(QuoteUtils.quote(colDefData.colname));
4590
+ }
4591
+ if (colDefData.typeName) {
4592
+ parts.push(this.TypeName(colDefData.typeName, context));
4593
+ }
4594
+ if (colDefData.collClause) {
4595
+ parts.push(this.CollateClause(colDefData.collClause, context));
4596
+ }
4597
+ if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4598
+ parts.push('OPTIONS');
4599
+ const columnContext = context.spawn('ColumnDef');
4600
+ const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4601
+ parts.push(`(${options.join(', ')})`);
4602
+ }
4603
+ if (colDefData.constraints) {
4604
+ const constraints = ListUtils.unwrapList(colDefData.constraints);
4605
+ const constraintStrs = constraints.map(constraint => {
4606
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4607
+ return this.visit(constraint, columnConstraintContext);
4608
+ });
4609
+ parts.push(...constraintStrs);
4610
+ }
4611
+ if (colDefData.raw_default) {
4612
+ parts.push('DEFAULT');
4613
+ parts.push(this.visit(colDefData.raw_default, context));
4614
+ }
4615
+ if (colDefData.is_not_null) {
4616
+ parts.push('NOT NULL');
4617
+ }
4618
+ output.push(parts.join(' '));
4130
4619
  }
4131
- output.push(parts.join(' '));
4132
4620
  }
4133
4621
  if (node.behavior === 'DROP_CASCADE') {
4134
4622
  output.push('CASCADE');
@@ -4136,7 +4624,7 @@ export class Deparser {
4136
4624
  break;
4137
4625
  case 'AT_DropColumn':
4138
4626
  if (node.missing_ok) {
4139
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4627
+ if (context.objtype === 'OBJECT_TYPE') {
4140
4628
  output.push('DROP ATTRIBUTE IF EXISTS');
4141
4629
  }
4142
4630
  else {
@@ -4144,7 +4632,7 @@ export class Deparser {
4144
4632
  }
4145
4633
  }
4146
4634
  else {
4147
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4635
+ if (context.objtype === 'OBJECT_TYPE') {
4148
4636
  output.push('DROP ATTRIBUTE');
4149
4637
  }
4150
4638
  else {
@@ -4162,7 +4650,7 @@ export class Deparser {
4162
4650
  }
4163
4651
  break;
4164
4652
  case 'AT_AlterColumnType':
4165
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4653
+ if (context.objtype === 'OBJECT_TYPE') {
4166
4654
  output.push('ALTER ATTRIBUTE');
4167
4655
  }
4168
4656
  else {
@@ -4226,7 +4714,7 @@ export class Deparser {
4226
4714
  case 'AT_SetRelOptions':
4227
4715
  output.push('SET');
4228
4716
  if (node.def) {
4229
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetRelOptions' };
4717
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetRelOptions' });
4230
4718
  const options = ListUtils.unwrapList(node.def)
4231
4719
  .map(option => this.visit(option, alterTableContext))
4232
4720
  .join(', ');
@@ -4239,7 +4727,7 @@ export class Deparser {
4239
4727
  case 'AT_ResetRelOptions':
4240
4728
  output.push('RESET');
4241
4729
  if (node.def) {
4242
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetRelOptions' };
4730
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetRelOptions' });
4243
4731
  const options = ListUtils.unwrapList(node.def)
4244
4732
  .map(option => this.visit(option, alterTableContext))
4245
4733
  .join(', ');
@@ -4334,7 +4822,7 @@ export class Deparser {
4334
4822
  }
4335
4823
  output.push('SET');
4336
4824
  if (node.def) {
4337
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetOptions' };
4825
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetOptions' });
4338
4826
  const options = ListUtils.unwrapList(node.def)
4339
4827
  .map(option => this.visit(option, alterTableContext))
4340
4828
  .join(', ');
@@ -4351,7 +4839,7 @@ export class Deparser {
4351
4839
  }
4352
4840
  output.push('RESET');
4353
4841
  if (node.def) {
4354
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetOptions' };
4842
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetOptions' });
4355
4843
  const options = ListUtils.unwrapList(node.def)
4356
4844
  .map(option => this.visit(option, alterTableContext))
4357
4845
  .join(', ');
@@ -4592,7 +5080,7 @@ export class Deparser {
4592
5080
  }
4593
5081
  output.push('OPTIONS');
4594
5082
  if (node.def) {
4595
- const alterColumnContext = { ...context, alterColumnOptions: true };
5083
+ const alterColumnContext = context.spawn('AlterTableCmd', { alterColumnOptions: true });
4596
5084
  const options = ListUtils.unwrapList(node.def)
4597
5085
  .map(option => this.visit(option, alterColumnContext))
4598
5086
  .join(', ');
@@ -4632,7 +5120,7 @@ export class Deparser {
4632
5120
  case 'AT_GenericOptions':
4633
5121
  output.push('OPTIONS');
4634
5122
  if (node.def) {
4635
- const alterTableContext = { ...context, alterTableOptions: true };
5123
+ const alterTableContext = context.spawn('AlterTableCmd', { alterTableOptions: true });
4636
5124
  const options = ListUtils.unwrapList(node.def)
4637
5125
  .map(option => this.visit(option, alterTableContext))
4638
5126
  .join(', ');
@@ -4644,11 +5132,10 @@ export class Deparser {
4644
5132
  if (node.name) {
4645
5133
  output.push(QuoteUtils.quote(node.name));
4646
5134
  }
4647
- output.push('ADD GENERATED');
5135
+ output.push('ADD');
4648
5136
  if (node.def) {
4649
5137
  output.push(this.visit(node.def, context));
4650
5138
  }
4651
- output.push('AS IDENTITY');
4652
5139
  break;
4653
5140
  case 'AT_SetIdentity':
4654
5141
  output.push('ALTER COLUMN');
@@ -4736,7 +5223,7 @@ export class Deparser {
4736
5223
  output.push(this.TypeName(node.returnType, context));
4737
5224
  }
4738
5225
  if (node.options && node.options.length > 0) {
4739
- const funcContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFunctionStmt'] };
5226
+ const funcContext = context.spawn('CreateFunctionStmt');
4740
5227
  const options = node.options.map((opt) => this.visit(opt, funcContext));
4741
5228
  output.push(...options);
4742
5229
  }
@@ -4823,7 +5310,7 @@ export class Deparser {
4823
5310
  }
4824
5311
  output.push('AS', 'ENUM');
4825
5312
  if (node.vals && node.vals.length > 0) {
4826
- const enumContext = { ...context, isEnumValue: true };
5313
+ const enumContext = context.spawn('CreateEnumStmt', { isEnumValue: true });
4827
5314
  const values = ListUtils.unwrapList(node.vals)
4828
5315
  .map(val => this.visit(val, enumContext))
4829
5316
  .join(', ');
@@ -4878,9 +5365,8 @@ export class Deparser {
4878
5365
  output.push(roleName);
4879
5366
  }
4880
5367
  if (node.options) {
4881
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateRoleStmt'] };
4882
5368
  const options = ListUtils.unwrapList(node.options)
4883
- .map(option => this.visit(option, roleContext))
5369
+ .map(option => this.visit(option, context.spawn('CreateRoleStmt')))
4884
5370
  .join(' ');
4885
5371
  if (options) {
4886
5372
  output.push('WITH');
@@ -4911,7 +5397,7 @@ export class Deparser {
4911
5397
  const stringData = this.getNodeData(node.arg);
4912
5398
  return `${node.defname}='${stringData.sval}'`;
4913
5399
  }
4914
- return `${node.defname}=${this.visit(node.arg, { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] })}`;
5400
+ return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
4915
5401
  }
4916
5402
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
4917
5403
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -4927,13 +5413,13 @@ export class Deparser {
4927
5413
  if (!node.arg) {
4928
5414
  return `NO ${node.defname.toUpperCase()}`;
4929
5415
  }
4930
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5416
+ const defElemContext = context.spawn('DefElem');
4931
5417
  const argValue = this.visit(node.arg, defElemContext);
4932
5418
  return `${node.defname.toUpperCase()} ${argValue}`;
4933
5419
  }
4934
5420
  // Handle OPTIONS clause - use space format, not equals format
4935
5421
  if (node.arg) {
4936
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5422
+ const defElemContext = context.spawn('DefElem');
4937
5423
  const argValue = this.visit(node.arg, defElemContext);
4938
5424
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
4939
5425
  const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
@@ -4987,7 +5473,7 @@ export class Deparser {
4987
5473
  if (!node.arg) {
4988
5474
  return 'PASSWORD NULL';
4989
5475
  }
4990
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5476
+ const defElemContext = context.spawn('DefElem');
4991
5477
  const argValue = this.visit(node.arg, defElemContext);
4992
5478
  const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
4993
5479
  ? `'${argValue}'`
@@ -4996,7 +5482,7 @@ export class Deparser {
4996
5482
  }
4997
5483
  }
4998
5484
  if (node.arg) {
4999
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5485
+ const defElemContext = context.spawn('DefElem');
5000
5486
  const argValue = this.visit(node.arg, defElemContext);
5001
5487
  if (context.parentNodeTypes.includes('AlterOperatorStmt')) {
5002
5488
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
@@ -5072,7 +5558,7 @@ export class Deparser {
5072
5558
  if (node.defname === 'sysid') {
5073
5559
  return `SYSID ${argValue}`;
5074
5560
  }
5075
- if (argValue === 'true') {
5561
+ if (String(argValue) === 'true') {
5076
5562
  // Handle special cases where the positive form has a different name
5077
5563
  if (node.defname === 'isreplication') {
5078
5564
  return 'REPLICATION';
@@ -5082,7 +5568,7 @@ export class Deparser {
5082
5568
  }
5083
5569
  return node.defname.toUpperCase();
5084
5570
  }
5085
- else if (argValue === 'false') {
5571
+ else if (String(argValue) === 'false') {
5086
5572
  // Handle special cases where the negative form has a different name
5087
5573
  if (node.defname === 'canlogin') {
5088
5574
  return 'NOLOGIN';
@@ -5146,7 +5632,7 @@ export class Deparser {
5146
5632
  }
5147
5633
  if (context.parentNodeTypes.includes('DoStmt')) {
5148
5634
  if (node.defname === 'as') {
5149
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5635
+ const defElemContext = context.spawn('DefElem');
5150
5636
  const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
5151
5637
  if (Array.isArray(argValue)) {
5152
5638
  const bodyParts = argValue;
@@ -5238,11 +5724,15 @@ export class Deparser {
5238
5724
  if (context.parentNodeTypes.includes('CreateExtensionStmt') || context.parentNodeTypes.includes('AlterExtensionStmt') || context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5239
5725
  // AlterExtensionStmt specific cases
5240
5726
  if (context.parentNodeTypes.includes('AlterExtensionStmt')) {
5241
- if (node.defname === 'to') {
5242
- return `TO ${argValue}`;
5727
+ if (node.defname === 'new_version') {
5728
+ // argValue is unquoted due to DefElem context, so we need to quote it
5729
+ const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5730
+ ? `'${argValue}'`
5731
+ : argValue;
5732
+ return `UPDATE TO ${quotedValue}`;
5243
5733
  }
5244
5734
  if (node.defname === 'schema') {
5245
- return `SCHEMA ${argValue}`;
5735
+ return `SET SCHEMA ${argValue}`;
5246
5736
  }
5247
5737
  }
5248
5738
  // CreateFdwStmt specific cases
@@ -5437,7 +5927,7 @@ export class Deparser {
5437
5927
  }
5438
5928
  if (node.options && node.options.length > 0) {
5439
5929
  output.push('WITH');
5440
- const tsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateTableSpaceStmt'] };
5930
+ const tsContext = context.spawn('CreateTableSpaceStmt');
5441
5931
  const options = ListUtils.unwrapList(node.options)
5442
5932
  .map(option => this.visit(option, tsContext))
5443
5933
  .join(', ');
@@ -5467,7 +5957,7 @@ export class Deparser {
5467
5957
  output.push('SET');
5468
5958
  }
5469
5959
  if (node.options && node.options.length > 0) {
5470
- const tablespaceContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableSpaceOptionsStmt'] };
5960
+ const tablespaceContext = context.spawn('AlterTableSpaceOptionsStmt');
5471
5961
  const options = ListUtils.unwrapList(node.options)
5472
5962
  .map(option => this.visit(option, tablespaceContext))
5473
5963
  .join(', ');
@@ -5484,7 +5974,7 @@ export class Deparser {
5484
5974
  output.push(this.quoteIfNeeded(node.extname));
5485
5975
  }
5486
5976
  if (node.options && node.options.length > 0) {
5487
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateExtensionStmt'] };
5977
+ const extContext = context.spawn('CreateExtensionStmt');
5488
5978
  const options = ListUtils.unwrapList(node.options)
5489
5979
  .map(option => this.visit(option, extContext))
5490
5980
  .join(' ');
@@ -5498,7 +5988,7 @@ export class Deparser {
5498
5988
  output.push(this.quoteIfNeeded(node.extname));
5499
5989
  }
5500
5990
  if (node.options && node.options.length > 0) {
5501
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterExtensionStmt'] };
5991
+ const extContext = context.spawn('AlterExtensionStmt');
5502
5992
  const options = ListUtils.unwrapList(node.options)
5503
5993
  .map(option => this.visit(option, extContext))
5504
5994
  .join(' ');
@@ -5506,13 +5996,41 @@ export class Deparser {
5506
5996
  }
5507
5997
  return output.join(' ');
5508
5998
  }
5999
+ AlterExtensionContentsStmt(node, context) {
6000
+ const output = ['ALTER', 'EXTENSION'];
6001
+ if (node.extname) {
6002
+ output.push(this.quoteIfNeeded(node.extname));
6003
+ }
6004
+ // Handle action: 1 = ADD, -1 = DROP
6005
+ if (node.action === 1) {
6006
+ output.push('ADD');
6007
+ }
6008
+ else if (node.action === -1) {
6009
+ output.push('DROP');
6010
+ }
6011
+ // Add object type keyword
6012
+ if (node.objtype) {
6013
+ try {
6014
+ output.push(this.getObjectTypeKeyword(node.objtype));
6015
+ }
6016
+ catch {
6017
+ // Fallback to the raw objtype if not supported
6018
+ output.push(node.objtype.toString());
6019
+ }
6020
+ }
6021
+ // Add the object itself
6022
+ if (node.object) {
6023
+ output.push(this.visit(node.object, context));
6024
+ }
6025
+ return output.join(' ');
6026
+ }
5509
6027
  CreateFdwStmt(node, context) {
5510
6028
  const output = ['CREATE', 'FOREIGN', 'DATA', 'WRAPPER'];
5511
6029
  if (node.fdwname) {
5512
6030
  output.push(node.fdwname);
5513
6031
  }
5514
6032
  if (node.func_options && node.func_options.length > 0) {
5515
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
6033
+ const fdwContext = context.spawn('CreateFdwStmt');
5516
6034
  const funcOptions = ListUtils.unwrapList(node.func_options)
5517
6035
  .map(option => this.visit(option, fdwContext))
5518
6036
  .join(' ');
@@ -5520,7 +6038,7 @@ export class Deparser {
5520
6038
  }
5521
6039
  if (node.options && node.options.length > 0) {
5522
6040
  output.push('OPTIONS');
5523
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
6041
+ const fdwContext = context.spawn('CreateFdwStmt');
5524
6042
  const options = ListUtils.unwrapList(node.options)
5525
6043
  .map(option => this.visit(option, fdwContext))
5526
6044
  .join(', ');
@@ -5622,7 +6140,7 @@ export class Deparser {
5622
6140
  output.push('ADD');
5623
6141
  if (node.def) {
5624
6142
  // Pass domain context to avoid adding constraint names for domain constraints
5625
- const domainContext = { ...context, isDomainConstraint: true };
6143
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5626
6144
  output.push(this.visit(node.def, domainContext));
5627
6145
  }
5628
6146
  break;
@@ -5648,7 +6166,7 @@ export class Deparser {
5648
6166
  output.push('ADD');
5649
6167
  if (node.def) {
5650
6168
  // Pass domain context to avoid adding constraint names for domain constraints
5651
- const domainContext = { ...context, isDomainConstraint: true };
6169
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5652
6170
  output.push(this.visit(node.def, domainContext));
5653
6171
  }
5654
6172
  break;
@@ -6010,7 +6528,7 @@ export class Deparser {
6010
6528
  output.push(`${operatorName}(${args.join(', ')})`);
6011
6529
  }
6012
6530
  else {
6013
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CommentStmt'], objtype: node.objtype };
6531
+ const objContext = context.spawn('CommentStmt', { objtype: node.objtype });
6014
6532
  output.push(this.visit(node.object, objContext));
6015
6533
  }
6016
6534
  }
@@ -6056,13 +6574,13 @@ export class Deparser {
6056
6574
  const output = [];
6057
6575
  const initialParts = ['CREATE', 'POLICY'];
6058
6576
  if (node.policy_name) {
6059
- initialParts.push(`"${node.policy_name}"`);
6577
+ initialParts.push(QuoteUtils.quote(node.policy_name));
6060
6578
  }
6061
6579
  output.push(initialParts.join(' '));
6062
6580
  // Add ON clause on new line in pretty mode
6063
6581
  if (node.table) {
6064
- if (this.formatter.isPretty()) {
6065
- output.push(this.formatter.newline() + this.formatter.indent(`ON ${this.RangeVar(node.table, context)}`));
6582
+ if (context.isPretty()) {
6583
+ output.push(context.newline() + context.indent(`ON ${this.RangeVar(node.table, context)}`));
6066
6584
  }
6067
6585
  else {
6068
6586
  output.push('ON');
@@ -6071,24 +6589,24 @@ export class Deparser {
6071
6589
  }
6072
6590
  // Handle AS RESTRICTIVE/PERMISSIVE clause
6073
6591
  if (node.permissive === undefined) {
6074
- if (this.formatter.isPretty()) {
6075
- output.push(this.formatter.newline() + this.formatter.indent('AS RESTRICTIVE'));
6592
+ if (context.isPretty()) {
6593
+ output.push(context.newline() + context.indent('AS RESTRICTIVE'));
6076
6594
  }
6077
6595
  else {
6078
6596
  output.push('AS', 'RESTRICTIVE');
6079
6597
  }
6080
6598
  }
6081
6599
  else if (node.permissive === true) {
6082
- if (this.formatter.isPretty()) {
6083
- output.push(this.formatter.newline() + this.formatter.indent('AS PERMISSIVE'));
6600
+ if (context.isPretty()) {
6601
+ output.push(context.newline() + context.indent('AS PERMISSIVE'));
6084
6602
  }
6085
6603
  else {
6086
6604
  output.push('AS', 'PERMISSIVE');
6087
6605
  }
6088
6606
  }
6089
6607
  if (node.cmd_name) {
6090
- if (this.formatter.isPretty()) {
6091
- output.push(this.formatter.newline() + this.formatter.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6608
+ if (context.isPretty()) {
6609
+ output.push(context.newline() + context.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6092
6610
  }
6093
6611
  else {
6094
6612
  output.push('FOR', node.cmd_name.toUpperCase());
@@ -6096,8 +6614,8 @@ export class Deparser {
6096
6614
  }
6097
6615
  if (node.roles && node.roles.length > 0) {
6098
6616
  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(', ')}`));
6617
+ if (context.isPretty()) {
6618
+ output.push(context.newline() + context.indent(`TO ${roles.join(', ')}`));
6101
6619
  }
6102
6620
  else {
6103
6621
  output.push('TO');
@@ -6105,11 +6623,11 @@ export class Deparser {
6105
6623
  }
6106
6624
  }
6107
6625
  if (node.qual) {
6108
- if (this.formatter.isPretty()) {
6626
+ if (context.isPretty()) {
6109
6627
  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(')'));
6628
+ output.push(context.newline() + context.indent('USING ('));
6629
+ output.push(context.newline() + context.indent(context.indent(qualExpr)));
6630
+ output.push(context.newline() + context.indent(')'));
6113
6631
  }
6114
6632
  else {
6115
6633
  output.push('USING');
@@ -6117,23 +6635,23 @@ export class Deparser {
6117
6635
  }
6118
6636
  }
6119
6637
  if (node.with_check) {
6120
- if (this.formatter.isPretty()) {
6638
+ if (context.isPretty()) {
6121
6639
  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(')'));
6640
+ output.push(context.newline() + context.indent('WITH CHECK ('));
6641
+ output.push(context.newline() + context.indent(context.indent(checkExpr)));
6642
+ output.push(context.newline() + context.indent(')'));
6125
6643
  }
6126
6644
  else {
6127
6645
  output.push('WITH CHECK');
6128
6646
  output.push(`(${this.visit(node.with_check, context)})`);
6129
6647
  }
6130
6648
  }
6131
- return this.formatter.isPretty() ? output.join('') : output.join(' ');
6649
+ return context.isPretty() ? output.join('') : output.join(' ');
6132
6650
  }
6133
6651
  AlterPolicyStmt(node, context) {
6134
6652
  const output = ['ALTER', 'POLICY'];
6135
6653
  if (node.policy_name) {
6136
- output.push(`"${node.policy_name}"`);
6654
+ output.push(QuoteUtils.quote(node.policy_name));
6137
6655
  }
6138
6656
  if (node.table) {
6139
6657
  output.push('ON');
@@ -6173,7 +6691,7 @@ export class Deparser {
6173
6691
  }
6174
6692
  if (node.options && node.options.length > 0) {
6175
6693
  output.push('OPTIONS');
6176
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateUserMappingStmt'] };
6694
+ const userMappingContext = context.spawn('CreateUserMappingStmt');
6177
6695
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6178
6696
  output.push(`(${options.join(', ')})`);
6179
6697
  }
@@ -6356,7 +6874,7 @@ export class Deparser {
6356
6874
  DoStmt(node, context) {
6357
6875
  const output = ['DO'];
6358
6876
  if (node.args && node.args.length > 0) {
6359
- const doContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DoStmt'] };
6877
+ const doContext = context.spawn('DoStmt');
6360
6878
  const args = ListUtils.unwrapList(node.args);
6361
6879
  const processedArgs = [];
6362
6880
  for (const arg of args) {
@@ -6661,7 +7179,7 @@ export class Deparser {
6661
7179
  ObjectWithArgs(node, context) {
6662
7180
  let result = '';
6663
7181
  if (node.objname && node.objname.length > 0) {
6664
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ObjectWithArgs'] };
7182
+ const objContext = context.spawn('ObjectWithArgs');
6665
7183
  const names = ListUtils.unwrapList(node.objname).map(name => this.visit(name, objContext));
6666
7184
  result = names.join('.');
6667
7185
  }
@@ -6703,7 +7221,7 @@ export class Deparser {
6703
7221
  }
6704
7222
  output.push('SET');
6705
7223
  if (node.options && node.options.length > 0) {
6706
- const alterOpContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterOperatorStmt'] };
7224
+ const alterOpContext = context.spawn('AlterOperatorStmt');
6707
7225
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, alterOpContext));
6708
7226
  output.push(`(${options.join(', ')})`);
6709
7227
  }
@@ -6715,13 +7233,13 @@ export class Deparser {
6715
7233
  output.push(QuoteUtils.quote(node.fdwname));
6716
7234
  }
6717
7235
  if (node.func_options && node.func_options.length > 0) {
6718
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
7236
+ const fdwContext = context.spawn('AlterFdwStmt');
6719
7237
  const funcOptions = ListUtils.unwrapList(node.func_options).map(opt => this.visit(opt, fdwContext));
6720
7238
  output.push(funcOptions.join(' '));
6721
7239
  }
6722
7240
  if (node.options && node.options.length > 0) {
6723
7241
  output.push('OPTIONS');
6724
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
7242
+ const fdwContext = context.spawn('AlterFdwStmt');
6725
7243
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, fdwContext));
6726
7244
  output.push(`(${options.join(', ')})`);
6727
7245
  }
@@ -6747,7 +7265,7 @@ export class Deparser {
6747
7265
  if (node.options && node.options.length > 0) {
6748
7266
  output.push('OPTIONS');
6749
7267
  output.push('(');
6750
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignServerStmt'] };
7268
+ const optionsContext = context.spawn('CreateForeignServerStmt');
6751
7269
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6752
7270
  output.push(options.join(', '));
6753
7271
  output.push(')');
@@ -6765,7 +7283,7 @@ export class Deparser {
6765
7283
  if (node.options && node.options.length > 0) {
6766
7284
  output.push('OPTIONS');
6767
7285
  output.push('(');
6768
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterForeignServerStmt'] };
7286
+ const optionsContext = context.spawn('AlterForeignServerStmt');
6769
7287
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6770
7288
  output.push(options.join(', '));
6771
7289
  output.push(')');
@@ -6786,7 +7304,7 @@ export class Deparser {
6786
7304
  }
6787
7305
  if (node.options && node.options.length > 0) {
6788
7306
  output.push('OPTIONS');
6789
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterUserMappingStmt'] };
7307
+ const userMappingContext = context.spawn('AlterUserMappingStmt');
6790
7308
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6791
7309
  output.push(`(${options.join(', ')})`);
6792
7310
  }
@@ -6846,7 +7364,7 @@ export class Deparser {
6846
7364
  output.push(QuoteUtils.quote(node.local_schema));
6847
7365
  }
6848
7366
  if (node.options && node.options.length > 0) {
6849
- const importSchemaContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ImportForeignSchemaStmt'] };
7367
+ const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
6850
7368
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, importSchemaContext));
6851
7369
  output.push(`OPTIONS (${options.join(', ')})`);
6852
7370
  }
@@ -6881,7 +7399,7 @@ export class Deparser {
6881
7399
  ExplainStmt(node, context) {
6882
7400
  const output = ['EXPLAIN'];
6883
7401
  if (node.options && node.options.length > 0) {
6884
- const explainContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ExplainStmt'] };
7402
+ const explainContext = context.spawn('ExplainStmt');
6885
7403
  const options = ListUtils.unwrapList(node.options).map(option => this.visit(option, explainContext));
6886
7404
  output.push(`(${options.join(', ')})`);
6887
7405
  }
@@ -7139,9 +7657,7 @@ export class Deparser {
7139
7657
  output.push(this.RangeVar(node.relation, context));
7140
7658
  }
7141
7659
  else if (node.relation) {
7142
- const rangeVarContext = node.relationType === 'OBJECT_TYPE'
7143
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
7144
- : context;
7660
+ const rangeVarContext = context.spawn('RenameStmt', { objtype: node.relationType });
7145
7661
  // Add ON keyword for policy operations
7146
7662
  if (node.renameType === 'OBJECT_POLICY') {
7147
7663
  output.push('ON');
@@ -7214,76 +7730,7 @@ export class Deparser {
7214
7730
  if (!node.objectType) {
7215
7731
  throw new Error('AlterOwnerStmt requires objectType');
7216
7732
  }
7217
- switch (node.objectType) {
7218
- case 'OBJECT_TABLE':
7219
- output.push('TABLE');
7220
- break;
7221
- case 'OBJECT_VIEW':
7222
- output.push('VIEW');
7223
- break;
7224
- case 'OBJECT_INDEX':
7225
- output.push('INDEX');
7226
- break;
7227
- case 'OBJECT_SEQUENCE':
7228
- output.push('SEQUENCE');
7229
- break;
7230
- case 'OBJECT_FUNCTION':
7231
- output.push('FUNCTION');
7232
- break;
7233
- case 'OBJECT_PROCEDURE':
7234
- output.push('PROCEDURE');
7235
- break;
7236
- case 'OBJECT_SCHEMA':
7237
- output.push('SCHEMA');
7238
- break;
7239
- case 'OBJECT_DATABASE':
7240
- output.push('DATABASE');
7241
- break;
7242
- case 'OBJECT_DOMAIN':
7243
- output.push('DOMAIN');
7244
- break;
7245
- case 'OBJECT_AGGREGATE':
7246
- output.push('AGGREGATE');
7247
- break;
7248
- case 'OBJECT_CONVERSION':
7249
- output.push('CONVERSION');
7250
- break;
7251
- case 'OBJECT_LANGUAGE':
7252
- output.push('LANGUAGE');
7253
- break;
7254
- case 'OBJECT_OPERATOR':
7255
- output.push('OPERATOR');
7256
- break;
7257
- case 'OBJECT_OPFAMILY':
7258
- output.push('OPERATOR FAMILY');
7259
- break;
7260
- case 'OBJECT_OPCLASS':
7261
- output.push('OPERATOR CLASS');
7262
- break;
7263
- case 'OBJECT_TSDICTIONARY':
7264
- output.push('TEXT SEARCH DICTIONARY');
7265
- break;
7266
- case 'OBJECT_TSCONFIGURATION':
7267
- output.push('TEXT SEARCH CONFIGURATION');
7268
- break;
7269
- case 'OBJECT_EVENT_TRIGGER':
7270
- output.push('EVENT TRIGGER');
7271
- break;
7272
- case 'OBJECT_FDW':
7273
- output.push('FOREIGN DATA WRAPPER');
7274
- break;
7275
- case 'OBJECT_FOREIGN_SERVER':
7276
- output.push('SERVER');
7277
- break;
7278
- case 'OBJECT_TYPE':
7279
- output.push('TYPE');
7280
- break;
7281
- case 'OBJECT_COLLATION':
7282
- output.push('COLLATION');
7283
- break;
7284
- default:
7285
- throw new Error(`Unsupported AlterOwnerStmt objectType: ${node.objectType}`);
7286
- }
7733
+ output.push(this.getObjectTypeKeyword(node.objectType));
7287
7734
  if (node.relation) {
7288
7735
  output.push(this.RangeVar(node.relation, context));
7289
7736
  }
@@ -7323,7 +7770,7 @@ export class Deparser {
7323
7770
  }
7324
7771
  }
7325
7772
  if (node.privileges && node.privileges.length > 0) {
7326
- const privilegeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'GrantStmt'] };
7773
+ const privilegeContext = context.spawn('GrantStmt');
7327
7774
  const privileges = ListUtils.unwrapList(node.privileges)
7328
7775
  .map(priv => this.visit(priv, privilegeContext))
7329
7776
  .join(', ');
@@ -7631,7 +8078,12 @@ export class Deparser {
7631
8078
  }
7632
8079
  if (node.action) {
7633
8080
  const actionStr = this.GrantStmt(node.action, context);
7634
- output.push(actionStr);
8081
+ if (context.isPretty()) {
8082
+ return output.join(' ') + context.newline() + context.indent(actionStr);
8083
+ }
8084
+ else {
8085
+ output.push(actionStr);
8086
+ }
7635
8087
  }
7636
8088
  return output.join(' ');
7637
8089
  }
@@ -7778,84 +8230,158 @@ export class Deparser {
7778
8230
  if (node.trigname) {
7779
8231
  output.push(QuoteUtils.quote(node.trigname));
7780
8232
  }
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(')');
8233
+ if (context.isPretty()) {
8234
+ const components = [];
8235
+ const timing = [];
8236
+ if (node.timing & 2)
8237
+ timing.push('BEFORE');
8238
+ else if (node.timing & 64)
8239
+ timing.push('INSTEAD OF');
8240
+ else
8241
+ timing.push('AFTER');
8242
+ const events = [];
8243
+ if (node.events & 4)
8244
+ events.push('INSERT');
8245
+ if (node.events & 8)
8246
+ events.push('DELETE');
8247
+ if (node.events & 16) {
8248
+ let updateStr = 'UPDATE';
8249
+ if (node.columns && node.columns.length > 0) {
8250
+ const columnNames = ListUtils.unwrapList(node.columns)
8251
+ .map(col => this.visit(col, context))
8252
+ .join(', ');
8253
+ updateStr += ' OF ' + columnNames;
8254
+ }
8255
+ events.push(updateStr);
8256
+ }
8257
+ if (node.events & 32)
8258
+ events.push('TRUNCATE');
8259
+ components.push(context.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8260
+ if (node.relation) {
8261
+ components.push(context.indent('ON ' + this.RangeVar(node.relation, context)));
8262
+ }
8263
+ if (node.transitionRels && node.transitionRels.length > 0) {
8264
+ const transitionClauses = ListUtils.unwrapList(node.transitionRels)
8265
+ .map(rel => this.visit(rel, context))
8266
+ .join(' ');
8267
+ components.push(context.indent('REFERENCING ' + transitionClauses));
8268
+ }
8269
+ if (node.deferrable) {
8270
+ components.push(context.indent('DEFERRABLE'));
8271
+ }
8272
+ if (node.initdeferred) {
8273
+ components.push(context.indent('INITIALLY DEFERRED'));
8274
+ }
8275
+ if (node.row) {
8276
+ components.push(context.indent('FOR EACH ROW'));
8277
+ }
8278
+ else {
8279
+ components.push(context.indent('FOR EACH STATEMENT'));
8280
+ }
8281
+ if (node.whenClause) {
8282
+ const whenStr = 'WHEN (' + this.visit(node.whenClause, context) + ')';
8283
+ components.push(context.indent(whenStr));
8284
+ }
8285
+ let executeStr = 'EXECUTE';
8286
+ if (node.funcname && node.funcname.length > 0) {
8287
+ const funcName = ListUtils.unwrapList(node.funcname)
8288
+ .map(name => this.visit(name, context))
8289
+ .join('.');
8290
+ executeStr += ' PROCEDURE ' + funcName;
8291
+ }
8292
+ if (node.args && node.args.length > 0) {
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
+ executeStr += '(' + args + ')';
8298
+ }
8299
+ else {
8300
+ executeStr += '()';
8301
+ }
8302
+ components.push(context.indent(executeStr));
8303
+ return output.join(' ') + context.newline() + components.join(context.newline());
7854
8304
  }
7855
8305
  else {
7856
- output.push('()');
8306
+ const timing = [];
8307
+ if (node.timing & 2)
8308
+ timing.push('BEFORE');
8309
+ else if (node.timing & 64)
8310
+ timing.push('INSTEAD OF');
8311
+ else
8312
+ timing.push('AFTER');
8313
+ output.push(timing.join(' '));
8314
+ const events = [];
8315
+ if (node.events & 4)
8316
+ events.push('INSERT');
8317
+ if (node.events & 8)
8318
+ events.push('DELETE');
8319
+ if (node.events & 16)
8320
+ events.push('UPDATE');
8321
+ if (node.events & 32)
8322
+ events.push('TRUNCATE');
8323
+ output.push(events.join(' OR '));
8324
+ if (node.columns && node.columns.length > 0) {
8325
+ output.push('OF');
8326
+ const columnNames = ListUtils.unwrapList(node.columns)
8327
+ .map(col => this.visit(col, context))
8328
+ .join(', ');
8329
+ output.push(columnNames);
8330
+ }
8331
+ output.push('ON');
8332
+ if (node.relation) {
8333
+ output.push(this.RangeVar(node.relation, context));
8334
+ }
8335
+ if (node.constrrel) {
8336
+ output.push('FROM');
8337
+ output.push(this.RangeVar(node.constrrel, context));
8338
+ }
8339
+ if (node.deferrable) {
8340
+ output.push('DEFERRABLE');
8341
+ }
8342
+ if (node.initdeferred) {
8343
+ output.push('INITIALLY DEFERRED');
8344
+ }
8345
+ if (node.transitionRels && node.transitionRels.length > 0) {
8346
+ output.push('REFERENCING');
8347
+ const transitionClauses = ListUtils.unwrapList(node.transitionRels)
8348
+ .map(rel => this.visit(rel, context))
8349
+ .join(' ');
8350
+ output.push(transitionClauses);
8351
+ }
8352
+ if (node.row) {
8353
+ output.push('FOR EACH ROW');
8354
+ }
8355
+ else {
8356
+ output.push('FOR EACH STATEMENT');
8357
+ }
8358
+ if (node.whenClause) {
8359
+ output.push('WHEN');
8360
+ output.push('(');
8361
+ output.push(this.visit(node.whenClause, context));
8362
+ output.push(')');
8363
+ }
8364
+ output.push('EXECUTE');
8365
+ if (node.funcname && node.funcname.length > 0) {
8366
+ const funcName = ListUtils.unwrapList(node.funcname)
8367
+ .map(name => this.visit(name, context))
8368
+ .join('.');
8369
+ output.push('FUNCTION', funcName);
8370
+ }
8371
+ if (node.args && node.args.length > 0) {
8372
+ output.push('(');
8373
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8374
+ const args = ListUtils.unwrapList(node.args)
8375
+ .map(arg => this.visit(arg, argContext))
8376
+ .join(', ');
8377
+ output.push(args);
8378
+ output.push(')');
8379
+ }
8380
+ else {
8381
+ output.push('()');
8382
+ }
8383
+ return output.join(' ');
7857
8384
  }
7858
- return output.join(' ');
7859
8385
  }
7860
8386
  TriggerTransition(node, context) {
7861
8387
  const output = [];
@@ -7881,7 +8407,7 @@ export class Deparser {
7881
8407
  }
7882
8408
  if (node.whenclause && node.whenclause.length > 0) {
7883
8409
  output.push('WHEN');
7884
- const eventTriggerContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateEventTrigStmt'] };
8410
+ const eventTriggerContext = context.spawn('CreateEventTrigStmt');
7885
8411
  const conditions = ListUtils.unwrapList(node.whenclause)
7886
8412
  .map(condition => this.visit(condition, eventTriggerContext))
7887
8413
  .join(' AND ');
@@ -8070,7 +8596,7 @@ export class Deparser {
8070
8596
  output.push(sequenceName.join('.'));
8071
8597
  }
8072
8598
  if (node.options && node.options.length > 0) {
8073
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateSeqStmt'] };
8599
+ const seqContext = context.spawn('CreateSeqStmt');
8074
8600
  const optionStrs = ListUtils.unwrapList(node.options)
8075
8601
  .filter(option => option != null && this.getNodeType(option) !== 'undefined')
8076
8602
  .map(option => {
@@ -8107,7 +8633,7 @@ export class Deparser {
8107
8633
  output.push(sequenceName.join('.'));
8108
8634
  }
8109
8635
  if (node.options && node.options.length > 0) {
8110
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterSeqStmt'] };
8636
+ const seqContext = context.spawn('AlterSeqStmt');
8111
8637
  const optionStrs = ListUtils.unwrapList(node.options)
8112
8638
  .filter(option => option && option !== undefined)
8113
8639
  .map(option => {
@@ -8136,7 +8662,7 @@ export class Deparser {
8136
8662
  CompositeTypeStmt(node, context) {
8137
8663
  const output = ['CREATE', 'TYPE'];
8138
8664
  if (node.typevar) {
8139
- const typeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CompositeTypeStmt'] };
8665
+ const typeContext = context.spawn('CompositeTypeStmt');
8140
8666
  output.push(this.RangeVar(node.typevar, typeContext));
8141
8667
  }
8142
8668
  output.push('AS');
@@ -8237,7 +8763,7 @@ export class Deparser {
8237
8763
  output.push(this.RoleSpec(node.role, context));
8238
8764
  }
8239
8765
  if (node.options) {
8240
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterRoleStmt'] };
8766
+ const roleContext = context.spawn('AlterRoleStmt');
8241
8767
  // Handle GROUP operations specially based on action value
8242
8768
  if (isGroupStatement) {
8243
8769
  const roleMembersOption = ListUtils.unwrapList(node.options).find(option => option.DefElem && option.DefElem.defname === 'rolemembers');
@@ -8433,14 +8959,14 @@ export class Deparser {
8433
8959
  AccessPriv(node, context) {
8434
8960
  const output = [];
8435
8961
  if (node.priv_name) {
8436
- output.push(node.priv_name);
8962
+ output.push(node.priv_name.toUpperCase());
8437
8963
  }
8438
8964
  else {
8439
8965
  output.push('ALL');
8440
8966
  }
8441
8967
  if (node.cols && node.cols.length > 0) {
8442
8968
  output.push('(');
8443
- const colContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AccessPriv'] };
8969
+ const colContext = context.spawn('AccessPriv');
8444
8970
  const columns = ListUtils.unwrapList(node.cols).map(col => this.visit(col, colContext));
8445
8971
  output.push(columns.join(', '));
8446
8972
  output.push(')');
@@ -8520,7 +9046,7 @@ export class Deparser {
8520
9046
  output.push(ListUtils.unwrapList(node.defnames).map(name => this.visit(name, context)).join('.'));
8521
9047
  }
8522
9048
  if (node.definition && node.definition.length > 0) {
8523
- const defineStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefineStmt'] };
9049
+ const defineStmtContext = context.spawn('DefineStmt');
8524
9050
  const definitions = ListUtils.unwrapList(node.definition).map(def => {
8525
9051
  return this.visit(def, defineStmtContext);
8526
9052
  });
@@ -9006,7 +9532,7 @@ export class Deparser {
9006
9532
  const operatorNumber = node.number !== undefined ? node.number : 0;
9007
9533
  output.push(operatorNumber.toString());
9008
9534
  if (node.name) {
9009
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9535
+ const opClassContext = context.spawn('CreateOpClassItem');
9010
9536
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9011
9537
  }
9012
9538
  }
@@ -9016,7 +9542,7 @@ export class Deparser {
9016
9542
  const functionNumber = node.number !== undefined ? node.number : 0;
9017
9543
  output.push(functionNumber.toString());
9018
9544
  if (node.name) {
9019
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9545
+ const opClassContext = context.spawn('CreateOpClassItem');
9020
9546
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9021
9547
  }
9022
9548
  }
@@ -9714,7 +10240,7 @@ export class Deparser {
9714
10240
  output.push(this.ObjectWithArgs(node.func, context));
9715
10241
  }
9716
10242
  if (node.actions && node.actions.length > 0) {
9717
- const alterFunctionContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFunctionStmt'] };
10243
+ const alterFunctionContext = context.spawn('AlterFunctionStmt');
9718
10244
  const actionStrs = ListUtils.unwrapList(node.actions).map(action => this.visit(action, alterFunctionContext));
9719
10245
  output.push(actionStrs.join(' '));
9720
10246
  }
@@ -9722,66 +10248,12 @@ export class Deparser {
9722
10248
  }
9723
10249
  AlterObjectSchemaStmt(node, context) {
9724
10250
  const output = ['ALTER'];
9725
- switch (node.objectType) {
9726
- case 'OBJECT_TABLE':
9727
- output.push('TABLE');
9728
- break;
9729
- case 'OBJECT_VIEW':
9730
- output.push('VIEW');
9731
- break;
9732
- case 'OBJECT_FUNCTION':
9733
- output.push('FUNCTION');
9734
- break;
9735
- case 'OBJECT_TYPE':
9736
- output.push('TYPE');
9737
- break;
9738
- case 'OBJECT_DOMAIN':
9739
- output.push('DOMAIN');
9740
- break;
9741
- case 'OBJECT_SEQUENCE':
9742
- output.push('SEQUENCE');
9743
- break;
9744
- case 'OBJECT_OPCLASS':
9745
- output.push('OPERATOR CLASS');
9746
- break;
9747
- case 'OBJECT_OPFAMILY':
9748
- output.push('OPERATOR FAMILY');
9749
- break;
9750
- case 'OBJECT_OPERATOR':
9751
- output.push('OPERATOR');
9752
- break;
9753
- case 'OBJECT_TYPE':
9754
- output.push('TYPE');
9755
- break;
9756
- case 'OBJECT_COLLATION':
9757
- output.push('COLLATION');
9758
- break;
9759
- case 'OBJECT_CONVERSION':
9760
- output.push('CONVERSION');
9761
- break;
9762
- case 'OBJECT_TSPARSER':
9763
- output.push('TEXT SEARCH PARSER');
9764
- break;
9765
- case 'OBJECT_TSCONFIGURATION':
9766
- output.push('TEXT SEARCH CONFIGURATION');
9767
- break;
9768
- case 'OBJECT_TSTEMPLATE':
9769
- output.push('TEXT SEARCH TEMPLATE');
9770
- break;
9771
- case 'OBJECT_TSDICTIONARY':
9772
- output.push('TEXT SEARCH DICTIONARY');
9773
- break;
9774
- case 'OBJECT_AGGREGATE':
9775
- output.push('AGGREGATE');
9776
- break;
9777
- case 'OBJECT_FOREIGN_TABLE':
9778
- output.push('FOREIGN TABLE');
9779
- break;
9780
- case 'OBJECT_MATVIEW':
9781
- output.push('MATERIALIZED VIEW');
9782
- break;
9783
- default:
9784
- output.push(node.objectType.toString());
10251
+ try {
10252
+ output.push(this.getObjectTypeKeyword(node.objectType));
10253
+ }
10254
+ catch {
10255
+ // Fallback to objectType string if not supported
10256
+ output.push(node.objectType.toString());
9785
10257
  }
9786
10258
  if (node.missing_ok) {
9787
10259
  output.push('IF EXISTS');
@@ -9958,7 +10430,7 @@ export class Deparser {
9958
10430
  CreateForeignTableStmt(node, context) {
9959
10431
  const output = ['CREATE FOREIGN TABLE'];
9960
10432
  if (node.base && node.base.relation) {
9961
- const relationContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10433
+ const relationContext = context.spawn('CreateForeignTableStmt');
9962
10434
  // Handle relation node directly as RangeVar since it contains the RangeVar properties
9963
10435
  output.push(this.RangeVar(node.base.relation, relationContext));
9964
10436
  }
@@ -9988,7 +10460,7 @@ export class Deparser {
9988
10460
  output.push(QuoteUtils.quote(node.servername));
9989
10461
  }
9990
10462
  if (node.options && node.options.length > 0) {
9991
- const foreignTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10463
+ const foreignTableContext = context.spawn('CreateForeignTableStmt');
9992
10464
  const optionStrs = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, foreignTableContext));
9993
10465
  output.push(`OPTIONS (${optionStrs.join(', ')})`);
9994
10466
  }