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.
@@ -5,9 +5,66 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.Deparser = void 0;
8
+ const base_1 = require("./visitors/base");
8
9
  const sql_formatter_1 = require("./utils/sql-formatter");
9
10
  const quote_utils_1 = require("./utils/quote-utils");
10
11
  const list_utils_1 = require("./utils/list-utils");
12
+ /**
13
+ * List of real PostgreSQL built-in types as they appear in pg_catalog.pg_type.typname.
14
+ * These are stored in lowercase in PostgreSQL system catalogs.
15
+ * Use these for lookups, validations, or introspection logic.
16
+ */
17
+ const pgCatalogTypes = [
18
+ // Integers
19
+ 'int2', // smallint
20
+ 'int4', // integer
21
+ 'int8', // bigint
22
+ // Floating-point & numeric
23
+ 'float4', // real
24
+ 'float8', // double precision
25
+ 'numeric', // arbitrary precision (aka "decimal")
26
+ // Text & string
27
+ 'varchar', // variable-length string
28
+ 'char', // internal one-byte type (used in special cases)
29
+ 'bpchar', // blank-padded char(n)
30
+ 'text', // unlimited string
31
+ 'bool', // boolean
32
+ // Dates & times
33
+ 'date', // calendar date
34
+ 'time', // time without time zone
35
+ 'timetz', // time with time zone
36
+ 'timestamp', // timestamp without time zone
37
+ 'timestamptz', // timestamp with time zone
38
+ 'interval', // duration
39
+ // Binary & structured
40
+ 'bytea', // binary data
41
+ 'uuid', // universally unique identifier
42
+ // JSON & XML
43
+ 'json', // textual JSON
44
+ 'jsonb', // binary JSON
45
+ 'xml', // XML format
46
+ // Money & bitstrings
47
+ 'money', // currency value
48
+ 'bit', // fixed-length bit string
49
+ 'varbit', // variable-length bit string
50
+ // Network types
51
+ 'inet', // IPv4 or IPv6 address
52
+ 'cidr', // network address
53
+ 'macaddr', // MAC address (6 bytes)
54
+ 'macaddr8' // MAC address (8 bytes)
55
+ ];
56
+ /**
57
+ * Parser-level type aliases accepted by PostgreSQL SQL syntax,
58
+ * but not present in pg_catalog.pg_type. These are resolved to
59
+ * real types during parsing and never appear in introspection.
60
+ */
61
+ const pgCatalogTypeAliases = [
62
+ ['numeric', ['decimal', 'dec']],
63
+ ['int4', ['int', 'integer']],
64
+ ['float8', ['float']],
65
+ ['bpchar', ['character']],
66
+ ['varchar', ['character varying']]
67
+ ];
11
68
  // Type guards for better type safety
12
69
  function isParseResult(obj) {
13
70
  // A ParseResult is an object that could have stmts (but not required)
@@ -51,11 +108,9 @@ function isWrappedParseResult(obj) {
51
108
  * compatibility and wraps them internally for consistent processing.
52
109
  */
53
110
  class Deparser {
54
- formatter;
55
111
  tree;
56
112
  options;
57
113
  constructor(tree, opts = {}) {
58
- this.formatter = new sql_formatter_1.SqlFormatter(opts.newline, opts.tab, opts.pretty);
59
114
  // Set default options
60
115
  this.options = {
61
116
  functionDelimiter: '$$',
@@ -92,15 +147,17 @@ class Deparser {
92
147
  return new Deparser(query, opts).deparseQuery();
93
148
  }
94
149
  deparseQuery() {
150
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
151
+ const context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
95
152
  return this.tree
96
153
  .map(node => {
97
154
  // All nodes should go through the standard deparse method
98
155
  // which will route to the appropriate handler
99
- const result = this.deparse(node);
156
+ const result = this.deparse(node, context);
100
157
  return result || '';
101
158
  })
102
159
  .filter(result => result !== '')
103
- .join(this.formatter.newline() + this.formatter.newline());
160
+ .join(context.newline() + context.newline());
104
161
  }
105
162
  /**
106
163
  * Get the appropriate function delimiter based on the body content
@@ -114,10 +171,128 @@ class Deparser {
114
171
  }
115
172
  return delimiter;
116
173
  }
117
- deparse(node, context = { parentNodeTypes: [] }) {
174
+ /**
175
+ * Maps ObjectType enum values to their corresponding SQL keywords
176
+ * Used by AlterOwnerStmt, AlterObjectSchemaStmt, and other statements that need object type keywords
177
+ */
178
+ getObjectTypeKeyword(objectType) {
179
+ switch (objectType) {
180
+ case 'OBJECT_TABLE':
181
+ return 'TABLE';
182
+ case 'OBJECT_VIEW':
183
+ return 'VIEW';
184
+ case 'OBJECT_INDEX':
185
+ return 'INDEX';
186
+ case 'OBJECT_SEQUENCE':
187
+ return 'SEQUENCE';
188
+ case 'OBJECT_FUNCTION':
189
+ return 'FUNCTION';
190
+ case 'OBJECT_PROCEDURE':
191
+ return 'PROCEDURE';
192
+ case 'OBJECT_SCHEMA':
193
+ return 'SCHEMA';
194
+ case 'OBJECT_DATABASE':
195
+ return 'DATABASE';
196
+ case 'OBJECT_DOMAIN':
197
+ return 'DOMAIN';
198
+ case 'OBJECT_AGGREGATE':
199
+ return 'AGGREGATE';
200
+ case 'OBJECT_CONVERSION':
201
+ return 'CONVERSION';
202
+ case 'OBJECT_LANGUAGE':
203
+ return 'LANGUAGE';
204
+ case 'OBJECT_OPERATOR':
205
+ return 'OPERATOR';
206
+ case 'OBJECT_OPFAMILY':
207
+ return 'OPERATOR FAMILY';
208
+ case 'OBJECT_OPCLASS':
209
+ return 'OPERATOR CLASS';
210
+ case 'OBJECT_TSDICTIONARY':
211
+ return 'TEXT SEARCH DICTIONARY';
212
+ case 'OBJECT_TSCONFIGURATION':
213
+ return 'TEXT SEARCH CONFIGURATION';
214
+ case 'OBJECT_EVENT_TRIGGER':
215
+ return 'EVENT TRIGGER';
216
+ case 'OBJECT_FDW':
217
+ return 'FOREIGN DATA WRAPPER';
218
+ case 'OBJECT_FOREIGN_SERVER':
219
+ return 'SERVER';
220
+ case 'OBJECT_TYPE':
221
+ return 'TYPE';
222
+ case 'OBJECT_COLLATION':
223
+ return 'COLLATION';
224
+ case 'OBJECT_PUBLICATION':
225
+ return 'PUBLICATION';
226
+ case 'OBJECT_ACCESS_METHOD':
227
+ return 'ACCESS METHOD';
228
+ case 'OBJECT_AMOP':
229
+ return 'OPERATOR CLASS';
230
+ case 'OBJECT_AMPROC':
231
+ return 'OPERATOR CLASS';
232
+ case 'OBJECT_ATTRIBUTE':
233
+ return 'ATTRIBUTE';
234
+ case 'OBJECT_CAST':
235
+ return 'CAST';
236
+ case 'OBJECT_COLUMN':
237
+ return 'COLUMN';
238
+ case 'OBJECT_DEFAULT':
239
+ return 'DEFAULT';
240
+ case 'OBJECT_DEFACL':
241
+ return 'DEFAULT PRIVILEGES';
242
+ case 'OBJECT_DOMCONSTRAINT':
243
+ return 'DOMAIN';
244
+ case 'OBJECT_EXTENSION':
245
+ return 'EXTENSION';
246
+ case 'OBJECT_FOREIGN_TABLE':
247
+ return 'FOREIGN TABLE';
248
+ case 'OBJECT_LARGEOBJECT':
249
+ return 'LARGE OBJECT';
250
+ case 'OBJECT_MATVIEW':
251
+ return 'MATERIALIZED VIEW';
252
+ case 'OBJECT_PARAMETER_ACL':
253
+ return 'PARAMETER';
254
+ case 'OBJECT_POLICY':
255
+ return 'POLICY';
256
+ case 'OBJECT_PUBLICATION_NAMESPACE':
257
+ return 'PUBLICATION';
258
+ case 'OBJECT_PUBLICATION_REL':
259
+ return 'PUBLICATION';
260
+ case 'OBJECT_ROLE':
261
+ return 'ROLE';
262
+ case 'OBJECT_ROUTINE':
263
+ return 'ROUTINE';
264
+ case 'OBJECT_RULE':
265
+ return 'RULE';
266
+ case 'OBJECT_STATISTIC_EXT':
267
+ return 'STATISTICS';
268
+ case 'OBJECT_SUBSCRIPTION':
269
+ return 'SUBSCRIPTION';
270
+ case 'OBJECT_TABCONSTRAINT':
271
+ return 'CONSTRAINT';
272
+ case 'OBJECT_TABLESPACE':
273
+ return 'TABLESPACE';
274
+ case 'OBJECT_TRANSFORM':
275
+ return 'TRANSFORM';
276
+ case 'OBJECT_TRIGGER':
277
+ return 'TRIGGER';
278
+ case 'OBJECT_TSPARSER':
279
+ return 'TEXT SEARCH PARSER';
280
+ case 'OBJECT_TSTEMPLATE':
281
+ return 'TEXT SEARCH TEMPLATE';
282
+ case 'OBJECT_USER_MAPPING':
283
+ return 'USER MAPPING';
284
+ default:
285
+ throw new Error(`Unsupported objectType: ${objectType}`);
286
+ }
287
+ }
288
+ deparse(node, context) {
118
289
  if (node == null) {
119
290
  return null;
120
291
  }
292
+ if (!context) {
293
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
294
+ context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
295
+ }
121
296
  if (typeof node === 'number' || node instanceof Number) {
122
297
  return node.toString();
123
298
  }
@@ -129,7 +304,11 @@ class Deparser {
129
304
  throw new Error(`Error deparsing ${nodeType}: ${error.message}`);
130
305
  }
131
306
  }
132
- visit(node, context = { parentNodeTypes: [] }) {
307
+ visit(node, context) {
308
+ if (!context) {
309
+ const formatter = new sql_formatter_1.SqlFormatter(this.options.newline, this.options.tab, this.options.pretty);
310
+ context = new base_1.DeparserContext({ formatter, prettyMode: this.options.pretty });
311
+ }
133
312
  const nodeType = this.getNodeType(node);
134
313
  // Handle empty objects
135
314
  if (!nodeType) {
@@ -138,11 +317,7 @@ class Deparser {
138
317
  const nodeData = this.getNodeData(node);
139
318
  const methodName = nodeType;
140
319
  if (typeof this[methodName] === 'function') {
141
- const childContext = {
142
- ...context,
143
- parentNodeTypes: [...context.parentNodeTypes, nodeType]
144
- };
145
- const result = this[methodName](nodeData, childContext);
320
+ const result = this[methodName](nodeData, context);
146
321
  return result;
147
322
  }
148
323
  throw new Error(`Deparser does not handle node type: ${nodeType}`);
@@ -168,7 +343,7 @@ class Deparser {
168
343
  .filter((rawStmt) => rawStmt != null)
169
344
  .map((rawStmt) => this.RawStmt(rawStmt, context))
170
345
  .filter((result) => result !== '')
171
- .join(this.formatter.newline() + this.formatter.newline());
346
+ .join(context.newline() + context.newline());
172
347
  }
173
348
  RawStmt(node, context) {
174
349
  if (!node.stmt) {
@@ -188,7 +363,7 @@ class Deparser {
188
363
  }
189
364
  if (!node.op || node.op === 'SETOP_NONE') {
190
365
  if (node.valuesLists == null) {
191
- if (!this.formatter.isPretty() || !node.targetList) {
366
+ if (!context.isPretty() || !node.targetList) {
192
367
  output.push('SELECT');
193
368
  }
194
369
  }
@@ -208,7 +383,7 @@ class Deparser {
208
383
  (node.rarg).limitOffset ||
209
384
  (node.rarg).withClause);
210
385
  if (leftNeedsParens) {
211
- output.push(this.formatter.parens(leftStmt));
386
+ output.push(context.parens(leftStmt));
212
387
  }
213
388
  else {
214
389
  output.push(leftStmt);
@@ -230,7 +405,7 @@ class Deparser {
230
405
  output.push('ALL');
231
406
  }
232
407
  if (rightNeedsParens) {
233
- output.push(this.formatter.parens(rightStmt));
408
+ output.push(context.parens(rightStmt));
234
409
  }
235
410
  else {
236
411
  output.push(rightStmt);
@@ -242,20 +417,20 @@ class Deparser {
242
417
  const distinctClause = list_utils_1.ListUtils.unwrapList(node.distinctClause);
243
418
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
244
419
  const clause = distinctClause
245
- .map(e => this.visit(e, { ...context, select: true }))
420
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
246
421
  .join(', ');
247
- distinctPart = ' DISTINCT ON ' + this.formatter.parens(clause);
422
+ distinctPart = ' DISTINCT ON ' + context.parens(clause);
248
423
  }
249
424
  else {
250
425
  distinctPart = ' DISTINCT';
251
426
  }
252
- if (!this.formatter.isPretty()) {
427
+ if (!context.isPretty()) {
253
428
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
254
429
  output.push('DISTINCT ON');
255
430
  const clause = distinctClause
256
- .map(e => this.visit(e, { ...context, select: true }))
431
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
257
432
  .join(', ');
258
- output.push(this.formatter.parens(clause));
433
+ output.push(context.parens(clause));
259
434
  }
260
435
  else {
261
436
  output.push('DISTINCT');
@@ -264,22 +439,41 @@ class Deparser {
264
439
  }
265
440
  if (node.targetList) {
266
441
  const targetList = list_utils_1.ListUtils.unwrapList(node.targetList);
267
- if (this.formatter.isPretty()) {
268
- const targetStrings = targetList
269
- .map(e => {
270
- const targetStr = this.visit(e, { ...context, select: true });
271
- if (this.containsMultilineStringLiteral(targetStr)) {
272
- return targetStr;
442
+ if (context.isPretty()) {
443
+ if (targetList.length === 1) {
444
+ const targetNode = targetList[0];
445
+ const target = this.visit(targetNode, context.spawn('SelectStmt', { select: true }));
446
+ // Check if single target is complex - if so, use multiline format
447
+ if (this.isComplexSelectTarget(targetNode)) {
448
+ output.push('SELECT' + distinctPart);
449
+ if (this.containsMultilineStringLiteral(target)) {
450
+ output.push(target);
451
+ }
452
+ else {
453
+ output.push(context.indent(target));
454
+ }
273
455
  }
274
- return this.formatter.indent(targetStr);
275
- });
276
- const formattedTargets = targetStrings.join(',' + this.formatter.newline());
277
- output.push('SELECT' + distinctPart);
278
- output.push(formattedTargets);
456
+ else {
457
+ output.push('SELECT' + distinctPart + ' ' + target);
458
+ }
459
+ }
460
+ else {
461
+ const targetStrings = targetList
462
+ .map(e => {
463
+ const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
464
+ if (this.containsMultilineStringLiteral(targetStr)) {
465
+ return targetStr;
466
+ }
467
+ return context.indent(targetStr);
468
+ });
469
+ const formattedTargets = targetStrings.join(',' + context.newline());
470
+ output.push('SELECT' + distinctPart);
471
+ output.push(formattedTargets);
472
+ }
279
473
  }
280
474
  else {
281
475
  const targets = targetList
282
- .map(e => this.visit(e, { ...context, select: true }))
476
+ .map(e => this.visit(e, context.spawn('SelectStmt', { select: true })))
283
477
  .join(', ');
284
478
  output.push(targets);
285
479
  }
@@ -291,22 +485,22 @@ class Deparser {
291
485
  if (node.fromClause) {
292
486
  const fromList = list_utils_1.ListUtils.unwrapList(node.fromClause);
293
487
  const fromItems = fromList
294
- .map(e => this.deparse(e, { ...context, from: true }))
488
+ .map(e => this.deparse(e, context.spawn('SelectStmt', { from: true })))
295
489
  .join(', ');
296
490
  output.push('FROM ' + fromItems.trim());
297
491
  }
298
492
  if (node.whereClause) {
299
- if (this.formatter.isPretty()) {
493
+ if (context.isPretty()) {
300
494
  output.push('WHERE');
301
495
  const whereExpr = this.visit(node.whereClause, context);
302
- const lines = whereExpr.split(this.formatter.newline());
496
+ const lines = whereExpr.split(context.newline());
303
497
  const indentedLines = lines.map((line, index) => {
304
498
  if (index === 0) {
305
- return this.formatter.indent(line);
499
+ return context.indent(line);
306
500
  }
307
501
  return line;
308
502
  });
309
- output.push(indentedLines.join(this.formatter.newline()));
503
+ output.push(indentedLines.join(context.newline()));
310
504
  }
311
505
  else {
312
506
  output.push('WHERE');
@@ -314,45 +508,61 @@ class Deparser {
314
508
  }
315
509
  }
316
510
  if (node.valuesLists) {
317
- output.push('VALUES');
318
- const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
319
- const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
320
- return this.formatter.parens(values.join(', '));
321
- });
322
- output.push(lists.join(', '));
511
+ if (context.isPretty()) {
512
+ output.push('VALUES');
513
+ const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
514
+ const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
515
+ return context.parens(values.join(', '));
516
+ });
517
+ const indentedTuples = lists.map(tuple => {
518
+ if (this.containsMultilineStringLiteral(tuple)) {
519
+ return tuple;
520
+ }
521
+ return context.indent(tuple);
522
+ });
523
+ output.push(indentedTuples.join(',\n'));
524
+ }
525
+ else {
526
+ output.push('VALUES');
527
+ const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
528
+ const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
529
+ return context.parens(values.join(', '));
530
+ });
531
+ output.push(lists.join(', '));
532
+ }
323
533
  }
324
534
  if (node.groupClause) {
325
535
  const groupList = list_utils_1.ListUtils.unwrapList(node.groupClause);
326
- if (this.formatter.isPretty()) {
536
+ if (context.isPretty()) {
327
537
  const groupItems = groupList
328
538
  .map(e => {
329
- const groupStr = this.visit(e, { ...context, group: true });
539
+ const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
330
540
  if (this.containsMultilineStringLiteral(groupStr)) {
331
541
  return groupStr;
332
542
  }
333
- return this.formatter.indent(groupStr);
543
+ return context.indent(groupStr);
334
544
  })
335
- .join(',' + this.formatter.newline());
545
+ .join(',' + context.newline());
336
546
  output.push('GROUP BY');
337
547
  output.push(groupItems);
338
548
  }
339
549
  else {
340
550
  output.push('GROUP BY');
341
551
  const groupItems = groupList
342
- .map(e => this.visit(e, { ...context, group: true }))
552
+ .map(e => this.visit(e, context.spawn('SelectStmt', { group: true })))
343
553
  .join(', ');
344
554
  output.push(groupItems);
345
555
  }
346
556
  }
347
557
  if (node.havingClause) {
348
- if (this.formatter.isPretty()) {
558
+ if (context.isPretty()) {
349
559
  output.push('HAVING');
350
560
  const havingStr = this.visit(node.havingClause, context);
351
561
  if (this.containsMultilineStringLiteral(havingStr)) {
352
562
  output.push(havingStr);
353
563
  }
354
564
  else {
355
- output.push(this.formatter.indent(havingStr));
565
+ output.push(context.indent(havingStr));
356
566
  }
357
567
  }
358
568
  else {
@@ -370,23 +580,23 @@ class Deparser {
370
580
  }
371
581
  if (node.sortClause) {
372
582
  const sortList = list_utils_1.ListUtils.unwrapList(node.sortClause);
373
- if (this.formatter.isPretty()) {
583
+ if (context.isPretty()) {
374
584
  const sortItems = sortList
375
585
  .map(e => {
376
- const sortStr = this.visit(e, { ...context, sort: true });
586
+ const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
377
587
  if (this.containsMultilineStringLiteral(sortStr)) {
378
588
  return sortStr;
379
589
  }
380
- return this.formatter.indent(sortStr);
590
+ return context.indent(sortStr);
381
591
  })
382
- .join(',' + this.formatter.newline());
592
+ .join(',' + context.newline());
383
593
  output.push('ORDER BY');
384
594
  output.push(sortItems);
385
595
  }
386
596
  else {
387
597
  output.push('ORDER BY');
388
598
  const sortItems = sortList
389
- .map(e => this.visit(e, { ...context, sort: true }))
599
+ .map(e => this.visit(e, context.spawn('SelectStmt', { sort: true })))
390
600
  .join(', ');
391
601
  output.push(sortItems);
392
602
  }
@@ -404,9 +614,9 @@ class Deparser {
404
614
  .join(' ');
405
615
  output.push(lockingClauses);
406
616
  }
407
- if (this.formatter.isPretty()) {
617
+ if (context.isPretty()) {
408
618
  const filteredOutput = output.filter(item => item.trim() !== '');
409
- return filteredOutput.join(this.formatter.newline());
619
+ return filteredOutput.join(context.newline());
410
620
  }
411
621
  return output.join(' ');
412
622
  }
@@ -418,13 +628,13 @@ class Deparser {
418
628
  switch (kind) {
419
629
  case 'AEXPR_OP':
420
630
  if (lexpr && rexpr) {
421
- const operator = this.deparseOperatorName(name);
631
+ const operator = this.deparseOperatorName(name, context);
422
632
  let leftExpr = this.visit(lexpr, context);
423
633
  let rightExpr = this.visit(rexpr, context);
424
634
  // Check if left expression needs parentheses
425
635
  let leftNeedsParens = false;
426
636
  if (lexpr && 'A_Expr' in lexpr && lexpr.A_Expr?.kind === 'AEXPR_OP') {
427
- const leftOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(lexpr.A_Expr.name));
637
+ const leftOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(lexpr.A_Expr.name), context);
428
638
  if (this.needsParentheses(leftOp, operator, 'left')) {
429
639
  leftNeedsParens = true;
430
640
  }
@@ -433,12 +643,12 @@ class Deparser {
433
643
  leftNeedsParens = true;
434
644
  }
435
645
  if (leftNeedsParens) {
436
- leftExpr = this.formatter.parens(leftExpr);
646
+ leftExpr = context.parens(leftExpr);
437
647
  }
438
648
  // Check if right expression needs parentheses
439
649
  let rightNeedsParens = false;
440
650
  if (rexpr && 'A_Expr' in rexpr && rexpr.A_Expr?.kind === 'AEXPR_OP') {
441
- const rightOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(rexpr.A_Expr.name));
651
+ const rightOp = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(rexpr.A_Expr.name), context);
442
652
  if (this.needsParentheses(rightOp, operator, 'right')) {
443
653
  rightNeedsParens = true;
444
654
  }
@@ -447,42 +657,42 @@ class Deparser {
447
657
  rightNeedsParens = true;
448
658
  }
449
659
  if (rightNeedsParens) {
450
- rightExpr = this.formatter.parens(rightExpr);
660
+ rightExpr = context.parens(rightExpr);
451
661
  }
452
- return this.formatter.format([leftExpr, operator, rightExpr]);
662
+ return context.format([leftExpr, operator, rightExpr]);
453
663
  }
454
664
  else if (rexpr) {
455
- return this.formatter.format([
456
- this.deparseOperatorName(name),
665
+ return context.format([
666
+ this.deparseOperatorName(name, context),
457
667
  this.visit(rexpr, context)
458
668
  ]);
459
669
  }
460
670
  break;
461
671
  case 'AEXPR_OP_ANY':
462
- return this.formatter.format([
672
+ return context.format([
463
673
  this.visit(lexpr, context),
464
- this.deparseOperatorName(name),
674
+ this.deparseOperatorName(name, context),
465
675
  'ANY',
466
- this.formatter.parens(this.visit(rexpr, context))
676
+ context.parens(this.visit(rexpr, context))
467
677
  ]);
468
678
  case 'AEXPR_OP_ALL':
469
- return this.formatter.format([
679
+ return context.format([
470
680
  this.visit(lexpr, context),
471
- this.deparseOperatorName(name),
681
+ this.deparseOperatorName(name, context),
472
682
  'ALL',
473
- this.formatter.parens(this.visit(rexpr, context))
683
+ context.parens(this.visit(rexpr, context))
474
684
  ]);
475
685
  case 'AEXPR_DISTINCT': {
476
686
  let leftExpr = this.visit(lexpr, context);
477
687
  let rightExpr = this.visit(rexpr, context);
478
688
  // Add parentheses for complex expressions
479
689
  if (lexpr && this.isComplexExpression(lexpr)) {
480
- leftExpr = this.formatter.parens(leftExpr);
690
+ leftExpr = context.parens(leftExpr);
481
691
  }
482
692
  if (rexpr && this.isComplexExpression(rexpr)) {
483
- rightExpr = this.formatter.parens(rightExpr);
693
+ rightExpr = context.parens(rightExpr);
484
694
  }
485
- return this.formatter.format([
695
+ return context.format([
486
696
  leftExpr,
487
697
  'IS DISTINCT FROM',
488
698
  rightExpr
@@ -493,75 +703,75 @@ class Deparser {
493
703
  let rightExpr = this.visit(rexpr, context);
494
704
  // Add parentheses for complex expressions
495
705
  if (lexpr && this.isComplexExpression(lexpr)) {
496
- leftExpr = this.formatter.parens(leftExpr);
706
+ leftExpr = context.parens(leftExpr);
497
707
  }
498
708
  if (rexpr && this.isComplexExpression(rexpr)) {
499
- rightExpr = this.formatter.parens(rightExpr);
709
+ rightExpr = context.parens(rightExpr);
500
710
  }
501
- return this.formatter.format([
711
+ return context.format([
502
712
  leftExpr,
503
713
  'IS NOT DISTINCT FROM',
504
714
  rightExpr
505
715
  ]);
506
716
  }
507
717
  case 'AEXPR_NULLIF':
508
- return this.formatter.format([
718
+ return context.format([
509
719
  'NULLIF',
510
- this.formatter.parens([
720
+ context.parens([
511
721
  this.visit(lexpr, context),
512
722
  this.visit(rexpr, context)
513
723
  ].join(', '))
514
724
  ]);
515
725
  case 'AEXPR_IN':
516
- const inOperator = this.deparseOperatorName(name);
726
+ const inOperator = this.deparseOperatorName(name, context);
517
727
  if (inOperator === '<>' || inOperator === '!=') {
518
- return this.formatter.format([
728
+ return context.format([
519
729
  this.visit(lexpr, context),
520
730
  'NOT IN',
521
- this.formatter.parens(this.visit(rexpr, context))
731
+ context.parens(this.visit(rexpr, context))
522
732
  ]);
523
733
  }
524
734
  else {
525
- return this.formatter.format([
735
+ return context.format([
526
736
  this.visit(lexpr, context),
527
737
  'IN',
528
- this.formatter.parens(this.visit(rexpr, context))
738
+ context.parens(this.visit(rexpr, context))
529
739
  ]);
530
740
  }
531
741
  case 'AEXPR_LIKE':
532
- const likeOp = this.deparseOperatorName(name);
742
+ const likeOp = this.deparseOperatorName(name, context);
533
743
  if (likeOp === '!~~') {
534
- return this.formatter.format([
744
+ return context.format([
535
745
  this.visit(lexpr, context),
536
746
  'NOT LIKE',
537
747
  this.visit(rexpr, context)
538
748
  ]);
539
749
  }
540
750
  else {
541
- return this.formatter.format([
751
+ return context.format([
542
752
  this.visit(lexpr, context),
543
753
  'LIKE',
544
754
  this.visit(rexpr, context)
545
755
  ]);
546
756
  }
547
757
  case 'AEXPR_ILIKE':
548
- const ilikeOp = this.deparseOperatorName(name);
758
+ const ilikeOp = this.deparseOperatorName(name, context);
549
759
  if (ilikeOp === '!~~*') {
550
- return this.formatter.format([
760
+ return context.format([
551
761
  this.visit(lexpr, context),
552
762
  'NOT ILIKE',
553
763
  this.visit(rexpr, context)
554
764
  ]);
555
765
  }
556
766
  else {
557
- return this.formatter.format([
767
+ return context.format([
558
768
  this.visit(lexpr, context),
559
769
  'ILIKE',
560
770
  this.visit(rexpr, context)
561
771
  ]);
562
772
  }
563
773
  case 'AEXPR_SIMILAR':
564
- const similarOp = this.deparseOperatorName(name);
774
+ const similarOp = this.deparseOperatorName(name, context);
565
775
  let rightExpr;
566
776
  if (rexpr && 'FuncCall' in rexpr &&
567
777
  rexpr.FuncCall?.funcname?.length === 2 &&
@@ -577,39 +787,39 @@ class Deparser {
577
787
  rightExpr = this.visit(rexpr, context);
578
788
  }
579
789
  if (similarOp === '!~') {
580
- return this.formatter.format([
790
+ return context.format([
581
791
  this.visit(lexpr, context),
582
792
  'NOT SIMILAR TO',
583
793
  rightExpr
584
794
  ]);
585
795
  }
586
796
  else {
587
- return this.formatter.format([
797
+ return context.format([
588
798
  this.visit(lexpr, context),
589
799
  'SIMILAR TO',
590
800
  rightExpr
591
801
  ]);
592
802
  }
593
803
  case 'AEXPR_BETWEEN':
594
- return this.formatter.format([
804
+ return context.format([
595
805
  this.visit(lexpr, context),
596
806
  'BETWEEN',
597
807
  this.visitBetweenRange(rexpr, context)
598
808
  ]);
599
809
  case 'AEXPR_NOT_BETWEEN':
600
- return this.formatter.format([
810
+ return context.format([
601
811
  this.visit(lexpr, context),
602
812
  'NOT BETWEEN',
603
813
  this.visitBetweenRange(rexpr, context)
604
814
  ]);
605
815
  case 'AEXPR_BETWEEN_SYM':
606
- return this.formatter.format([
816
+ return context.format([
607
817
  this.visit(lexpr, context),
608
818
  'BETWEEN SYMMETRIC',
609
819
  this.visitBetweenRange(rexpr, context)
610
820
  ]);
611
821
  case 'AEXPR_NOT_BETWEEN_SYM':
612
- return this.formatter.format([
822
+ return context.format([
613
823
  this.visit(lexpr, context),
614
824
  'NOT BETWEEN SYMMETRIC',
615
825
  this.visitBetweenRange(rexpr, context)
@@ -617,7 +827,7 @@ class Deparser {
617
827
  }
618
828
  throw new Error(`Unhandled A_Expr kind: ${kind}`);
619
829
  }
620
- deparseOperatorName(name) {
830
+ deparseOperatorName(name, context) {
621
831
  if (!name || name.length === 0) {
622
832
  return '';
623
833
  }
@@ -625,7 +835,7 @@ class Deparser {
625
835
  if (n.String) {
626
836
  return n.String.sval || n.String.str;
627
837
  }
628
- return this.visit(n, { parentNodeTypes: [] });
838
+ return this.visit(n, context);
629
839
  });
630
840
  if (parts.length > 1) {
631
841
  return `OPERATOR(${parts.join('.')})`;
@@ -688,6 +898,64 @@ class Deparser {
688
898
  node.SubLink ||
689
899
  node.A_Expr);
690
900
  }
901
+ isComplexSelectTarget(node) {
902
+ if (!node)
903
+ return false;
904
+ if (node.ResTarget?.val) {
905
+ return this.isComplexExpression(node.ResTarget.val);
906
+ }
907
+ // Always complex: CASE expressions
908
+ if (node.CaseExpr)
909
+ return true;
910
+ // Always complex: Subqueries and subselects
911
+ if (node.SubLink)
912
+ return true;
913
+ // Always complex: Boolean tests and expressions
914
+ if (node.NullTest || node.BooleanTest || node.BoolExpr)
915
+ return true;
916
+ // COALESCE and similar functions - complex if multiple arguments
917
+ if (node.CoalesceExpr) {
918
+ const args = node.CoalesceExpr.args;
919
+ if (args && Array.isArray(args) && args.length > 1)
920
+ return true;
921
+ }
922
+ // Function calls - complex if multiple args or has clauses
923
+ if (node.FuncCall) {
924
+ const funcCall = node.FuncCall;
925
+ const args = funcCall.args ? (Array.isArray(funcCall.args) ? funcCall.args : [funcCall.args]) : [];
926
+ // Complex if has window clause, filter, order by, etc.
927
+ if (funcCall.over || funcCall.agg_filter || funcCall.agg_order || funcCall.agg_distinct) {
928
+ return true;
929
+ }
930
+ // Complex if multiple arguments
931
+ if (args.length > 1)
932
+ return true;
933
+ if (args.length === 1) {
934
+ return this.isComplexSelectTarget(args[0]);
935
+ }
936
+ }
937
+ if (node.A_Expr) {
938
+ const expr = node.A_Expr;
939
+ // Check if operands are complex
940
+ if (expr.lexpr && this.isComplexSelectTarget(expr.lexpr))
941
+ return true;
942
+ if (expr.rexpr && this.isComplexSelectTarget(expr.rexpr))
943
+ return true;
944
+ return false;
945
+ }
946
+ if (node.TypeCast) {
947
+ return this.isComplexSelectTarget(node.TypeCast.arg);
948
+ }
949
+ if (node.A_ArrayExpr)
950
+ return true;
951
+ if (node.A_Indirection) {
952
+ return this.isComplexSelectTarget(node.A_Indirection.arg);
953
+ }
954
+ if (node.A_Const || node.ColumnRef || node.ParamRef || node.A_Star) {
955
+ return false;
956
+ }
957
+ return false;
958
+ }
691
959
  visitBetweenRange(rexpr, context) {
692
960
  if (rexpr && 'List' in rexpr && rexpr.List?.items) {
693
961
  const items = rexpr.List.items.map((item) => this.visit(item, context));
@@ -704,9 +972,16 @@ class Deparser {
704
972
  output.push(this.RangeVar(node.relation, context));
705
973
  if (node.cols) {
706
974
  const cols = list_utils_1.ListUtils.unwrapList(node.cols);
707
- const insertContext = { ...context, insertColumns: true };
975
+ const insertContext = context.spawn('InsertStmt', { insertColumns: true });
708
976
  const columnNames = cols.map(col => this.visit(col, insertContext));
709
- output.push(this.formatter.parens(columnNames.join(', ')));
977
+ if (context.isPretty()) {
978
+ // Always format columns in multiline parentheses for pretty printing
979
+ const indentedColumns = columnNames.map(col => context.indent(col));
980
+ output.push('(\n' + indentedColumns.join(',\n') + '\n)');
981
+ }
982
+ else {
983
+ output.push(context.parens(columnNames.join(', ')));
984
+ }
710
985
  }
711
986
  if (node.selectStmt) {
712
987
  output.push(this.visit(node.selectStmt, context));
@@ -727,7 +1002,7 @@ class Deparser {
727
1002
  else if (infer.indexElems) {
728
1003
  const elems = list_utils_1.ListUtils.unwrapList(infer.indexElems);
729
1004
  const indexElems = elems.map(elem => this.visit(elem, context));
730
- output.push(this.formatter.parens(indexElems.join(', ')));
1005
+ output.push(context.parens(indexElems.join(', ')));
731
1006
  }
732
1007
  // Handle WHERE clause for conflict detection
733
1008
  if (infer.whereClause) {
@@ -743,12 +1018,12 @@ class Deparser {
743
1018
  if (firstTarget.ResTarget?.val?.MultiAssignRef && targetList.every(target => target.ResTarget?.val?.MultiAssignRef)) {
744
1019
  const sortedTargets = targetList.sort((a, b) => a.ResTarget.val.MultiAssignRef.colno - b.ResTarget.val.MultiAssignRef.colno);
745
1020
  const names = sortedTargets.map(target => target.ResTarget.name);
746
- output.push(this.formatter.parens(names.join(', ')));
1021
+ output.push(context.parens(names.join(', ')));
747
1022
  output.push('=');
748
1023
  output.push(this.visit(firstTarget.ResTarget.val.MultiAssignRef.source, context));
749
1024
  }
750
1025
  else {
751
- const updateContext = { ...context, update: true };
1026
+ const updateContext = context.spawn('UpdateStmt', { update: true });
752
1027
  const targets = targetList.map(target => this.visit(target, updateContext));
753
1028
  output.push(targets.join(', '));
754
1029
  }
@@ -802,12 +1077,12 @@ class Deparser {
802
1077
  }
803
1078
  }
804
1079
  const names = relatedTargets.map(t => t.ResTarget.name);
805
- const multiAssignment = `${this.formatter.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
1080
+ const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
806
1081
  assignmentParts.push(multiAssignment);
807
1082
  }
808
1083
  else {
809
1084
  // Handle regular single-column assignment
810
- assignmentParts.push(this.visit(target, { ...context, update: true }));
1085
+ assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
811
1086
  processedTargets.add(i);
812
1087
  }
813
1088
  }
@@ -899,14 +1174,14 @@ class Deparser {
899
1174
  }
900
1175
  if (node.ctes && node.ctes.length > 0) {
901
1176
  const ctes = list_utils_1.ListUtils.unwrapList(node.ctes);
902
- if (this.formatter.isPretty()) {
1177
+ if (context.isPretty()) {
903
1178
  const cteStrings = ctes.map((cte, index) => {
904
1179
  const cteStr = this.visit(cte, context);
905
- const prefix = index === 0 ? this.formatter.newline() : ',' + this.formatter.newline();
1180
+ const prefix = index === 0 ? context.newline() : ',' + context.newline();
906
1181
  if (this.containsMultilineStringLiteral(cteStr)) {
907
1182
  return prefix + cteStr;
908
1183
  }
909
- return prefix + this.formatter.indent(cteStr);
1184
+ return prefix + context.indent(cteStr);
910
1185
  });
911
1186
  output.push(cteStrings.join(''));
912
1187
  }
@@ -992,14 +1267,14 @@ class Deparser {
992
1267
  if (context.bool) {
993
1268
  formatStr = '(%s)';
994
1269
  }
995
- const boolContext = { ...context, bool: true };
1270
+ const boolContext = context.spawn('BoolExpr', { bool: true });
996
1271
  // explanation of our syntax/fix below:
997
1272
  // return formatStr.replace('%s', andArgs); // ❌ Interprets $ as special syntax
998
1273
  // return formatStr.replace('%s', () => andArgs); // ✅ Function callback prevents interpretation
999
1274
  switch (boolop) {
1000
1275
  case 'AND_EXPR':
1001
- if (this.formatter.isPretty() && args.length > 1) {
1002
- const andArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' AND ');
1276
+ if (context.isPretty() && args.length > 1) {
1277
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('AND '));
1003
1278
  return formatStr.replace('%s', () => andArgs);
1004
1279
  }
1005
1280
  else {
@@ -1007,8 +1282,8 @@ class Deparser {
1007
1282
  return formatStr.replace('%s', () => andArgs);
1008
1283
  }
1009
1284
  case 'OR_EXPR':
1010
- if (this.formatter.isPretty() && args.length > 1) {
1011
- const orArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' OR ');
1285
+ if (context.isPretty() && args.length > 1) {
1286
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(context.newline() + context.indent('OR '));
1012
1287
  return formatStr.replace('%s', () => orArgs);
1013
1288
  }
1014
1289
  else {
@@ -1139,9 +1414,9 @@ class Deparser {
1139
1414
  const timezone = this.visit(args[0], context);
1140
1415
  // Add parentheses around timestamp if it contains arithmetic operations
1141
1416
  if (args[1] && 'A_Expr' in args[1] && args[1].A_Expr?.kind === 'AEXPR_OP') {
1142
- const op = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(args[1].A_Expr.name));
1417
+ const op = this.deparseOperatorName(list_utils_1.ListUtils.unwrapList(args[1].A_Expr.name), context);
1143
1418
  if (op === '+' || op === '-' || op === '*' || op === '/') {
1144
- timestamp = this.formatter.parens(timestamp);
1419
+ timestamp = context.parens(timestamp);
1145
1420
  }
1146
1421
  }
1147
1422
  return `${timestamp} AT TIME ZONE ${timezone}`;
@@ -1211,14 +1486,14 @@ class Deparser {
1211
1486
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1212
1487
  }
1213
1488
  // Handle window frame specifications using the dedicated formatWindowFrame method
1214
- const frameClause = this.formatWindowFrame(node.over);
1489
+ const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1215
1490
  if (frameClause) {
1216
1491
  windowParts.push(frameClause);
1217
1492
  }
1218
1493
  if (windowParts.length > 0) {
1219
- if (this.formatter.isPretty() && windowParts.length > 1) {
1220
- const formattedParts = windowParts.map(part => this.formatter.indent(part));
1221
- result += ` OVER (${this.formatter.newline()}${formattedParts.join(this.formatter.newline())}${this.formatter.newline()})`;
1494
+ if (context.isPretty() && windowParts.length > 1) {
1495
+ const formattedParts = windowParts.map(part => context.indent(part));
1496
+ result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1222
1497
  }
1223
1498
  else {
1224
1499
  result += ` OVER (${windowParts.join(' ')})`;
@@ -1498,9 +1773,6 @@ class Deparser {
1498
1773
  return output.join(' ');
1499
1774
  }
1500
1775
  if (catalog === 'pg_catalog') {
1501
- const builtinTypes = ['int2', 'int4', 'int8', 'float4', 'float8', 'numeric', 'decimal',
1502
- 'varchar', 'char', 'bpchar', 'text', 'bool', 'date', 'time', 'timestamp',
1503
- 'timestamptz', 'interval', 'bytea', 'uuid', 'json', 'jsonb'];
1504
1776
  let typeName = `${catalog}.${type}`;
1505
1777
  if (type === 'bpchar' && args) {
1506
1778
  typeName = 'char';
@@ -1557,6 +1829,24 @@ class Deparser {
1557
1829
  typeName = 'time with time zone';
1558
1830
  }
1559
1831
  }
1832
+ else if (type === 'timestamp') {
1833
+ if (args) {
1834
+ typeName = `timestamp(${args})`;
1835
+ args = null; // Don't apply args again in mods()
1836
+ }
1837
+ else {
1838
+ typeName = 'timestamp';
1839
+ }
1840
+ }
1841
+ else if (type === 'time') {
1842
+ if (args) {
1843
+ typeName = `time(${args})`;
1844
+ args = null; // Don't apply args again in mods()
1845
+ }
1846
+ else {
1847
+ typeName = 'time';
1848
+ }
1849
+ }
1560
1850
  let result = mods(typeName, args);
1561
1851
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1562
1852
  result += formatArrayBounds(node.arrayBounds);
@@ -1586,7 +1876,7 @@ class Deparser {
1586
1876
  }
1587
1877
  return this.quoteIfNeeded(colStr);
1588
1878
  });
1589
- output.push('AS', this.quoteIfNeeded(name) + this.formatter.parens(quotedColnames.join(', ')));
1879
+ output.push('AS', this.quoteIfNeeded(name) + context.parens(quotedColnames.join(', ')));
1590
1880
  }
1591
1881
  else {
1592
1882
  output.push('AS', this.quoteIfNeeded(name));
@@ -1598,7 +1888,7 @@ class Deparser {
1598
1888
  // Handle ONLY keyword for inheritance control (but not for type definitions, ALTER TYPE, or CREATE FOREIGN TABLE)
1599
1889
  if (node && (!('inh' in node) || node.inh === undefined) &&
1600
1890
  !context.parentNodeTypes.includes('CompositeTypeStmt') &&
1601
- !context.parentNodeTypes.includes('AlterTypeStmt') &&
1891
+ (!context.parentNodeTypes.includes('AlterTypeStmt') && context.objtype !== 'OBJECT_TYPE') &&
1602
1892
  !context.parentNodeTypes.includes('CreateForeignTableStmt')) {
1603
1893
  output.push('ONLY');
1604
1894
  }
@@ -1795,6 +2085,18 @@ class Deparser {
1795
2085
  return `pg_catalog.${typeName}`;
1796
2086
  }
1797
2087
  }
2088
+ isPgCatalogType(typeName) {
2089
+ const cleanTypeName = typeName.replace(/^pg_catalog\./, '');
2090
+ if (pgCatalogTypes.includes(cleanTypeName)) {
2091
+ return true;
2092
+ }
2093
+ for (const [realType, aliases] of pgCatalogTypeAliases) {
2094
+ if (aliases.includes(cleanTypeName)) {
2095
+ return true;
2096
+ }
2097
+ }
2098
+ return false;
2099
+ }
1798
2100
  A_ArrayExpr(node, context) {
1799
2101
  const elements = list_utils_1.ListUtils.unwrapList(node.elements);
1800
2102
  const elementStrs = elements.map(el => this.visit(el, context));
@@ -1846,26 +2148,26 @@ class Deparser {
1846
2148
  output.push(this.visit(node.arg, context));
1847
2149
  }
1848
2150
  const args = list_utils_1.ListUtils.unwrapList(node.args);
1849
- if (this.formatter.isPretty() && args.length > 0) {
2151
+ if (context.isPretty() && args.length > 0) {
1850
2152
  for (const arg of args) {
1851
2153
  const whenClause = this.visit(arg, context);
1852
2154
  if (this.containsMultilineStringLiteral(whenClause)) {
1853
- output.push(this.formatter.newline() + whenClause);
2155
+ output.push(context.newline() + whenClause);
1854
2156
  }
1855
2157
  else {
1856
- output.push(this.formatter.newline() + this.formatter.indent(whenClause));
2158
+ output.push(context.newline() + context.indent(whenClause));
1857
2159
  }
1858
2160
  }
1859
2161
  if (node.defresult) {
1860
2162
  const elseResult = this.visit(node.defresult, context);
1861
2163
  if (this.containsMultilineStringLiteral(elseResult)) {
1862
- output.push(this.formatter.newline() + 'ELSE ' + elseResult);
2164
+ output.push(context.newline() + 'ELSE ' + elseResult);
1863
2165
  }
1864
2166
  else {
1865
- output.push(this.formatter.newline() + this.formatter.indent('ELSE ' + elseResult));
2167
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
1866
2168
  }
1867
2169
  }
1868
- output.push(this.formatter.newline() + 'END');
2170
+ output.push(context.newline() + 'END');
1869
2171
  return output.join(' ');
1870
2172
  }
1871
2173
  else {
@@ -1888,28 +2190,32 @@ class Deparser {
1888
2190
  TypeCast(node, context) {
1889
2191
  const arg = this.visit(node.arg, context);
1890
2192
  const typeName = this.TypeName(node.typeName, context);
1891
- // Check if this is a bpchar typecast that should use traditional char syntax
1892
- if (typeName === 'bpchar' && node.typeName && node.typeName.names) {
1893
- const names = list_utils_1.ListUtils.unwrapList(node.typeName.names);
1894
- if (names.length === 2 &&
1895
- names[0].String?.sval === 'pg_catalog' &&
1896
- names[1].String?.sval === 'bpchar') {
1897
- return `char ${arg}`;
2193
+ // Check if this is a bpchar typecast that should preserve original syntax for AST consistency
2194
+ if (typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
2195
+ const names = node.typeName?.names;
2196
+ const isQualifiedBpchar = names && names.length === 2 &&
2197
+ names[0]?.String?.sval === 'pg_catalog' &&
2198
+ names[1]?.String?.sval === 'bpchar';
2199
+ if (isQualifiedBpchar) {
2200
+ return `CAST(${arg} AS ${typeName})`;
1898
2201
  }
1899
2202
  }
1900
- // Check if the argument is a complex expression that should preserve CAST syntax
1901
- const argType = this.getNodeType(node.arg);
1902
- const isComplexExpression = argType === 'A_Expr' || argType === 'FuncCall' || argType === 'OpExpr';
1903
- if (!isComplexExpression && (typeName.startsWith('interval') ||
1904
- typeName.startsWith('char') ||
1905
- typeName === '"char"' ||
1906
- typeName.startsWith('bpchar') ||
1907
- typeName === 'bytea' ||
1908
- typeName === 'orderedarray' ||
1909
- typeName === 'date')) {
1910
- // Remove pg_catalog prefix for :: syntax
1911
- const cleanTypeName = typeName.replace('pg_catalog.', '');
1912
- return `${arg}::${cleanTypeName}`;
2203
+ if (this.isPgCatalogType(typeName)) {
2204
+ const argType = this.getNodeType(node.arg);
2205
+ const isSimpleArgument = argType === 'A_Const' || argType === 'ColumnRef';
2206
+ const isFunctionCall = argType === 'FuncCall';
2207
+ if (isSimpleArgument || isFunctionCall) {
2208
+ // For simple arguments, avoid :: syntax if they have complex structure
2209
+ const shouldUseCastSyntax = isSimpleArgument && (arg.includes('(') || arg.startsWith('-'));
2210
+ if (!shouldUseCastSyntax) {
2211
+ const cleanTypeName = typeName.replace('pg_catalog.', '');
2212
+ // Wrap FuncCall arguments in parentheses to prevent operator precedence issues
2213
+ if (isFunctionCall) {
2214
+ return `${context.parens(arg)}::${cleanTypeName}`;
2215
+ }
2216
+ return `${arg}::${cleanTypeName}`;
2217
+ }
2218
+ }
1913
2219
  }
1914
2220
  return `CAST(${arg} AS ${typeName})`;
1915
2221
  }
@@ -1932,7 +2238,7 @@ class Deparser {
1932
2238
  }
1933
2239
  BooleanTest(node, context) {
1934
2240
  const output = [];
1935
- const boolContext = { ...context, bool: true };
2241
+ const boolContext = context.spawn('BooleanTest', { bool: true });
1936
2242
  output.push(this.visit(node.arg, boolContext));
1937
2243
  switch (node.booltesttype) {
1938
2244
  case 'IS_TRUE':
@@ -2083,24 +2389,31 @@ class Deparser {
2083
2389
  const elementStrs = elements.map(el => {
2084
2390
  return this.deparse(el, context);
2085
2391
  });
2086
- output.push(this.formatter.parens(elementStrs.join(', ')));
2392
+ output.push(context.parens(elementStrs.join(', ')));
2087
2393
  }
2088
2394
  }
2089
2395
  else if (node.tableElts) {
2090
2396
  const elements = list_utils_1.ListUtils.unwrapList(node.tableElts);
2091
2397
  const elementStrs = elements.map(el => {
2092
- return this.deparse(el, context);
2398
+ return this.deparse(el, context.spawn('CreateStmt'));
2093
2399
  });
2094
- if (this.formatter.isPretty()) {
2095
- const formattedElements = elementStrs.map(el => this.formatter.indent(el)).join(',' + this.formatter.newline());
2096
- output.push('(' + this.formatter.newline() + formattedElements + this.formatter.newline() + ')');
2400
+ if (context.isPretty()) {
2401
+ const formattedElements = elementStrs.map(el => {
2402
+ const trimmedEl = el.trim();
2403
+ // Remove leading newlines from constraint elements to avoid extra blank lines
2404
+ if (trimmedEl.startsWith('\n')) {
2405
+ return context.indent(trimmedEl.substring(1));
2406
+ }
2407
+ return context.indent(trimmedEl);
2408
+ }).join(',' + context.newline());
2409
+ output.push('(' + context.newline() + formattedElements + context.newline() + ')');
2097
2410
  }
2098
2411
  else {
2099
- output.push(this.formatter.parens(elementStrs.join(', ')));
2412
+ output.push(context.parens(elementStrs.join(', ')));
2100
2413
  }
2101
2414
  }
2102
2415
  else if (!node.partbound) {
2103
- output.push(this.formatter.parens(''));
2416
+ output.push(context.parens(''));
2104
2417
  }
2105
2418
  if (node.partbound && node.inhRelations && node.inhRelations.length > 0) {
2106
2419
  output.push('PARTITION OF');
@@ -2143,7 +2456,7 @@ class Deparser {
2143
2456
  output.push('INHERITS');
2144
2457
  const inherits = list_utils_1.ListUtils.unwrapList(node.inhRelations);
2145
2458
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2146
- output.push(this.formatter.parens(inheritStrs.join(', ')));
2459
+ output.push(context.parens(inheritStrs.join(', ')));
2147
2460
  }
2148
2461
  if (node.partspec) {
2149
2462
  output.push('PARTITION BY');
@@ -2181,7 +2494,7 @@ class Deparser {
2181
2494
  }
2182
2495
  // Handle table options like WITH (fillfactor=10)
2183
2496
  if (node.options && node.options.length > 0) {
2184
- const createStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateStmt'] };
2497
+ const createStmtContext = context.spawn('CreateStmt');
2185
2498
  const optionStrs = node.options.map((option) => {
2186
2499
  return this.deparse(option, createStmtContext);
2187
2500
  });
@@ -2204,7 +2517,7 @@ class Deparser {
2204
2517
  }
2205
2518
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2206
2519
  output.push('OPTIONS');
2207
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
2520
+ const columnContext = context.spawn('ColumnDef');
2208
2521
  const options = list_utils_1.ListUtils.unwrapList(node.fdwoptions).map(opt => this.visit(opt, columnContext));
2209
2522
  output.push(`(${options.join(', ')})`);
2210
2523
  }
@@ -2214,7 +2527,7 @@ class Deparser {
2214
2527
  if (node.constraints) {
2215
2528
  const constraints = list_utils_1.ListUtils.unwrapList(node.constraints);
2216
2529
  const constraintStrs = constraints.map(constraint => {
2217
- const columnConstraintContext = { ...context, isColumnConstraint: true };
2530
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
2218
2531
  return this.visit(constraint, columnConstraintContext);
2219
2532
  });
2220
2533
  output.push(...constraintStrs);
@@ -2254,9 +2567,25 @@ class Deparser {
2254
2567
  }
2255
2568
  break;
2256
2569
  case 'CONSTR_CHECK':
2257
- output.push('CHECK');
2570
+ if (context.isPretty() && !context.isColumnConstraint) {
2571
+ output.push('\n' + context.indent('CHECK'));
2572
+ }
2573
+ else {
2574
+ output.push('CHECK');
2575
+ }
2258
2576
  if (node.raw_expr) {
2259
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2577
+ if (context.isPretty()) {
2578
+ const checkExpr = this.visit(node.raw_expr, context);
2579
+ if (checkExpr.includes('\n')) {
2580
+ output.push('(\n' + context.indent(checkExpr) + '\n)');
2581
+ }
2582
+ else {
2583
+ output.push(`(${checkExpr})`);
2584
+ }
2585
+ }
2586
+ else {
2587
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2588
+ }
2260
2589
  }
2261
2590
  // Handle NOT VALID for check constraints
2262
2591
  if (node.skip_validation) {
@@ -2277,7 +2606,7 @@ class Deparser {
2277
2606
  }
2278
2607
  output.push('AS');
2279
2608
  if (node.raw_expr) {
2280
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2609
+ output.push(context.parens(this.visit(node.raw_expr, context)));
2281
2610
  }
2282
2611
  output.push('STORED');
2283
2612
  break;
@@ -2295,30 +2624,61 @@ class Deparser {
2295
2624
  .map(option => {
2296
2625
  if (option.DefElem) {
2297
2626
  const defElem = option.DefElem;
2298
- const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2299
- if (defElem.defname === 'start') {
2627
+ if (defElem.defname === 'sequence_name') {
2628
+ if (defElem.arg && defElem.arg.List) {
2629
+ const nameList = list_utils_1.ListUtils.unwrapList(defElem.arg)
2630
+ .map(item => this.visit(item, context))
2631
+ .join('.');
2632
+ return `SEQUENCE NAME ${nameList}`;
2633
+ }
2634
+ return 'SEQUENCE NAME';
2635
+ }
2636
+ else if (defElem.defname === 'start') {
2637
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2300
2638
  return `START WITH ${argValue}`;
2301
2639
  }
2302
2640
  else if (defElem.defname === 'increment') {
2641
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2303
2642
  return `INCREMENT BY ${argValue}`;
2304
2643
  }
2305
2644
  else if (defElem.defname === 'minvalue') {
2306
- return `MINVALUE ${argValue}`;
2645
+ if (defElem.arg) {
2646
+ const argValue = this.visit(defElem.arg, context);
2647
+ return `MINVALUE ${argValue}`;
2648
+ }
2649
+ else {
2650
+ return 'NO MINVALUE';
2651
+ }
2307
2652
  }
2308
2653
  else if (defElem.defname === 'maxvalue') {
2309
- return `MAXVALUE ${argValue}`;
2654
+ if (defElem.arg) {
2655
+ const argValue = this.visit(defElem.arg, context);
2656
+ return `MAXVALUE ${argValue}`;
2657
+ }
2658
+ else {
2659
+ return 'NO MAXVALUE';
2660
+ }
2310
2661
  }
2311
2662
  else if (defElem.defname === 'cache') {
2663
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2312
2664
  return `CACHE ${argValue}`;
2313
2665
  }
2314
2666
  else if (defElem.defname === 'cycle') {
2667
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2315
2668
  return argValue === 'true' ? 'CYCLE' : 'NO CYCLE';
2316
2669
  }
2670
+ const argValue = defElem.arg ? this.visit(defElem.arg, context) : '';
2317
2671
  return `${defElem.defname.toUpperCase()} ${argValue}`;
2318
2672
  }
2319
2673
  return this.visit(option, context);
2320
2674
  });
2321
- output.push(`(${optionStrs.join(' ')})`);
2675
+ if (context.isPretty()) {
2676
+ const indentedOptions = optionStrs.map(option => context.indent(option));
2677
+ output.push('(\n' + indentedOptions.join('\n') + '\n)');
2678
+ }
2679
+ else {
2680
+ output.push(`(${optionStrs.join(' ')})`);
2681
+ }
2322
2682
  }
2323
2683
  break;
2324
2684
  case 'CONSTR_PRIMARY':
@@ -2335,7 +2695,12 @@ class Deparser {
2335
2695
  }
2336
2696
  break;
2337
2697
  case 'CONSTR_UNIQUE':
2338
- output.push('UNIQUE');
2698
+ if (context.isPretty() && !context.isColumnConstraint) {
2699
+ output.push('\n' + context.indent('UNIQUE'));
2700
+ }
2701
+ else {
2702
+ output.push('UNIQUE');
2703
+ }
2339
2704
  if (node.nulls_not_distinct) {
2340
2705
  output.push('NULLS NOT DISTINCT');
2341
2706
  }
@@ -2353,33 +2718,77 @@ class Deparser {
2353
2718
  case 'CONSTR_FOREIGN':
2354
2719
  // Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
2355
2720
  if (!context.isColumnConstraint) {
2356
- output.push('FOREIGN KEY');
2357
- if (node.fk_attrs && node.fk_attrs.length > 0) {
2358
- const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2359
- .map(attr => this.visit(attr, context))
2360
- .join(', ');
2361
- output.push(`(${fkAttrs})`);
2721
+ if (context.isPretty()) {
2722
+ output.push('\n' + context.indent('FOREIGN KEY'));
2723
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2724
+ const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2725
+ .map(attr => this.visit(attr, context))
2726
+ .join(', ');
2727
+ output.push(`(${fkAttrs})`);
2728
+ }
2729
+ output.push('\n' + context.indent('REFERENCES'));
2362
2730
  }
2731
+ else {
2732
+ output.push('FOREIGN KEY');
2733
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2734
+ const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2735
+ .map(attr => this.visit(attr, context))
2736
+ .join(', ');
2737
+ output.push(`(${fkAttrs})`);
2738
+ }
2739
+ output.push('REFERENCES');
2740
+ }
2741
+ }
2742
+ else {
2743
+ output.push('REFERENCES');
2363
2744
  }
2364
- output.push('REFERENCES');
2365
2745
  if (node.pktable) {
2366
- output.push(this.RangeVar(node.pktable, context));
2746
+ if (context.isPretty() && !context.isColumnConstraint) {
2747
+ const lastIndex = output.length - 1;
2748
+ if (lastIndex >= 0 && output[lastIndex].includes('REFERENCES')) {
2749
+ output[lastIndex] += ' ' + this.RangeVar(node.pktable, context);
2750
+ }
2751
+ else {
2752
+ output.push(this.RangeVar(node.pktable, context));
2753
+ }
2754
+ }
2755
+ else {
2756
+ output.push(this.RangeVar(node.pktable, context));
2757
+ }
2367
2758
  }
2368
2759
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2369
2760
  const pkAttrs = list_utils_1.ListUtils.unwrapList(node.pk_attrs)
2370
2761
  .map(attr => this.visit(attr, context))
2371
2762
  .join(', ');
2372
- output.push(`(${pkAttrs})`);
2763
+ if (context.isPretty() && !context.isColumnConstraint) {
2764
+ const lastIndex = output.length - 1;
2765
+ if (lastIndex >= 0) {
2766
+ output[lastIndex] += ` (${pkAttrs})`;
2767
+ }
2768
+ else {
2769
+ output.push(`(${pkAttrs})`);
2770
+ }
2771
+ }
2772
+ else {
2773
+ output.push(`(${pkAttrs})`);
2774
+ }
2373
2775
  }
2374
2776
  if (node.fk_matchtype && node.fk_matchtype !== 's') {
2777
+ let matchClause = '';
2375
2778
  switch (node.fk_matchtype) {
2376
2779
  case 'f':
2377
- output.push('MATCH FULL');
2780
+ matchClause = 'MATCH FULL';
2378
2781
  break;
2379
2782
  case 'p':
2380
- output.push('MATCH PARTIAL');
2783
+ matchClause = 'MATCH PARTIAL';
2381
2784
  break;
2382
2785
  }
2786
+ if (context.isPretty() && !context.isColumnConstraint) {
2787
+ output.push('\n' + context.indent(matchClause));
2788
+ }
2789
+ else {
2790
+ output.push(matchClause);
2791
+ }
2383
2792
  }
2384
2793
  if (node.fk_upd_action && node.fk_upd_action !== 'a') {
2385
2794
  let updateClause = 'ON UPDATE ';
@@ -2397,8 +2806,8 @@ class Deparser {
2397
2806
  updateClause += 'SET DEFAULT';
2398
2807
  break;
2399
2808
  }
2400
- if (this.formatter.isPretty()) {
2401
- output.push('\n' + this.formatter.indent(updateClause));
2809
+ if (context.isPretty()) {
2810
+ output.push('\n' + context.indent(updateClause));
2402
2811
  }
2403
2812
  else {
2404
2813
  output.push('ON UPDATE');
@@ -2421,8 +2830,8 @@ class Deparser {
2421
2830
  deleteClause += 'SET DEFAULT';
2422
2831
  break;
2423
2832
  }
2424
- if (this.formatter.isPretty()) {
2425
- output.push('\n' + this.formatter.indent(deleteClause));
2833
+ if (context.isPretty()) {
2834
+ output.push('\n' + context.indent(deleteClause));
2426
2835
  }
2427
2836
  else {
2428
2837
  output.push('ON DELETE');
@@ -2431,7 +2840,12 @@ class Deparser {
2431
2840
  }
2432
2841
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2433
2842
  if (node.skip_validation && !context.isDomainConstraint) {
2434
- output.push('NOT VALID');
2843
+ if (context.isPretty() && !context.isColumnConstraint) {
2844
+ output.push('\n' + context.indent('NOT VALID'));
2845
+ }
2846
+ else {
2847
+ output.push('NOT VALID');
2848
+ }
2435
2849
  }
2436
2850
  break;
2437
2851
  case 'CONSTR_ATTR_DEFERRABLE':
@@ -2485,13 +2899,13 @@ class Deparser {
2485
2899
  // Handle deferrable constraints for all constraint types that support it
2486
2900
  if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2487
2901
  if (node.deferrable) {
2488
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2489
- output.push('\n' + this.formatter.indent('DEFERRABLE'));
2902
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2903
+ output.push('\n' + context.indent('DEFERRABLE'));
2490
2904
  if (node.initdeferred === true) {
2491
- output.push('\n' + this.formatter.indent('INITIALLY DEFERRED'));
2905
+ output.push('\n' + context.indent('INITIALLY DEFERRED'));
2492
2906
  }
2493
2907
  else if (node.initdeferred === false) {
2494
- output.push('\n' + this.formatter.indent('INITIALLY IMMEDIATE'));
2908
+ output.push('\n' + context.indent('INITIALLY IMMEDIATE'));
2495
2909
  }
2496
2910
  }
2497
2911
  else {
@@ -2505,15 +2919,15 @@ class Deparser {
2505
2919
  }
2506
2920
  }
2507
2921
  else if (node.deferrable === false) {
2508
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2509
- output.push('\n' + this.formatter.indent('NOT DEFERRABLE'));
2922
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2923
+ output.push('\n' + context.indent('NOT DEFERRABLE'));
2510
2924
  }
2511
2925
  else {
2512
2926
  output.push('NOT DEFERRABLE');
2513
2927
  }
2514
2928
  }
2515
2929
  }
2516
- if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2930
+ if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2517
2931
  let result = '';
2518
2932
  for (let i = 0; i < output.length; i++) {
2519
2933
  if (output[i].startsWith('\n')) {
@@ -2531,12 +2945,12 @@ class Deparser {
2531
2945
  return output.join(' ');
2532
2946
  }
2533
2947
  SubLink(node, context) {
2534
- const subselect = this.formatter.parens(this.visit(node.subselect, context));
2948
+ const subselect = context.parens(this.visit(node.subselect, context));
2535
2949
  switch (node.subLinkType) {
2536
2950
  case 'ANY_SUBLINK':
2537
2951
  if (node.testexpr && node.operName) {
2538
2952
  const testExpr = this.visit(node.testexpr, context);
2539
- const operator = this.deparseOperatorName(node.operName);
2953
+ const operator = this.deparseOperatorName(node.operName, context);
2540
2954
  return `${testExpr} ${operator} ANY ${subselect}`;
2541
2955
  }
2542
2956
  else if (node.testexpr) {
@@ -2547,7 +2961,7 @@ class Deparser {
2547
2961
  case 'ALL_SUBLINK':
2548
2962
  if (node.testexpr && node.operName) {
2549
2963
  const testExpr = this.visit(node.testexpr, context);
2550
- const operator = this.deparseOperatorName(node.operName);
2964
+ const operator = this.deparseOperatorName(node.operName, context);
2551
2965
  return `${testExpr} ${operator} ALL ${subselect}`;
2552
2966
  }
2553
2967
  return subselect;
@@ -2589,7 +3003,7 @@ class Deparser {
2589
3003
  }
2590
3004
  // Only add frame clause if frameOptions indicates non-default framing
2591
3005
  if (node.frameOptions && node.frameOptions !== 1058) {
2592
- const frameClause = this.formatWindowFrame(node);
3006
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2593
3007
  if (frameClause) {
2594
3008
  windowParts.push(frameClause);
2595
3009
  }
@@ -2609,7 +3023,7 @@ class Deparser {
2609
3023
  }
2610
3024
  return output.join(' ');
2611
3025
  }
2612
- formatWindowFrame(node) {
3026
+ formatWindowFrame(node, context) {
2613
3027
  if (!node.frameOptions)
2614
3028
  return null;
2615
3029
  const frameOptions = node.frameOptions;
@@ -2618,7 +3032,7 @@ class Deparser {
2618
3032
  if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
2619
3033
  frameParts.push('RANGE');
2620
3034
  }
2621
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
3035
+ else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2622
3036
  frameParts.push('ROWS');
2623
3037
  }
2624
3038
  else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
@@ -2639,8 +3053,8 @@ class Deparser {
2639
3053
  }
2640
3054
  else if (frameOptions === 18453) {
2641
3055
  if (node.startOffset && node.endOffset) {
2642
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
2643
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
3056
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
3057
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2644
3058
  }
2645
3059
  }
2646
3060
  else if (frameOptions === 1557) {
@@ -2650,7 +3064,7 @@ class Deparser {
2650
3064
  else if (frameOptions === 16917) {
2651
3065
  boundsParts.push('CURRENT ROW');
2652
3066
  if (node.endOffset) {
2653
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
3067
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2654
3068
  }
2655
3069
  }
2656
3070
  else if (frameOptions === 1058) {
@@ -2660,13 +3074,13 @@ class Deparser {
2660
3074
  // Handle start bound - prioritize explicit offset values over bit flags
2661
3075
  if (node.startOffset) {
2662
3076
  if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
2663
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
3077
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2664
3078
  }
2665
3079
  else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
2666
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} FOLLOWING`);
3080
+ boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
2667
3081
  }
2668
3082
  else {
2669
- boundsParts.push(`${this.visit(node.startOffset, { parentNodeTypes: [] })} PRECEDING`);
3083
+ boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2670
3084
  }
2671
3085
  }
2672
3086
  else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
@@ -2679,13 +3093,13 @@ class Deparser {
2679
3093
  if (node.endOffset) {
2680
3094
  if (boundsParts.length > 0) {
2681
3095
  if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
2682
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} PRECEDING`);
3096
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
2683
3097
  }
2684
3098
  else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
2685
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
3099
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2686
3100
  }
2687
3101
  else {
2688
- boundsParts.push(`AND ${this.visit(node.endOffset, { parentNodeTypes: [] })} FOLLOWING`);
3102
+ boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2689
3103
  }
2690
3104
  }
2691
3105
  }
@@ -2770,7 +3184,7 @@ class Deparser {
2770
3184
  const colnames = list_utils_1.ListUtils.unwrapList(node.aliascolnames);
2771
3185
  const colnameStrs = colnames.map(col => this.visit(col, context));
2772
3186
  // Don't add space before column list parentheses to match original formatting
2773
- output[output.length - 1] += this.formatter.parens(colnameStrs.join(', '));
3187
+ output[output.length - 1] += context.parens(colnameStrs.join(', '));
2774
3188
  }
2775
3189
  output.push('AS');
2776
3190
  // Handle materialization clauses
@@ -2781,7 +3195,7 @@ class Deparser {
2781
3195
  output.push('MATERIALIZED');
2782
3196
  }
2783
3197
  if (node.ctequery) {
2784
- output.push(this.formatter.parens(this.visit(node.ctequery, context)));
3198
+ output.push(context.parens(this.visit(node.ctequery, context)));
2785
3199
  }
2786
3200
  return output.join(' ');
2787
3201
  }
@@ -2857,7 +3271,7 @@ class Deparser {
2857
3271
  DistinctExpr(node, context) {
2858
3272
  const args = list_utils_1.ListUtils.unwrapList(node.args);
2859
3273
  if (args.length === 2) {
2860
- const literalContext = { ...context, isStringLiteral: true };
3274
+ const literalContext = context.spawn('DistinctExpr', { isStringLiteral: true });
2861
3275
  const left = this.visit(args[0], literalContext);
2862
3276
  const right = this.visit(args[1], literalContext);
2863
3277
  return `${left} IS DISTINCT FROM ${right}`;
@@ -2867,7 +3281,7 @@ class Deparser {
2867
3281
  NullIfExpr(node, context) {
2868
3282
  const args = list_utils_1.ListUtils.unwrapList(node.args);
2869
3283
  if (args.length === 2) {
2870
- const literalContext = { ...context, isStringLiteral: true };
3284
+ const literalContext = context.spawn('NullIfExpr', { isStringLiteral: true });
2871
3285
  const left = this.visit(args[0], literalContext);
2872
3286
  const right = this.visit(args[1], literalContext);
2873
3287
  return `NULLIF(${left}, ${right})`;
@@ -2946,7 +3360,7 @@ class Deparser {
2946
3360
  }
2947
3361
  RelabelType(node, context) {
2948
3362
  if (node.arg) {
2949
- const literalContext = { ...context, isStringLiteral: true };
3363
+ const literalContext = context.spawn('RelabelType', { isStringLiteral: true });
2950
3364
  return this.visit(node.arg, literalContext);
2951
3365
  }
2952
3366
  return '';
@@ -2965,7 +3379,7 @@ class Deparser {
2965
3379
  }
2966
3380
  ConvertRowtypeExpr(node, context) {
2967
3381
  if (node.arg) {
2968
- const literalContext = { ...context, isStringLiteral: true };
3382
+ const literalContext = context.spawn('ConvertRowtypeExpr', { isStringLiteral: true });
2969
3383
  return this.visit(node.arg, literalContext);
2970
3384
  }
2971
3385
  return '';
@@ -2996,10 +3410,10 @@ class Deparser {
2996
3410
  }
2997
3411
  if (node.aliases && node.aliases.length > 0) {
2998
3412
  const aliasStrs = list_utils_1.ListUtils.unwrapList(node.aliases).map(alias => this.visit(alias, context));
2999
- output.push(this.formatter.parens(aliasStrs.join(', ')));
3413
+ output.push(context.parens(aliasStrs.join(', ')));
3000
3414
  }
3001
3415
  if (node.options && node.options.length > 0) {
3002
- const viewContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ViewStmt'] };
3416
+ const viewContext = context.spawn('ViewStmt');
3003
3417
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
3004
3418
  .map(option => this.visit(option, viewContext));
3005
3419
  output.push(`WITH (${optionStrs.join(', ')})`);
@@ -3046,22 +3460,22 @@ class Deparser {
3046
3460
  }
3047
3461
  if (node.indexParams && node.indexParams.length > 0) {
3048
3462
  const paramStrs = list_utils_1.ListUtils.unwrapList(node.indexParams).map(param => this.visit(param, context));
3049
- output.push(this.formatter.parens(paramStrs.join(', ')));
3463
+ output.push(context.parens(paramStrs.join(', ')));
3050
3464
  }
3051
3465
  if (node.indexIncludingParams && node.indexIncludingParams.length > 0) {
3052
3466
  const includeStrs = list_utils_1.ListUtils.unwrapList(node.indexIncludingParams).map(param => this.visit(param, context));
3053
3467
  output.push('INCLUDE');
3054
- output.push(this.formatter.parens(includeStrs.join(', ')));
3468
+ output.push(context.parens(includeStrs.join(', ')));
3055
3469
  }
3056
3470
  if (node.whereClause) {
3057
3471
  output.push('WHERE');
3058
3472
  output.push(this.visit(node.whereClause, context));
3059
3473
  }
3060
3474
  if (node.options && node.options.length > 0) {
3061
- const indexContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'IndexStmt'] };
3475
+ const indexContext = context.spawn('IndexStmt');
3062
3476
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options).map(option => this.visit(option, indexContext));
3063
3477
  output.push('WITH');
3064
- output.push(this.formatter.parens(optionStrs.join(', ')));
3478
+ output.push(context.parens(optionStrs.join(', ')));
3065
3479
  }
3066
3480
  if (node.nulls_not_distinct) {
3067
3481
  output.push('NULLS NOT DISTINCT');
@@ -3078,7 +3492,7 @@ class Deparser {
3078
3492
  output.push(quote_utils_1.QuoteUtils.quote(node.name));
3079
3493
  }
3080
3494
  else if (node.expr) {
3081
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3495
+ output.push(context.parens(this.visit(node.expr, context)));
3082
3496
  }
3083
3497
  if (node.collation && node.collation.length > 0) {
3084
3498
  const collationStrs = list_utils_1.ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3095,7 +3509,7 @@ class Deparser {
3095
3509
  const stringData = this.getNodeData(opt.DefElem.arg);
3096
3510
  return `${opt.DefElem.defname}='${stringData.sval}'`;
3097
3511
  }
3098
- return this.visit(opt, context);
3512
+ return this.visit(opt, context.spawn('IndexElem'));
3099
3513
  });
3100
3514
  opclassStr += `(${opclassOpts.join(', ')})`;
3101
3515
  }
@@ -3129,7 +3543,7 @@ class Deparser {
3129
3543
  output.push(quote_utils_1.QuoteUtils.quote(node.name));
3130
3544
  }
3131
3545
  else if (node.expr) {
3132
- output.push(this.formatter.parens(this.visit(node.expr, context)));
3546
+ output.push(context.parens(this.visit(node.expr, context)));
3133
3547
  }
3134
3548
  if (node.collation && node.collation.length > 0) {
3135
3549
  const collationStrs = list_utils_1.ListUtils.unwrapList(node.collation).map(coll => this.visit(coll, context));
@@ -3277,16 +3691,16 @@ class Deparser {
3277
3691
  if (node.rarg && 'JoinExpr' in node.rarg && !node.rarg.JoinExpr.alias) {
3278
3692
  rargStr = `(${rargStr})`;
3279
3693
  }
3280
- if (this.formatter.isPretty()) {
3281
- output.push(this.formatter.newline() + joinStr + ' ' + rargStr);
3694
+ if (context.isPretty()) {
3695
+ output.push(context.newline() + joinStr + ' ' + rargStr);
3282
3696
  }
3283
3697
  else {
3284
3698
  output.push(joinStr + ' ' + rargStr);
3285
3699
  }
3286
3700
  }
3287
3701
  else {
3288
- if (this.formatter.isPretty()) {
3289
- output.push(this.formatter.newline() + joinStr);
3702
+ if (context.isPretty()) {
3703
+ output.push(context.newline() + joinStr);
3290
3704
  }
3291
3705
  else {
3292
3706
  output.push(joinStr);
@@ -3295,7 +3709,7 @@ class Deparser {
3295
3709
  if (node.usingClause && node.usingClause.length > 0) {
3296
3710
  const usingList = list_utils_1.ListUtils.unwrapList(node.usingClause);
3297
3711
  const columnNames = usingList.map(col => this.visit(col, context));
3298
- if (this.formatter.isPretty()) {
3712
+ if (context.isPretty()) {
3299
3713
  output.push(` USING (${columnNames.join(', ')})`);
3300
3714
  }
3301
3715
  else {
@@ -3304,14 +3718,14 @@ class Deparser {
3304
3718
  }
3305
3719
  else if (node.quals) {
3306
3720
  const qualsStr = this.visit(node.quals, context);
3307
- if (this.formatter.isPretty()) {
3721
+ if (context.isPretty()) {
3308
3722
  // For complex JOIN conditions, format with proper indentation
3309
3723
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3310
3724
  if (this.containsMultilineStringLiteral(qualsStr)) {
3311
3725
  output.push(` ON ${qualsStr}`);
3312
3726
  }
3313
3727
  else {
3314
- output.push(` ON${this.formatter.newline()}${this.formatter.indent(qualsStr)}`);
3728
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3315
3729
  }
3316
3730
  }
3317
3731
  else {
@@ -3323,7 +3737,7 @@ class Deparser {
3323
3737
  }
3324
3738
  }
3325
3739
  let result;
3326
- if (this.formatter.isPretty()) {
3740
+ if (context.isPretty()) {
3327
3741
  result = output.join('');
3328
3742
  }
3329
3743
  else {
@@ -3430,8 +3844,8 @@ class Deparser {
3430
3844
  else if (nodeData.sval !== undefined) {
3431
3845
  // Handle nested sval structure: { sval: { sval: "value" } }
3432
3846
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3433
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3434
- boolValue = stringValue === 'on' || stringValue === 'true';
3847
+ const stringValue = svalValue.replace(/'/g, '');
3848
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3435
3849
  }
3436
3850
  }
3437
3851
  return boolValue ? 'READ ONLY' : 'READ WRITE';
@@ -3454,8 +3868,8 @@ class Deparser {
3454
3868
  else if (nodeData.sval !== undefined) {
3455
3869
  // Handle nested sval structure: { sval: { sval: "value" } }
3456
3870
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3457
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3458
- boolValue = stringValue === 'on' || stringValue === 'true';
3871
+ const stringValue = svalValue.replace(/'/g, '');
3872
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3459
3873
  }
3460
3874
  }
3461
3875
  return boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE';
@@ -3522,8 +3936,8 @@ class Deparser {
3522
3936
  else if (nodeData.sval !== undefined) {
3523
3937
  // Handle nested sval structure: { sval: { sval: "value" } }
3524
3938
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3525
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3526
- boolValue = stringValue === 'on' || stringValue === 'true';
3939
+ const stringValue = svalValue.replace(/'/g, '');
3940
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3527
3941
  }
3528
3942
  }
3529
3943
  transactionOptions.push(boolValue ? 'READ ONLY' : 'READ WRITE');
@@ -3541,8 +3955,8 @@ class Deparser {
3541
3955
  else if (nodeData.sval !== undefined) {
3542
3956
  // Handle nested sval structure: { sval: { sval: "value" } }
3543
3957
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3544
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3545
- boolValue = stringValue === 'on' || stringValue === 'true';
3958
+ const stringValue = svalValue.replace(/'/g, '');
3959
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3546
3960
  }
3547
3961
  }
3548
3962
  transactionOptions.push(boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE');
@@ -3608,7 +4022,7 @@ class Deparser {
3608
4022
  }
3609
4023
  switch (node.roletype) {
3610
4024
  case 'ROLESPEC_PUBLIC':
3611
- return 'public';
4025
+ return 'PUBLIC';
3612
4026
  case 'ROLESPEC_CURRENT_USER':
3613
4027
  return 'CURRENT_USER';
3614
4028
  case 'ROLESPEC_SESSION_USER':
@@ -3616,7 +4030,7 @@ class Deparser {
3616
4030
  case 'ROLESPEC_CURRENT_ROLE':
3617
4031
  return 'CURRENT_ROLE';
3618
4032
  default:
3619
- return 'public';
4033
+ return 'PUBLIC';
3620
4034
  }
3621
4035
  }
3622
4036
  roletype(node, context) {
@@ -3926,7 +4340,7 @@ class Deparser {
3926
4340
  }).filter((name) => name && name.trim());
3927
4341
  return items.join('.');
3928
4342
  }
3929
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DropStmt'], objtype: node.removeType };
4343
+ const objContext = context.spawn('DropStmt', { objtype: node.removeType });
3930
4344
  const objName = this.visit(objList, objContext);
3931
4345
  return objName;
3932
4346
  }).filter((name) => name && name.trim()).join(', ');
@@ -4026,7 +4440,7 @@ class Deparser {
4026
4440
  if (node.options && node.options.length > 0) {
4027
4441
  output.push('WITH');
4028
4442
  const optionsStr = list_utils_1.ListUtils.unwrapList(node.options)
4029
- .map(opt => this.visit(opt, context))
4443
+ .map(opt => this.visit(opt, context.spawn('CopyStmt')))
4030
4444
  .join(', ');
4031
4445
  output.push(`(${optionsStr})`);
4032
4446
  }
@@ -4072,18 +4486,28 @@ class Deparser {
4072
4486
  if (node.missing_ok) {
4073
4487
  output.push('IF EXISTS');
4074
4488
  }
4075
- const alterContext = node.objtype === 'OBJECT_TYPE'
4076
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
4077
- : context;
4489
+ const alterContext = context.spawn('AlterTableStmt', { objtype: node.objtype });
4078
4490
  if (node.relation) {
4079
4491
  const relationStr = this.RangeVar(node.relation, alterContext);
4080
4492
  output.push(relationStr);
4081
4493
  }
4082
4494
  if (node.cmds && node.cmds.length > 0) {
4083
- const commandsStr = list_utils_1.ListUtils.unwrapList(node.cmds)
4084
- .map(cmd => this.visit(cmd, alterContext))
4085
- .join(', ');
4086
- output.push(commandsStr);
4495
+ const commands = list_utils_1.ListUtils.unwrapList(node.cmds);
4496
+ if (context.isPretty()) {
4497
+ const commandsStr = commands
4498
+ .map(cmd => {
4499
+ const cmdStr = this.visit(cmd, alterContext);
4500
+ return context.newline() + context.indent(cmdStr);
4501
+ })
4502
+ .join(',');
4503
+ output.push(commandsStr);
4504
+ }
4505
+ else {
4506
+ const commandsStr = commands
4507
+ .map(cmd => this.visit(cmd, alterContext))
4508
+ .join(', ');
4509
+ output.push(commandsStr);
4510
+ }
4087
4511
  }
4088
4512
  return output.join(' ');
4089
4513
  }
@@ -4092,7 +4516,7 @@ class Deparser {
4092
4516
  if (node.subtype) {
4093
4517
  switch (node.subtype) {
4094
4518
  case 'AT_AddColumn':
4095
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4519
+ if (context.objtype === 'OBJECT_TYPE') {
4096
4520
  output.push('ADD ATTRIBUTE');
4097
4521
  }
4098
4522
  else {
@@ -4103,35 +4527,99 @@ class Deparser {
4103
4527
  }
4104
4528
  if (node.def) {
4105
4529
  const colDefData = this.getNodeData(node.def);
4106
- const parts = [];
4107
- if (colDefData.colname) {
4108
- parts.push(quote_utils_1.QuoteUtils.quote(colDefData.colname));
4109
- }
4110
- if (colDefData.typeName) {
4111
- parts.push(this.TypeName(colDefData.typeName, context));
4112
- }
4113
- if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4114
- parts.push('OPTIONS');
4115
- const columnContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ColumnDef'] };
4116
- const options = list_utils_1.ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4117
- parts.push(`(${options.join(', ')})`);
4118
- }
4119
- if (colDefData.constraints) {
4120
- const constraints = list_utils_1.ListUtils.unwrapList(colDefData.constraints);
4121
- const constraintStrs = constraints.map(constraint => {
4122
- const columnConstraintContext = { ...context, isColumnConstraint: true };
4123
- return this.visit(constraint, columnConstraintContext);
4124
- });
4125
- parts.push(...constraintStrs);
4126
- }
4127
- if (colDefData.raw_default) {
4128
- parts.push('DEFAULT');
4129
- parts.push(this.visit(colDefData.raw_default, context));
4530
+ if (context.isPretty()) {
4531
+ const parts = [];
4532
+ const indentedParts = [];
4533
+ if (colDefData.colname) {
4534
+ parts.push(quote_utils_1.QuoteUtils.quote(colDefData.colname));
4535
+ }
4536
+ if (colDefData.typeName) {
4537
+ parts.push(this.TypeName(colDefData.typeName, context));
4538
+ }
4539
+ if (colDefData.is_not_null) {
4540
+ indentedParts.push('NOT NULL');
4541
+ }
4542
+ if (colDefData.collClause) {
4543
+ indentedParts.push(this.CollateClause(colDefData.collClause, context));
4544
+ }
4545
+ if (colDefData.constraints) {
4546
+ const constraints = list_utils_1.ListUtils.unwrapList(colDefData.constraints);
4547
+ constraints.forEach(constraint => {
4548
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4549
+ const constraintStr = this.visit(constraint, columnConstraintContext);
4550
+ if (constraintStr.includes('REFERENCES') && constraintStr.includes('ON DELETE')) {
4551
+ const refMatch = constraintStr.match(/^(.*REFERENCES[^)]*\([^)]*\))\s*(ON\s+DELETE\s+CASCADE.*)$/);
4552
+ if (refMatch) {
4553
+ indentedParts.push(refMatch[1]);
4554
+ indentedParts.push(refMatch[2]);
4555
+ }
4556
+ else {
4557
+ indentedParts.push(constraintStr);
4558
+ }
4559
+ }
4560
+ else if (constraintStr === 'UNIQUE' && colDefData.raw_default) {
4561
+ const defaultStr = 'DEFAULT ' + this.visit(colDefData.raw_default, context);
4562
+ indentedParts.push('UNIQUE ' + defaultStr);
4563
+ }
4564
+ else {
4565
+ indentedParts.push(constraintStr);
4566
+ }
4567
+ });
4568
+ }
4569
+ if (colDefData.raw_default && !colDefData.constraints?.some((c) => {
4570
+ const constraintStr = this.visit(c, context.spawn('ColumnDef', { isColumnConstraint: true }));
4571
+ return constraintStr === 'UNIQUE';
4572
+ })) {
4573
+ const defaultStr = 'DEFAULT ' + this.visit(colDefData.raw_default, context);
4574
+ indentedParts.push(defaultStr);
4575
+ }
4576
+ if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4577
+ indentedParts.push('OPTIONS');
4578
+ const columnContext = context.spawn('ColumnDef');
4579
+ const options = list_utils_1.ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4580
+ indentedParts.push(`(${options.join(', ')})`);
4581
+ }
4582
+ let result = parts.join(' ');
4583
+ if (indentedParts.length > 0) {
4584
+ const indentedStr = indentedParts.map(part => context.indent(part)).join(context.newline());
4585
+ result += context.newline() + indentedStr;
4586
+ }
4587
+ output.push(result);
4130
4588
  }
4131
- if (colDefData.is_not_null) {
4132
- parts.push('NOT NULL');
4589
+ else {
4590
+ const parts = [];
4591
+ if (colDefData.colname) {
4592
+ parts.push(quote_utils_1.QuoteUtils.quote(colDefData.colname));
4593
+ }
4594
+ if (colDefData.typeName) {
4595
+ parts.push(this.TypeName(colDefData.typeName, context));
4596
+ }
4597
+ if (colDefData.collClause) {
4598
+ parts.push(this.CollateClause(colDefData.collClause, context));
4599
+ }
4600
+ if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4601
+ parts.push('OPTIONS');
4602
+ const columnContext = context.spawn('ColumnDef');
4603
+ const options = list_utils_1.ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4604
+ parts.push(`(${options.join(', ')})`);
4605
+ }
4606
+ if (colDefData.constraints) {
4607
+ const constraints = list_utils_1.ListUtils.unwrapList(colDefData.constraints);
4608
+ const constraintStrs = constraints.map(constraint => {
4609
+ const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4610
+ return this.visit(constraint, columnConstraintContext);
4611
+ });
4612
+ parts.push(...constraintStrs);
4613
+ }
4614
+ if (colDefData.raw_default) {
4615
+ parts.push('DEFAULT');
4616
+ parts.push(this.visit(colDefData.raw_default, context));
4617
+ }
4618
+ if (colDefData.is_not_null) {
4619
+ parts.push('NOT NULL');
4620
+ }
4621
+ output.push(parts.join(' '));
4133
4622
  }
4134
- output.push(parts.join(' '));
4135
4623
  }
4136
4624
  if (node.behavior === 'DROP_CASCADE') {
4137
4625
  output.push('CASCADE');
@@ -4139,7 +4627,7 @@ class Deparser {
4139
4627
  break;
4140
4628
  case 'AT_DropColumn':
4141
4629
  if (node.missing_ok) {
4142
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4630
+ if (context.objtype === 'OBJECT_TYPE') {
4143
4631
  output.push('DROP ATTRIBUTE IF EXISTS');
4144
4632
  }
4145
4633
  else {
@@ -4147,7 +4635,7 @@ class Deparser {
4147
4635
  }
4148
4636
  }
4149
4637
  else {
4150
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4638
+ if (context.objtype === 'OBJECT_TYPE') {
4151
4639
  output.push('DROP ATTRIBUTE');
4152
4640
  }
4153
4641
  else {
@@ -4165,7 +4653,7 @@ class Deparser {
4165
4653
  }
4166
4654
  break;
4167
4655
  case 'AT_AlterColumnType':
4168
- if (context.parentNodeTypes.includes('AlterTypeStmt')) {
4656
+ if (context.objtype === 'OBJECT_TYPE') {
4169
4657
  output.push('ALTER ATTRIBUTE');
4170
4658
  }
4171
4659
  else {
@@ -4229,7 +4717,7 @@ class Deparser {
4229
4717
  case 'AT_SetRelOptions':
4230
4718
  output.push('SET');
4231
4719
  if (node.def) {
4232
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetRelOptions' };
4720
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetRelOptions' });
4233
4721
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4234
4722
  .map(option => this.visit(option, alterTableContext))
4235
4723
  .join(', ');
@@ -4242,7 +4730,7 @@ class Deparser {
4242
4730
  case 'AT_ResetRelOptions':
4243
4731
  output.push('RESET');
4244
4732
  if (node.def) {
4245
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetRelOptions' };
4733
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetRelOptions' });
4246
4734
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4247
4735
  .map(option => this.visit(option, alterTableContext))
4248
4736
  .join(', ');
@@ -4337,7 +4825,7 @@ class Deparser {
4337
4825
  }
4338
4826
  output.push('SET');
4339
4827
  if (node.def) {
4340
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_SetOptions' };
4828
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_SetOptions' });
4341
4829
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4342
4830
  .map(option => this.visit(option, alterTableContext))
4343
4831
  .join(', ');
@@ -4354,7 +4842,7 @@ class Deparser {
4354
4842
  }
4355
4843
  output.push('RESET');
4356
4844
  if (node.def) {
4357
- const alterTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableCmd'], subtype: 'AT_ResetOptions' };
4845
+ const alterTableContext = context.spawn('AlterTableCmd', { subtype: 'AT_ResetOptions' });
4358
4846
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4359
4847
  .map(option => this.visit(option, alterTableContext))
4360
4848
  .join(', ');
@@ -4595,7 +5083,7 @@ class Deparser {
4595
5083
  }
4596
5084
  output.push('OPTIONS');
4597
5085
  if (node.def) {
4598
- const alterColumnContext = { ...context, alterColumnOptions: true };
5086
+ const alterColumnContext = context.spawn('AlterTableCmd', { alterColumnOptions: true });
4599
5087
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4600
5088
  .map(option => this.visit(option, alterColumnContext))
4601
5089
  .join(', ');
@@ -4635,7 +5123,7 @@ class Deparser {
4635
5123
  case 'AT_GenericOptions':
4636
5124
  output.push('OPTIONS');
4637
5125
  if (node.def) {
4638
- const alterTableContext = { ...context, alterTableOptions: true };
5126
+ const alterTableContext = context.spawn('AlterTableCmd', { alterTableOptions: true });
4639
5127
  const options = list_utils_1.ListUtils.unwrapList(node.def)
4640
5128
  .map(option => this.visit(option, alterTableContext))
4641
5129
  .join(', ');
@@ -4647,11 +5135,10 @@ class Deparser {
4647
5135
  if (node.name) {
4648
5136
  output.push(quote_utils_1.QuoteUtils.quote(node.name));
4649
5137
  }
4650
- output.push('ADD GENERATED');
5138
+ output.push('ADD');
4651
5139
  if (node.def) {
4652
5140
  output.push(this.visit(node.def, context));
4653
5141
  }
4654
- output.push('AS IDENTITY');
4655
5142
  break;
4656
5143
  case 'AT_SetIdentity':
4657
5144
  output.push('ALTER COLUMN');
@@ -4739,7 +5226,7 @@ class Deparser {
4739
5226
  output.push(this.TypeName(node.returnType, context));
4740
5227
  }
4741
5228
  if (node.options && node.options.length > 0) {
4742
- const funcContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFunctionStmt'] };
5229
+ const funcContext = context.spawn('CreateFunctionStmt');
4743
5230
  const options = node.options.map((opt) => this.visit(opt, funcContext));
4744
5231
  output.push(...options);
4745
5232
  }
@@ -4826,7 +5313,7 @@ class Deparser {
4826
5313
  }
4827
5314
  output.push('AS', 'ENUM');
4828
5315
  if (node.vals && node.vals.length > 0) {
4829
- const enumContext = { ...context, isEnumValue: true };
5316
+ const enumContext = context.spawn('CreateEnumStmt', { isEnumValue: true });
4830
5317
  const values = list_utils_1.ListUtils.unwrapList(node.vals)
4831
5318
  .map(val => this.visit(val, enumContext))
4832
5319
  .join(', ');
@@ -4881,9 +5368,8 @@ class Deparser {
4881
5368
  output.push(roleName);
4882
5369
  }
4883
5370
  if (node.options) {
4884
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateRoleStmt'] };
4885
5371
  const options = list_utils_1.ListUtils.unwrapList(node.options)
4886
- .map(option => this.visit(option, roleContext))
5372
+ .map(option => this.visit(option, context.spawn('CreateRoleStmt')))
4887
5373
  .join(' ');
4888
5374
  if (options) {
4889
5375
  output.push('WITH');
@@ -4914,7 +5400,7 @@ class Deparser {
4914
5400
  const stringData = this.getNodeData(node.arg);
4915
5401
  return `${node.defname}='${stringData.sval}'`;
4916
5402
  }
4917
- return `${node.defname}=${this.visit(node.arg, { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] })}`;
5403
+ return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
4918
5404
  }
4919
5405
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
4920
5406
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -4930,13 +5416,13 @@ class Deparser {
4930
5416
  if (!node.arg) {
4931
5417
  return `NO ${node.defname.toUpperCase()}`;
4932
5418
  }
4933
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5419
+ const defElemContext = context.spawn('DefElem');
4934
5420
  const argValue = this.visit(node.arg, defElemContext);
4935
5421
  return `${node.defname.toUpperCase()} ${argValue}`;
4936
5422
  }
4937
5423
  // Handle OPTIONS clause - use space format, not equals format
4938
5424
  if (node.arg) {
4939
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5425
+ const defElemContext = context.spawn('DefElem');
4940
5426
  const argValue = this.visit(node.arg, defElemContext);
4941
5427
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
4942
5428
  const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
@@ -4990,7 +5476,7 @@ class Deparser {
4990
5476
  if (!node.arg) {
4991
5477
  return 'PASSWORD NULL';
4992
5478
  }
4993
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5479
+ const defElemContext = context.spawn('DefElem');
4994
5480
  const argValue = this.visit(node.arg, defElemContext);
4995
5481
  const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
4996
5482
  ? `'${argValue}'`
@@ -4999,7 +5485,7 @@ class Deparser {
4999
5485
  }
5000
5486
  }
5001
5487
  if (node.arg) {
5002
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5488
+ const defElemContext = context.spawn('DefElem');
5003
5489
  const argValue = this.visit(node.arg, defElemContext);
5004
5490
  if (context.parentNodeTypes.includes('AlterOperatorStmt')) {
5005
5491
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
@@ -5075,7 +5561,7 @@ class Deparser {
5075
5561
  if (node.defname === 'sysid') {
5076
5562
  return `SYSID ${argValue}`;
5077
5563
  }
5078
- if (argValue === 'true') {
5564
+ if (String(argValue) === 'true') {
5079
5565
  // Handle special cases where the positive form has a different name
5080
5566
  if (node.defname === 'isreplication') {
5081
5567
  return 'REPLICATION';
@@ -5085,7 +5571,7 @@ class Deparser {
5085
5571
  }
5086
5572
  return node.defname.toUpperCase();
5087
5573
  }
5088
- else if (argValue === 'false') {
5574
+ else if (String(argValue) === 'false') {
5089
5575
  // Handle special cases where the negative form has a different name
5090
5576
  if (node.defname === 'canlogin') {
5091
5577
  return 'NOLOGIN';
@@ -5149,7 +5635,7 @@ class Deparser {
5149
5635
  }
5150
5636
  if (context.parentNodeTypes.includes('DoStmt')) {
5151
5637
  if (node.defname === 'as') {
5152
- const defElemContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefElem'] };
5638
+ const defElemContext = context.spawn('DefElem');
5153
5639
  const argValue = node.arg ? this.visit(node.arg, defElemContext) : '';
5154
5640
  if (Array.isArray(argValue)) {
5155
5641
  const bodyParts = argValue;
@@ -5241,11 +5727,15 @@ class Deparser {
5241
5727
  if (context.parentNodeTypes.includes('CreateExtensionStmt') || context.parentNodeTypes.includes('AlterExtensionStmt') || context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5242
5728
  // AlterExtensionStmt specific cases
5243
5729
  if (context.parentNodeTypes.includes('AlterExtensionStmt')) {
5244
- if (node.defname === 'to') {
5245
- return `TO ${argValue}`;
5730
+ if (node.defname === 'new_version') {
5731
+ // argValue is unquoted due to DefElem context, so we need to quote it
5732
+ const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5733
+ ? `'${argValue}'`
5734
+ : argValue;
5735
+ return `UPDATE TO ${quotedValue}`;
5246
5736
  }
5247
5737
  if (node.defname === 'schema') {
5248
- return `SCHEMA ${argValue}`;
5738
+ return `SET SCHEMA ${argValue}`;
5249
5739
  }
5250
5740
  }
5251
5741
  // CreateFdwStmt specific cases
@@ -5440,7 +5930,7 @@ class Deparser {
5440
5930
  }
5441
5931
  if (node.options && node.options.length > 0) {
5442
5932
  output.push('WITH');
5443
- const tsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateTableSpaceStmt'] };
5933
+ const tsContext = context.spawn('CreateTableSpaceStmt');
5444
5934
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5445
5935
  .map(option => this.visit(option, tsContext))
5446
5936
  .join(', ');
@@ -5470,7 +5960,7 @@ class Deparser {
5470
5960
  output.push('SET');
5471
5961
  }
5472
5962
  if (node.options && node.options.length > 0) {
5473
- const tablespaceContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTableSpaceOptionsStmt'] };
5963
+ const tablespaceContext = context.spawn('AlterTableSpaceOptionsStmt');
5474
5964
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5475
5965
  .map(option => this.visit(option, tablespaceContext))
5476
5966
  .join(', ');
@@ -5487,7 +5977,7 @@ class Deparser {
5487
5977
  output.push(this.quoteIfNeeded(node.extname));
5488
5978
  }
5489
5979
  if (node.options && node.options.length > 0) {
5490
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateExtensionStmt'] };
5980
+ const extContext = context.spawn('CreateExtensionStmt');
5491
5981
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5492
5982
  .map(option => this.visit(option, extContext))
5493
5983
  .join(' ');
@@ -5501,7 +5991,7 @@ class Deparser {
5501
5991
  output.push(this.quoteIfNeeded(node.extname));
5502
5992
  }
5503
5993
  if (node.options && node.options.length > 0) {
5504
- const extContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterExtensionStmt'] };
5994
+ const extContext = context.spawn('AlterExtensionStmt');
5505
5995
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5506
5996
  .map(option => this.visit(option, extContext))
5507
5997
  .join(' ');
@@ -5509,13 +5999,41 @@ class Deparser {
5509
5999
  }
5510
6000
  return output.join(' ');
5511
6001
  }
6002
+ AlterExtensionContentsStmt(node, context) {
6003
+ const output = ['ALTER', 'EXTENSION'];
6004
+ if (node.extname) {
6005
+ output.push(this.quoteIfNeeded(node.extname));
6006
+ }
6007
+ // Handle action: 1 = ADD, -1 = DROP
6008
+ if (node.action === 1) {
6009
+ output.push('ADD');
6010
+ }
6011
+ else if (node.action === -1) {
6012
+ output.push('DROP');
6013
+ }
6014
+ // Add object type keyword
6015
+ if (node.objtype) {
6016
+ try {
6017
+ output.push(this.getObjectTypeKeyword(node.objtype));
6018
+ }
6019
+ catch {
6020
+ // Fallback to the raw objtype if not supported
6021
+ output.push(node.objtype.toString());
6022
+ }
6023
+ }
6024
+ // Add the object itself
6025
+ if (node.object) {
6026
+ output.push(this.visit(node.object, context));
6027
+ }
6028
+ return output.join(' ');
6029
+ }
5512
6030
  CreateFdwStmt(node, context) {
5513
6031
  const output = ['CREATE', 'FOREIGN', 'DATA', 'WRAPPER'];
5514
6032
  if (node.fdwname) {
5515
6033
  output.push(node.fdwname);
5516
6034
  }
5517
6035
  if (node.func_options && node.func_options.length > 0) {
5518
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
6036
+ const fdwContext = context.spawn('CreateFdwStmt');
5519
6037
  const funcOptions = list_utils_1.ListUtils.unwrapList(node.func_options)
5520
6038
  .map(option => this.visit(option, fdwContext))
5521
6039
  .join(' ');
@@ -5523,7 +6041,7 @@ class Deparser {
5523
6041
  }
5524
6042
  if (node.options && node.options.length > 0) {
5525
6043
  output.push('OPTIONS');
5526
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateFdwStmt'] };
6044
+ const fdwContext = context.spawn('CreateFdwStmt');
5527
6045
  const options = list_utils_1.ListUtils.unwrapList(node.options)
5528
6046
  .map(option => this.visit(option, fdwContext))
5529
6047
  .join(', ');
@@ -5625,7 +6143,7 @@ class Deparser {
5625
6143
  output.push('ADD');
5626
6144
  if (node.def) {
5627
6145
  // Pass domain context to avoid adding constraint names for domain constraints
5628
- const domainContext = { ...context, isDomainConstraint: true };
6146
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5629
6147
  output.push(this.visit(node.def, domainContext));
5630
6148
  }
5631
6149
  break;
@@ -5651,7 +6169,7 @@ class Deparser {
5651
6169
  output.push('ADD');
5652
6170
  if (node.def) {
5653
6171
  // Pass domain context to avoid adding constraint names for domain constraints
5654
- const domainContext = { ...context, isDomainConstraint: true };
6172
+ const domainContext = context.spawn('CreateDomainStmt', { isDomainConstraint: true });
5655
6173
  output.push(this.visit(node.def, domainContext));
5656
6174
  }
5657
6175
  break;
@@ -6013,7 +6531,7 @@ class Deparser {
6013
6531
  output.push(`${operatorName}(${args.join(', ')})`);
6014
6532
  }
6015
6533
  else {
6016
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CommentStmt'], objtype: node.objtype };
6534
+ const objContext = context.spawn('CommentStmt', { objtype: node.objtype });
6017
6535
  output.push(this.visit(node.object, objContext));
6018
6536
  }
6019
6537
  }
@@ -6059,13 +6577,13 @@ class Deparser {
6059
6577
  const output = [];
6060
6578
  const initialParts = ['CREATE', 'POLICY'];
6061
6579
  if (node.policy_name) {
6062
- initialParts.push(`"${node.policy_name}"`);
6580
+ initialParts.push(quote_utils_1.QuoteUtils.quote(node.policy_name));
6063
6581
  }
6064
6582
  output.push(initialParts.join(' '));
6065
6583
  // Add ON clause on new line in pretty mode
6066
6584
  if (node.table) {
6067
- if (this.formatter.isPretty()) {
6068
- output.push(this.formatter.newline() + this.formatter.indent(`ON ${this.RangeVar(node.table, context)}`));
6585
+ if (context.isPretty()) {
6586
+ output.push(context.newline() + context.indent(`ON ${this.RangeVar(node.table, context)}`));
6069
6587
  }
6070
6588
  else {
6071
6589
  output.push('ON');
@@ -6074,24 +6592,24 @@ class Deparser {
6074
6592
  }
6075
6593
  // Handle AS RESTRICTIVE/PERMISSIVE clause
6076
6594
  if (node.permissive === undefined) {
6077
- if (this.formatter.isPretty()) {
6078
- output.push(this.formatter.newline() + this.formatter.indent('AS RESTRICTIVE'));
6595
+ if (context.isPretty()) {
6596
+ output.push(context.newline() + context.indent('AS RESTRICTIVE'));
6079
6597
  }
6080
6598
  else {
6081
6599
  output.push('AS', 'RESTRICTIVE');
6082
6600
  }
6083
6601
  }
6084
6602
  else if (node.permissive === true) {
6085
- if (this.formatter.isPretty()) {
6086
- output.push(this.formatter.newline() + this.formatter.indent('AS PERMISSIVE'));
6603
+ if (context.isPretty()) {
6604
+ output.push(context.newline() + context.indent('AS PERMISSIVE'));
6087
6605
  }
6088
6606
  else {
6089
6607
  output.push('AS', 'PERMISSIVE');
6090
6608
  }
6091
6609
  }
6092
6610
  if (node.cmd_name) {
6093
- if (this.formatter.isPretty()) {
6094
- output.push(this.formatter.newline() + this.formatter.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6611
+ if (context.isPretty()) {
6612
+ output.push(context.newline() + context.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6095
6613
  }
6096
6614
  else {
6097
6615
  output.push('FOR', node.cmd_name.toUpperCase());
@@ -6099,8 +6617,8 @@ class Deparser {
6099
6617
  }
6100
6618
  if (node.roles && node.roles.length > 0) {
6101
6619
  const roles = list_utils_1.ListUtils.unwrapList(node.roles).map(role => this.visit(role, context));
6102
- if (this.formatter.isPretty()) {
6103
- output.push(this.formatter.newline() + this.formatter.indent(`TO ${roles.join(', ')}`));
6620
+ if (context.isPretty()) {
6621
+ output.push(context.newline() + context.indent(`TO ${roles.join(', ')}`));
6104
6622
  }
6105
6623
  else {
6106
6624
  output.push('TO');
@@ -6108,11 +6626,11 @@ class Deparser {
6108
6626
  }
6109
6627
  }
6110
6628
  if (node.qual) {
6111
- if (this.formatter.isPretty()) {
6629
+ if (context.isPretty()) {
6112
6630
  const qualExpr = this.visit(node.qual, context);
6113
- output.push(this.formatter.newline() + this.formatter.indent('USING ('));
6114
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(qualExpr)));
6115
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6631
+ output.push(context.newline() + context.indent('USING ('));
6632
+ output.push(context.newline() + context.indent(context.indent(qualExpr)));
6633
+ output.push(context.newline() + context.indent(')'));
6116
6634
  }
6117
6635
  else {
6118
6636
  output.push('USING');
@@ -6120,23 +6638,23 @@ class Deparser {
6120
6638
  }
6121
6639
  }
6122
6640
  if (node.with_check) {
6123
- if (this.formatter.isPretty()) {
6641
+ if (context.isPretty()) {
6124
6642
  const checkExpr = this.visit(node.with_check, context);
6125
- output.push(this.formatter.newline() + this.formatter.indent('WITH CHECK ('));
6126
- output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(checkExpr)));
6127
- output.push(this.formatter.newline() + this.formatter.indent(')'));
6643
+ output.push(context.newline() + context.indent('WITH CHECK ('));
6644
+ output.push(context.newline() + context.indent(context.indent(checkExpr)));
6645
+ output.push(context.newline() + context.indent(')'));
6128
6646
  }
6129
6647
  else {
6130
6648
  output.push('WITH CHECK');
6131
6649
  output.push(`(${this.visit(node.with_check, context)})`);
6132
6650
  }
6133
6651
  }
6134
- return this.formatter.isPretty() ? output.join('') : output.join(' ');
6652
+ return context.isPretty() ? output.join('') : output.join(' ');
6135
6653
  }
6136
6654
  AlterPolicyStmt(node, context) {
6137
6655
  const output = ['ALTER', 'POLICY'];
6138
6656
  if (node.policy_name) {
6139
- output.push(`"${node.policy_name}"`);
6657
+ output.push(quote_utils_1.QuoteUtils.quote(node.policy_name));
6140
6658
  }
6141
6659
  if (node.table) {
6142
6660
  output.push('ON');
@@ -6176,7 +6694,7 @@ class Deparser {
6176
6694
  }
6177
6695
  if (node.options && node.options.length > 0) {
6178
6696
  output.push('OPTIONS');
6179
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateUserMappingStmt'] };
6697
+ const userMappingContext = context.spawn('CreateUserMappingStmt');
6180
6698
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6181
6699
  output.push(`(${options.join(', ')})`);
6182
6700
  }
@@ -6359,7 +6877,7 @@ class Deparser {
6359
6877
  DoStmt(node, context) {
6360
6878
  const output = ['DO'];
6361
6879
  if (node.args && node.args.length > 0) {
6362
- const doContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DoStmt'] };
6880
+ const doContext = context.spawn('DoStmt');
6363
6881
  const args = list_utils_1.ListUtils.unwrapList(node.args);
6364
6882
  const processedArgs = [];
6365
6883
  for (const arg of args) {
@@ -6664,7 +7182,7 @@ class Deparser {
6664
7182
  ObjectWithArgs(node, context) {
6665
7183
  let result = '';
6666
7184
  if (node.objname && node.objname.length > 0) {
6667
- const objContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ObjectWithArgs'] };
7185
+ const objContext = context.spawn('ObjectWithArgs');
6668
7186
  const names = list_utils_1.ListUtils.unwrapList(node.objname).map(name => this.visit(name, objContext));
6669
7187
  result = names.join('.');
6670
7188
  }
@@ -6706,7 +7224,7 @@ class Deparser {
6706
7224
  }
6707
7225
  output.push('SET');
6708
7226
  if (node.options && node.options.length > 0) {
6709
- const alterOpContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterOperatorStmt'] };
7227
+ const alterOpContext = context.spawn('AlterOperatorStmt');
6710
7228
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, alterOpContext));
6711
7229
  output.push(`(${options.join(', ')})`);
6712
7230
  }
@@ -6718,13 +7236,13 @@ class Deparser {
6718
7236
  output.push(quote_utils_1.QuoteUtils.quote(node.fdwname));
6719
7237
  }
6720
7238
  if (node.func_options && node.func_options.length > 0) {
6721
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
7239
+ const fdwContext = context.spawn('AlterFdwStmt');
6722
7240
  const funcOptions = list_utils_1.ListUtils.unwrapList(node.func_options).map(opt => this.visit(opt, fdwContext));
6723
7241
  output.push(funcOptions.join(' '));
6724
7242
  }
6725
7243
  if (node.options && node.options.length > 0) {
6726
7244
  output.push('OPTIONS');
6727
- const fdwContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFdwStmt'] };
7245
+ const fdwContext = context.spawn('AlterFdwStmt');
6728
7246
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, fdwContext));
6729
7247
  output.push(`(${options.join(', ')})`);
6730
7248
  }
@@ -6750,7 +7268,7 @@ class Deparser {
6750
7268
  if (node.options && node.options.length > 0) {
6751
7269
  output.push('OPTIONS');
6752
7270
  output.push('(');
6753
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignServerStmt'] };
7271
+ const optionsContext = context.spawn('CreateForeignServerStmt');
6754
7272
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6755
7273
  output.push(options.join(', '));
6756
7274
  output.push(')');
@@ -6768,7 +7286,7 @@ class Deparser {
6768
7286
  if (node.options && node.options.length > 0) {
6769
7287
  output.push('OPTIONS');
6770
7288
  output.push('(');
6771
- const optionsContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterForeignServerStmt'] };
7289
+ const optionsContext = context.spawn('AlterForeignServerStmt');
6772
7290
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, optionsContext));
6773
7291
  output.push(options.join(', '));
6774
7292
  output.push(')');
@@ -6789,7 +7307,7 @@ class Deparser {
6789
7307
  }
6790
7308
  if (node.options && node.options.length > 0) {
6791
7309
  output.push('OPTIONS');
6792
- const userMappingContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterUserMappingStmt'] };
7310
+ const userMappingContext = context.spawn('AlterUserMappingStmt');
6793
7311
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, userMappingContext));
6794
7312
  output.push(`(${options.join(', ')})`);
6795
7313
  }
@@ -6849,7 +7367,7 @@ class Deparser {
6849
7367
  output.push(quote_utils_1.QuoteUtils.quote(node.local_schema));
6850
7368
  }
6851
7369
  if (node.options && node.options.length > 0) {
6852
- const importSchemaContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ImportForeignSchemaStmt'] };
7370
+ const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
6853
7371
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, importSchemaContext));
6854
7372
  output.push(`OPTIONS (${options.join(', ')})`);
6855
7373
  }
@@ -6884,7 +7402,7 @@ class Deparser {
6884
7402
  ExplainStmt(node, context) {
6885
7403
  const output = ['EXPLAIN'];
6886
7404
  if (node.options && node.options.length > 0) {
6887
- const explainContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'ExplainStmt'] };
7405
+ const explainContext = context.spawn('ExplainStmt');
6888
7406
  const options = list_utils_1.ListUtils.unwrapList(node.options).map(option => this.visit(option, explainContext));
6889
7407
  output.push(`(${options.join(', ')})`);
6890
7408
  }
@@ -7142,9 +7660,7 @@ class Deparser {
7142
7660
  output.push(this.RangeVar(node.relation, context));
7143
7661
  }
7144
7662
  else if (node.relation) {
7145
- const rangeVarContext = node.relationType === 'OBJECT_TYPE'
7146
- ? { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterTypeStmt'] }
7147
- : context;
7663
+ const rangeVarContext = context.spawn('RenameStmt', { objtype: node.relationType });
7148
7664
  // Add ON keyword for policy operations
7149
7665
  if (node.renameType === 'OBJECT_POLICY') {
7150
7666
  output.push('ON');
@@ -7217,76 +7733,7 @@ class Deparser {
7217
7733
  if (!node.objectType) {
7218
7734
  throw new Error('AlterOwnerStmt requires objectType');
7219
7735
  }
7220
- switch (node.objectType) {
7221
- case 'OBJECT_TABLE':
7222
- output.push('TABLE');
7223
- break;
7224
- case 'OBJECT_VIEW':
7225
- output.push('VIEW');
7226
- break;
7227
- case 'OBJECT_INDEX':
7228
- output.push('INDEX');
7229
- break;
7230
- case 'OBJECT_SEQUENCE':
7231
- output.push('SEQUENCE');
7232
- break;
7233
- case 'OBJECT_FUNCTION':
7234
- output.push('FUNCTION');
7235
- break;
7236
- case 'OBJECT_PROCEDURE':
7237
- output.push('PROCEDURE');
7238
- break;
7239
- case 'OBJECT_SCHEMA':
7240
- output.push('SCHEMA');
7241
- break;
7242
- case 'OBJECT_DATABASE':
7243
- output.push('DATABASE');
7244
- break;
7245
- case 'OBJECT_DOMAIN':
7246
- output.push('DOMAIN');
7247
- break;
7248
- case 'OBJECT_AGGREGATE':
7249
- output.push('AGGREGATE');
7250
- break;
7251
- case 'OBJECT_CONVERSION':
7252
- output.push('CONVERSION');
7253
- break;
7254
- case 'OBJECT_LANGUAGE':
7255
- output.push('LANGUAGE');
7256
- break;
7257
- case 'OBJECT_OPERATOR':
7258
- output.push('OPERATOR');
7259
- break;
7260
- case 'OBJECT_OPFAMILY':
7261
- output.push('OPERATOR FAMILY');
7262
- break;
7263
- case 'OBJECT_OPCLASS':
7264
- output.push('OPERATOR CLASS');
7265
- break;
7266
- case 'OBJECT_TSDICTIONARY':
7267
- output.push('TEXT SEARCH DICTIONARY');
7268
- break;
7269
- case 'OBJECT_TSCONFIGURATION':
7270
- output.push('TEXT SEARCH CONFIGURATION');
7271
- break;
7272
- case 'OBJECT_EVENT_TRIGGER':
7273
- output.push('EVENT TRIGGER');
7274
- break;
7275
- case 'OBJECT_FDW':
7276
- output.push('FOREIGN DATA WRAPPER');
7277
- break;
7278
- case 'OBJECT_FOREIGN_SERVER':
7279
- output.push('SERVER');
7280
- break;
7281
- case 'OBJECT_TYPE':
7282
- output.push('TYPE');
7283
- break;
7284
- case 'OBJECT_COLLATION':
7285
- output.push('COLLATION');
7286
- break;
7287
- default:
7288
- throw new Error(`Unsupported AlterOwnerStmt objectType: ${node.objectType}`);
7289
- }
7736
+ output.push(this.getObjectTypeKeyword(node.objectType));
7290
7737
  if (node.relation) {
7291
7738
  output.push(this.RangeVar(node.relation, context));
7292
7739
  }
@@ -7326,7 +7773,7 @@ class Deparser {
7326
7773
  }
7327
7774
  }
7328
7775
  if (node.privileges && node.privileges.length > 0) {
7329
- const privilegeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'GrantStmt'] };
7776
+ const privilegeContext = context.spawn('GrantStmt');
7330
7777
  const privileges = list_utils_1.ListUtils.unwrapList(node.privileges)
7331
7778
  .map(priv => this.visit(priv, privilegeContext))
7332
7779
  .join(', ');
@@ -7634,7 +8081,12 @@ class Deparser {
7634
8081
  }
7635
8082
  if (node.action) {
7636
8083
  const actionStr = this.GrantStmt(node.action, context);
7637
- output.push(actionStr);
8084
+ if (context.isPretty()) {
8085
+ return output.join(' ') + context.newline() + context.indent(actionStr);
8086
+ }
8087
+ else {
8088
+ output.push(actionStr);
8089
+ }
7638
8090
  }
7639
8091
  return output.join(' ');
7640
8092
  }
@@ -7781,84 +8233,158 @@ class Deparser {
7781
8233
  if (node.trigname) {
7782
8234
  output.push(quote_utils_1.QuoteUtils.quote(node.trigname));
7783
8235
  }
7784
- const timing = [];
7785
- if (node.timing & 2)
7786
- timing.push('BEFORE');
7787
- else if (node.timing & 64)
7788
- timing.push('INSTEAD OF');
7789
- else
7790
- timing.push('AFTER'); // Default timing when no specific timing is set
7791
- output.push(timing.join(' '));
7792
- const events = [];
7793
- if (node.events & 4)
7794
- events.push('INSERT');
7795
- if (node.events & 8)
7796
- events.push('DELETE');
7797
- if (node.events & 16)
7798
- events.push('UPDATE');
7799
- if (node.events & 32)
7800
- events.push('TRUNCATE');
7801
- output.push(events.join(' OR '));
7802
- if (node.columns && node.columns.length > 0) {
7803
- output.push('OF');
7804
- const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
7805
- .map(col => this.visit(col, context))
7806
- .join(', ');
7807
- output.push(columnNames);
7808
- }
7809
- output.push('ON');
7810
- if (node.relation) {
7811
- output.push(this.RangeVar(node.relation, context));
7812
- }
7813
- if (node.constrrel) {
7814
- output.push('FROM');
7815
- output.push(this.RangeVar(node.constrrel, context));
7816
- }
7817
- if (node.deferrable) {
7818
- output.push('DEFERRABLE');
7819
- }
7820
- if (node.initdeferred) {
7821
- output.push('INITIALLY DEFERRED');
7822
- }
7823
- // Handle REFERENCING clauses
7824
- if (node.transitionRels && node.transitionRels.length > 0) {
7825
- output.push('REFERENCING');
7826
- const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
7827
- .map(rel => this.visit(rel, context))
7828
- .join(' ');
7829
- output.push(transitionClauses);
7830
- }
7831
- if (node.row) {
7832
- output.push('FOR EACH ROW');
7833
- }
7834
- else {
7835
- output.push('FOR EACH STATEMENT');
7836
- }
7837
- if (node.whenClause) {
7838
- output.push('WHEN');
7839
- output.push('(');
7840
- output.push(this.visit(node.whenClause, context));
7841
- output.push(')');
7842
- }
7843
- output.push('EXECUTE');
7844
- if (node.funcname && node.funcname.length > 0) {
7845
- const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
7846
- .map(name => this.visit(name, context))
7847
- .join('.');
7848
- output.push('FUNCTION', funcName);
7849
- }
7850
- if (node.args && node.args.length > 0) {
7851
- output.push('(');
7852
- const args = list_utils_1.ListUtils.unwrapList(node.args)
7853
- .map(arg => this.visit(arg, context))
7854
- .join(', ');
7855
- output.push(args);
7856
- output.push(')');
8236
+ if (context.isPretty()) {
8237
+ const components = [];
8238
+ const timing = [];
8239
+ if (node.timing & 2)
8240
+ timing.push('BEFORE');
8241
+ else if (node.timing & 64)
8242
+ timing.push('INSTEAD OF');
8243
+ else
8244
+ timing.push('AFTER');
8245
+ const events = [];
8246
+ if (node.events & 4)
8247
+ events.push('INSERT');
8248
+ if (node.events & 8)
8249
+ events.push('DELETE');
8250
+ if (node.events & 16) {
8251
+ let updateStr = 'UPDATE';
8252
+ if (node.columns && node.columns.length > 0) {
8253
+ const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
8254
+ .map(col => this.visit(col, context))
8255
+ .join(', ');
8256
+ updateStr += ' OF ' + columnNames;
8257
+ }
8258
+ events.push(updateStr);
8259
+ }
8260
+ if (node.events & 32)
8261
+ events.push('TRUNCATE');
8262
+ components.push(context.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8263
+ if (node.relation) {
8264
+ components.push(context.indent('ON ' + this.RangeVar(node.relation, context)));
8265
+ }
8266
+ if (node.transitionRels && node.transitionRels.length > 0) {
8267
+ const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
8268
+ .map(rel => this.visit(rel, context))
8269
+ .join(' ');
8270
+ components.push(context.indent('REFERENCING ' + transitionClauses));
8271
+ }
8272
+ if (node.deferrable) {
8273
+ components.push(context.indent('DEFERRABLE'));
8274
+ }
8275
+ if (node.initdeferred) {
8276
+ components.push(context.indent('INITIALLY DEFERRED'));
8277
+ }
8278
+ if (node.row) {
8279
+ components.push(context.indent('FOR EACH ROW'));
8280
+ }
8281
+ else {
8282
+ components.push(context.indent('FOR EACH STATEMENT'));
8283
+ }
8284
+ if (node.whenClause) {
8285
+ const whenStr = 'WHEN (' + this.visit(node.whenClause, context) + ')';
8286
+ components.push(context.indent(whenStr));
8287
+ }
8288
+ let executeStr = 'EXECUTE';
8289
+ if (node.funcname && node.funcname.length > 0) {
8290
+ const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
8291
+ .map(name => this.visit(name, context))
8292
+ .join('.');
8293
+ executeStr += ' PROCEDURE ' + funcName;
8294
+ }
8295
+ if (node.args && node.args.length > 0) {
8296
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8297
+ const args = list_utils_1.ListUtils.unwrapList(node.args)
8298
+ .map(arg => this.visit(arg, argContext))
8299
+ .join(', ');
8300
+ executeStr += '(' + args + ')';
8301
+ }
8302
+ else {
8303
+ executeStr += '()';
8304
+ }
8305
+ components.push(context.indent(executeStr));
8306
+ return output.join(' ') + context.newline() + components.join(context.newline());
7857
8307
  }
7858
8308
  else {
7859
- output.push('()');
8309
+ const timing = [];
8310
+ if (node.timing & 2)
8311
+ timing.push('BEFORE');
8312
+ else if (node.timing & 64)
8313
+ timing.push('INSTEAD OF');
8314
+ else
8315
+ timing.push('AFTER');
8316
+ output.push(timing.join(' '));
8317
+ const events = [];
8318
+ if (node.events & 4)
8319
+ events.push('INSERT');
8320
+ if (node.events & 8)
8321
+ events.push('DELETE');
8322
+ if (node.events & 16)
8323
+ events.push('UPDATE');
8324
+ if (node.events & 32)
8325
+ events.push('TRUNCATE');
8326
+ output.push(events.join(' OR '));
8327
+ if (node.columns && node.columns.length > 0) {
8328
+ output.push('OF');
8329
+ const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
8330
+ .map(col => this.visit(col, context))
8331
+ .join(', ');
8332
+ output.push(columnNames);
8333
+ }
8334
+ output.push('ON');
8335
+ if (node.relation) {
8336
+ output.push(this.RangeVar(node.relation, context));
8337
+ }
8338
+ if (node.constrrel) {
8339
+ output.push('FROM');
8340
+ output.push(this.RangeVar(node.constrrel, context));
8341
+ }
8342
+ if (node.deferrable) {
8343
+ output.push('DEFERRABLE');
8344
+ }
8345
+ if (node.initdeferred) {
8346
+ output.push('INITIALLY DEFERRED');
8347
+ }
8348
+ if (node.transitionRels && node.transitionRels.length > 0) {
8349
+ output.push('REFERENCING');
8350
+ const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
8351
+ .map(rel => this.visit(rel, context))
8352
+ .join(' ');
8353
+ output.push(transitionClauses);
8354
+ }
8355
+ if (node.row) {
8356
+ output.push('FOR EACH ROW');
8357
+ }
8358
+ else {
8359
+ output.push('FOR EACH STATEMENT');
8360
+ }
8361
+ if (node.whenClause) {
8362
+ output.push('WHEN');
8363
+ output.push('(');
8364
+ output.push(this.visit(node.whenClause, context));
8365
+ output.push(')');
8366
+ }
8367
+ output.push('EXECUTE');
8368
+ if (node.funcname && node.funcname.length > 0) {
8369
+ const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
8370
+ .map(name => this.visit(name, context))
8371
+ .join('.');
8372
+ output.push('FUNCTION', funcName);
8373
+ }
8374
+ if (node.args && node.args.length > 0) {
8375
+ output.push('(');
8376
+ const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
8377
+ const args = list_utils_1.ListUtils.unwrapList(node.args)
8378
+ .map(arg => this.visit(arg, argContext))
8379
+ .join(', ');
8380
+ output.push(args);
8381
+ output.push(')');
8382
+ }
8383
+ else {
8384
+ output.push('()');
8385
+ }
8386
+ return output.join(' ');
7860
8387
  }
7861
- return output.join(' ');
7862
8388
  }
7863
8389
  TriggerTransition(node, context) {
7864
8390
  const output = [];
@@ -7884,7 +8410,7 @@ class Deparser {
7884
8410
  }
7885
8411
  if (node.whenclause && node.whenclause.length > 0) {
7886
8412
  output.push('WHEN');
7887
- const eventTriggerContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateEventTrigStmt'] };
8413
+ const eventTriggerContext = context.spawn('CreateEventTrigStmt');
7888
8414
  const conditions = list_utils_1.ListUtils.unwrapList(node.whenclause)
7889
8415
  .map(condition => this.visit(condition, eventTriggerContext))
7890
8416
  .join(' AND ');
@@ -8073,7 +8599,7 @@ class Deparser {
8073
8599
  output.push(sequenceName.join('.'));
8074
8600
  }
8075
8601
  if (node.options && node.options.length > 0) {
8076
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateSeqStmt'] };
8602
+ const seqContext = context.spawn('CreateSeqStmt');
8077
8603
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
8078
8604
  .filter(option => option != null && this.getNodeType(option) !== 'undefined')
8079
8605
  .map(option => {
@@ -8110,7 +8636,7 @@ class Deparser {
8110
8636
  output.push(sequenceName.join('.'));
8111
8637
  }
8112
8638
  if (node.options && node.options.length > 0) {
8113
- const seqContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterSeqStmt'] };
8639
+ const seqContext = context.spawn('AlterSeqStmt');
8114
8640
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options)
8115
8641
  .filter(option => option && option !== undefined)
8116
8642
  .map(option => {
@@ -8139,7 +8665,7 @@ class Deparser {
8139
8665
  CompositeTypeStmt(node, context) {
8140
8666
  const output = ['CREATE', 'TYPE'];
8141
8667
  if (node.typevar) {
8142
- const typeContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CompositeTypeStmt'] };
8668
+ const typeContext = context.spawn('CompositeTypeStmt');
8143
8669
  output.push(this.RangeVar(node.typevar, typeContext));
8144
8670
  }
8145
8671
  output.push('AS');
@@ -8240,7 +8766,7 @@ class Deparser {
8240
8766
  output.push(this.RoleSpec(node.role, context));
8241
8767
  }
8242
8768
  if (node.options) {
8243
- const roleContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterRoleStmt'] };
8769
+ const roleContext = context.spawn('AlterRoleStmt');
8244
8770
  // Handle GROUP operations specially based on action value
8245
8771
  if (isGroupStatement) {
8246
8772
  const roleMembersOption = list_utils_1.ListUtils.unwrapList(node.options).find(option => option.DefElem && option.DefElem.defname === 'rolemembers');
@@ -8436,14 +8962,14 @@ class Deparser {
8436
8962
  AccessPriv(node, context) {
8437
8963
  const output = [];
8438
8964
  if (node.priv_name) {
8439
- output.push(node.priv_name);
8965
+ output.push(node.priv_name.toUpperCase());
8440
8966
  }
8441
8967
  else {
8442
8968
  output.push('ALL');
8443
8969
  }
8444
8970
  if (node.cols && node.cols.length > 0) {
8445
8971
  output.push('(');
8446
- const colContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AccessPriv'] };
8972
+ const colContext = context.spawn('AccessPriv');
8447
8973
  const columns = list_utils_1.ListUtils.unwrapList(node.cols).map(col => this.visit(col, colContext));
8448
8974
  output.push(columns.join(', '));
8449
8975
  output.push(')');
@@ -8523,7 +9049,7 @@ class Deparser {
8523
9049
  output.push(list_utils_1.ListUtils.unwrapList(node.defnames).map(name => this.visit(name, context)).join('.'));
8524
9050
  }
8525
9051
  if (node.definition && node.definition.length > 0) {
8526
- const defineStmtContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'DefineStmt'] };
9052
+ const defineStmtContext = context.spawn('DefineStmt');
8527
9053
  const definitions = list_utils_1.ListUtils.unwrapList(node.definition).map(def => {
8528
9054
  return this.visit(def, defineStmtContext);
8529
9055
  });
@@ -9009,7 +9535,7 @@ class Deparser {
9009
9535
  const operatorNumber = node.number !== undefined ? node.number : 0;
9010
9536
  output.push(operatorNumber.toString());
9011
9537
  if (node.name) {
9012
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9538
+ const opClassContext = context.spawn('CreateOpClassItem');
9013
9539
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9014
9540
  }
9015
9541
  }
@@ -9019,7 +9545,7 @@ class Deparser {
9019
9545
  const functionNumber = node.number !== undefined ? node.number : 0;
9020
9546
  output.push(functionNumber.toString());
9021
9547
  if (node.name) {
9022
- const opClassContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateOpClassItem'] };
9548
+ const opClassContext = context.spawn('CreateOpClassItem');
9023
9549
  output.push(this.ObjectWithArgs(node.name, opClassContext));
9024
9550
  }
9025
9551
  }
@@ -9717,7 +10243,7 @@ class Deparser {
9717
10243
  output.push(this.ObjectWithArgs(node.func, context));
9718
10244
  }
9719
10245
  if (node.actions && node.actions.length > 0) {
9720
- const alterFunctionContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'AlterFunctionStmt'] };
10246
+ const alterFunctionContext = context.spawn('AlterFunctionStmt');
9721
10247
  const actionStrs = list_utils_1.ListUtils.unwrapList(node.actions).map(action => this.visit(action, alterFunctionContext));
9722
10248
  output.push(actionStrs.join(' '));
9723
10249
  }
@@ -9725,66 +10251,12 @@ class Deparser {
9725
10251
  }
9726
10252
  AlterObjectSchemaStmt(node, context) {
9727
10253
  const output = ['ALTER'];
9728
- switch (node.objectType) {
9729
- case 'OBJECT_TABLE':
9730
- output.push('TABLE');
9731
- break;
9732
- case 'OBJECT_VIEW':
9733
- output.push('VIEW');
9734
- break;
9735
- case 'OBJECT_FUNCTION':
9736
- output.push('FUNCTION');
9737
- break;
9738
- case 'OBJECT_TYPE':
9739
- output.push('TYPE');
9740
- break;
9741
- case 'OBJECT_DOMAIN':
9742
- output.push('DOMAIN');
9743
- break;
9744
- case 'OBJECT_SEQUENCE':
9745
- output.push('SEQUENCE');
9746
- break;
9747
- case 'OBJECT_OPCLASS':
9748
- output.push('OPERATOR CLASS');
9749
- break;
9750
- case 'OBJECT_OPFAMILY':
9751
- output.push('OPERATOR FAMILY');
9752
- break;
9753
- case 'OBJECT_OPERATOR':
9754
- output.push('OPERATOR');
9755
- break;
9756
- case 'OBJECT_TYPE':
9757
- output.push('TYPE');
9758
- break;
9759
- case 'OBJECT_COLLATION':
9760
- output.push('COLLATION');
9761
- break;
9762
- case 'OBJECT_CONVERSION':
9763
- output.push('CONVERSION');
9764
- break;
9765
- case 'OBJECT_TSPARSER':
9766
- output.push('TEXT SEARCH PARSER');
9767
- break;
9768
- case 'OBJECT_TSCONFIGURATION':
9769
- output.push('TEXT SEARCH CONFIGURATION');
9770
- break;
9771
- case 'OBJECT_TSTEMPLATE':
9772
- output.push('TEXT SEARCH TEMPLATE');
9773
- break;
9774
- case 'OBJECT_TSDICTIONARY':
9775
- output.push('TEXT SEARCH DICTIONARY');
9776
- break;
9777
- case 'OBJECT_AGGREGATE':
9778
- output.push('AGGREGATE');
9779
- break;
9780
- case 'OBJECT_FOREIGN_TABLE':
9781
- output.push('FOREIGN TABLE');
9782
- break;
9783
- case 'OBJECT_MATVIEW':
9784
- output.push('MATERIALIZED VIEW');
9785
- break;
9786
- default:
9787
- output.push(node.objectType.toString());
10254
+ try {
10255
+ output.push(this.getObjectTypeKeyword(node.objectType));
10256
+ }
10257
+ catch {
10258
+ // Fallback to objectType string if not supported
10259
+ output.push(node.objectType.toString());
9788
10260
  }
9789
10261
  if (node.missing_ok) {
9790
10262
  output.push('IF EXISTS');
@@ -9961,7 +10433,7 @@ class Deparser {
9961
10433
  CreateForeignTableStmt(node, context) {
9962
10434
  const output = ['CREATE FOREIGN TABLE'];
9963
10435
  if (node.base && node.base.relation) {
9964
- const relationContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10436
+ const relationContext = context.spawn('CreateForeignTableStmt');
9965
10437
  // Handle relation node directly as RangeVar since it contains the RangeVar properties
9966
10438
  output.push(this.RangeVar(node.base.relation, relationContext));
9967
10439
  }
@@ -9991,7 +10463,7 @@ class Deparser {
9991
10463
  output.push(quote_utils_1.QuoteUtils.quote(node.servername));
9992
10464
  }
9993
10465
  if (node.options && node.options.length > 0) {
9994
- const foreignTableContext = { ...context, parentNodeTypes: [...context.parentNodeTypes, 'CreateForeignTableStmt'] };
10466
+ const foreignTableContext = context.spawn('CreateForeignTableStmt');
9995
10467
  const optionStrs = list_utils_1.ListUtils.unwrapList(node.options).map(opt => this.visit(opt, foreignTableContext));
9996
10468
  output.push(`OPTIONS (${optionStrs.join(', ')})`);
9997
10469
  }