pgsql-deparser 17.18.4 → 17.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,30 @@
1
- import { DeparserContext } from './visitors/base';
2
- import { SqlFormatter } from './utils/sql-formatter';
3
- import { QuoteUtils } from '@pgsql/quotes';
1
+ /**
2
+ * Auto-generated file with types stripped for better tree-shaking
3
+ * DO NOT EDIT - Generated by strip-deparser-types.ts
4
+ */
5
+ import { keywordKindOf, QuoteUtils } from '@pgsql/quotes';
4
6
  import { ListUtils } from './utils/list-utils';
7
+ import { SqlFormatter } from './utils/sql-formatter';
8
+ import { DeparserContext } from './visitors/base';
9
+ // WindowDef.frameOptions bit flags (src/include/nodes/parsenodes.h)
10
+ const FRAMEOPTION_NONDEFAULT = 0x00001;
11
+ const FRAMEOPTION_RANGE = 0x00002;
12
+ const FRAMEOPTION_ROWS = 0x00004;
13
+ const FRAMEOPTION_GROUPS = 0x00008;
14
+ const FRAMEOPTION_BETWEEN = 0x00010;
15
+ const FRAMEOPTION_START_UNBOUNDED_PRECEDING = 0x00020;
16
+ const FRAMEOPTION_END_UNBOUNDED_PRECEDING = 0x00040;
17
+ const FRAMEOPTION_START_UNBOUNDED_FOLLOWING = 0x00080;
18
+ const FRAMEOPTION_END_UNBOUNDED_FOLLOWING = 0x00100;
19
+ const FRAMEOPTION_START_CURRENT_ROW = 0x00200;
20
+ const FRAMEOPTION_END_CURRENT_ROW = 0x00400;
21
+ const FRAMEOPTION_START_OFFSET_PRECEDING = 0x00800;
22
+ const FRAMEOPTION_END_OFFSET_PRECEDING = 0x01000;
23
+ const FRAMEOPTION_START_OFFSET_FOLLOWING = 0x02000;
24
+ const FRAMEOPTION_END_OFFSET_FOLLOWING = 0x04000;
25
+ const FRAMEOPTION_EXCLUDE_CURRENT_ROW = 0x08000;
26
+ const FRAMEOPTION_EXCLUDE_GROUP = 0x10000;
27
+ const FRAMEOPTION_EXCLUDE_TIES = 0x20000;
5
28
  /**
6
29
  * List of real PostgreSQL built-in types as they appear in pg_catalog.pg_type.typname.
7
30
  * These are stored in lowercase in PostgreSQL system catalogs.
@@ -100,6 +123,36 @@ function isWrappedParseResult(obj) {
100
123
  * The deparser automatically detects bare ParseResult objects for backward
101
124
  * compatibility and wraps them internally for consistent processing.
102
125
  */
126
+ function formatBitString(bsval) {
127
+ const quote = (value) => value.replace(/'/g, "''");
128
+ if (/^x[0-9A-Fa-f]*$/.test(bsval)) {
129
+ return `x'${bsval.substring(1)}'`;
130
+ }
131
+ if (bsval.startsWith('b')) {
132
+ return `b'${quote(bsval.substring(1))}'`;
133
+ }
134
+ return `b'${quote(bsval)}'`;
135
+ }
136
+ // A dollar-quoted body ends at the first occurrence of its tag, including one
137
+ // formed by the body's trailing characters plus the closing tag (`...$` + `$$`).
138
+ function dollarTagConflicts(body, tag) {
139
+ return (body + tag).indexOf(tag) < body.length;
140
+ }
141
+ const NUMERIC_LITERAL = /^[+-]?(?:0[xX](?:_?[0-9A-Fa-f])+|0[oO](?:_?[0-7])+|0[bB](?:_?[01])+|(?:\d(?:_?\d)*(?:\.(?:\d(?:_?\d)*)?)?|\.\d(?:_?\d)*)(?:[eE][+-]?\d(?:_?\d)*)?)$/;
142
+ function formatInteger(ival) {
143
+ const value = String(ival);
144
+ if (!/^[+-]?\d+$/.test(value)) {
145
+ throw new Error(`Invalid integer literal: ${value}`);
146
+ }
147
+ return value;
148
+ }
149
+ function formatNumeric(fval) {
150
+ const value = String(fval);
151
+ if (!NUMERIC_LITERAL.test(value)) {
152
+ throw new Error(`Invalid numeric literal: ${value}`);
153
+ }
154
+ return value;
155
+ }
103
156
  export class Deparser {
104
157
  tree;
105
158
  options;
@@ -159,10 +212,19 @@ export class Deparser {
159
212
  */
160
213
  getFunctionDelimiter(body) {
161
214
  const delimiter = this.options.functionDelimiter || '$$';
162
- if (body.includes(delimiter)) {
163
- return this.options.functionDelimiterFallback || '$EOFCODE$';
215
+ if (!dollarTagConflicts(body, delimiter)) {
216
+ return delimiter;
164
217
  }
165
- return delimiter;
218
+ const fallback = this.options.functionDelimiterFallback || '$EOFCODE$';
219
+ if (!dollarTagConflicts(body, fallback)) {
220
+ return fallback;
221
+ }
222
+ const base = fallback.slice(0, -1);
223
+ let counter = 1;
224
+ while (dollarTagConflicts(body, `${base}${counter}$`)) {
225
+ counter++;
226
+ }
227
+ return `${base}${counter}$`;
166
228
  }
167
229
  /**
168
230
  * Maps ObjectType enum values to their corresponding SQL keywords
@@ -365,16 +427,16 @@ export class Deparser {
365
427
  const leftStmt = this.SelectStmt(node.larg, context);
366
428
  const rightStmt = this.SelectStmt(node.rarg, context);
367
429
  // Add parentheses if the operand is a set operation OR has ORDER BY/LIMIT clauses OR has WITH clause
368
- const leftNeedsParens = node.larg && ((node.larg.op && node.larg.op !== 'SETOP_NONE') ||
369
- node.larg.sortClause ||
370
- node.larg.limitCount ||
371
- node.larg.limitOffset ||
372
- node.larg.withClause);
373
- const rightNeedsParens = node.rarg && ((node.rarg.op && node.rarg.op !== 'SETOP_NONE') ||
374
- node.rarg.sortClause ||
375
- node.rarg.limitCount ||
376
- node.rarg.limitOffset ||
377
- node.rarg.withClause);
430
+ const leftNeedsParens = node.larg && (((node.larg).op && (node.larg).op !== 'SETOP_NONE') ||
431
+ (node.larg).sortClause ||
432
+ (node.larg).limitCount ||
433
+ (node.larg).limitOffset ||
434
+ (node.larg).withClause);
435
+ const rightNeedsParens = node.rarg && (((node.rarg).op && (node.rarg).op !== 'SETOP_NONE') ||
436
+ (node.rarg).sortClause ||
437
+ (node.rarg).limitCount ||
438
+ (node.rarg).limitOffset ||
439
+ (node.rarg).withClause);
378
440
  if (leftNeedsParens) {
379
441
  output.push(context.parens(leftStmt));
380
442
  }
@@ -439,12 +501,7 @@ export class Deparser {
439
501
  // Check if single target is complex - if so, use multiline format
440
502
  if (this.isComplexSelectTarget(targetNode)) {
441
503
  output.push('SELECT' + distinctPart);
442
- if (this.containsMultilineStringLiteral(target)) {
443
- output.push(target);
444
- }
445
- else {
446
- output.push(context.indent(target));
447
- }
504
+ output.push(context.indent(target));
448
505
  }
449
506
  else {
450
507
  output.push('SELECT' + distinctPart + ' ' + target);
@@ -454,9 +511,6 @@ export class Deparser {
454
511
  const targetStrings = targetList
455
512
  .map(e => {
456
513
  const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
457
- if (this.containsMultilineStringLiteral(targetStr)) {
458
- return targetStr;
459
- }
460
514
  return context.indent(targetStr);
461
515
  });
462
516
  const formattedTargets = targetStrings.join(',' + context.newline());
@@ -508,9 +562,6 @@ export class Deparser {
508
562
  return context.parens(values.join(', '));
509
563
  });
510
564
  const indentedTuples = lists.map(tuple => {
511
- if (this.containsMultilineStringLiteral(tuple)) {
512
- return tuple;
513
- }
514
565
  return context.indent(tuple);
515
566
  });
516
567
  output.push(indentedTuples.join(',\n'));
@@ -530,9 +581,6 @@ export class Deparser {
530
581
  const groupItems = groupList
531
582
  .map(e => {
532
583
  const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
533
- if (this.containsMultilineStringLiteral(groupStr)) {
534
- return groupStr;
535
- }
536
584
  return context.indent(groupStr);
537
585
  })
538
586
  .join(',' + context.newline());
@@ -551,12 +599,7 @@ export class Deparser {
551
599
  if (context.isPretty()) {
552
600
  output.push('HAVING');
553
601
  const havingStr = this.visit(node.havingClause, context);
554
- if (this.containsMultilineStringLiteral(havingStr)) {
555
- output.push(havingStr);
556
- }
557
- else {
558
- output.push(context.indent(havingStr));
559
- }
602
+ output.push(context.indent(havingStr));
560
603
  }
561
604
  else {
562
605
  output.push('HAVING');
@@ -577,9 +620,6 @@ export class Deparser {
577
620
  const sortItems = sortList
578
621
  .map(e => {
579
622
  const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
580
- if (this.containsMultilineStringLiteral(sortStr)) {
581
- return sortStr;
582
- }
583
623
  return context.indent(sortStr);
584
624
  })
585
625
  .join(',' + context.newline());
@@ -843,15 +883,15 @@ export class Deparser {
843
883
  getOperatorPrecedence(operator) {
844
884
  const precedence = {
845
885
  '||': 1, // string concatenation
846
- 'OR': 2, // logical OR
847
- 'AND': 3, // logical AND
848
- 'NOT': 4, // logical NOT
849
- 'IS': 5, // IS NULL, IS NOT NULL, etc.
850
- 'IN': 5, // IN, NOT IN
851
- 'BETWEEN': 5, // BETWEEN, NOT BETWEEN
852
- 'LIKE': 5, // LIKE, ILIKE, SIMILAR TO
853
- 'ILIKE': 5,
854
- 'SIMILAR': 5,
886
+ OR: 2, // logical OR
887
+ AND: 3, // logical AND
888
+ NOT: 4, // logical NOT
889
+ IS: 5, // IS NULL, IS NOT NULL, etc.
890
+ IN: 5, // IN, NOT IN
891
+ BETWEEN: 5, // BETWEEN, NOT BETWEEN
892
+ LIKE: 5, // LIKE, ILIKE, SIMILAR TO
893
+ ILIKE: 5,
894
+ SIMILAR: 5,
855
895
  '<': 6, // comparison operators
856
896
  '<=': 6,
857
897
  '>': 6,
@@ -981,6 +1021,12 @@ export class Deparser {
981
1021
  output.push(context.parens(columnNames.join(', ')));
982
1022
  }
983
1023
  }
1024
+ if (node.override === 'OVERRIDING_USER_VALUE') {
1025
+ output.push('OVERRIDING USER VALUE');
1026
+ }
1027
+ else if (node.override === 'OVERRIDING_SYSTEM_VALUE') {
1028
+ output.push('OVERRIDING SYSTEM VALUE');
1029
+ }
984
1030
  if (node.selectStmt) {
985
1031
  output.push(this.visit(node.selectStmt, context));
986
1032
  }
@@ -1026,7 +1072,7 @@ export class Deparser {
1026
1072
  if (context.isPretty()) {
1027
1073
  // Put each assignment on its own line for pretty printing
1028
1074
  const indentedTargets = targets.map(target => context.indent(target));
1029
- output.push('\n' + indentedTargets.join(',\n'));
1075
+ output[output.length - 1] += '\n' + indentedTargets.join(',\n');
1030
1076
  }
1031
1077
  else {
1032
1078
  output.push(targets.join(', '));
@@ -1042,11 +1088,8 @@ export class Deparser {
1042
1088
  output.push('DO NOTHING');
1043
1089
  }
1044
1090
  }
1045
- if (node.returningList) {
1046
- output.push('RETURNING');
1047
- const returningList = ListUtils.unwrapList(node.returningList);
1048
- const returns = returningList.map(ret => this.visit(ret, context));
1049
- output.push(returns.join(', '));
1091
+ if (node.returningClause) {
1092
+ output.push(this.ReturningClause(node.returningClause, context));
1050
1093
  }
1051
1094
  return output.join(' ');
1052
1095
  }
@@ -1060,38 +1103,8 @@ export class Deparser {
1060
1103
  output.push(this.RangeVar(node.relation, context));
1061
1104
  }
1062
1105
  output.push('SET');
1063
- const targetList = ListUtils.unwrapList(node.targetList);
1064
- if (targetList && targetList.length) {
1065
- const firstTarget = targetList[0];
1066
- const processedTargets = new Set();
1067
- const assignmentParts = [];
1068
- for (let i = 0; i < targetList.length; i++) {
1069
- if (processedTargets.has(i))
1070
- continue;
1071
- const target = targetList[i];
1072
- const multiAssignRef = target.ResTarget?.val?.MultiAssignRef;
1073
- if (multiAssignRef) {
1074
- const relatedTargets = [];
1075
- for (let j = i; j < targetList.length; j++) {
1076
- const otherTarget = targetList[j];
1077
- const otherMultiAssignRef = otherTarget.ResTarget?.val?.MultiAssignRef;
1078
- if (otherMultiAssignRef &&
1079
- JSON.stringify(otherMultiAssignRef.source) === JSON.stringify(multiAssignRef.source)) {
1080
- relatedTargets.push(otherTarget);
1081
- processedTargets.add(j);
1082
- }
1083
- }
1084
- const names = relatedTargets.map(t => t.ResTarget.name);
1085
- const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
1086
- assignmentParts.push(multiAssignment);
1087
- }
1088
- else {
1089
- // Handle regular single-column assignment
1090
- assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
1091
- processedTargets.add(i);
1092
- }
1093
- }
1094
- output.push(assignmentParts.join(','));
1106
+ if (node.targetList && node.targetList.length > 0) {
1107
+ output.push(this.formatUpdateAssignments(node.targetList, context));
1095
1108
  }
1096
1109
  if (node.fromClause) {
1097
1110
  output.push('FROM');
@@ -1103,12 +1116,41 @@ export class Deparser {
1103
1116
  output.push('WHERE');
1104
1117
  output.push(this.visit(node.whereClause, context));
1105
1118
  }
1106
- if (node.returningList) {
1107
- output.push('RETURNING');
1108
- output.push(this.deparseReturningList(node.returningList, context));
1119
+ if (node.returningClause) {
1120
+ output.push(this.ReturningClause(node.returningClause, context));
1109
1121
  }
1110
1122
  return output.join(' ');
1111
1123
  }
1124
+ formatUpdateAssignments(targets, context) {
1125
+ const targetList = ListUtils.unwrapList(targets);
1126
+ const processedTargets = new Set();
1127
+ const assignmentParts = [];
1128
+ for (let i = 0; i < targetList.length; i++) {
1129
+ if (processedTargets.has(i))
1130
+ continue;
1131
+ const target = targetList[i];
1132
+ const multiAssignRef = target.ResTarget?.val?.MultiAssignRef;
1133
+ if (multiAssignRef) {
1134
+ const relatedTargets = [];
1135
+ for (let j = i; j < targetList.length; j++) {
1136
+ const otherTarget = targetList[j];
1137
+ const otherMultiAssignRef = otherTarget.ResTarget?.val?.MultiAssignRef;
1138
+ if (otherMultiAssignRef &&
1139
+ JSON.stringify(otherMultiAssignRef.source) === JSON.stringify(multiAssignRef.source)) {
1140
+ relatedTargets.push(otherTarget);
1141
+ processedTargets.add(j);
1142
+ }
1143
+ }
1144
+ const names = relatedTargets.map(t => QuoteUtils.quoteIdentifier(t.ResTarget.name));
1145
+ assignmentParts.push(`${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`);
1146
+ }
1147
+ else {
1148
+ assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
1149
+ processedTargets.add(i);
1150
+ }
1151
+ }
1152
+ return assignmentParts.join(', ');
1153
+ }
1112
1154
  DeleteStmt(node, context) {
1113
1155
  try {
1114
1156
  const output = [];
@@ -1156,13 +1198,12 @@ export class Deparser {
1156
1198
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1157
1199
  }
1158
1200
  }
1159
- if (node.returningList) {
1160
- output.push('RETURNING');
1201
+ if (node.returningClause) {
1161
1202
  try {
1162
- output.push(this.deparseReturningList(node.returningList, context));
1203
+ output.push(this.ReturningClause(node.returningClause, context));
1163
1204
  }
1164
1205
  catch (error) {
1165
- console.warn(`Error processing returningList in DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1206
+ console.warn(`Error processing returningClause in DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1166
1207
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1167
1208
  }
1168
1209
  }
@@ -1183,9 +1224,6 @@ export class Deparser {
1183
1224
  const cteStrings = ctes.map((cte, index) => {
1184
1225
  const cteStr = this.visit(cte, context);
1185
1226
  const prefix = index === 0 ? context.newline() : ',' + context.newline();
1186
- if (this.containsMultilineStringLiteral(cteStr)) {
1187
- return prefix + cteStr;
1188
- }
1189
1227
  return prefix + context.indent(cteStr);
1190
1228
  });
1191
1229
  output.push(cteStrings.join(''));
@@ -1240,6 +1278,48 @@ export class Deparser {
1240
1278
  }
1241
1279
  return output.join(' ');
1242
1280
  }
1281
+ ATAlterConstraint(node, context) {
1282
+ const output = ['ALTER CONSTRAINT'];
1283
+ if (node.conname) {
1284
+ output.push(QuoteUtils.quoteIdentifier(node.conname));
1285
+ }
1286
+ if (node.alterEnforceability) {
1287
+ output.push(node.is_enforced ? 'ENFORCED' : 'NOT ENFORCED');
1288
+ }
1289
+ if (node.alterDeferrability) {
1290
+ output.push(node.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
1291
+ if (node.initdeferred) {
1292
+ output.push('INITIALLY DEFERRED');
1293
+ }
1294
+ }
1295
+ if (node.alterInheritability) {
1296
+ output.push(node.noinherit ? 'NO INHERIT' : 'INHERIT');
1297
+ }
1298
+ return output.join(' ');
1299
+ }
1300
+ deparseKeyAttrs(attrs, withPeriod, context) {
1301
+ const items = ListUtils.unwrapList(attrs).map(attr => this.visit(attr, context));
1302
+ if (withPeriod && items.length > 0) {
1303
+ items[items.length - 1] = `PERIOD ${items[items.length - 1]}`;
1304
+ }
1305
+ return items.join(', ');
1306
+ }
1307
+ ReturningClause(node, context) {
1308
+ const output = ['RETURNING'];
1309
+ if (node.options && node.options.length > 0) {
1310
+ const options = ListUtils.unwrapList(node.options)
1311
+ .map(option => this.visit(option, context));
1312
+ output.push(`WITH (${options.join(', ')})`);
1313
+ }
1314
+ if (node.exprs && node.exprs.length > 0) {
1315
+ output.push(this.deparseReturningList(node.exprs, context));
1316
+ }
1317
+ return output.join(' ');
1318
+ }
1319
+ ReturningOption(node, context) {
1320
+ const keyword = node.option === 'RETURNING_OPTION_NEW' ? 'NEW' : 'OLD';
1321
+ return `${keyword} AS ${QuoteUtils.quoteIdentifier(node.value)}`;
1322
+ }
1243
1323
  deparseReturningList(list, context) {
1244
1324
  return ListUtils.unwrapList(list)
1245
1325
  .filter(item => item != null && this.getNodeType(item) !== 'undefined')
@@ -1318,7 +1398,9 @@ export class Deparser {
1318
1398
  const source = this.visit(args[1], context);
1319
1399
  // EXTRACT requires an unquoted uppercase keyword (EPOCH, YEAR, etc.)
1320
1400
  // but A_Const sval deparsing wraps it in single quotes — strip and uppercase
1321
- if (field.startsWith("'") && field.endsWith("'")) {
1401
+ // only when it is spelled the way the bare-keyword form parses back
1402
+ if (/^'(?:year|month|day|hour|minute|second)'$/.test(field)
1403
+ || (/^'[a-z_][a-z0-9_]*'$/.test(field) && keywordKindOf(field.slice(1, -1)) === 'NO_KEYWORD')) {
1322
1404
  field = field.slice(1, -1).toUpperCase();
1323
1405
  }
1324
1406
  return `EXTRACT(${field} FROM ${source})`;
@@ -1480,43 +1562,41 @@ export class Deparser {
1480
1562
  result += ` FILTER (WHERE ${this.visit(node.agg_filter, context)})`;
1481
1563
  }
1482
1564
  if (node.over) {
1483
- // Handle named window references first
1484
- if (node.over.name) {
1485
- result += ` OVER ${node.over.name}`;
1486
- }
1487
- else {
1488
- const windowParts = [];
1489
- if (node.over.partitionClause) {
1490
- const partitions = ListUtils.unwrapList(node.over.partitionClause);
1491
- const partitionStrs = partitions.map(p => this.visit(p, context));
1492
- windowParts.push(`PARTITION BY ${partitionStrs.join(', ')}`);
1493
- }
1494
- if (node.over.orderClause) {
1495
- const orders = ListUtils.unwrapList(node.over.orderClause);
1496
- const orderStrs = orders.map(o => this.visit(o, context));
1497
- windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1498
- }
1499
- // Handle window frame specifications using the dedicated formatWindowFrame method
1500
- const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1501
- if (frameClause) {
1502
- windowParts.push(frameClause);
1503
- }
1504
- if (windowParts.length > 0) {
1505
- if (context.isPretty() && windowParts.length > 1) {
1506
- const formattedParts = windowParts.map(part => context.indent(part));
1507
- result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1508
- }
1509
- else {
1510
- result += ` OVER (${windowParts.join(' ')})`;
1511
- }
1512
- }
1513
- else {
1514
- result += ` OVER ()`;
1515
- }
1516
- }
1565
+ result += this.formatOverClause(node.over, context);
1517
1566
  }
1518
1567
  return result;
1519
1568
  }
1569
+ formatOverClause(over, context) {
1570
+ if (over.name) {
1571
+ return ` OVER ${over.name}`;
1572
+ }
1573
+ const windowParts = [];
1574
+ if (over.refname) {
1575
+ windowParts.push(over.refname);
1576
+ }
1577
+ if (over.partitionClause) {
1578
+ const partitions = ListUtils.unwrapList(over.partitionClause);
1579
+ const partitionStrs = partitions.map(p => this.visit(p, context));
1580
+ windowParts.push(`PARTITION BY ${partitionStrs.join(', ')}`);
1581
+ }
1582
+ if (over.orderClause) {
1583
+ const orders = ListUtils.unwrapList(over.orderClause);
1584
+ const orderStrs = orders.map(o => this.visit(o, context));
1585
+ windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1586
+ }
1587
+ const frameClause = this.formatWindowFrame(over, context.spawn('FuncCall'));
1588
+ if (frameClause) {
1589
+ windowParts.push(frameClause);
1590
+ }
1591
+ if (windowParts.length === 0) {
1592
+ return ' OVER ()';
1593
+ }
1594
+ if (context.isPretty() && windowParts.length > 1) {
1595
+ const formattedParts = windowParts.map(part => context.indent(part));
1596
+ return ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1597
+ }
1598
+ return ` OVER (${windowParts.join(' ')})`;
1599
+ }
1520
1600
  FuncExpr(node, context) {
1521
1601
  const funcName = `func_${node.funcid}`;
1522
1602
  const args = node.args ? ListUtils.unwrapList(node.args).map(arg => this.visit(arg, context)).join(', ') : '';
@@ -1527,39 +1607,39 @@ export class Deparser {
1527
1607
  if (nodeAny.ival !== undefined) {
1528
1608
  if (typeof nodeAny.ival === 'object' && nodeAny.ival !== null) {
1529
1609
  if (nodeAny.ival.ival !== undefined) {
1530
- return nodeAny.ival.ival.toString();
1610
+ return formatInteger(nodeAny.ival.ival);
1531
1611
  }
1532
1612
  else if (Object.keys(nodeAny.ival).length === 0) {
1533
1613
  return '0';
1534
1614
  }
1535
1615
  else {
1536
- return nodeAny.ival.toString();
1616
+ return formatInteger(nodeAny.ival);
1537
1617
  }
1538
1618
  }
1539
1619
  else if (nodeAny.ival === null) {
1540
1620
  return 'NULL';
1541
1621
  }
1542
1622
  else {
1543
- return nodeAny.ival.toString();
1623
+ return formatInteger(nodeAny.ival);
1544
1624
  }
1545
1625
  }
1546
1626
  else if (nodeAny.fval !== undefined) {
1547
1627
  if (typeof nodeAny.fval === 'object' && nodeAny.fval !== null) {
1548
1628
  if (nodeAny.fval.fval !== undefined) {
1549
- return nodeAny.fval.fval.toString();
1629
+ return formatNumeric(nodeAny.fval.fval);
1550
1630
  }
1551
1631
  else if (Object.keys(nodeAny.fval).length === 0) {
1552
1632
  return '0.0';
1553
1633
  }
1554
1634
  else {
1555
- return nodeAny.fval.toString();
1635
+ return formatNumeric(nodeAny.fval);
1556
1636
  }
1557
1637
  }
1558
1638
  else if (nodeAny.fval === null) {
1559
1639
  return 'NULL';
1560
1640
  }
1561
1641
  else {
1562
- return nodeAny.fval.toString();
1642
+ return formatNumeric(nodeAny.fval);
1563
1643
  }
1564
1644
  }
1565
1645
  else if (nodeAny.sval !== undefined) {
@@ -1606,15 +1686,7 @@ export class Deparser {
1606
1686
  else if (nodeAny.bsval !== undefined) {
1607
1687
  if (typeof nodeAny.bsval === 'object' && nodeAny.bsval !== null) {
1608
1688
  if (nodeAny.bsval.bsval !== undefined) {
1609
- const bsval = nodeAny.bsval.bsval;
1610
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1611
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1612
- return `x'${bsval.substring(1)}'`;
1613
- }
1614
- if (bsval.startsWith('b')) {
1615
- return `b'${bsval.substring(1)}'`;
1616
- }
1617
- return `b'${bsval}'`;
1689
+ return formatBitString(nodeAny.bsval.bsval);
1618
1690
  }
1619
1691
  else if (Object.keys(nodeAny.bsval).length === 0) {
1620
1692
  return "''";
@@ -1627,23 +1699,15 @@ export class Deparser {
1627
1699
  return 'NULL';
1628
1700
  }
1629
1701
  else {
1630
- const bsval = nodeAny.bsval;
1631
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1632
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1633
- return `x'${bsval.substring(1)}'`;
1634
- }
1635
- if (bsval.startsWith('b')) {
1636
- return `b'${bsval.substring(1)}'`;
1637
- }
1638
- return `b'${bsval}'`;
1702
+ return formatBitString(nodeAny.bsval);
1639
1703
  }
1640
1704
  }
1641
1705
  if (nodeAny.val) {
1642
1706
  if (nodeAny.val.Integer?.ival !== undefined) {
1643
- return nodeAny.val.Integer.ival.toString();
1707
+ return formatInteger(nodeAny.val.Integer.ival);
1644
1708
  }
1645
1709
  else if (nodeAny.val.Float?.fval !== undefined) {
1646
- return nodeAny.val.Float.fval.toString();
1710
+ return formatNumeric(nodeAny.val.Float.fval);
1647
1711
  }
1648
1712
  else if (nodeAny.val.String?.sval !== undefined) {
1649
1713
  return QuoteUtils.escape(nodeAny.val.String.sval);
@@ -1652,7 +1716,7 @@ export class Deparser {
1652
1716
  return nodeAny.val.Boolean.boolval ? 'true' : 'false';
1653
1717
  }
1654
1718
  else if (nodeAny.val.BitString?.bsval !== undefined) {
1655
- return nodeAny.val.BitString.bsval;
1719
+ return formatBitString(nodeAny.val.BitString.bsval);
1656
1720
  }
1657
1721
  }
1658
1722
  if (nodeAny.isnull === true) {
@@ -1664,15 +1728,15 @@ export class Deparser {
1664
1728
  }
1665
1729
  if (nodeAny.Integer !== undefined) {
1666
1730
  if (typeof nodeAny.Integer === 'object' && nodeAny.Integer.ival !== undefined) {
1667
- return nodeAny.Integer.ival.toString();
1731
+ return formatInteger(nodeAny.Integer.ival);
1668
1732
  }
1669
- return nodeAny.Integer.toString();
1733
+ return formatInteger(nodeAny.Integer);
1670
1734
  }
1671
1735
  if (nodeAny.Float !== undefined) {
1672
1736
  if (typeof nodeAny.Float === 'object' && nodeAny.Float.fval !== undefined) {
1673
- return nodeAny.Float.fval.toString();
1737
+ return formatNumeric(nodeAny.Float.fval);
1674
1738
  }
1675
- return nodeAny.Float.toString();
1739
+ return formatNumeric(nodeAny.Float);
1676
1740
  }
1677
1741
  if (nodeAny.String !== undefined) {
1678
1742
  if (typeof nodeAny.String === 'object' && nodeAny.String.sval !== undefined) {
@@ -2134,7 +2198,9 @@ export class Deparser {
2134
2198
  A_Indirection(node, context) {
2135
2199
  let argStr = this.visit(node.arg, context);
2136
2200
  const argType = this.getNodeType(node.arg);
2137
- if (argType === 'TypeCast' || argType === 'SubLink' || argType === 'A_Expr' || argType === 'FuncCall' || argType === 'A_Indirection' || argType === 'ColumnRef' || argType === 'RowExpr') {
2201
+ // Grammar: only a parameter takes indirection bare; every other target is
2202
+ // `'(' a_expr ')' opt_indirection` (a plain column subscript is the same node).
2203
+ if (argType !== 'ParamRef') {
2138
2204
  argStr = `(${argStr})`;
2139
2205
  }
2140
2206
  const output = [argStr];
@@ -2162,21 +2228,11 @@ export class Deparser {
2162
2228
  if (context.isPretty() && args.length > 0) {
2163
2229
  for (const arg of args) {
2164
2230
  const whenClause = this.visit(arg, context);
2165
- if (this.containsMultilineStringLiteral(whenClause)) {
2166
- output.push(context.newline() + whenClause);
2167
- }
2168
- else {
2169
- output.push(context.newline() + context.indent(whenClause));
2170
- }
2231
+ output.push(context.newline() + context.indent(whenClause));
2171
2232
  }
2172
2233
  if (node.defresult) {
2173
2234
  const elseResult = this.visit(node.defresult, context);
2174
- if (this.containsMultilineStringLiteral(elseResult)) {
2175
- output.push(context.newline() + 'ELSE ' + elseResult);
2176
- }
2177
- else {
2178
- output.push(context.newline() + context.indent('ELSE ' + elseResult));
2179
- }
2235
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
2180
2236
  }
2181
2237
  output.push(context.newline() + 'END');
2182
2238
  return output.join(' ');
@@ -2291,16 +2347,16 @@ export class Deparser {
2291
2347
  }
2292
2348
  preserveOperatorDefElemCase(defName) {
2293
2349
  const caseMap = {
2294
- 'leftarg': 'Leftarg',
2295
- 'rightarg': 'Rightarg',
2296
- 'procedure': 'Procedure',
2297
- 'function': 'Function',
2298
- 'commutator': 'Commutator',
2299
- 'negator': 'Negator',
2300
- 'restrict': 'Restrict',
2301
- 'join': 'Join',
2302
- 'hashes': 'Hashes',
2303
- 'merges': 'Merges'
2350
+ leftarg: 'Leftarg',
2351
+ rightarg: 'Rightarg',
2352
+ procedure: 'Procedure',
2353
+ function: 'Function',
2354
+ commutator: 'Commutator',
2355
+ negator: 'Negator',
2356
+ restrict: 'Restrict',
2357
+ join: 'Join',
2358
+ hashes: 'Hashes',
2359
+ merges: 'Merges'
2304
2360
  };
2305
2361
  return caseMap[defName.toLowerCase()] || defName;
2306
2362
  }
@@ -2323,24 +2379,16 @@ export class Deparser {
2323
2379
  return QuoteUtils.quoteIdentifier(value);
2324
2380
  }
2325
2381
  Integer(node, context) {
2326
- return node.ival?.toString() || '0';
2382
+ return node.ival === undefined || node.ival === null ? '0' : formatInteger(node.ival);
2327
2383
  }
2328
2384
  Float(node, context) {
2329
- return node.fval || '0.0';
2385
+ return node.fval ? formatNumeric(node.fval) : '0.0';
2330
2386
  }
2331
2387
  Boolean(node, context) {
2332
2388
  return node.boolval ? 'true' : 'false';
2333
2389
  }
2334
2390
  BitString(node, context) {
2335
- // Check if this is a hexadecimal bit string (starts with x)
2336
- if (node.bsval.startsWith('x')) {
2337
- return `x'${node.bsval.substring(1)}'`;
2338
- }
2339
- if (node.bsval.startsWith('b')) {
2340
- return `b'${node.bsval.substring(1)}'`;
2341
- }
2342
- // Fallback for raw values without prefix
2343
- return `b'${node.bsval}'`;
2391
+ return formatBitString(node.bsval);
2344
2392
  }
2345
2393
  Null(node, context) {
2346
2394
  return 'NULL';
@@ -2379,7 +2427,7 @@ export class Deparser {
2379
2427
  output.push(context.parens(elementStrs.join(', ')));
2380
2428
  }
2381
2429
  }
2382
- else if (node.tableElts) {
2430
+ else if (node.tableElts && !node.partbound) {
2383
2431
  const elements = ListUtils.unwrapList(node.tableElts);
2384
2432
  const elementStrs = elements.map(el => {
2385
2433
  return this.deparse(el, context.spawn('CreateStmt'));
@@ -2407,7 +2455,16 @@ export class Deparser {
2407
2455
  const inherits = ListUtils.unwrapList(node.inhRelations);
2408
2456
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2409
2457
  output.push(inheritStrs[0]);
2410
- if (node.partbound.strategy === 'l' && node.partbound.listdatums) {
2458
+ if (node.tableElts) {
2459
+ const elementStrs = ListUtils.unwrapList(node.tableElts).map(el => {
2460
+ return this.deparse(el, context.spawn('CreateStmt'));
2461
+ });
2462
+ output.push(context.parens(elementStrs.join(', ')));
2463
+ }
2464
+ if (node.partbound.is_default) {
2465
+ output.push('DEFAULT');
2466
+ }
2467
+ else if (node.partbound.strategy === 'l' && node.partbound.listdatums) {
2411
2468
  output.push('FOR VALUES IN');
2412
2469
  const listValues = ListUtils.unwrapList(node.partbound.listdatums)
2413
2470
  .map(datum => this.visit(datum, context))
@@ -2430,13 +2487,9 @@ export class Deparser {
2430
2487
  output.push(`(${upperValues})`);
2431
2488
  }
2432
2489
  }
2433
- else if (node.partbound.strategy === 'h' && node.partbound.modulus !== undefined) {
2490
+ else if (node.partbound.strategy === 'h') {
2434
2491
  output.push('FOR VALUES WITH');
2435
- const remainder = node.partbound.remainder !== undefined ? node.partbound.remainder : 0;
2436
- output.push(`(MODULUS ${node.partbound.modulus}, REMAINDER ${remainder})`);
2437
- }
2438
- else if (node.partbound.is_default) {
2439
- output.push('DEFAULT');
2492
+ output.push(`(MODULUS ${node.partbound.modulus ?? 0}, REMAINDER ${node.partbound.remainder ?? 0})`);
2440
2493
  }
2441
2494
  }
2442
2495
  else if (node.inhRelations) {
@@ -2502,6 +2555,12 @@ export class Deparser {
2502
2555
  if (node.typeName) {
2503
2556
  output.push(this.TypeName(node.typeName, context));
2504
2557
  }
2558
+ if (node.storage_name) {
2559
+ output.push('STORAGE', node.storage_name.toUpperCase());
2560
+ }
2561
+ if (node.compression) {
2562
+ output.push('COMPRESSION', QuoteUtils.quoteIdentifier(node.compression));
2563
+ }
2505
2564
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2506
2565
  output.push('OPTIONS');
2507
2566
  const columnContext = context.spawn('ColumnDef');
@@ -2536,7 +2595,7 @@ export class Deparser {
2536
2595
  Constraint(node, context) {
2537
2596
  const output = [];
2538
2597
  // Handle constraint name if present
2539
- if (node.conname && (node.contype === 'CONSTR_CHECK' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_FOREIGN')) {
2598
+ if (node.conname && (node.contype === 'CONSTR_CHECK' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_FOREIGN' || node.contype === 'CONSTR_NOTNULL' || node.contype === 'CONSTR_EXCLUSION')) {
2540
2599
  output.push('CONSTRAINT');
2541
2600
  output.push(QuoteUtils.quoteIdentifier(node.conname));
2542
2601
  }
@@ -2546,11 +2605,24 @@ export class Deparser {
2546
2605
  break;
2547
2606
  case 'CONSTR_NOTNULL':
2548
2607
  output.push('NOT NULL');
2608
+ if (node.keys && node.keys.length > 0 && !context.isColumnConstraint) {
2609
+ const keyList = ListUtils.unwrapList(node.keys)
2610
+ .map(key => this.visit(key, context))
2611
+ .join(', ');
2612
+ output.push(keyList);
2613
+ }
2614
+ if (node.is_no_inherit) {
2615
+ output.push('NO INHERIT');
2616
+ }
2617
+ if (node.skip_validation) {
2618
+ output.push('NOT VALID');
2619
+ }
2549
2620
  break;
2550
2621
  case 'CONSTR_DEFAULT':
2551
2622
  output.push('DEFAULT');
2552
2623
  if (node.raw_expr) {
2553
- output.push(this.visit(node.raw_expr, context));
2624
+ const defaultExpr = this.visit(node.raw_expr, context);
2625
+ output.push(this.needsBExprParens(node.raw_expr) ? `(${defaultExpr})` : defaultExpr);
2554
2626
  }
2555
2627
  break;
2556
2628
  case 'CONSTR_CHECK':
@@ -2574,8 +2646,13 @@ export class Deparser {
2574
2646
  output.push(context.parens(this.visit(node.raw_expr, context)));
2575
2647
  }
2576
2648
  }
2577
- // Handle NOT VALID for check constraints
2578
- if (node.skip_validation) {
2649
+ // PG18: constraints are enforced unless explicitly NOT ENFORCED
2650
+ // (libpg-query omits false booleans, so undefined means not enforced)
2651
+ if (node.is_enforced !== true && !context.isDomainConstraint) {
2652
+ output.push('NOT ENFORCED');
2653
+ }
2654
+ else if (node.skip_validation) {
2655
+ // NOT ENFORCED implies NOT VALID, so only emit NOT VALID when enforced
2579
2656
  output.push('NOT VALID');
2580
2657
  }
2581
2658
  // Handle NO INHERIT for check constraints - only for table constraints, not domain constraints
@@ -2595,7 +2672,7 @@ export class Deparser {
2595
2672
  if (node.raw_expr) {
2596
2673
  output.push(context.parens(this.visit(node.raw_expr, context)));
2597
2674
  }
2598
- output.push('STORED');
2675
+ output.push(node.generated_kind === 'v' ? 'VIRTUAL' : 'STORED');
2599
2676
  break;
2600
2677
  case 'CONSTR_IDENTITY':
2601
2678
  output.push('GENERATED');
@@ -2671,10 +2748,12 @@ export class Deparser {
2671
2748
  case 'CONSTR_PRIMARY':
2672
2749
  output.push('PRIMARY KEY');
2673
2750
  if (node.keys && node.keys.length > 0) {
2674
- const keyList = ListUtils.unwrapList(node.keys)
2675
- .map(key => this.visit(key, context))
2676
- .join(', ');
2677
- output.push(`(${keyList})`);
2751
+ const keyItems = ListUtils.unwrapList(node.keys)
2752
+ .map(key => this.visit(key, context));
2753
+ if (node.without_overlaps) {
2754
+ keyItems[keyItems.length - 1] += ' WITHOUT OVERLAPS';
2755
+ }
2756
+ output.push(`(${keyItems.join(', ')})`);
2678
2757
  }
2679
2758
  if (node.indexname) {
2680
2759
  output.push('USING INDEX');
@@ -2692,10 +2771,12 @@ export class Deparser {
2692
2771
  output.push('NULLS NOT DISTINCT');
2693
2772
  }
2694
2773
  if (node.keys && node.keys.length > 0) {
2695
- const keyList = ListUtils.unwrapList(node.keys)
2696
- .map(key => this.visit(key, context))
2697
- .join(', ');
2698
- output.push(`(${keyList})`);
2774
+ const keyItems = ListUtils.unwrapList(node.keys)
2775
+ .map(key => this.visit(key, context));
2776
+ if (node.without_overlaps) {
2777
+ keyItems[keyItems.length - 1] += ' WITHOUT OVERLAPS';
2778
+ }
2779
+ output.push(`(${keyItems.join(', ')})`);
2699
2780
  }
2700
2781
  if (node.indexname) {
2701
2782
  output.push('USING INDEX');
@@ -2708,20 +2789,14 @@ export class Deparser {
2708
2789
  if (context.isPretty()) {
2709
2790
  output.push('\n' + context.indent('FOREIGN KEY'));
2710
2791
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2711
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2712
- .map(attr => this.visit(attr, context))
2713
- .join(', ');
2714
- output.push(`(${fkAttrs})`);
2792
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2715
2793
  }
2716
2794
  output.push('\n' + context.indent('REFERENCES'));
2717
2795
  }
2718
2796
  else {
2719
2797
  output.push('FOREIGN KEY');
2720
2798
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2721
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2722
- .map(attr => this.visit(attr, context))
2723
- .join(', ');
2724
- output.push(`(${fkAttrs})`);
2799
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2725
2800
  }
2726
2801
  output.push('REFERENCES');
2727
2802
  }
@@ -2744,9 +2819,7 @@ export class Deparser {
2744
2819
  }
2745
2820
  }
2746
2821
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2747
- const pkAttrs = ListUtils.unwrapList(node.pk_attrs)
2748
- .map(attr => this.visit(attr, context))
2749
- .join(', ');
2822
+ const pkAttrs = this.deparseKeyAttrs(node.pk_attrs, node.pk_with_period, context);
2750
2823
  if (context.isPretty() && !context.isColumnConstraint) {
2751
2824
  const lastIndex = output.length - 1;
2752
2825
  if (lastIndex >= 0) {
@@ -2817,6 +2890,15 @@ export class Deparser {
2817
2890
  deleteClause += 'SET DEFAULT';
2818
2891
  break;
2819
2892
  }
2893
+ if ((node.fk_del_action === 'n' || node.fk_del_action === 'd') && node.fk_del_set_cols && node.fk_del_set_cols.length > 0) {
2894
+ const setColumns = ListUtils.unwrapList(node.fk_del_set_cols)
2895
+ .map(column => {
2896
+ const stringNode = column.String;
2897
+ return QuoteUtils.quoteIdentifier(stringNode.sval || stringNode.str || '');
2898
+ })
2899
+ .join(', ');
2900
+ deleteClause += ` (${setColumns})`;
2901
+ }
2820
2902
  if (context.isPretty()) {
2821
2903
  output.push('\n' + context.indent(deleteClause));
2822
2904
  }
@@ -2825,8 +2907,18 @@ export class Deparser {
2825
2907
  output.push(deleteClause.replace('ON DELETE ', ''));
2826
2908
  }
2827
2909
  }
2828
- // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2829
- if (node.skip_validation && !context.isDomainConstraint) {
2910
+ // PG18: constraints are enforced unless explicitly NOT ENFORCED
2911
+ // (libpg-query omits false booleans, so undefined means not enforced)
2912
+ if (node.is_enforced !== true && !context.isDomainConstraint) {
2913
+ if (context.isPretty() && !context.isColumnConstraint) {
2914
+ output.push('\n' + context.indent('NOT ENFORCED'));
2915
+ }
2916
+ else {
2917
+ output.push('NOT ENFORCED');
2918
+ }
2919
+ }
2920
+ else if (node.skip_validation && !context.isDomainConstraint) {
2921
+ // NOT ENFORCED implies NOT VALID, so only emit NOT VALID when enforced
2830
2922
  if (context.isPretty() && !context.isColumnConstraint) {
2831
2923
  output.push('\n' + context.indent('NOT VALID'));
2832
2924
  }
@@ -2847,6 +2939,12 @@ export class Deparser {
2847
2939
  case 'CONSTR_ATTR_IMMEDIATE':
2848
2940
  output.push('INITIALLY IMMEDIATE');
2849
2941
  break;
2942
+ case 'CONSTR_ATTR_ENFORCED':
2943
+ output.push('ENFORCED');
2944
+ break;
2945
+ case 'CONSTR_ATTR_NOT_ENFORCED':
2946
+ output.push('NOT ENFORCED');
2947
+ break;
2850
2948
  case 'CONSTR_EXCLUSION':
2851
2949
  output.push('EXCLUDE');
2852
2950
  if (node.access_method) {
@@ -2888,7 +2986,7 @@ export class Deparser {
2888
2986
  break;
2889
2987
  }
2890
2988
  // Handle deferrable constraints for all constraint types that support it
2891
- if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2989
+ if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN' || node.contype === 'CONSTR_EXCLUSION') {
2892
2990
  if (node.deferrable) {
2893
2991
  if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2894
2992
  output.push('\n' + context.indent('DEFERRABLE'));
@@ -2917,6 +3015,9 @@ export class Deparser {
2917
3015
  output.push('NOT DEFERRABLE');
2918
3016
  }
2919
3017
  }
3018
+ else if (node.initdeferred) {
3019
+ output.push('INITIALLY DEFERRED');
3020
+ }
2920
3021
  }
2921
3022
  if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2922
3023
  let result = '';
@@ -2982,6 +3083,9 @@ export class Deparser {
2982
3083
  output.push(node.name);
2983
3084
  }
2984
3085
  const windowParts = [];
3086
+ if (node.refname) {
3087
+ windowParts.push(node.refname);
3088
+ }
2985
3089
  if (node.partitionClause) {
2986
3090
  const partitions = ListUtils.unwrapList(node.partitionClause);
2987
3091
  const partitionStrs = partitions.map(p => this.visit(p, context));
@@ -2992,12 +3096,9 @@ export class Deparser {
2992
3096
  const orderStrs = orders.map(o => this.visit(o, context));
2993
3097
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
2994
3098
  }
2995
- // Only add frame clause if frameOptions indicates non-default framing
2996
- if (node.frameOptions && node.frameOptions !== 1058) {
2997
- const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2998
- if (frameClause) {
2999
- windowParts.push(frameClause);
3000
- }
3099
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
3100
+ if (frameClause) {
3101
+ windowParts.push(frameClause);
3001
3102
  }
3002
3103
  if (windowParts.length > 0) {
3003
3104
  if (node.name) {
@@ -3014,114 +3115,96 @@ export class Deparser {
3014
3115
  }
3015
3116
  return output.join(' ');
3016
3117
  }
3118
+ formatIdentityColumnOptions(def, context) {
3119
+ const seqContext = context.spawn('AlterSeqStmt');
3120
+ return ListUtils.unwrapList(def).map(item => {
3121
+ const el = item.DefElem;
3122
+ if (!el)
3123
+ return this.visit(item, context);
3124
+ if (el.defname === 'generated') {
3125
+ const ival = el.arg?.Integer?.ival ?? 0;
3126
+ return ival === 97 ? 'SET GENERATED ALWAYS' : 'SET GENERATED BY DEFAULT';
3127
+ }
3128
+ if (el.defname === 'restart') {
3129
+ return el.arg ? `RESTART WITH ${this.visit(el.arg, context)}` : 'RESTART';
3130
+ }
3131
+ return `SET ${this.visit(item, seqContext)}`;
3132
+ }).join(' ');
3133
+ }
3134
+ // b_expr (used by DEFAULT) excludes IN/LIKE/BETWEEN and boolean operators.
3135
+ needsBExprParens(node) {
3136
+ const nodeType = this.getNodeType(node);
3137
+ if (nodeType === 'BoolExpr')
3138
+ return true;
3139
+ if (nodeType === 'A_Expr') {
3140
+ const kind = this.getNodeData(node).kind;
3141
+ return kind !== 'AEXPR_OP' && kind !== 'AEXPR_DISTINCT' && kind !== 'AEXPR_NOT_DISTINCT';
3142
+ }
3143
+ return false;
3144
+ }
3017
3145
  formatWindowFrame(node, context) {
3018
- if (!node.frameOptions)
3146
+ if (!node.frameOptions || !(node.frameOptions & FRAMEOPTION_NONDEFAULT))
3019
3147
  return null;
3020
3148
  const frameOptions = node.frameOptions;
3021
- const EXCLUDE_MASK = 0x8000 | 0x10000 | 0x20000;
3022
- const baseFrameOptions = frameOptions & ~EXCLUDE_MASK;
3023
3149
  const frameParts = [];
3024
- if (frameOptions & 0x01) { // FRAMEOPTION_NONDEFAULT
3025
- if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
3026
- frameParts.push('RANGE');
3027
- }
3028
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
3029
- frameParts.push('ROWS');
3030
- }
3031
- else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
3032
- frameParts.push('GROUPS');
3033
- }
3150
+ if (frameOptions & FRAMEOPTION_RANGE) {
3151
+ frameParts.push('RANGE');
3152
+ }
3153
+ else if (frameOptions & FRAMEOPTION_ROWS) {
3154
+ frameParts.push('ROWS');
3155
+ }
3156
+ else if (frameOptions & FRAMEOPTION_GROUPS) {
3157
+ frameParts.push('GROUPS');
3034
3158
  }
3035
3159
  if (frameParts.length === 0)
3036
3160
  return null;
3037
- const boundsParts = [];
3038
- // Handle specific frameOptions values that have known mappings
3039
- if (baseFrameOptions === 789) {
3040
- boundsParts.push('CURRENT ROW');
3041
- boundsParts.push('AND UNBOUNDED FOLLOWING');
3161
+ let start = null;
3162
+ if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) {
3163
+ start = 'UNBOUNDED PRECEDING';
3042
3164
  }
3043
- else if (baseFrameOptions === 1077) {
3044
- boundsParts.push('UNBOUNDED PRECEDING');
3045
- boundsParts.push('AND CURRENT ROW');
3165
+ else if (frameOptions & FRAMEOPTION_START_UNBOUNDED_FOLLOWING) {
3166
+ start = 'UNBOUNDED FOLLOWING';
3046
3167
  }
3047
- else if (baseFrameOptions === 18453) {
3048
- if (node.startOffset && node.endOffset) {
3049
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
3050
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
3051
- }
3168
+ else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW) {
3169
+ start = 'CURRENT ROW';
3052
3170
  }
3053
- else if (baseFrameOptions === 1557) {
3054
- boundsParts.push('CURRENT ROW');
3055
- boundsParts.push('AND CURRENT ROW');
3171
+ else if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING && node.startOffset) {
3172
+ start = `${this.visit(node.startOffset, context)} PRECEDING`;
3056
3173
  }
3057
- else if (baseFrameOptions === 16917) {
3058
- boundsParts.push('CURRENT ROW');
3059
- if (node.endOffset) {
3060
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
3061
- }
3174
+ else if (frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING && node.startOffset) {
3175
+ start = `${this.visit(node.startOffset, context)} FOLLOWING`;
3062
3176
  }
3063
- else if (baseFrameOptions === 1058) {
3064
- return null;
3177
+ let end = null;
3178
+ if (frameOptions & FRAMEOPTION_END_UNBOUNDED_PRECEDING) {
3179
+ end = 'UNBOUNDED PRECEDING';
3065
3180
  }
3066
- else {
3067
- // Handle start bound - prioritize explicit offset values over bit flags
3068
- if (node.startOffset) {
3069
- if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
3070
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
3071
- }
3072
- else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
3073
- boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
3074
- }
3075
- else {
3076
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
3077
- }
3078
- }
3079
- else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
3080
- boundsParts.push('UNBOUNDED PRECEDING');
3081
- }
3082
- else if (frameOptions & 0x20) { // FRAMEOPTION_START_CURRENT_ROW
3083
- boundsParts.push('CURRENT ROW');
3084
- }
3085
- // Handle end bound - prioritize explicit offset values over bit flags
3086
- if (node.endOffset) {
3087
- if (boundsParts.length > 0) {
3088
- if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
3089
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
3090
- }
3091
- else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
3092
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
3093
- }
3094
- else {
3095
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
3096
- }
3097
- }
3098
- }
3099
- else if (frameOptions & 0x80) { // FRAMEOPTION_END_UNBOUNDED_FOLLOWING
3100
- if (boundsParts.length > 0) {
3101
- boundsParts.push('AND UNBOUNDED FOLLOWING');
3102
- }
3103
- }
3104
- else if (frameOptions & 0x100) { // FRAMEOPTION_END_CURRENT_ROW
3105
- if (boundsParts.length > 0) {
3106
- boundsParts.push('AND CURRENT ROW');
3107
- }
3181
+ else if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING) {
3182
+ end = 'UNBOUNDED FOLLOWING';
3183
+ }
3184
+ else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW) {
3185
+ end = 'CURRENT ROW';
3186
+ }
3187
+ else if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING && node.endOffset) {
3188
+ end = `${this.visit(node.endOffset, context)} PRECEDING`;
3189
+ }
3190
+ else if (frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING && node.endOffset) {
3191
+ end = `${this.visit(node.endOffset, context)} FOLLOWING`;
3192
+ }
3193
+ if (start) {
3194
+ if (frameOptions & FRAMEOPTION_BETWEEN) {
3195
+ frameParts.push(`BETWEEN ${start} AND ${end ?? 'CURRENT ROW'}`);
3108
3196
  }
3109
- else if (boundsParts.length > 0) {
3110
- boundsParts.push('AND CURRENT ROW');
3197
+ else {
3198
+ frameParts.push(start);
3111
3199
  }
3112
3200
  }
3113
- if (boundsParts.length > 0) {
3114
- frameParts.push('BETWEEN');
3115
- frameParts.push(boundsParts.join(' '));
3116
- }
3117
- // EXCLUDE clause
3118
- if (frameOptions & 0x8000) { // FRAMEOPTION_EXCLUDE_CURRENT_ROW
3201
+ if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW) {
3119
3202
  frameParts.push('EXCLUDE CURRENT ROW');
3120
3203
  }
3121
- else if (frameOptions & 0x10000) { // FRAMEOPTION_EXCLUDE_GROUP
3204
+ else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP) {
3122
3205
  frameParts.push('EXCLUDE GROUP');
3123
3206
  }
3124
- else if (frameOptions & 0x20000) { // FRAMEOPTION_EXCLUDE_TIES
3207
+ else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES) {
3125
3208
  frameParts.push('EXCLUDE TIES');
3126
3209
  }
3127
3210
  return frameParts.join(' ');
@@ -3200,8 +3283,35 @@ export class Deparser {
3200
3283
  if (node.ctequery) {
3201
3284
  output.push(context.parens(this.visit(node.ctequery, context)));
3202
3285
  }
3286
+ if (node.search_clause) {
3287
+ const sc = node.search_clause;
3288
+ const cols = ListUtils.unwrapList(sc.search_col_list).map(c => this.visit(c, context));
3289
+ output.push('SEARCH', sc.search_breadth_first ? 'BREADTH' : 'DEPTH', 'FIRST BY');
3290
+ output.push(cols.join(', '));
3291
+ output.push('SET', QuoteUtils.quoteIdentifier(sc.search_seq_column));
3292
+ }
3293
+ if (node.cycle_clause) {
3294
+ const cc = node.cycle_clause;
3295
+ const cols = ListUtils.unwrapList(cc.cycle_col_list).map(c => this.visit(c, context));
3296
+ output.push('CYCLE', cols.join(', '));
3297
+ output.push('SET', QuoteUtils.quoteIdentifier(cc.cycle_mark_column));
3298
+ if (cc.cycle_mark_value && cc.cycle_mark_default) {
3299
+ output.push('TO', this.formatAexprConst(cc.cycle_mark_value, context));
3300
+ output.push('DEFAULT', this.formatAexprConst(cc.cycle_mark_default, context));
3301
+ }
3302
+ output.push('USING', QuoteUtils.quoteIdentifier(cc.cycle_path_column));
3303
+ }
3203
3304
  return output.join(' ');
3204
3305
  }
3306
+ // Renders a constant in the restricted AexprConst grammar (e.g. `point '(1,1)'`
3307
+ // rather than `CAST('(1,1)' AS point)`).
3308
+ formatAexprConst(node, context) {
3309
+ const cast = node.TypeCast;
3310
+ if (cast?.arg?.A_Const?.sval && cast.typeName) {
3311
+ return `${this.TypeName(cast.typeName, context)} ${this.visit(cast.arg, context)}`;
3312
+ }
3313
+ return this.visit(node, context);
3314
+ }
3205
3315
  ParamRef(node, context) {
3206
3316
  return `$${node.number}`;
3207
3317
  }
@@ -3470,9 +3580,8 @@ export class Deparser {
3470
3580
  output.push('INCLUDE');
3471
3581
  output.push(context.parens(includeStrs.join(', ')));
3472
3582
  }
3473
- if (node.whereClause) {
3474
- output.push('WHERE');
3475
- output.push(this.visit(node.whereClause, context));
3583
+ if (node.nulls_not_distinct) {
3584
+ output.push('NULLS NOT DISTINCT');
3476
3585
  }
3477
3586
  if (node.options && node.options.length > 0) {
3478
3587
  const indexContext = context.spawn('IndexStmt');
@@ -3480,13 +3589,14 @@ export class Deparser {
3480
3589
  output.push('WITH');
3481
3590
  output.push(context.parens(optionStrs.join(', ')));
3482
3591
  }
3483
- if (node.nulls_not_distinct) {
3484
- output.push('NULLS NOT DISTINCT');
3485
- }
3486
3592
  if (node.tableSpace) {
3487
3593
  output.push('TABLESPACE');
3488
3594
  output.push(QuoteUtils.quoteIdentifier(node.tableSpace));
3489
3595
  }
3596
+ if (node.whereClause) {
3597
+ output.push('WHERE');
3598
+ output.push(this.visit(node.whereClause, context));
3599
+ }
3490
3600
  return output.join(' ');
3491
3601
  }
3492
3602
  IndexElem(node, context) {
@@ -3510,7 +3620,7 @@ export class Deparser {
3510
3620
  const opclassOpts = ListUtils.unwrapList(node.opclassopts).map(opt => {
3511
3621
  if (opt.DefElem && opt.DefElem.arg && this.getNodeType(opt.DefElem.arg) === 'String') {
3512
3622
  const stringData = this.getNodeData(opt.DefElem.arg);
3513
- return `${opt.DefElem.defname}='${stringData.sval}'`;
3623
+ return `${QuoteUtils.quoteIdentifierAfterDot(opt.DefElem.defname)}=${QuoteUtils.escape(stringData.sval)}`;
3514
3624
  }
3515
3625
  return this.visit(opt, context.spawn('IndexElem'));
3516
3626
  });
@@ -3561,12 +3671,12 @@ export class Deparser {
3561
3671
  }
3562
3672
  PartitionCmd(node, context) {
3563
3673
  const output = [];
3674
+ if (node.name) {
3675
+ output.push(this.RangeVar(node.name, context));
3676
+ }
3564
3677
  if (node.concurrent) {
3565
3678
  output.push('CONCURRENTLY');
3566
3679
  }
3567
- if (node.name) {
3568
- output.push(this.visit(node.name, context));
3569
- }
3570
3680
  if (node.bound) {
3571
3681
  if (node.bound.strategy === 'l' && node.bound.listdatums) {
3572
3682
  output.push('FOR VALUES IN');
@@ -3591,9 +3701,9 @@ export class Deparser {
3591
3701
  output.push(`(${upperValues})`);
3592
3702
  }
3593
3703
  }
3594
- else if (node.bound.strategy === 'h' && node.bound.modulus !== undefined && node.bound.remainder !== undefined) {
3704
+ else if (node.bound.strategy === 'h') {
3595
3705
  output.push('FOR VALUES WITH');
3596
- output.push(`(modulus ${node.bound.modulus}, remainder ${node.bound.remainder})`);
3706
+ output.push(`(MODULUS ${node.bound.modulus ?? 0}, REMAINDER ${node.bound.remainder ?? 0})`);
3597
3707
  }
3598
3708
  else if (node.bound.is_default) {
3599
3709
  output.push('DEFAULT');
@@ -3724,12 +3834,7 @@ export class Deparser {
3724
3834
  if (context.isPretty()) {
3725
3835
  // For complex JOIN conditions, format with proper indentation
3726
3836
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3727
- if (this.containsMultilineStringLiteral(qualsStr)) {
3728
- output.push(` ON ${qualsStr}`);
3729
- }
3730
- else {
3731
- output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3732
- }
3837
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3733
3838
  }
3734
3839
  else {
3735
3840
  output.push(` ON ${qualsStr}`);
@@ -3811,19 +3916,19 @@ export class Deparser {
3811
3916
  case 'TRANS_STMT_PREPARE':
3812
3917
  output.push('PREPARE TRANSACTION');
3813
3918
  if (node.gid) {
3814
- output.push(`'${node.gid}'`);
3919
+ output.push(QuoteUtils.escape(node.gid));
3815
3920
  }
3816
3921
  break;
3817
3922
  case 'TRANS_STMT_COMMIT_PREPARED':
3818
3923
  output.push('COMMIT PREPARED');
3819
3924
  if (node.gid) {
3820
- output.push(`'${node.gid}'`);
3925
+ output.push(QuoteUtils.escape(node.gid));
3821
3926
  }
3822
3927
  break;
3823
3928
  case 'TRANS_STMT_ROLLBACK_PREPARED':
3824
3929
  output.push('ROLLBACK PREPARED');
3825
3930
  if (node.gid) {
3826
- output.push(`'${node.gid}'`);
3931
+ output.push(QuoteUtils.escape(node.gid));
3827
3932
  }
3828
3933
  break;
3829
3934
  default:
@@ -3890,17 +3995,25 @@ export class Deparser {
3890
3995
  switch (node.kind) {
3891
3996
  case 'VAR_SET_VALUE':
3892
3997
  const localPrefix = node.is_local ? 'LOCAL ' : '';
3998
+ if (node.jumble_args && node.name === 'xmloption' && node.args) {
3999
+ const xmlValue = this.getNodeData(ListUtils.unwrapList(node.args)[0]);
4000
+ const xmlOption = (typeof xmlValue.sval === 'object' ? xmlValue.sval.sval : xmlValue.sval) || '';
4001
+ return `SET ${localPrefix}XML OPTION ${xmlOption.toUpperCase()}`;
4002
+ }
3893
4003
  const args = node.args ? ListUtils.unwrapList(node.args).map(arg => {
3894
4004
  const nodeData = this.getNodeData(arg);
3895
4005
  if (nodeData.sval !== undefined) {
3896
4006
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3897
- if (svalValue === '' || svalValue.includes(' ') || svalValue.includes('-') || /[A-Z]/.test(svalValue) || /^\d/.test(svalValue) || svalValue.includes('.') || svalValue.includes('$') || svalValue.toLowerCase() === 'all' || /^[+-]\d/.test(svalValue)) {
3898
- return `'${svalValue}'`;
4007
+ if (!/^[a-z_][a-z0-9_]*$/.test(svalValue) || svalValue.includes(' ') || svalValue.includes('-') || /[A-Z]/.test(svalValue) || /^\d/.test(svalValue) || svalValue.includes('.') || svalValue.includes('$') || svalValue.toLowerCase() === 'all' || /^[+-]\d/.test(svalValue)) {
4008
+ return QuoteUtils.escape(svalValue);
3899
4009
  }
3900
4010
  return svalValue;
3901
4011
  }
3902
4012
  return this.visit(arg, context);
3903
4013
  }).join(', ') : '';
4014
+ if (node.jumble_args && node.name === 'timezone') {
4015
+ return `SET ${localPrefix}TIME ZONE ${args}`;
4016
+ }
3904
4017
  // Handle args - always include TO clause if args exist (even if empty string)
3905
4018
  const paramName = QuoteUtils.quoteIdentifier(node.name);
3906
4019
  if (!node.args || node.args.length === 0) {
@@ -3908,6 +4021,9 @@ export class Deparser {
3908
4021
  }
3909
4022
  return `SET ${localPrefix}${paramName} TO ${args}`;
3910
4023
  case 'VAR_SET_DEFAULT':
4024
+ if (node.jumble_args && node.name === 'timezone') {
4025
+ return `SET ${node.is_local ? 'LOCAL ' : ''}TIME ZONE DEFAULT`;
4026
+ }
3911
4027
  const defaultParamName = QuoteUtils.quoteIdentifier(node.name);
3912
4028
  return `SET ${defaultParamName} TO DEFAULT`;
3913
4029
  case 'VAR_SET_CURRENT':
@@ -4212,7 +4328,7 @@ export class Deparser {
4212
4328
  if (objList && objList.List && objList.List.items) {
4213
4329
  const items = objList.List.items.map((item) => {
4214
4330
  if (item.String && item.String.sval) {
4215
- return item.String.sval;
4331
+ return QuoteUtils.quoteIdentifier(item.String.sval);
4216
4332
  }
4217
4333
  return this.visit(item, context);
4218
4334
  });
@@ -4435,10 +4551,10 @@ export class Deparser {
4435
4551
  output.push('PROGRAM');
4436
4552
  }
4437
4553
  if (node.filename) {
4438
- output.push(`'${node.filename}'`);
4554
+ output.push(QuoteUtils.escape(node.filename));
4439
4555
  }
4440
4556
  else {
4441
- output.push('STDIN');
4557
+ output.push(node.is_from ? 'STDIN' : 'STDOUT');
4442
4558
  }
4443
4559
  if (node.options && node.options.length > 0) {
4444
4560
  output.push('WITH');
@@ -4670,6 +4786,9 @@ export class Deparser {
4670
4786
  const nodeData = this.getNodeData(node.def);
4671
4787
  if (nodeData && nodeData.typeName) {
4672
4788
  output.push(this.TypeName(nodeData.typeName, context));
4789
+ if (nodeData.collClause) {
4790
+ output.push(this.CollateClause(nodeData.collClause, context));
4791
+ }
4673
4792
  // Handle USING clause (stored in raw_default for ALTER COLUMN TYPE)
4674
4793
  if (nodeData.raw_default) {
4675
4794
  output.push('USING');
@@ -5019,13 +5138,6 @@ export class Deparser {
5019
5138
  output.push('IF EXISTS');
5020
5139
  }
5021
5140
  break;
5022
- case 'AT_CheckNotNull':
5023
- output.push('ALTER COLUMN');
5024
- if (node.name) {
5025
- output.push(QuoteUtils.quoteIdentifier(node.name));
5026
- }
5027
- output.push('SET NOT NULL');
5028
- break;
5029
5141
  case 'AT_AddIndex':
5030
5142
  output.push('ADD');
5031
5143
  if (node.def) {
@@ -5051,23 +5163,28 @@ export class Deparser {
5051
5163
  }
5052
5164
  break;
5053
5165
  case 'AT_AlterConstraint':
5054
- output.push('ALTER CONSTRAINT');
5055
- if (node.def && this.getNodeType(node.def) === 'Constraint') {
5056
- const constraintData = this.getNodeData(node.def);
5057
- if (constraintData.conname) {
5058
- output.push(QuoteUtils.quoteIdentifier(constraintData.conname));
5059
- if (constraintData.deferrable !== undefined) {
5060
- output.push(constraintData.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
5061
- }
5062
- if (constraintData.initdeferred !== undefined) {
5063
- output.push(constraintData.initdeferred ? 'INITIALLY DEFERRED' : 'INITIALLY IMMEDIATE');
5166
+ if (node.def && this.getNodeType(node.def) === 'ATAlterConstraint') {
5167
+ output.push(this.ATAlterConstraint(this.getNodeData(node.def), context));
5168
+ }
5169
+ else {
5170
+ output.push('ALTER CONSTRAINT');
5171
+ if (node.def && this.getNodeType(node.def) === 'Constraint') {
5172
+ const constraintData = this.getNodeData(node.def);
5173
+ if (constraintData.conname) {
5174
+ output.push(QuoteUtils.quoteIdentifier(constraintData.conname));
5175
+ if (constraintData.deferrable !== undefined) {
5176
+ output.push(constraintData.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
5177
+ }
5178
+ if (constraintData.initdeferred !== undefined) {
5179
+ output.push(constraintData.initdeferred ? 'INITIALLY DEFERRED' : 'INITIALLY IMMEDIATE');
5180
+ }
5064
5181
  }
5065
5182
  }
5066
- }
5067
- else if (node.name) {
5068
- output.push(QuoteUtils.quoteIdentifier(node.name));
5069
- if (node.def) {
5070
- output.push(this.visit(node.def, context));
5183
+ else if (node.name) {
5184
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5185
+ if (node.def) {
5186
+ output.push(this.visit(node.def, context));
5187
+ }
5071
5188
  }
5072
5189
  }
5073
5190
  break;
@@ -5152,9 +5269,8 @@ export class Deparser {
5152
5269
  if (node.name) {
5153
5270
  output.push(QuoteUtils.quoteIdentifier(node.name));
5154
5271
  }
5155
- output.push('SET');
5156
5272
  if (node.def) {
5157
- output.push(this.visit(node.def, context));
5273
+ output.push(this.formatIdentityColumnOptions(node.def, context));
5158
5274
  }
5159
5275
  break;
5160
5276
  case 'AT_DropIdentity':
@@ -5163,11 +5279,8 @@ export class Deparser {
5163
5279
  output.push(QuoteUtils.quoteIdentifier(node.name));
5164
5280
  }
5165
5281
  output.push('DROP IDENTITY');
5166
- if (node.behavior === 'DROP_CASCADE') {
5167
- output.push('CASCADE');
5168
- }
5169
- else if (node.behavior === 'DROP_RESTRICT') {
5170
- output.push('RESTRICT');
5282
+ if (node.missing_ok) {
5283
+ output.push('IF EXISTS');
5171
5284
  }
5172
5285
  break;
5173
5286
  case 'AT_ReAddStatistics':
@@ -5261,6 +5374,7 @@ export class Deparser {
5261
5374
  if (bodyType === 'List') {
5262
5375
  const statements = ListUtils.unwrapList(node.sql_body);
5263
5376
  if (statements.length === 0 || (statements.length === 1 && Object.keys(statements[0]).length === 0)) {
5377
+ // empty body: nothing to emit
5264
5378
  }
5265
5379
  else {
5266
5380
  // Handle nested List structure in BEGIN ATOMIC blocks
@@ -5408,19 +5522,22 @@ export class Deparser {
5408
5522
  if (node.arg && this.getNodeType(node.arg) === 'List') {
5409
5523
  const listData = this.getNodeData(node.arg);
5410
5524
  const listItems = ListUtils.unwrapList(listData.items);
5525
+ const preservedName = this.preserveOperatorDefElemCase(node.defname);
5411
5526
  if (listItems.length === 1 && listItems[0].String) {
5412
- const preservedName = this.preserveOperatorDefElemCase(node.defname);
5413
5527
  return `${preservedName} = ${listItems[0].String.sval}`;
5414
5528
  }
5529
+ if (listItems.length > 1 && listItems.every((item) => item.String)) {
5530
+ return `${preservedName} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
5531
+ }
5415
5532
  }
5416
5533
  }
5417
5534
  // Handle IndexElem opclassopts - MUST be first to preserve string types
5418
5535
  if (context.parentNodeTypes.includes('IndexElem')) {
5419
5536
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5420
5537
  const stringData = this.getNodeData(node.arg);
5421
- return `${node.defname}='${stringData.sval}'`;
5538
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${QuoteUtils.escape(stringData.sval)}`;
5422
5539
  }
5423
- return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
5540
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, this.visit(node.arg, context.spawn('DefElem')))}`;
5424
5541
  }
5425
5542
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
5426
5543
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -5445,9 +5562,7 @@ export class Deparser {
5445
5562
  const defElemContext = context.spawn('DefElem');
5446
5563
  const argValue = this.visit(node.arg, defElemContext);
5447
5564
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5448
- const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
5449
- ? `'${argValue}'`
5450
- : argValue;
5565
+ const finalValue = this.quoteDefElemArg(node.arg, argValue);
5451
5566
  const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5452
5567
  if (node.defaction === 'DEFELEM_ADD') {
5453
5568
  return `ADD ${quotedDefname} ${finalValue}`;
@@ -5460,9 +5575,7 @@ export class Deparser {
5460
5575
  }
5461
5576
  return `${quotedDefname} ${finalValue}`;
5462
5577
  }
5463
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5464
- ? `'${argValue}'`
5465
- : argValue;
5578
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5466
5579
  if (node.defaction === 'DEFELEM_ADD') {
5467
5580
  return `ADD ${node.defname} ${quotedValue}`;
5468
5581
  }
@@ -5494,9 +5607,7 @@ export class Deparser {
5494
5607
  }
5495
5608
  const defElemContext = context.spawn('DefElem');
5496
5609
  const argValue = this.visit(node.arg, defElemContext);
5497
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5498
- ? `'${argValue}'`
5499
- : argValue;
5610
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5500
5611
  return `PASSWORD ${quotedValue}`;
5501
5612
  }
5502
5613
  }
@@ -5519,6 +5630,9 @@ export class Deparser {
5519
5630
  if (listItems.length === 1 && listItems[0].String) {
5520
5631
  return `${node.defname} = ${listItems[0].String.sval}`;
5521
5632
  }
5633
+ if (listItems.length > 1 && listItems.every((item) => item.String)) {
5634
+ return `${node.defname} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
5635
+ }
5522
5636
  }
5523
5637
  }
5524
5638
  if (context.parentNodeTypes.includes('CreatedbStmt') || context.parentNodeTypes.includes('DropdbStmt')) {
@@ -5561,9 +5675,7 @@ export class Deparser {
5561
5675
  }
5562
5676
  }
5563
5677
  if (node.defname === 'validUntil') {
5564
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5565
- ? `'${argValue}'`
5566
- : argValue;
5678
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5567
5679
  return `VALID UNTIL ${quotedValue}`;
5568
5680
  }
5569
5681
  if (node.defname === 'adminmembers') {
@@ -5602,6 +5714,9 @@ export class Deparser {
5602
5714
  if (node.arg && this.getNodeType(node.arg) === 'List') {
5603
5715
  const listData = this.getNodeData(node.arg);
5604
5716
  const listItems = ListUtils.unwrapList(listData.items);
5717
+ if (listItems.length === 1 && listItems[0].String?.sval === 'none') {
5718
+ return 'OWNED BY NONE';
5719
+ }
5605
5720
  const parts = listItems.map(item => {
5606
5721
  const itemData = this.getNodeData(item);
5607
5722
  if (this.getNodeType(item) === 'String') {
@@ -5740,10 +5855,8 @@ export class Deparser {
5740
5855
  if (context.parentNodeTypes.includes('AlterExtensionStmt')) {
5741
5856
  if (node.defname === 'new_version') {
5742
5857
  // argValue is unquoted due to DefElem context, so we need to quote it
5743
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5744
- ? `'${argValue}'`
5745
- : argValue;
5746
- return `UPDATE TO ${quotedValue}`;
5858
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5859
+ return `TO ${quotedValue}`;
5747
5860
  }
5748
5861
  if (node.defname === 'schema') {
5749
5862
  return `SET SCHEMA ${argValue}`;
@@ -5763,28 +5876,37 @@ export class Deparser {
5763
5876
  if (node.defname === 'cascade') {
5764
5877
  return argValue === 'true' ? 'CASCADE' : '';
5765
5878
  }
5879
+ if (node.defname === 'new_version') {
5880
+ return `VERSION ${this.quoteDefElemArg(node.arg, argValue)}`;
5881
+ }
5882
+ if (node.defname === 'schema' && node.arg && this.getNodeType(node.arg) === 'String') {
5883
+ return `SCHEMA ${QuoteUtils.quoteIdentifier(this.getNodeData(node.arg).sval)}`;
5884
+ }
5766
5885
  return `${node.defname.toUpperCase()} ${argValue}`;
5767
5886
  }
5768
5887
  // Handle IndexStmt WITH clause options - no quotes, compact formatting
5769
5888
  if (context.parentNodeTypes.includes('IndexStmt')) {
5770
- return `${node.defname}=${argValue}`;
5889
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5771
5890
  }
5772
5891
  // Handle IndexElem opclassopts - preserve string values as strings
5773
5892
  if (context.parentNodeTypes.includes('IndexElem')) {
5774
5893
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5775
5894
  const stringData = this.getNodeData(node.arg);
5776
- return `${node.defname}='${stringData.sval}'`;
5895
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${QuoteUtils.escape(stringData.sval)}`;
5777
5896
  }
5778
- return `${node.defname}=${argValue}`;
5897
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5779
5898
  }
5780
5899
  // Handle CreateStmt table options - no quotes, compact formatting
5781
5900
  if (context.parentNodeTypes.includes('CreateStmt')) {
5901
+ const relOptName = node.defnamespace
5902
+ ? `${QuoteUtils.quoteIdentifier(node.defnamespace)}.${QuoteUtils.quoteIdentifier(node.defname)}`
5903
+ : QuoteUtils.quoteIdentifier(node.defname);
5782
5904
  // For numeric values, use the raw value without quotes
5783
5905
  if (node.arg && this.getNodeType(node.arg) === 'Integer') {
5784
5906
  const integerData = this.getNodeData(node.arg);
5785
- return `${node.defname}=${integerData.ival}`;
5907
+ return `${relOptName}=${formatInteger(integerData.ival)}`;
5786
5908
  }
5787
- return `${node.defname}=${argValue}`;
5909
+ return `${relOptName}=${this.formatOptionValue(node.arg, argValue)}`;
5788
5910
  }
5789
5911
  // Handle CreateEventTrigStmt WHEN clause - use IN syntax for List arguments
5790
5912
  if (context.parentNodeTypes.includes('CreateEventTrigStmt')) {
@@ -5794,7 +5916,7 @@ export class Deparser {
5794
5916
  const values = listItems.map(item => {
5795
5917
  if (this.getNodeType(item) === 'String') {
5796
5918
  const stringData = this.getNodeData(item);
5797
- return `'${stringData.sval || ''}'`;
5919
+ return QuoteUtils.escape(stringData.sval || '');
5798
5920
  }
5799
5921
  return this.visit(item, context);
5800
5922
  });
@@ -5809,7 +5931,7 @@ export class Deparser {
5809
5931
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
5810
5932
  return `${optionName} = ${argValue}`;
5811
5933
  }
5812
- return `${optionName} = ${argValue}`;
5934
+ return `${optionName} = ${this.formatOptionValue(node.arg, argValue)}`;
5813
5935
  }
5814
5936
  // Handle ViewStmt WITH options - don't quote numeric values
5815
5937
  if (context.parentNodeTypes.includes('ViewStmt')) {
@@ -5823,15 +5945,27 @@ export class Deparser {
5823
5945
  }
5824
5946
  // Handle CopyStmt WITH clause options - uppercase format without quotes
5825
5947
  if (context.parentNodeTypes.includes('CopyStmt')) {
5826
- if (node.defname === 'format' && node.arg && this.getNodeType(node.arg) === 'String') {
5827
- const stringData = this.getNodeData(node.arg);
5828
- return `FORMAT ${stringData.sval.toUpperCase()}`;
5948
+ const optName = node.defname.toUpperCase();
5949
+ if (!node.arg) {
5950
+ return optName;
5951
+ }
5952
+ const copyArgType = this.getNodeType(node.arg);
5953
+ if (copyArgType === 'Boolean' && this.getNodeData(node.arg).boolval !== false) {
5954
+ return optName;
5829
5955
  }
5830
- // Handle other COPY options with uppercase defname
5831
- if (node.arg) {
5832
- return `${node.defname.toUpperCase()} ${argValue}`;
5956
+ if (copyArgType === 'List') {
5957
+ const items = ListUtils.unwrapList(this.getNodeData(node.arg).items)
5958
+ .map(item => this.visit(item, context));
5959
+ return `${optName} (${items.join(', ')})`;
5833
5960
  }
5834
- return node.defname.toUpperCase();
5961
+ if (copyArgType === 'String') {
5962
+ const sval = this.getNodeData(node.arg).sval;
5963
+ if (node.defname === 'format') {
5964
+ return `${optName} ${sval}`;
5965
+ }
5966
+ return `${optName} ${QuoteUtils.escape(sval)}`;
5967
+ }
5968
+ return `${optName} ${argValue}`;
5835
5969
  }
5836
5970
  // Handle CREATE OPERATOR and CREATE TYPE context
5837
5971
  if (context.parentNodeTypes.includes('DefineStmt')) {
@@ -5870,7 +6004,7 @@ export class Deparser {
5870
6004
  return `${quotedDefname} = ${stringData.sval}`;
5871
6005
  }
5872
6006
  // Regular string literals get single quotes
5873
- return `${quotedDefname} = '${stringData.sval}'`;
6007
+ return `${quotedDefname} = ${QuoteUtils.escape(stringData.sval)}`;
5874
6008
  }
5875
6009
  return `${quotedDefname} = ${argValue}`;
5876
6010
  }
@@ -5891,7 +6025,7 @@ export class Deparser {
5891
6025
  return `${node.defname} = ${stringData.sval}`;
5892
6026
  }
5893
6027
  // Regular string literals get single quotes
5894
- return `${node.defname} = '${stringData.sval}'`;
6028
+ return `${node.defname} = ${QuoteUtils.escape(stringData.sval)}`;
5895
6029
  }
5896
6030
  if (node.arg && this.getNodeType(node.arg) === 'Boolean') {
5897
6031
  const boolData = this.getNodeData(node.arg);
@@ -5912,6 +6046,10 @@ export class Deparser {
5912
6046
  return `${node.defname} = ${argValue}`;
5913
6047
  }
5914
6048
  }
6049
+ const argType = this.getNodeType(node.arg);
6050
+ if (argType === 'TypeName' || argType === 'Integer' || argType === 'Float' || argType === 'Boolean') {
6051
+ return `${node.defname} = ${argValue}`;
6052
+ }
5915
6053
  const quotedValue = typeof argValue === 'string'
5916
6054
  ? QuoteUtils.escape(argValue)
5917
6055
  : argValue;
@@ -5937,7 +6075,7 @@ export class Deparser {
5937
6075
  }
5938
6076
  if (node.location) {
5939
6077
  output.push('LOCATION');
5940
- output.push(`'${node.location}'`);
6078
+ output.push(QuoteUtils.escape(node.location));
5941
6079
  }
5942
6080
  if (node.options && node.options.length > 0) {
5943
6081
  output.push('WITH');
@@ -6001,6 +6139,7 @@ export class Deparser {
6001
6139
  if (node.extname) {
6002
6140
  output.push(this.quoteIfNeeded(node.extname));
6003
6141
  }
6142
+ output.push('UPDATE');
6004
6143
  if (node.options && node.options.length > 0) {
6005
6144
  const extContext = context.spawn('AlterExtensionStmt');
6006
6145
  const options = ListUtils.unwrapList(node.options)
@@ -6270,7 +6409,7 @@ export class Deparser {
6270
6409
  }
6271
6410
  if (node.payload !== null && node.payload !== undefined) {
6272
6411
  output.push(',');
6273
- output.push(`'${node.payload}'`);
6412
+ output.push(QuoteUtils.escape(node.payload));
6274
6413
  }
6275
6414
  return output.join(' ');
6276
6415
  }
@@ -6298,7 +6437,7 @@ export class Deparser {
6298
6437
  if (!node.filename) {
6299
6438
  throw new Error('LoadStmt requires filename');
6300
6439
  }
6301
- return `LOAD '${node.filename}'`;
6440
+ return `LOAD ${QuoteUtils.escape(node.filename)}`;
6302
6441
  }
6303
6442
  DiscardStmt(node, context) {
6304
6443
  switch (node.target) {
@@ -6314,6 +6453,13 @@ export class Deparser {
6314
6453
  throw new Error(`Unsupported DiscardStmt target: ${node.target}`);
6315
6454
  }
6316
6455
  }
6456
+ /** `schema.op` for an operator name list: schema parts quoted, the operator symbol as is. */
6457
+ qualifiedOperatorName(objname) {
6458
+ const names = ListUtils.unwrapList(objname ?? []).map((n) => (n.String ? n.String.sval : 'unknown'));
6459
+ if (names.length === 0)
6460
+ return 'unknown';
6461
+ return [...names.slice(0, -1).map((n) => QuoteUtils.quoteIdentifier(n)), names[names.length - 1]].join('.');
6462
+ }
6317
6463
  CommentStmt(node, context) {
6318
6464
  const output = ['COMMENT ON'];
6319
6465
  if (node.objtype) {
@@ -6345,6 +6491,9 @@ export class Deparser {
6345
6491
  case 'OBJECT_TABCONSTRAINT':
6346
6492
  output.push('CONSTRAINT');
6347
6493
  break;
6494
+ case 'OBJECT_DOMCONSTRAINT':
6495
+ output.push('CONSTRAINT');
6496
+ break;
6348
6497
  case 'OBJECT_TRIGGER':
6349
6498
  output.push('TRIGGER');
6350
6499
  break;
@@ -6421,23 +6570,30 @@ export class Deparser {
6421
6570
  output.push(objectParts.join('.'));
6422
6571
  }
6423
6572
  }
6424
- else if (node.objtype === 'OBJECT_TRIGGER') {
6573
+ else if (node.objtype === 'OBJECT_DOMCONSTRAINT') {
6425
6574
  if (objectParts.length === 2) {
6426
- const [table, trigger] = objectParts;
6427
- output.push(trigger);
6575
+ const [domain, constraint] = objectParts;
6576
+ output.push(constraint, 'ON DOMAIN', domain);
6577
+ }
6578
+ else {
6579
+ output.push(objectParts.join('.'));
6580
+ }
6581
+ }
6582
+ else if (node.objtype === 'OBJECT_TRIGGER') {
6583
+ if (objectParts.length >= 2) {
6584
+ output.push(objectParts[objectParts.length - 1]);
6428
6585
  output.push('ON');
6429
- output.push(table);
6586
+ output.push(objectParts.slice(0, -1).join('.'));
6430
6587
  }
6431
6588
  else {
6432
6589
  output.push(objectParts.join('.'));
6433
6590
  }
6434
6591
  }
6435
6592
  else if (node.objtype === 'OBJECT_RULE') {
6436
- if (objectParts.length === 2) {
6437
- const [table, rule] = objectParts;
6438
- output.push(rule);
6593
+ if (objectParts.length >= 2) {
6594
+ output.push(objectParts[objectParts.length - 1]);
6439
6595
  output.push('ON');
6440
- output.push(table);
6596
+ output.push(objectParts.slice(0, -1).join('.'));
6441
6597
  }
6442
6598
  else {
6443
6599
  output.push(objectParts.join('.'));
@@ -6448,8 +6604,7 @@ export class Deparser {
6448
6604
  // For operators, we need to handle ObjectWithArgs structure
6449
6605
  if (node.object && node.object.ObjectWithArgs) {
6450
6606
  const objWithArgs = node.object.ObjectWithArgs;
6451
- let operatorName = objWithArgs.objname && objWithArgs.objname[0] && objWithArgs.objname[0].String
6452
- ? objWithArgs.objname[0].String.sval : 'unknown';
6607
+ let operatorName = this.qualifiedOperatorName(objWithArgs.objname);
6453
6608
  if (operatorName.startsWith('"') && operatorName.endsWith('"')) {
6454
6609
  operatorName = operatorName.slice(1, -1);
6455
6610
  }
@@ -6487,7 +6642,13 @@ export class Deparser {
6487
6642
  }
6488
6643
  else if (node.objtype === 'OBJECT_POLICY') {
6489
6644
  // Handle POLICY syntax: COMMENT ON POLICY policy_name ON table_name IS 'comment'
6490
- if (objectParts.length === 2) {
6645
+ if (objectParts.length === 3) {
6646
+ const [schema, table, policy] = objectParts;
6647
+ output.push(policy);
6648
+ output.push('ON');
6649
+ output.push(`${schema}.${table}`);
6650
+ }
6651
+ else if (objectParts.length === 2) {
6491
6652
  const [table, policy] = objectParts;
6492
6653
  output.push(policy);
6493
6654
  output.push('ON');
@@ -6510,6 +6671,9 @@ export class Deparser {
6510
6671
  output.push(objectParts.join('.'));
6511
6672
  }
6512
6673
  }
6674
+ else if (node.objtype === 'OBJECT_CAST' && objectParts.length === 2) {
6675
+ output.push(`(${objectParts[0]} AS ${objectParts[1]})`);
6676
+ }
6513
6677
  else {
6514
6678
  output.push(objectParts.join('.'));
6515
6679
  }
@@ -6518,8 +6682,7 @@ export class Deparser {
6518
6682
  else if (node.objtype === 'OBJECT_OPERATOR' && node.object && node.object.ObjectWithArgs) {
6519
6683
  // Handle direct ObjectWithArgs for OPERATOR syntax: COMMENT ON OPERATOR -(NONE, integer) IS 'comment'
6520
6684
  const objWithArgs = node.object.ObjectWithArgs;
6521
- let operatorName = objWithArgs.objname && objWithArgs.objname[0] && objWithArgs.objname[0].String
6522
- ? objWithArgs.objname[0].String.sval : 'unknown';
6685
+ let operatorName = this.qualifiedOperatorName(objWithArgs.objname);
6523
6686
  // Remove quotes from operator name if present
6524
6687
  if (operatorName.startsWith('"') && operatorName.endsWith('"')) {
6525
6688
  operatorName = operatorName.slice(1, -1);
@@ -6752,7 +6915,7 @@ export class Deparser {
6752
6915
  output.push(QuoteUtils.quoteIdentifier(node.pubname));
6753
6916
  }
6754
6917
  if (node.pubobjects && node.pubobjects.length > 0) {
6755
- output.push('FOR', 'TABLE');
6918
+ output.push('FOR');
6756
6919
  const tables = ListUtils.unwrapList(node.pubobjects).map(table => this.visit(table, context));
6757
6920
  output.push(tables.join(', '));
6758
6921
  }
@@ -6773,7 +6936,7 @@ export class Deparser {
6773
6936
  }
6774
6937
  output.push('CONNECTION');
6775
6938
  if (node.conninfo) {
6776
- output.push(`'${node.conninfo}'`);
6939
+ output.push(QuoteUtils.escape(node.conninfo));
6777
6940
  }
6778
6941
  output.push('PUBLICATION');
6779
6942
  if (node.publication && node.publication.length > 0) {
@@ -6792,8 +6955,9 @@ export class Deparser {
6792
6955
  if (node.pubname) {
6793
6956
  output.push(QuoteUtils.quoteIdentifier(node.pubname));
6794
6957
  }
6795
- if (node.action) {
6796
- switch (node.action) {
6958
+ const hasObjects = node.pubobjects && node.pubobjects.length > 0;
6959
+ if (hasObjects) {
6960
+ switch (node.action || 'AP_AddObjects') {
6797
6961
  case 'AP_AddObjects':
6798
6962
  output.push('ADD');
6799
6963
  break;
@@ -6806,17 +6970,11 @@ export class Deparser {
6806
6970
  default:
6807
6971
  throw new Error(`Unsupported AlterPublicationStmt action: ${node.action}`);
6808
6972
  }
6809
- }
6810
- if (node.for_all_tables) {
6811
- output.push('FOR ALL TABLES');
6812
- }
6813
- else if (node.pubobjects && node.pubobjects.length > 0) {
6814
- output.push('FOR TABLE');
6815
6973
  const objects = ListUtils.unwrapList(node.pubobjects).map(obj => this.visit(obj, context));
6816
6974
  output.push(objects.join(', '));
6817
6975
  }
6818
- if (node.options && node.options.length > 0) {
6819
- output.push('WITH');
6976
+ else if (node.options && node.options.length > 0) {
6977
+ output.push('SET');
6820
6978
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context));
6821
6979
  output.push(`(${options.join(', ')})`);
6822
6980
  }
@@ -6827,42 +6985,67 @@ export class Deparser {
6827
6985
  if (node.subname) {
6828
6986
  output.push(QuoteUtils.quoteIdentifier(node.subname));
6829
6987
  }
6830
- if (node.kind) {
6831
- switch (node.kind) {
6988
+ const options = node.options && node.options.length > 0
6989
+ ? `(${ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context)).join(', ')})`
6990
+ : null;
6991
+ const publications = node.publication && node.publication.length > 0
6992
+ ? ListUtils.unwrapList(node.publication).map(pub => this.visit(pub, context)).join(', ')
6993
+ : null;
6994
+ {
6995
+ switch (node.kind || 'ALTER_SUBSCRIPTION_OPTIONS') {
6832
6996
  case 'ALTER_SUBSCRIPTION_OPTIONS':
6833
6997
  output.push('SET');
6998
+ if (options)
6999
+ output.push(options);
6834
7000
  break;
6835
7001
  case 'ALTER_SUBSCRIPTION_CONNECTION':
6836
7002
  output.push('CONNECTION');
6837
7003
  if (node.conninfo) {
6838
- output.push(`'${node.conninfo}'`);
7004
+ output.push(QuoteUtils.escape(node.conninfo));
6839
7005
  }
6840
7006
  break;
6841
7007
  case 'ALTER_SUBSCRIPTION_SET_PUBLICATION':
6842
7008
  output.push('SET PUBLICATION');
6843
- if (node.publication && node.publication.length > 0) {
6844
- const publications = ListUtils.unwrapList(node.publication).map(pub => this.visit(pub, context));
6845
- output.push(publications.join(', '));
6846
- }
7009
+ if (publications)
7010
+ output.push(publications);
7011
+ if (options)
7012
+ output.push('WITH', options);
7013
+ break;
7014
+ case 'ALTER_SUBSCRIPTION_ADD_PUBLICATION':
7015
+ output.push('ADD PUBLICATION');
7016
+ if (publications)
7017
+ output.push(publications);
7018
+ if (options)
7019
+ output.push('WITH', options);
7020
+ break;
7021
+ case 'ALTER_SUBSCRIPTION_DROP_PUBLICATION':
7022
+ output.push('DROP PUBLICATION');
7023
+ if (publications)
7024
+ output.push(publications);
7025
+ if (options)
7026
+ output.push('WITH', options);
6847
7027
  break;
6848
7028
  case 'ALTER_SUBSCRIPTION_REFRESH':
6849
7029
  output.push('REFRESH PUBLICATION');
7030
+ if (options)
7031
+ output.push('WITH', options);
6850
7032
  break;
6851
- case 'ALTER_SUBSCRIPTION_ENABLED':
6852
- output.push('ENABLE');
7033
+ case 'ALTER_SUBSCRIPTION_ENABLED': {
7034
+ const enabled = ListUtils.unwrapList(node.options).find(opt => opt.DefElem?.defname === 'enabled');
7035
+ const arg = enabled?.DefElem?.arg;
7036
+ const isEnabled = arg?.Boolean ? arg.Boolean.boolval !== false : true;
7037
+ output.push(isEnabled ? 'ENABLE' : 'DISABLE');
6853
7038
  break;
7039
+ }
6854
7040
  case 'ALTER_SUBSCRIPTION_SKIP':
6855
7041
  output.push('SKIP');
7042
+ if (options)
7043
+ output.push(options);
6856
7044
  break;
6857
7045
  default:
6858
7046
  throw new Error(`Unsupported AlterSubscriptionStmt kind: ${node.kind}`);
6859
7047
  }
6860
7048
  }
6861
- if (node.options && node.options.length > 0) {
6862
- output.push('WITH');
6863
- const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context));
6864
- output.push(`(${options.join(', ')})`);
6865
- }
6866
7049
  return output.join(' ');
6867
7050
  }
6868
7051
  DropSubscriptionStmt(node, context) {
@@ -7691,6 +7874,15 @@ export class Deparser {
7691
7874
  output.push(this.visit(node.object, context));
7692
7875
  }
7693
7876
  }
7877
+ else if ((node.renameType === 'OBJECT_TYPE' || node.renameType === 'OBJECT_DOMAIN') && node.object.List) {
7878
+ // Qualified type names - join List parts with dots
7879
+ const items = ListUtils.unwrapList(node.object);
7880
+ const parts = items
7881
+ .map((item) => item.String?.sval)
7882
+ .filter((s) => typeof s === 'string')
7883
+ .map((s) => this.quoteIfNeeded(s));
7884
+ output.push(parts.join('.'));
7885
+ }
7694
7886
  else if (node.renameType === 'OBJECT_SCHEMA' && node.object.List) {
7695
7887
  // Handle schema names - extract from List structure
7696
7888
  const items = ListUtils.unwrapList(node.object);
@@ -7830,6 +8022,9 @@ export class Deparser {
7830
8022
  else if (node.objtype === 'OBJECT_LANGUAGE') {
7831
8023
  output.push('LANGUAGE');
7832
8024
  }
8025
+ else if (node.objtype === 'OBJECT_SEQUENCE') {
8026
+ output.push('SEQUENCE');
8027
+ }
7833
8028
  else if (node.objtype === 'OBJECT_FUNCTION') {
7834
8029
  output.push('FUNCTION');
7835
8030
  }
@@ -7855,8 +8050,11 @@ export class Deparser {
7855
8050
  output.push('DATABASE');
7856
8051
  }
7857
8052
  if (node.objects && node.objects.length > 0) {
8053
+ const qualifiedKinds = ['OBJECT_TYPE', 'OBJECT_DOMAIN'];
7858
8054
  const objects = ListUtils.unwrapList(node.objects)
7859
- .map(obj => this.visit(obj, context))
8055
+ .map(obj => qualifiedKinds.includes(node.objtype) && obj.List
8056
+ ? ListUtils.unwrapList(obj.List.items).map((item) => this.visit(item, context)).join('.')
8057
+ : this.visit(obj, context))
7860
8058
  .join(', ');
7861
8059
  output.push(objects);
7862
8060
  }
@@ -7895,8 +8093,11 @@ export class Deparser {
7895
8093
  break;
7896
8094
  default:
7897
8095
  if (node.objects && node.objects.length > 0) {
8096
+ const qualifiedKinds = ['OBJECT_TYPE', 'OBJECT_DOMAIN'];
7898
8097
  const objects = ListUtils.unwrapList(node.objects)
7899
- .map(obj => this.visit(obj, context))
8098
+ .map(obj => qualifiedKinds.includes(node.objtype) && obj.List
8099
+ ? ListUtils.unwrapList(obj.List.items).map((item) => this.visit(item, context)).join('.')
8100
+ : this.visit(obj, context))
7900
8101
  .join(', ');
7901
8102
  output.push(objects);
7902
8103
  }
@@ -7916,6 +8117,9 @@ export class Deparser {
7916
8117
  if (node.grant_option && node.is_grant) {
7917
8118
  output.push('WITH GRANT OPTION');
7918
8119
  }
8120
+ if (node.grantor) {
8121
+ output.push('GRANTED BY', this.RoleSpec(node.grantor, context));
8122
+ }
7919
8123
  // Only add behavior clauses for REVOKE statements, not for GRANT statements in ALTER DEFAULT PRIVILEGES
7920
8124
  if (!node.is_grant) {
7921
8125
  if (node.behavior === 'DROP_CASCADE') {
@@ -7929,57 +8133,34 @@ export class Deparser {
7929
8133
  }
7930
8134
  GrantRoleStmt(node, context) {
7931
8135
  const output = [];
7932
- // Check for inherit, admin, and set options first to place them correctly
7933
- let hasInheritOption = false;
7934
- let hasAdminOption = false;
7935
- let hasSetOption = false;
7936
- let inheritValue;
7937
- let adminValue;
7938
- let setValue;
7939
- if (node.opt && node.opt.length > 0) {
7940
- const options = ListUtils.unwrapList(node.opt);
7941
- const inheritOption = options.find(opt => opt.DefElem && opt.DefElem.defname === 'inherit');
7942
- const adminOption = options.find(opt => (opt.String && opt.String.sval === 'admin') ||
7943
- (opt.DefElem && opt.DefElem.defname === 'admin'));
7944
- const setOption = options.find(opt => opt.DefElem && opt.DefElem.defname === 'set');
7945
- if (inheritOption && inheritOption.DefElem) {
7946
- hasInheritOption = true;
7947
- inheritValue = inheritOption.DefElem.arg?.Boolean?.boolval;
7948
- }
7949
- if (adminOption) {
7950
- hasAdminOption = true;
7951
- if (adminOption.DefElem && adminOption.DefElem.arg) {
7952
- adminValue = adminOption.DefElem.arg.Boolean?.boolval;
7953
- }
7954
- }
7955
- if (setOption && setOption.DefElem) {
7956
- hasSetOption = true;
7957
- setValue = setOption.DefElem.arg?.Boolean?.boolval;
7958
- }
7959
- }
8136
+ const options = ListUtils.unwrapList(node.opt)
8137
+ .map(opt => opt.DefElem)
8138
+ .filter((opt) => !!opt && !!opt.defname);
8139
+ const optionValue = (opt) => {
8140
+ if (!opt.arg)
8141
+ return true;
8142
+ const argType = this.getNodeType(opt.arg);
8143
+ const argData = this.getNodeData(opt.arg);
8144
+ if (argType === 'Boolean')
8145
+ return argData.boolval !== false;
8146
+ if (argType === 'String')
8147
+ return argData.sval !== 'false';
8148
+ return true;
8149
+ };
8150
+ const roles = ListUtils.unwrapList(node.granted_roles)
8151
+ .map(role => role.AccessPriv?.priv_name
8152
+ ? QuoteUtils.quoteIdentifier(role.AccessPriv.priv_name)
8153
+ : this.visit(role, context))
8154
+ .join(', ');
7960
8155
  if (node.is_grant) {
7961
- output.push('GRANT');
8156
+ output.push('GRANT', roles, 'TO');
7962
8157
  }
7963
8158
  else {
7964
8159
  output.push('REVOKE');
7965
- if (hasInheritOption) {
7966
- output.push('INHERIT OPTION FOR');
7967
- }
7968
- else if (hasAdminOption) {
7969
- output.push('ADMIN OPTION FOR');
8160
+ if (options.length > 0) {
8161
+ output.push(`${options[0].defname.toUpperCase()} OPTION FOR`);
7970
8162
  }
7971
- }
7972
- if (node.granted_roles && node.granted_roles.length > 0) {
7973
- const roles = ListUtils.unwrapList(node.granted_roles)
7974
- .map(role => this.visit(role, context))
7975
- .join(', ');
7976
- output.push(roles);
7977
- }
7978
- if (node.is_grant) {
7979
- output.push('TO');
7980
- }
7981
- else {
7982
- output.push('FROM');
8163
+ output.push(roles, 'FROM');
7983
8164
  }
7984
8165
  if (node.grantee_roles && node.grantee_roles.length > 0) {
7985
8166
  const grantees = ListUtils.unwrapList(node.grantee_roles)
@@ -7987,38 +8168,15 @@ export class Deparser {
7987
8168
  .join(', ');
7988
8169
  output.push(grantees);
7989
8170
  }
7990
- if (node.is_grant) {
7991
- const withOptions = [];
7992
- if (hasAdminOption) {
7993
- if (adminValue === true) {
7994
- withOptions.push('ADMIN OPTION');
7995
- }
7996
- else if (adminValue === false) {
7997
- withOptions.push('ADMIN FALSE');
7998
- }
7999
- else {
8000
- withOptions.push('ADMIN OPTION');
8001
- }
8002
- }
8003
- if (hasInheritOption) {
8004
- if (inheritValue === true) {
8005
- withOptions.push('INHERIT OPTION');
8006
- }
8007
- else if (inheritValue === false) {
8008
- withOptions.push('INHERIT FALSE');
8009
- }
8010
- }
8011
- if (hasSetOption) {
8012
- if (setValue === true) {
8013
- withOptions.push('SET TRUE');
8014
- }
8015
- else if (setValue === false) {
8016
- withOptions.push('SET FALSE');
8017
- }
8018
- }
8019
- if (withOptions.length > 0) {
8020
- output.push('WITH', withOptions.join(', '));
8021
- }
8171
+ if (node.is_grant && options.length > 0) {
8172
+ const withOptions = options.map(opt => `${opt.defname.toUpperCase()} ${optionValue(opt) ? 'TRUE' : 'FALSE'}`);
8173
+ output.push('WITH', withOptions.join(', '));
8174
+ }
8175
+ if (node.grantor) {
8176
+ output.push('GRANTED BY', this.RoleSpec(node.grantor, context));
8177
+ }
8178
+ if (!node.is_grant && node.behavior === 'DROP_CASCADE') {
8179
+ output.push('CASCADE');
8022
8180
  }
8023
8181
  return output.join(' ');
8024
8182
  }
@@ -8053,11 +8211,21 @@ export class Deparser {
8053
8211
  }
8054
8212
  }
8055
8213
  if (node.object) {
8056
- output.push(this.visit(node.object, context));
8214
+ // Qualified names arrive as a List of String nodes and must be
8215
+ // dot-joined (visiting the List directly would comma-join them).
8216
+ if (typeof node.object === 'object' && 'List' in node.object) {
8217
+ const list = node.object.List;
8218
+ const objectParts = ListUtils.unwrapList(list.items)
8219
+ .map(item => this.visit(item, context));
8220
+ output.push(objectParts.join('.'));
8221
+ }
8222
+ else {
8223
+ output.push(this.visit(node.object, context));
8224
+ }
8057
8225
  }
8058
8226
  output.push('IS');
8059
8227
  if (node.label) {
8060
- output.push(`'${node.label}'`);
8228
+ output.push(QuoteUtils.escape(node.label));
8061
8229
  }
8062
8230
  else {
8063
8231
  output.push('NULL');
@@ -8114,10 +8282,10 @@ export class Deparser {
8114
8282
  output.push(conversionName);
8115
8283
  }
8116
8284
  if (node.for_encoding_name) {
8117
- output.push('FOR', `'${node.for_encoding_name}'`);
8285
+ output.push('FOR', QuoteUtils.escape(node.for_encoding_name));
8118
8286
  }
8119
8287
  if (node.to_encoding_name) {
8120
- output.push('TO', `'${node.to_encoding_name}'`);
8288
+ output.push('TO', QuoteUtils.escape(node.to_encoding_name));
8121
8289
  }
8122
8290
  if (node.func_name && node.func_name.length > 0) {
8123
8291
  output.push('FROM');
@@ -8280,6 +8448,9 @@ export class Deparser {
8280
8448
  .join(' ');
8281
8449
  components.push(context.indent('REFERENCING ' + transitionClauses));
8282
8450
  }
8451
+ if (node.constrrel) {
8452
+ components.push(context.indent('FROM ' + this.RangeVar(node.constrrel, context)));
8453
+ }
8283
8454
  if (node.deferrable) {
8284
8455
  components.push(context.indent('DEFERRABLE'));
8285
8456
  }
@@ -8301,7 +8472,7 @@ export class Deparser {
8301
8472
  const funcName = ListUtils.unwrapList(node.funcname)
8302
8473
  .map(name => this.visit(name, context))
8303
8474
  .join('.');
8304
- executeStr += ' PROCEDURE ' + funcName;
8475
+ executeStr += ' FUNCTION ' + funcName;
8305
8476
  }
8306
8477
  if (node.args && node.args.length > 0) {
8307
8478
  const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
@@ -8559,6 +8730,74 @@ export class Deparser {
8559
8730
  .join(' ');
8560
8731
  output.push(whenClauses);
8561
8732
  }
8733
+ if (node.returningClause) {
8734
+ output.push(this.ReturningClause(node.returningClause, context));
8735
+ }
8736
+ return output.join(' ');
8737
+ }
8738
+ MergeSupportFunc(node, context) {
8739
+ return 'merge_action()';
8740
+ }
8741
+ MergeWhenClause(node, context) {
8742
+ const output = ['WHEN'];
8743
+ switch (node.matchKind) {
8744
+ case 'MERGE_WHEN_MATCHED':
8745
+ output.push('MATCHED');
8746
+ break;
8747
+ case 'MERGE_WHEN_NOT_MATCHED_BY_SOURCE':
8748
+ output.push('NOT MATCHED BY SOURCE');
8749
+ break;
8750
+ case 'MERGE_WHEN_NOT_MATCHED_BY_TARGET':
8751
+ output.push('NOT MATCHED');
8752
+ break;
8753
+ }
8754
+ if (node.condition) {
8755
+ output.push('AND');
8756
+ output.push(this.visit(node.condition, context));
8757
+ }
8758
+ output.push('THEN');
8759
+ switch (node.commandType) {
8760
+ case 'CMD_UPDATE': {
8761
+ output.push('UPDATE SET');
8762
+ if (node.targetList && node.targetList.length > 0) {
8763
+ output.push(this.formatUpdateAssignments(node.targetList, context));
8764
+ }
8765
+ break;
8766
+ }
8767
+ case 'CMD_INSERT': {
8768
+ output.push('INSERT');
8769
+ const targetList = ListUtils.unwrapList(node.targetList);
8770
+ if (targetList.length > 0) {
8771
+ const columns = targetList
8772
+ .map(target => QuoteUtils.quoteIdentifier(target.ResTarget?.name))
8773
+ .join(', ');
8774
+ output.push(`(${columns})`);
8775
+ }
8776
+ if (node.override === 'OVERRIDING_USER_VALUE') {
8777
+ output.push('OVERRIDING USER VALUE');
8778
+ }
8779
+ else if (node.override === 'OVERRIDING_SYSTEM_VALUE') {
8780
+ output.push('OVERRIDING SYSTEM VALUE');
8781
+ }
8782
+ const values = ListUtils.unwrapList(node.values);
8783
+ if (values.length > 0) {
8784
+ const valueItems = values
8785
+ .map(value => this.visit(value, context))
8786
+ .join(', ');
8787
+ output.push(`VALUES (${valueItems})`);
8788
+ }
8789
+ else {
8790
+ output.push('DEFAULT VALUES');
8791
+ }
8792
+ break;
8793
+ }
8794
+ case 'CMD_DELETE':
8795
+ output.push('DELETE');
8796
+ break;
8797
+ case 'CMD_NOTHING':
8798
+ output.push('DO NOTHING');
8799
+ break;
8800
+ }
8562
8801
  return output.join(' ');
8563
8802
  }
8564
8803
  AlterTableMoveAllStmt(node, context) {
@@ -8927,6 +9166,13 @@ export class Deparser {
8927
9166
  output.push('USING');
8928
9167
  output.push(node.into.accessMethod);
8929
9168
  }
9169
+ if (node.into && node.into.options && node.into.options.length > 0) {
9170
+ output.push('WITH');
9171
+ const options = ListUtils.unwrapList(node.into.options)
9172
+ .map(option => this.visit(option, context.spawn('CreateStmt')))
9173
+ .join(', ');
9174
+ output.push(`(${options})`);
9175
+ }
8930
9176
  if (node.into && node.into.onCommit && node.into.onCommit !== 'ONCOMMIT_NOOP') {
8931
9177
  output.push('ON COMMIT');
8932
9178
  switch (node.into.onCommit) {
@@ -8941,17 +9187,13 @@ export class Deparser {
8941
9187
  break;
8942
9188
  }
8943
9189
  }
9190
+ if (node.into && node.into.tableSpaceName) {
9191
+ output.push('TABLESPACE', QuoteUtils.quoteIdentifier(node.into.tableSpaceName));
9192
+ }
8944
9193
  output.push('AS');
8945
9194
  if (node.query) {
8946
9195
  output.push(this.visit(node.query, context));
8947
9196
  }
8948
- if (node.into && node.into.options && node.into.options.length > 0) {
8949
- output.push('WITH');
8950
- const options = ListUtils.unwrapList(node.into.options)
8951
- .map(option => this.visit(option, context))
8952
- .join(', ');
8953
- output.push(`(${options})`);
8954
- }
8955
9197
  if (node.into && node.into.skipData) {
8956
9198
  output.push('WITH NO DATA');
8957
9199
  }
@@ -9028,6 +9270,9 @@ export class Deparser {
9028
9270
  if (listItems.length === 1 && listItems[0].String) {
9029
9271
  return `${preservedDefName} = ${listItems[0].String.sval}`;
9030
9272
  }
9273
+ if (listItems.length > 1 && listItems.every(item => item.String)) {
9274
+ return `${preservedDefName} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
9275
+ }
9031
9276
  }
9032
9277
  // For commutator/negator, we already handled them above
9033
9278
  if ((defName.toLowerCase() === 'commutator' || defName.toLowerCase() === 'negator')) {
@@ -9181,7 +9426,7 @@ export class Deparser {
9181
9426
  const preservedDefName = quotedDefName !== defName ? quotedDefName : defName;
9182
9427
  // Handle String arguments with single quotes for string literals
9183
9428
  if (defValue.String) {
9184
- return `${preservedDefName} = '${defValue.String.sval}'`;
9429
+ return `${preservedDefName} = ${QuoteUtils.escape(defValue.String.sval)}`;
9185
9430
  }
9186
9431
  return `${preservedDefName} = ${this.visit(defValue, context)}`;
9187
9432
  }
@@ -9304,7 +9549,7 @@ export class Deparser {
9304
9549
  // For CREATE COLLATION, ensure String nodes are quoted as string literals
9305
9550
  let valueStr;
9306
9551
  if (defValue.String) {
9307
- valueStr = `'${defValue.String.sval}'`;
9552
+ valueStr = QuoteUtils.escape(defValue.String.sval);
9308
9553
  }
9309
9554
  else {
9310
9555
  valueStr = this.visit(defValue, context);
@@ -9424,7 +9669,7 @@ export class Deparser {
9424
9669
  }
9425
9670
  if (node.whereClause) {
9426
9671
  output.push('WHERE');
9427
- output.push(this.visit(node.whereClause, context));
9672
+ output.push(`(${this.visit(node.whereClause, context)})`);
9428
9673
  }
9429
9674
  return output.join(' ');
9430
9675
  }
@@ -9571,6 +9816,23 @@ export class Deparser {
9571
9816
  }
9572
9817
  return output.join(' ');
9573
9818
  }
9819
+ quoteDefElemArg(arg, argValue) {
9820
+ if (arg && this.getNodeType(arg) === 'String') {
9821
+ return QuoteUtils.escape(this.getNodeData(arg).sval || '');
9822
+ }
9823
+ return typeof argValue === 'string' && !argValue.startsWith("'")
9824
+ ? QuoteUtils.escape(argValue)
9825
+ : argValue;
9826
+ }
9827
+ formatOptionValue(arg, argValue) {
9828
+ if (arg && this.getNodeType(arg) === 'String') {
9829
+ const sval = this.getNodeData(arg).sval || '';
9830
+ return /^[a-z_][a-z0-9_]*$/.test(sval) && keywordKindOf(sval) === 'NO_KEYWORD'
9831
+ ? sval
9832
+ : QuoteUtils.escape(sval);
9833
+ }
9834
+ return argValue;
9835
+ }
9574
9836
  Var(node, context) {
9575
9837
  if (node.varno && node.varattno) {
9576
9838
  return `$${node.varno}.${node.varattno}`;
@@ -9610,7 +9872,7 @@ export class Deparser {
9610
9872
  }
9611
9873
  if (node.rowexpr) {
9612
9874
  output.push(',');
9613
- output.push(`'${this.visit(node.rowexpr, context)}'`);
9875
+ output.push(this.visit(node.rowexpr, context));
9614
9876
  }
9615
9877
  if (node.colexprs && node.colexprs.length > 0) {
9616
9878
  output.push('COLUMNS');
@@ -9627,25 +9889,40 @@ export class Deparser {
9627
9889
  if (node.lateral) {
9628
9890
  output.push('LATERAL');
9629
9891
  }
9630
- if (node.docexpr) {
9631
- output.push(this.visit(node.docexpr, context));
9892
+ const args = [];
9893
+ if (node.namespaces && node.namespaces.length > 0) {
9894
+ const namespaces = ListUtils.unwrapList(node.namespaces).map(ns => {
9895
+ const target = this.getNodeData(ns);
9896
+ const uri = this.visit(target.val, context);
9897
+ return target.name ? `${uri} AS ${QuoteUtils.quoteIdentifier(target.name)}` : `DEFAULT ${uri}`;
9898
+ });
9899
+ args.push(`XMLNAMESPACES(${namespaces.join(', ')}),`);
9632
9900
  }
9633
9901
  if (node.rowexpr) {
9634
- output.push('PASSING');
9635
- output.push(this.visit(node.rowexpr, context));
9902
+ args.push(this.formatXmlTableExpr(node.rowexpr, context));
9903
+ }
9904
+ if (node.docexpr) {
9905
+ args.push('PASSING');
9906
+ args.push(this.formatXmlTableExpr(node.docexpr, context));
9636
9907
  }
9637
9908
  if (node.columns && node.columns.length > 0) {
9638
- output.push('COLUMNS');
9639
- const columns = ListUtils.unwrapList(node.columns)
9909
+ args.push('COLUMNS');
9910
+ args.push(ListUtils.unwrapList(node.columns)
9640
9911
  .map(col => this.visit(col, context))
9641
- .join(', ');
9642
- output.push(`(${columns})`);
9912
+ .join(', '));
9643
9913
  }
9914
+ output.push(`XMLTABLE(${args.join(' ')})`);
9644
9915
  if (node.alias) {
9645
9916
  output.push(this.Alias(node.alias, context));
9646
9917
  }
9647
9918
  return output.join(' ');
9648
9919
  }
9920
+ formatXmlTableExpr(expr, context) {
9921
+ const value = this.visit(expr, context);
9922
+ return ['A_Const', 'ColumnRef', 'ParamRef', 'FuncCall'].includes(this.getNodeType(expr))
9923
+ ? value
9924
+ : `(${value})`;
9925
+ }
9649
9926
  RangeTableFuncCol(node, context) {
9650
9927
  const output = [];
9651
9928
  if (node.colname) {
@@ -9659,32 +9936,193 @@ export class Deparser {
9659
9936
  }
9660
9937
  if (node.colexpr) {
9661
9938
  output.push('PATH');
9662
- output.push(`'${this.visit(node.colexpr, context)}'`);
9939
+ output.push(this.visit(node.colexpr, context));
9663
9940
  }
9664
9941
  if (node.coldefexpr) {
9665
9942
  output.push('DEFAULT');
9666
9943
  output.push(this.visit(node.coldefexpr, context));
9667
9944
  }
9945
+ if (node.is_not_null) {
9946
+ output.push('NOT NULL');
9947
+ }
9668
9948
  return output.join(' ');
9669
9949
  }
9950
+ formatJsonFormat(format) {
9951
+ if (!format || !format.format_type || format.format_type === 'JS_FORMAT_DEFAULT') {
9952
+ return null;
9953
+ }
9954
+ const parts = ['FORMAT JSON'];
9955
+ switch (format.encoding) {
9956
+ case 'JS_ENC_UTF8':
9957
+ parts.push('ENCODING UTF8');
9958
+ break;
9959
+ case 'JS_ENC_UTF16':
9960
+ parts.push('ENCODING UTF16');
9961
+ break;
9962
+ case 'JS_ENC_UTF32':
9963
+ parts.push('ENCODING UTF32');
9964
+ break;
9965
+ }
9966
+ return parts.join(' ');
9967
+ }
9968
+ formatJsonOutput(output, context) {
9969
+ if (!output || !output.typeName)
9970
+ return null;
9971
+ const parts = ['RETURNING', this.TypeName(output.typeName, context)];
9972
+ const format = this.formatJsonFormat(output.returning?.format);
9973
+ if (format)
9974
+ parts.push(format);
9975
+ return parts.join(' ');
9976
+ }
9977
+ formatJsonNullClause(absentOnNull) {
9978
+ return absentOnNull ? 'ABSENT ON NULL' : 'NULL ON NULL';
9979
+ }
9980
+ formatJsonUniqueClause(unique) {
9981
+ return unique ? 'WITH UNIQUE KEYS' : null;
9982
+ }
9983
+ JsonValueExpr(node, context) {
9984
+ const parts = [];
9985
+ if (node.raw_expr) {
9986
+ parts.push(this.visit(node.raw_expr, context));
9987
+ }
9988
+ const format = this.formatJsonFormat(node.format);
9989
+ if (format)
9990
+ parts.push(format);
9991
+ return parts.join(' ');
9992
+ }
9993
+ JsonKeyValue(node, context) {
9994
+ const key = node.key ? this.visit(node.key, context) : '';
9995
+ const value = node.value ? this.JsonValueExpr(node.value, context) : '';
9996
+ return `${key} : ${value}`;
9997
+ }
9998
+ JsonObjectConstructor(node, context) {
9999
+ const args = [];
10000
+ if (node.exprs && node.exprs.length > 0) {
10001
+ args.push(ListUtils.unwrapList(node.exprs).map(e => this.visit(e, context)).join(', '));
10002
+ args.push(this.formatJsonNullClause(node.absent_on_null));
10003
+ const unique = this.formatJsonUniqueClause(node.unique);
10004
+ if (unique)
10005
+ args.push(unique);
10006
+ }
10007
+ const output = this.formatJsonOutput(node.output, context);
10008
+ if (output)
10009
+ args.push(output);
10010
+ return `JSON_OBJECT(${args.join(' ')})`;
10011
+ }
10012
+ JsonArrayConstructor(node, context) {
10013
+ const args = [];
10014
+ if (node.exprs && node.exprs.length > 0) {
10015
+ args.push(ListUtils.unwrapList(node.exprs).map(e => this.visit(e, context)).join(', '));
10016
+ args.push(this.formatJsonNullClause(node.absent_on_null));
10017
+ }
10018
+ const output = this.formatJsonOutput(node.output, context);
10019
+ if (output)
10020
+ args.push(output);
10021
+ return `JSON_ARRAY(${args.join(' ')})`;
10022
+ }
9670
10023
  JsonArrayQueryConstructor(node, context) {
9671
- const output = ['JSON_ARRAYAGG'];
10024
+ const args = [];
9672
10025
  if (node.query) {
9673
- output.push(`(${this.visit(node.query, context)})`);
10026
+ args.push(this.visit(node.query, context));
10027
+ }
10028
+ const format = this.formatJsonFormat(node.format);
10029
+ if (format)
10030
+ args.push(format);
10031
+ const output = this.formatJsonOutput(node.output, context);
10032
+ if (output)
10033
+ args.push(output);
10034
+ return `JSON_ARRAY(${args.join(' ')})`;
10035
+ }
10036
+ formatJsonAggSuffix(ctor, context) {
10037
+ if (!ctor)
10038
+ return '';
10039
+ let result = '';
10040
+ if (ctor.agg_filter) {
10041
+ result += ` FILTER (WHERE ${this.visit(ctor.agg_filter, context)})`;
9674
10042
  }
9675
- if (node.format) {
9676
- output.push('FORMAT JSON');
10043
+ if (ctor.over) {
10044
+ result += this.formatOverClause(ctor.over, context);
9677
10045
  }
9678
- if (node.output) {
9679
- output.push('RETURNING TEXT');
10046
+ return result;
10047
+ }
10048
+ JsonObjectAgg(node, context) {
10049
+ const args = [];
10050
+ if (node.arg) {
10051
+ args.push(this.JsonKeyValue(node.arg, context));
10052
+ }
10053
+ args.push(this.formatJsonNullClause(node.absent_on_null));
10054
+ const unique = this.formatJsonUniqueClause(node.unique);
10055
+ if (unique)
10056
+ args.push(unique);
10057
+ const output = this.formatJsonOutput(node.constructor?.output, context);
10058
+ if (output)
10059
+ args.push(output);
10060
+ return `JSON_OBJECTAGG(${args.join(' ')})${this.formatJsonAggSuffix(node.constructor, context)}`;
10061
+ }
10062
+ JsonArrayAgg(node, context) {
10063
+ const args = [];
10064
+ if (node.arg) {
10065
+ args.push(this.JsonValueExpr(node.arg, context));
9680
10066
  }
9681
- if (node.absent_on_null) {
9682
- output.push('ABSENT ON NULL');
10067
+ const aggOrder = node.constructor?.agg_order;
10068
+ if (aggOrder && aggOrder.length > 0) {
10069
+ const orderItems = ListUtils.unwrapList(aggOrder).map(item => this.visit(item, context));
10070
+ args.push(`ORDER BY ${orderItems.join(', ')}`);
9683
10071
  }
9684
- else {
9685
- output.push('NULL ON NULL');
10072
+ args.push(this.formatJsonNullClause(node.absent_on_null));
10073
+ const output = this.formatJsonOutput(node.constructor?.output, context);
10074
+ if (output)
10075
+ args.push(output);
10076
+ return `JSON_ARRAYAGG(${args.join(' ')})${this.formatJsonAggSuffix(node.constructor, context)}`;
10077
+ }
10078
+ JsonParseExpr(node, context) {
10079
+ const args = [];
10080
+ if (node.expr) {
10081
+ args.push(this.JsonValueExpr(node.expr, context));
9686
10082
  }
9687
- return output.join(' ');
10083
+ const unique = this.formatJsonUniqueClause(node.unique_keys);
10084
+ if (unique)
10085
+ args.push(unique);
10086
+ return `JSON(${args.join(' ')})`;
10087
+ }
10088
+ JsonScalarExpr(node, context) {
10089
+ const expr = node.expr ? this.visit(node.expr, context) : '';
10090
+ return `JSON_SCALAR(${expr})`;
10091
+ }
10092
+ JsonSerializeExpr(node, context) {
10093
+ const args = [];
10094
+ if (node.expr) {
10095
+ args.push(this.JsonValueExpr(node.expr, context));
10096
+ }
10097
+ const output = this.formatJsonOutput(node.output, context);
10098
+ if (output)
10099
+ args.push(output);
10100
+ return `JSON_SERIALIZE(${args.join(' ')})`;
10101
+ }
10102
+ JsonIsPredicate(node, context) {
10103
+ const parts = [];
10104
+ if (node.expr) {
10105
+ parts.push(this.visit(node.expr, context));
10106
+ }
10107
+ const format = this.formatJsonFormat(node.format);
10108
+ if (format)
10109
+ parts.push(format);
10110
+ parts.push('IS JSON');
10111
+ switch (node.item_type) {
10112
+ case 'JS_TYPE_OBJECT':
10113
+ parts.push('OBJECT');
10114
+ break;
10115
+ case 'JS_TYPE_ARRAY':
10116
+ parts.push('ARRAY');
10117
+ break;
10118
+ case 'JS_TYPE_SCALAR':
10119
+ parts.push('SCALAR');
10120
+ break;
10121
+ }
10122
+ if (node.unique_keys) {
10123
+ parts.push('WITH UNIQUE KEYS');
10124
+ }
10125
+ return parts.join(' ');
9688
10126
  }
9689
10127
  RangeFunction(node, context) {
9690
10128
  const output = [];
@@ -9878,6 +10316,7 @@ export class Deparser {
9878
10316
  rootParts.push('STANDALONE NO VALUE');
9879
10317
  }
9880
10318
  else if (standaloneArg.A_Const.ival.ival === 3) {
10319
+ // STANDALONE omitted
9881
10320
  }
9882
10321
  else if (Object.keys(standaloneArg.A_Const.ival).length === 0) {
9883
10322
  rootParts.push('STANDALONE YES');
@@ -10470,8 +10909,4 @@ export class Deparser {
10470
10909
  }
10471
10910
  return output.join(' ');
10472
10911
  }
10473
- containsMultilineStringLiteral(content) {
10474
- const stringLiteralRegex = /'[^']*\n[^']*'/g;
10475
- return stringLiteralRegex.test(content);
10476
- }
10477
10912
  }