pgsql-deparser 15.1.1 → 15.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,10 +2,29 @@
2
2
  * Auto-generated file with types stripped for better tree-shaking
3
3
  * DO NOT EDIT - Generated by strip-deparser-types.ts
4
4
  */
5
- import { DeparserContext } from './visitors/base';
6
- import { SqlFormatter } from './utils/sql-formatter';
7
- import { QuoteUtils } from './utils/quote-utils';
5
+ import { keywordKindOf, QuoteUtils } from '@pgsql/quotes';
8
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;
9
28
  /**
10
29
  * List of real PostgreSQL built-in types as they appear in pg_catalog.pg_type.typname.
11
30
  * These are stored in lowercase in PostgreSQL system catalogs.
@@ -104,6 +123,36 @@ function isWrappedParseResult(obj) {
104
123
  * The deparser automatically detects bare ParseResult objects for backward
105
124
  * compatibility and wraps them internally for consistent processing.
106
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
+ }
107
156
  export class Deparser {
108
157
  tree;
109
158
  options;
@@ -163,10 +212,19 @@ export class Deparser {
163
212
  */
164
213
  getFunctionDelimiter(body) {
165
214
  const delimiter = this.options.functionDelimiter || '$$';
166
- if (body.includes(delimiter)) {
167
- return this.options.functionDelimiterFallback || '$EOFCODE$';
215
+ if (!dollarTagConflicts(body, delimiter)) {
216
+ return delimiter;
168
217
  }
169
- 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}$`;
170
228
  }
171
229
  /**
172
230
  * Maps ObjectType enum values to their corresponding SQL keywords
@@ -443,12 +501,7 @@ export class Deparser {
443
501
  // Check if single target is complex - if so, use multiline format
444
502
  if (this.isComplexSelectTarget(targetNode)) {
445
503
  output.push('SELECT' + distinctPart);
446
- if (this.containsMultilineStringLiteral(target)) {
447
- output.push(target);
448
- }
449
- else {
450
- output.push(context.indent(target));
451
- }
504
+ output.push(context.indent(target));
452
505
  }
453
506
  else {
454
507
  output.push('SELECT' + distinctPart + ' ' + target);
@@ -458,9 +511,6 @@ export class Deparser {
458
511
  const targetStrings = targetList
459
512
  .map(e => {
460
513
  const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
461
- if (this.containsMultilineStringLiteral(targetStr)) {
462
- return targetStr;
463
- }
464
514
  return context.indent(targetStr);
465
515
  });
466
516
  const formattedTargets = targetStrings.join(',' + context.newline());
@@ -512,9 +562,6 @@ export class Deparser {
512
562
  return context.parens(values.join(', '));
513
563
  });
514
564
  const indentedTuples = lists.map(tuple => {
515
- if (this.containsMultilineStringLiteral(tuple)) {
516
- return tuple;
517
- }
518
565
  return context.indent(tuple);
519
566
  });
520
567
  output.push(indentedTuples.join(',\n'));
@@ -534,9 +581,6 @@ export class Deparser {
534
581
  const groupItems = groupList
535
582
  .map(e => {
536
583
  const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
537
- if (this.containsMultilineStringLiteral(groupStr)) {
538
- return groupStr;
539
- }
540
584
  return context.indent(groupStr);
541
585
  })
542
586
  .join(',' + context.newline());
@@ -555,12 +599,7 @@ export class Deparser {
555
599
  if (context.isPretty()) {
556
600
  output.push('HAVING');
557
601
  const havingStr = this.visit(node.havingClause, context);
558
- if (this.containsMultilineStringLiteral(havingStr)) {
559
- output.push(havingStr);
560
- }
561
- else {
562
- output.push(context.indent(havingStr));
563
- }
602
+ output.push(context.indent(havingStr));
564
603
  }
565
604
  else {
566
605
  output.push('HAVING');
@@ -581,9 +620,6 @@ export class Deparser {
581
620
  const sortItems = sortList
582
621
  .map(e => {
583
622
  const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
584
- if (this.containsMultilineStringLiteral(sortStr)) {
585
- return sortStr;
586
- }
587
623
  return context.indent(sortStr);
588
624
  })
589
625
  .join(',' + context.newline());
@@ -659,10 +695,15 @@ export class Deparser {
659
695
  return context.format([leftExpr, operator, rightExpr]);
660
696
  }
661
697
  else if (rexpr) {
662
- return context.format([
663
- this.deparseOperatorName(name, context),
664
- this.visit(rexpr, context)
665
- ]);
698
+ // Unary operator (e.g., unary minus: - expr)
699
+ const operator = this.deparseOperatorName(name, context);
700
+ let rightExpr = this.visit(rexpr, context);
701
+ // Wrap in parentheses if rexpr is a binary A_Expr to preserve semantics
702
+ // e.g., -(a - b) must stay -(a - b), not become -a - b
703
+ if (rexpr && 'A_Expr' in rexpr && rexpr.A_Expr?.kind === 'AEXPR_OP' && rexpr.A_Expr?.lexpr) {
704
+ rightExpr = context.parens(rightExpr);
705
+ }
706
+ return context.format([operator, rightExpr]);
666
707
  }
667
708
  break;
668
709
  case 'AEXPR_OP_ANY':
@@ -842,15 +883,15 @@ export class Deparser {
842
883
  getOperatorPrecedence(operator) {
843
884
  const precedence = {
844
885
  '||': 1, // string concatenation
845
- 'OR': 2, // logical OR
846
- 'AND': 3, // logical AND
847
- 'NOT': 4, // logical NOT
848
- 'IS': 5, // IS NULL, IS NOT NULL, etc.
849
- 'IN': 5, // IN, NOT IN
850
- 'BETWEEN': 5, // BETWEEN, NOT BETWEEN
851
- 'LIKE': 5, // LIKE, ILIKE, SIMILAR TO
852
- 'ILIKE': 5,
853
- '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,
854
895
  '<': 6, // comparison operators
855
896
  '<=': 6,
856
897
  '>': 6,
@@ -980,6 +1021,12 @@ export class Deparser {
980
1021
  output.push(context.parens(columnNames.join(', ')));
981
1022
  }
982
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
+ }
983
1030
  if (node.selectStmt) {
984
1031
  output.push(this.visit(node.selectStmt, context));
985
1032
  }
@@ -1022,7 +1069,14 @@ export class Deparser {
1022
1069
  else {
1023
1070
  const updateContext = context.spawn('UpdateStmt', { update: true });
1024
1071
  const targets = targetList.map(target => this.visit(target, updateContext));
1025
- output.push(targets.join(', '));
1072
+ if (context.isPretty()) {
1073
+ // Put each assignment on its own line for pretty printing
1074
+ const indentedTargets = targets.map(target => context.indent(target));
1075
+ output[output.length - 1] += '\n' + indentedTargets.join(',\n');
1076
+ }
1077
+ else {
1078
+ output.push(targets.join(', '));
1079
+ }
1026
1080
  }
1027
1081
  }
1028
1082
  if (node.onConflictClause.whereClause) {
@@ -1034,11 +1088,8 @@ export class Deparser {
1034
1088
  output.push('DO NOTHING');
1035
1089
  }
1036
1090
  }
1037
- if (node.returningList) {
1038
- output.push('RETURNING');
1039
- const returningList = ListUtils.unwrapList(node.returningList);
1040
- const returns = returningList.map(ret => this.visit(ret, context));
1041
- output.push(returns.join(', '));
1091
+ if (node.returningClause) {
1092
+ output.push(this.ReturningClause(node.returningClause, context));
1042
1093
  }
1043
1094
  return output.join(' ');
1044
1095
  }
@@ -1052,38 +1103,8 @@ export class Deparser {
1052
1103
  output.push(this.RangeVar(node.relation, context));
1053
1104
  }
1054
1105
  output.push('SET');
1055
- const targetList = ListUtils.unwrapList(node.targetList);
1056
- if (targetList && targetList.length) {
1057
- const firstTarget = targetList[0];
1058
- const processedTargets = new Set();
1059
- const assignmentParts = [];
1060
- for (let i = 0; i < targetList.length; i++) {
1061
- if (processedTargets.has(i))
1062
- continue;
1063
- const target = targetList[i];
1064
- const multiAssignRef = target.ResTarget?.val?.MultiAssignRef;
1065
- if (multiAssignRef) {
1066
- const relatedTargets = [];
1067
- for (let j = i; j < targetList.length; j++) {
1068
- const otherTarget = targetList[j];
1069
- const otherMultiAssignRef = otherTarget.ResTarget?.val?.MultiAssignRef;
1070
- if (otherMultiAssignRef &&
1071
- JSON.stringify(otherMultiAssignRef.source) === JSON.stringify(multiAssignRef.source)) {
1072
- relatedTargets.push(otherTarget);
1073
- processedTargets.add(j);
1074
- }
1075
- }
1076
- const names = relatedTargets.map(t => t.ResTarget.name);
1077
- const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
1078
- assignmentParts.push(multiAssignment);
1079
- }
1080
- else {
1081
- // Handle regular single-column assignment
1082
- assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
1083
- processedTargets.add(i);
1084
- }
1085
- }
1086
- output.push(assignmentParts.join(','));
1106
+ if (node.targetList && node.targetList.length > 0) {
1107
+ output.push(this.formatUpdateAssignments(node.targetList, context));
1087
1108
  }
1088
1109
  if (node.fromClause) {
1089
1110
  output.push('FROM');
@@ -1095,12 +1116,41 @@ export class Deparser {
1095
1116
  output.push('WHERE');
1096
1117
  output.push(this.visit(node.whereClause, context));
1097
1118
  }
1098
- if (node.returningList) {
1099
- output.push('RETURNING');
1100
- output.push(this.deparseReturningList(node.returningList, context));
1119
+ if (node.returningClause) {
1120
+ output.push(this.ReturningClause(node.returningClause, context));
1101
1121
  }
1102
1122
  return output.join(' ');
1103
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
+ }
1104
1154
  DeleteStmt(node, context) {
1105
1155
  try {
1106
1156
  const output = [];
@@ -1148,13 +1198,12 @@ export class Deparser {
1148
1198
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1149
1199
  }
1150
1200
  }
1151
- if (node.returningList) {
1152
- output.push('RETURNING');
1201
+ if (node.returningClause) {
1153
1202
  try {
1154
- output.push(this.deparseReturningList(node.returningList, context));
1203
+ output.push(this.ReturningClause(node.returningClause, context));
1155
1204
  }
1156
1205
  catch (error) {
1157
- 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)}`);
1158
1207
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1159
1208
  }
1160
1209
  }
@@ -1175,9 +1224,6 @@ export class Deparser {
1175
1224
  const cteStrings = ctes.map((cte, index) => {
1176
1225
  const cteStr = this.visit(cte, context);
1177
1226
  const prefix = index === 0 ? context.newline() : ',' + context.newline();
1178
- if (this.containsMultilineStringLiteral(cteStr)) {
1179
- return prefix + cteStr;
1180
- }
1181
1227
  return prefix + context.indent(cteStr);
1182
1228
  });
1183
1229
  output.push(cteStrings.join(''));
@@ -1192,12 +1238,12 @@ export class Deparser {
1192
1238
  ResTarget(node, context) {
1193
1239
  const output = [];
1194
1240
  if (context.update && node.name) {
1195
- output.push(QuoteUtils.quote(node.name));
1241
+ output.push(QuoteUtils.quoteIdentifier(node.name));
1196
1242
  // Handle indirection (array indexing, field access, etc.)
1197
1243
  if (node.indirection && node.indirection.length > 0) {
1198
1244
  const indirectionStrs = ListUtils.unwrapList(node.indirection).map(item => {
1199
1245
  if (item.String) {
1200
- return `.${QuoteUtils.quote(item.String.sval || item.String.str)}`;
1246
+ return `.${QuoteUtils.quoteIdentifierAfterDot(item.String.sval || item.String.str)}`;
1201
1247
  }
1202
1248
  return this.visit(item, context);
1203
1249
  });
@@ -1209,12 +1255,12 @@ export class Deparser {
1209
1255
  }
1210
1256
  }
1211
1257
  else if (context.insertColumns && node.name) {
1212
- output.push(QuoteUtils.quote(node.name));
1258
+ output.push(QuoteUtils.quoteIdentifier(node.name));
1213
1259
  // Handle indirection for INSERT column lists (e.g., q.c1.r)
1214
1260
  if (node.indirection && node.indirection.length > 0) {
1215
1261
  const indirectionStrs = ListUtils.unwrapList(node.indirection).map(item => {
1216
1262
  if (item.String) {
1217
- return `.${QuoteUtils.quote(item.String.sval || item.String.str)}`;
1263
+ return `.${QuoteUtils.quoteIdentifierAfterDot(item.String.sval || item.String.str)}`;
1218
1264
  }
1219
1265
  return this.visit(item, context);
1220
1266
  });
@@ -1227,11 +1273,53 @@ export class Deparser {
1227
1273
  }
1228
1274
  if (node.name) {
1229
1275
  output.push('AS');
1230
- output.push(QuoteUtils.quote(node.name));
1276
+ output.push(QuoteUtils.quoteIdentifier(node.name));
1231
1277
  }
1232
1278
  }
1233
1279
  return output.join(' ');
1234
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
+ }
1235
1323
  deparseReturningList(list, context) {
1236
1324
  return ListUtils.unwrapList(list)
1237
1325
  .filter(item => item != null && this.getNodeType(item) !== 'undefined')
@@ -1241,7 +1329,7 @@ export class Deparser {
1241
1329
  if (this.getNodeType(item) === 'ResTarget') {
1242
1330
  const resTarget = this.getNodeData(item);
1243
1331
  const val = resTarget.val ? this.visit(resTarget.val, context) : '';
1244
- const alias = resTarget.name ? ` AS ${QuoteUtils.quote(resTarget.name)}` : '';
1332
+ const alias = resTarget.name ? ` AS ${QuoteUtils.quoteIdentifier(resTarget.name)}` : '';
1245
1333
  return val + alias;
1246
1334
  }
1247
1335
  else {
@@ -1296,7 +1384,8 @@ export class Deparser {
1296
1384
  FuncCall(node, context) {
1297
1385
  const funcname = ListUtils.unwrapList(node.funcname);
1298
1386
  const args = ListUtils.unwrapList(node.args);
1299
- const name = funcname.map(n => this.visit(n, context)).join('.');
1387
+ const funcnameParts = funcname.map((n) => n.String?.sval || n.String?.str || '').filter((s) => s);
1388
+ const name = QuoteUtils.quoteDottedName(funcnameParts);
1300
1389
  // Handle special SQL syntax functions like XMLEXISTS and EXTRACT
1301
1390
  if (node.funcformat === 'COERCE_SQL_SYNTAX' && name === 'pg_catalog.xmlexists' && args.length >= 2) {
1302
1391
  const xpath = this.visit(args[0], context);
@@ -1305,8 +1394,15 @@ export class Deparser {
1305
1394
  }
1306
1395
  // Handle EXTRACT function with SQL syntax
1307
1396
  if (node.funcformat === 'COERCE_SQL_SYNTAX' && name === 'pg_catalog.extract' && args.length >= 2) {
1308
- const field = this.visit(args[0], context);
1397
+ let field = this.visit(args[0], context);
1309
1398
  const source = this.visit(args[1], context);
1399
+ // EXTRACT requires an unquoted uppercase keyword (EPOCH, YEAR, etc.)
1400
+ // but A_Const sval deparsing wraps it in single quotes — strip and uppercase
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')) {
1404
+ field = field.slice(1, -1).toUpperCase();
1405
+ }
1310
1406
  return `EXTRACT(${field} FROM ${source})`;
1311
1407
  }
1312
1408
  // Handle TRIM function with SQL syntax (TRIM TRAILING/LEADING/BOTH)
@@ -1466,43 +1562,41 @@ export class Deparser {
1466
1562
  result += ` FILTER (WHERE ${this.visit(node.agg_filter, context)})`;
1467
1563
  }
1468
1564
  if (node.over) {
1469
- // Handle named window references first
1470
- if (node.over.name) {
1471
- result += ` OVER ${node.over.name}`;
1472
- }
1473
- else {
1474
- const windowParts = [];
1475
- if (node.over.partitionClause) {
1476
- const partitions = ListUtils.unwrapList(node.over.partitionClause);
1477
- const partitionStrs = partitions.map(p => this.visit(p, context));
1478
- windowParts.push(`PARTITION BY ${partitionStrs.join(', ')}`);
1479
- }
1480
- if (node.over.orderClause) {
1481
- const orders = ListUtils.unwrapList(node.over.orderClause);
1482
- const orderStrs = orders.map(o => this.visit(o, context));
1483
- windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1484
- }
1485
- // Handle window frame specifications using the dedicated formatWindowFrame method
1486
- const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1487
- if (frameClause) {
1488
- windowParts.push(frameClause);
1489
- }
1490
- if (windowParts.length > 0) {
1491
- if (context.isPretty() && windowParts.length > 1) {
1492
- const formattedParts = windowParts.map(part => context.indent(part));
1493
- result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1494
- }
1495
- else {
1496
- result += ` OVER (${windowParts.join(' ')})`;
1497
- }
1498
- }
1499
- else {
1500
- result += ` OVER ()`;
1501
- }
1502
- }
1565
+ result += this.formatOverClause(node.over, context);
1503
1566
  }
1504
1567
  return result;
1505
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
+ }
1506
1600
  FuncExpr(node, context) {
1507
1601
  const funcName = `func_${node.funcid}`;
1508
1602
  const args = node.args ? ListUtils.unwrapList(node.args).map(arg => this.visit(arg, context)).join(', ') : '';
@@ -1513,39 +1607,39 @@ export class Deparser {
1513
1607
  if (nodeAny.ival !== undefined) {
1514
1608
  if (typeof nodeAny.ival === 'object' && nodeAny.ival !== null) {
1515
1609
  if (nodeAny.ival.ival !== undefined) {
1516
- return nodeAny.ival.ival.toString();
1610
+ return formatInteger(nodeAny.ival.ival);
1517
1611
  }
1518
1612
  else if (Object.keys(nodeAny.ival).length === 0) {
1519
1613
  return '0';
1520
1614
  }
1521
1615
  else {
1522
- return nodeAny.ival.toString();
1616
+ return formatInteger(nodeAny.ival);
1523
1617
  }
1524
1618
  }
1525
1619
  else if (nodeAny.ival === null) {
1526
1620
  return 'NULL';
1527
1621
  }
1528
1622
  else {
1529
- return nodeAny.ival.toString();
1623
+ return formatInteger(nodeAny.ival);
1530
1624
  }
1531
1625
  }
1532
1626
  else if (nodeAny.fval !== undefined) {
1533
1627
  if (typeof nodeAny.fval === 'object' && nodeAny.fval !== null) {
1534
1628
  if (nodeAny.fval.fval !== undefined) {
1535
- return nodeAny.fval.fval.toString();
1629
+ return formatNumeric(nodeAny.fval.fval);
1536
1630
  }
1537
1631
  else if (Object.keys(nodeAny.fval).length === 0) {
1538
1632
  return '0.0';
1539
1633
  }
1540
1634
  else {
1541
- return nodeAny.fval.toString();
1635
+ return formatNumeric(nodeAny.fval);
1542
1636
  }
1543
1637
  }
1544
1638
  else if (nodeAny.fval === null) {
1545
1639
  return 'NULL';
1546
1640
  }
1547
1641
  else {
1548
- return nodeAny.fval.toString();
1642
+ return formatNumeric(nodeAny.fval);
1549
1643
  }
1550
1644
  }
1551
1645
  else if (nodeAny.sval !== undefined) {
@@ -1592,15 +1686,7 @@ export class Deparser {
1592
1686
  else if (nodeAny.bsval !== undefined) {
1593
1687
  if (typeof nodeAny.bsval === 'object' && nodeAny.bsval !== null) {
1594
1688
  if (nodeAny.bsval.bsval !== undefined) {
1595
- const bsval = nodeAny.bsval.bsval;
1596
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1597
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1598
- return `x'${bsval.substring(1)}'`;
1599
- }
1600
- if (bsval.startsWith('b')) {
1601
- return `b'${bsval.substring(1)}'`;
1602
- }
1603
- return `b'${bsval}'`;
1689
+ return formatBitString(nodeAny.bsval.bsval);
1604
1690
  }
1605
1691
  else if (Object.keys(nodeAny.bsval).length === 0) {
1606
1692
  return "''";
@@ -1613,23 +1699,15 @@ export class Deparser {
1613
1699
  return 'NULL';
1614
1700
  }
1615
1701
  else {
1616
- const bsval = nodeAny.bsval;
1617
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1618
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1619
- return `x'${bsval.substring(1)}'`;
1620
- }
1621
- if (bsval.startsWith('b')) {
1622
- return `b'${bsval.substring(1)}'`;
1623
- }
1624
- return `b'${bsval}'`;
1702
+ return formatBitString(nodeAny.bsval);
1625
1703
  }
1626
1704
  }
1627
1705
  if (nodeAny.val) {
1628
1706
  if (nodeAny.val.Integer?.ival !== undefined) {
1629
- return nodeAny.val.Integer.ival.toString();
1707
+ return formatInteger(nodeAny.val.Integer.ival);
1630
1708
  }
1631
1709
  else if (nodeAny.val.Float?.fval !== undefined) {
1632
- return nodeAny.val.Float.fval.toString();
1710
+ return formatNumeric(nodeAny.val.Float.fval);
1633
1711
  }
1634
1712
  else if (nodeAny.val.String?.sval !== undefined) {
1635
1713
  return QuoteUtils.escape(nodeAny.val.String.sval);
@@ -1638,7 +1716,7 @@ export class Deparser {
1638
1716
  return nodeAny.val.Boolean.boolval ? 'true' : 'false';
1639
1717
  }
1640
1718
  else if (nodeAny.val.BitString?.bsval !== undefined) {
1641
- return nodeAny.val.BitString.bsval;
1719
+ return formatBitString(nodeAny.val.BitString.bsval);
1642
1720
  }
1643
1721
  }
1644
1722
  if (nodeAny.isnull === true) {
@@ -1650,15 +1728,15 @@ export class Deparser {
1650
1728
  }
1651
1729
  if (nodeAny.Integer !== undefined) {
1652
1730
  if (typeof nodeAny.Integer === 'object' && nodeAny.Integer.ival !== undefined) {
1653
- return nodeAny.Integer.ival.toString();
1731
+ return formatInteger(nodeAny.Integer.ival);
1654
1732
  }
1655
- return nodeAny.Integer.toString();
1733
+ return formatInteger(nodeAny.Integer);
1656
1734
  }
1657
1735
  if (nodeAny.Float !== undefined) {
1658
1736
  if (typeof nodeAny.Float === 'object' && nodeAny.Float.fval !== undefined) {
1659
- return nodeAny.Float.fval.toString();
1737
+ return formatNumeric(nodeAny.Float.fval);
1660
1738
  }
1661
- return nodeAny.Float.toString();
1739
+ return formatNumeric(nodeAny.Float);
1662
1740
  }
1663
1741
  if (nodeAny.String !== undefined) {
1664
1742
  if (typeof nodeAny.String === 'object' && nodeAny.String.sval !== undefined) {
@@ -1678,7 +1756,7 @@ export class Deparser {
1678
1756
  const fields = ListUtils.unwrapList(node.fields);
1679
1757
  return fields.map(field => {
1680
1758
  if (field.String) {
1681
- return QuoteUtils.quote(field.String.sval || field.String.str);
1759
+ return QuoteUtils.quoteIdentifier(field.String.sval || field.String.str);
1682
1760
  }
1683
1761
  else if (field.A_Star) {
1684
1762
  return '*';
@@ -1755,8 +1833,7 @@ export class Deparser {
1755
1833
  }
1756
1834
  return output.join(' ');
1757
1835
  }
1758
- const quotedTypeName = QuoteUtils.quote(typeName);
1759
- let result = mods(quotedTypeName, args);
1836
+ let result = mods(QuoteUtils.quoteIdentifierTypeName(typeName), args);
1760
1837
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1761
1838
  result += formatArrayBounds(node.arrayBounds);
1762
1839
  }
@@ -1852,8 +1929,9 @@ export class Deparser {
1852
1929
  return output.join(' ');
1853
1930
  }
1854
1931
  }
1855
- const quotedNames = names.map((name) => QuoteUtils.quote(name));
1856
- let result = mods(quotedNames.join('.'), args);
1932
+ // Use type-name quoting for non-pg_catalog types
1933
+ // This allows keywords like 'json', 'int', 'boolean' to remain unquoted in type positions
1934
+ let result = mods(QuoteUtils.quoteTypeDottedName(names), args);
1857
1935
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1858
1936
  result += formatArrayBounds(node.arrayBounds);
1859
1937
  }
@@ -1891,17 +1969,17 @@ export class Deparser {
1891
1969
  }
1892
1970
  let tableName = '';
1893
1971
  if (node.catalogname) {
1894
- tableName = QuoteUtils.quote(node.catalogname);
1972
+ tableName = QuoteUtils.quoteIdentifier(node.catalogname);
1895
1973
  if (node.schemaname) {
1896
- tableName += '.' + QuoteUtils.quote(node.schemaname);
1974
+ tableName += '.' + QuoteUtils.quoteIdentifierAfterDot(node.schemaname);
1897
1975
  }
1898
- tableName += '.' + QuoteUtils.quote(node.relname);
1976
+ tableName += '.' + QuoteUtils.quoteIdentifierAfterDot(node.relname);
1899
1977
  }
1900
1978
  else if (node.schemaname) {
1901
- tableName = QuoteUtils.quote(node.schemaname) + '.' + QuoteUtils.quote(node.relname);
1979
+ tableName = QuoteUtils.quoteQualifiedIdentifier(node.schemaname, node.relname);
1902
1980
  }
1903
1981
  else {
1904
- tableName = QuoteUtils.quote(node.relname);
1982
+ tableName = QuoteUtils.quoteIdentifier(node.relname);
1905
1983
  }
1906
1984
  output.push(tableName);
1907
1985
  if (node.alias) {
@@ -2055,7 +2133,7 @@ export class Deparser {
2055
2133
  if (size != null) {
2056
2134
  return 'char';
2057
2135
  }
2058
- return 'pg_catalog.bpchar';
2136
+ return 'bpchar';
2059
2137
  case 'varchar':
2060
2138
  return 'varchar';
2061
2139
  case 'numeric':
@@ -2069,7 +2147,7 @@ export class Deparser {
2069
2147
  case 'int8':
2070
2148
  return 'bigint';
2071
2149
  case 'real':
2072
- return 'pg_catalog.float4';
2150
+ return 'float4';
2073
2151
  case 'time':
2074
2152
  return 'time';
2075
2153
  case 'timestamp':
@@ -2079,7 +2157,7 @@ export class Deparser {
2079
2157
  case 'bit':
2080
2158
  return 'bit';
2081
2159
  default:
2082
- return `pg_catalog.${typeName}`;
2160
+ return typeName;
2083
2161
  }
2084
2162
  }
2085
2163
  isPgCatalogType(typeName) {
@@ -2120,14 +2198,16 @@ export class Deparser {
2120
2198
  A_Indirection(node, context) {
2121
2199
  let argStr = this.visit(node.arg, context);
2122
2200
  const argType = this.getNodeType(node.arg);
2123
- 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') {
2124
2204
  argStr = `(${argStr})`;
2125
2205
  }
2126
2206
  const output = [argStr];
2127
2207
  const indirection = ListUtils.unwrapList(node.indirection);
2128
2208
  for (const subnode of indirection) {
2129
2209
  if (subnode.String || subnode.A_Star) {
2130
- const value = subnode.A_Star ? '*' : QuoteUtils.quote(subnode.String.sval || subnode.String.str);
2210
+ const value = subnode.A_Star ? '*' : QuoteUtils.quoteIdentifier(subnode.String.sval || subnode.String.str);
2131
2211
  output.push(`.${value}`);
2132
2212
  }
2133
2213
  else {
@@ -2148,21 +2228,11 @@ export class Deparser {
2148
2228
  if (context.isPretty() && args.length > 0) {
2149
2229
  for (const arg of args) {
2150
2230
  const whenClause = this.visit(arg, context);
2151
- if (this.containsMultilineStringLiteral(whenClause)) {
2152
- output.push(context.newline() + whenClause);
2153
- }
2154
- else {
2155
- output.push(context.newline() + context.indent(whenClause));
2156
- }
2231
+ output.push(context.newline() + context.indent(whenClause));
2157
2232
  }
2158
2233
  if (node.defresult) {
2159
2234
  const elseResult = this.visit(node.defresult, context);
2160
- if (this.containsMultilineStringLiteral(elseResult)) {
2161
- output.push(context.newline() + 'ELSE ' + elseResult);
2162
- }
2163
- else {
2164
- output.push(context.newline() + context.indent('ELSE ' + elseResult));
2165
- }
2235
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
2166
2236
  }
2167
2237
  output.push(context.newline() + 'END');
2168
2238
  return output.join(' ');
@@ -2272,45 +2342,21 @@ export class Deparser {
2272
2342
  }
2273
2343
  return output.join(' ');
2274
2344
  }
2275
- static RESERVED_WORDS = new Set([
2276
- 'all', 'analyse', 'analyze', 'and', 'any', 'array', 'as', 'asc', 'asymmetric', 'both',
2277
- 'case', 'cast', 'check', 'collate', 'column', 'constraint', 'create', 'current_catalog',
2278
- 'current_date', 'current_role', 'current_time', 'current_timestamp', 'current_user',
2279
- 'default', 'deferrable', 'desc', 'distinct', 'do', 'else', 'end', 'except', 'false',
2280
- 'fetch', 'for', 'foreign', 'from', 'grant', 'group', 'having', 'in', 'initially',
2281
- 'intersect', 'into', 'lateral', 'leading', 'limit', 'localtime', 'localtimestamp',
2282
- 'not', 'null', 'offset', 'on', 'only', 'or', 'order', 'placing', 'primary',
2283
- 'references', 'returning', 'select', 'session_user', 'some', 'symmetric', 'table',
2284
- 'then', 'to', 'trailing', 'true', 'union', 'unique', 'user', 'using', 'variadic',
2285
- 'when', 'where', 'window', 'with'
2286
- ]);
2287
- static needsQuotes(value) {
2288
- if (!value)
2289
- return false;
2290
- const needsQuotesRegex = /[a-z]+[\W\w]*[A-Z]+|[A-Z]+[\W\w]*[a-z]+|\W/;
2291
- const isAllUppercase = /^[A-Z]+$/.test(value);
2292
- return needsQuotesRegex.test(value) ||
2293
- Deparser.RESERVED_WORDS.has(value.toLowerCase()) ||
2294
- isAllUppercase;
2295
- }
2296
2345
  quoteIfNeeded(value) {
2297
- if (Deparser.needsQuotes(value)) {
2298
- return `"${value}"`;
2299
- }
2300
- return value;
2346
+ return QuoteUtils.quoteIdentifier(value);
2301
2347
  }
2302
2348
  preserveOperatorDefElemCase(defName) {
2303
2349
  const caseMap = {
2304
- 'leftarg': 'Leftarg',
2305
- 'rightarg': 'Rightarg',
2306
- 'procedure': 'Procedure',
2307
- 'function': 'Function',
2308
- 'commutator': 'Commutator',
2309
- 'negator': 'Negator',
2310
- 'restrict': 'Restrict',
2311
- 'join': 'Join',
2312
- 'hashes': 'Hashes',
2313
- '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'
2314
2360
  };
2315
2361
  return caseMap[defName.toLowerCase()] || defName;
2316
2362
  }
@@ -2330,27 +2376,19 @@ export class Deparser {
2330
2376
  return value; // Don't quote pure operator symbols like "=" or "-"
2331
2377
  }
2332
2378
  }
2333
- return Deparser.needsQuotes(value) ? `"${value}"` : value;
2379
+ return QuoteUtils.quoteIdentifier(value);
2334
2380
  }
2335
2381
  Integer(node, context) {
2336
- return node.ival?.toString() || '0';
2382
+ return node.ival === undefined || node.ival === null ? '0' : formatInteger(node.ival);
2337
2383
  }
2338
2384
  Float(node, context) {
2339
- return node.fval || '0.0';
2385
+ return node.fval ? formatNumeric(node.fval) : '0.0';
2340
2386
  }
2341
2387
  Boolean(node, context) {
2342
2388
  return node.boolval ? 'true' : 'false';
2343
2389
  }
2344
2390
  BitString(node, context) {
2345
- // Check if this is a hexadecimal bit string (starts with x)
2346
- if (node.bsval.startsWith('x')) {
2347
- return `x'${node.bsval.substring(1)}'`;
2348
- }
2349
- if (node.bsval.startsWith('b')) {
2350
- return `b'${node.bsval.substring(1)}'`;
2351
- }
2352
- // Fallback for raw values without prefix
2353
- return `b'${node.bsval}'`;
2391
+ return formatBitString(node.bsval);
2354
2392
  }
2355
2393
  Null(node, context) {
2356
2394
  return 'NULL';
@@ -2389,7 +2427,7 @@ export class Deparser {
2389
2427
  output.push(context.parens(elementStrs.join(', ')));
2390
2428
  }
2391
2429
  }
2392
- else if (node.tableElts) {
2430
+ else if (node.tableElts && !node.partbound) {
2393
2431
  const elements = ListUtils.unwrapList(node.tableElts);
2394
2432
  const elementStrs = elements.map(el => {
2395
2433
  return this.deparse(el, context.spawn('CreateStmt'));
@@ -2417,7 +2455,16 @@ export class Deparser {
2417
2455
  const inherits = ListUtils.unwrapList(node.inhRelations);
2418
2456
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2419
2457
  output.push(inheritStrs[0]);
2420
- 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) {
2421
2468
  output.push('FOR VALUES IN');
2422
2469
  const listValues = ListUtils.unwrapList(node.partbound.listdatums)
2423
2470
  .map(datum => this.visit(datum, context))
@@ -2440,13 +2487,9 @@ export class Deparser {
2440
2487
  output.push(`(${upperValues})`);
2441
2488
  }
2442
2489
  }
2443
- else if (node.partbound.strategy === 'h' && node.partbound.modulus !== undefined) {
2490
+ else if (node.partbound.strategy === 'h') {
2444
2491
  output.push('FOR VALUES WITH');
2445
- const remainder = node.partbound.remainder !== undefined ? node.partbound.remainder : 0;
2446
- output.push(`(MODULUS ${node.partbound.modulus}, REMAINDER ${remainder})`);
2447
- }
2448
- else if (node.partbound.is_default) {
2449
- output.push('DEFAULT');
2492
+ output.push(`(MODULUS ${node.partbound.modulus ?? 0}, REMAINDER ${node.partbound.remainder ?? 0})`);
2450
2493
  }
2451
2494
  }
2452
2495
  else if (node.inhRelations) {
@@ -2507,11 +2550,17 @@ export class Deparser {
2507
2550
  ColumnDef(node, context) {
2508
2551
  const output = [];
2509
2552
  if (node.colname) {
2510
- output.push(QuoteUtils.quote(node.colname));
2553
+ output.push(QuoteUtils.quoteIdentifier(node.colname));
2511
2554
  }
2512
2555
  if (node.typeName) {
2513
2556
  output.push(this.TypeName(node.typeName, context));
2514
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
+ }
2515
2564
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2516
2565
  output.push('OPTIONS');
2517
2566
  const columnContext = context.spawn('ColumnDef');
@@ -2546,9 +2595,9 @@ export class Deparser {
2546
2595
  Constraint(node, context) {
2547
2596
  const output = [];
2548
2597
  // Handle constraint name if present
2549
- 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')) {
2550
2599
  output.push('CONSTRAINT');
2551
- output.push(QuoteUtils.quote(node.conname));
2600
+ output.push(QuoteUtils.quoteIdentifier(node.conname));
2552
2601
  }
2553
2602
  switch (node.contype) {
2554
2603
  case 'CONSTR_NULL':
@@ -2556,11 +2605,24 @@ export class Deparser {
2556
2605
  break;
2557
2606
  case 'CONSTR_NOTNULL':
2558
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
+ }
2559
2620
  break;
2560
2621
  case 'CONSTR_DEFAULT':
2561
2622
  output.push('DEFAULT');
2562
2623
  if (node.raw_expr) {
2563
- 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);
2564
2626
  }
2565
2627
  break;
2566
2628
  case 'CONSTR_CHECK':
@@ -2584,8 +2646,13 @@ export class Deparser {
2584
2646
  output.push(context.parens(this.visit(node.raw_expr, context)));
2585
2647
  }
2586
2648
  }
2587
- // Handle NOT VALID for check constraints
2588
- 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
2589
2656
  output.push('NOT VALID');
2590
2657
  }
2591
2658
  // Handle NO INHERIT for check constraints - only for table constraints, not domain constraints
@@ -2605,7 +2672,7 @@ export class Deparser {
2605
2672
  if (node.raw_expr) {
2606
2673
  output.push(context.parens(this.visit(node.raw_expr, context)));
2607
2674
  }
2608
- output.push('STORED');
2675
+ output.push(node.generated_kind === 'v' ? 'VIRTUAL' : 'STORED');
2609
2676
  break;
2610
2677
  case 'CONSTR_IDENTITY':
2611
2678
  output.push('GENERATED');
@@ -2681,10 +2748,12 @@ export class Deparser {
2681
2748
  case 'CONSTR_PRIMARY':
2682
2749
  output.push('PRIMARY KEY');
2683
2750
  if (node.keys && node.keys.length > 0) {
2684
- const keyList = ListUtils.unwrapList(node.keys)
2685
- .map(key => this.visit(key, context))
2686
- .join(', ');
2687
- 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(', ')})`);
2688
2757
  }
2689
2758
  if (node.indexname) {
2690
2759
  output.push('USING INDEX');
@@ -2702,10 +2771,12 @@ export class Deparser {
2702
2771
  output.push('NULLS NOT DISTINCT');
2703
2772
  }
2704
2773
  if (node.keys && node.keys.length > 0) {
2705
- const keyList = ListUtils.unwrapList(node.keys)
2706
- .map(key => this.visit(key, context))
2707
- .join(', ');
2708
- 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(', ')})`);
2709
2780
  }
2710
2781
  if (node.indexname) {
2711
2782
  output.push('USING INDEX');
@@ -2718,20 +2789,14 @@ export class Deparser {
2718
2789
  if (context.isPretty()) {
2719
2790
  output.push('\n' + context.indent('FOREIGN KEY'));
2720
2791
  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})`);
2792
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2725
2793
  }
2726
2794
  output.push('\n' + context.indent('REFERENCES'));
2727
2795
  }
2728
2796
  else {
2729
2797
  output.push('FOREIGN KEY');
2730
2798
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2731
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2732
- .map(attr => this.visit(attr, context))
2733
- .join(', ');
2734
- output.push(`(${fkAttrs})`);
2799
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2735
2800
  }
2736
2801
  output.push('REFERENCES');
2737
2802
  }
@@ -2754,9 +2819,7 @@ export class Deparser {
2754
2819
  }
2755
2820
  }
2756
2821
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2757
- const pkAttrs = ListUtils.unwrapList(node.pk_attrs)
2758
- .map(attr => this.visit(attr, context))
2759
- .join(', ');
2822
+ const pkAttrs = this.deparseKeyAttrs(node.pk_attrs, node.pk_with_period, context);
2760
2823
  if (context.isPretty() && !context.isColumnConstraint) {
2761
2824
  const lastIndex = output.length - 1;
2762
2825
  if (lastIndex >= 0) {
@@ -2827,6 +2890,15 @@ export class Deparser {
2827
2890
  deleteClause += 'SET DEFAULT';
2828
2891
  break;
2829
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
+ }
2830
2902
  if (context.isPretty()) {
2831
2903
  output.push('\n' + context.indent(deleteClause));
2832
2904
  }
@@ -2835,8 +2907,18 @@ export class Deparser {
2835
2907
  output.push(deleteClause.replace('ON DELETE ', ''));
2836
2908
  }
2837
2909
  }
2838
- // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2839
- 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
2840
2922
  if (context.isPretty() && !context.isColumnConstraint) {
2841
2923
  output.push('\n' + context.indent('NOT VALID'));
2842
2924
  }
@@ -2857,6 +2939,12 @@ export class Deparser {
2857
2939
  case 'CONSTR_ATTR_IMMEDIATE':
2858
2940
  output.push('INITIALLY IMMEDIATE');
2859
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;
2860
2948
  case 'CONSTR_EXCLUSION':
2861
2949
  output.push('EXCLUDE');
2862
2950
  if (node.access_method) {
@@ -2891,10 +2979,14 @@ export class Deparser {
2891
2979
  });
2892
2980
  output.push(`(${exclusionElements.join(', ')})`);
2893
2981
  }
2982
+ if (node.where_clause) {
2983
+ output.push('WHERE');
2984
+ output.push(context.parens(this.visit(node.where_clause, context)));
2985
+ }
2894
2986
  break;
2895
2987
  }
2896
2988
  // Handle deferrable constraints for all constraint types that support it
2897
- 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') {
2898
2990
  if (node.deferrable) {
2899
2991
  if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2900
2992
  output.push('\n' + context.indent('DEFERRABLE'));
@@ -2923,6 +3015,9 @@ export class Deparser {
2923
3015
  output.push('NOT DEFERRABLE');
2924
3016
  }
2925
3017
  }
3018
+ else if (node.initdeferred) {
3019
+ output.push('INITIALLY DEFERRED');
3020
+ }
2926
3021
  }
2927
3022
  if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2928
3023
  let result = '';
@@ -2988,6 +3083,9 @@ export class Deparser {
2988
3083
  output.push(node.name);
2989
3084
  }
2990
3085
  const windowParts = [];
3086
+ if (node.refname) {
3087
+ windowParts.push(node.refname);
3088
+ }
2991
3089
  if (node.partitionClause) {
2992
3090
  const partitions = ListUtils.unwrapList(node.partitionClause);
2993
3091
  const partitionStrs = partitions.map(p => this.visit(p, context));
@@ -2998,12 +3096,9 @@ export class Deparser {
2998
3096
  const orderStrs = orders.map(o => this.visit(o, context));
2999
3097
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
3000
3098
  }
3001
- // Only add frame clause if frameOptions indicates non-default framing
3002
- if (node.frameOptions && node.frameOptions !== 1058) {
3003
- const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
3004
- if (frameClause) {
3005
- windowParts.push(frameClause);
3006
- }
3099
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
3100
+ if (frameClause) {
3101
+ windowParts.push(frameClause);
3007
3102
  }
3008
3103
  if (windowParts.length > 0) {
3009
3104
  if (node.name) {
@@ -3020,103 +3115,97 @@ export class Deparser {
3020
3115
  }
3021
3116
  return output.join(' ');
3022
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
+ }
3023
3145
  formatWindowFrame(node, context) {
3024
- if (!node.frameOptions)
3146
+ if (!node.frameOptions || !(node.frameOptions & FRAMEOPTION_NONDEFAULT))
3025
3147
  return null;
3026
3148
  const frameOptions = node.frameOptions;
3027
3149
  const frameParts = [];
3028
- if (frameOptions & 0x01) { // FRAMEOPTION_NONDEFAULT
3029
- if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
3030
- frameParts.push('RANGE');
3031
- }
3032
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
3033
- frameParts.push('ROWS');
3034
- }
3035
- else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
3036
- frameParts.push('GROUPS');
3037
- }
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');
3038
3158
  }
3039
3159
  if (frameParts.length === 0)
3040
3160
  return null;
3041
- const boundsParts = [];
3042
- // Handle specific frameOptions values that have known mappings
3043
- if (frameOptions === 789) {
3044
- boundsParts.push('CURRENT ROW');
3045
- boundsParts.push('AND UNBOUNDED FOLLOWING');
3161
+ let start = null;
3162
+ if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) {
3163
+ start = 'UNBOUNDED PRECEDING';
3046
3164
  }
3047
- else if (frameOptions === 1077) {
3048
- boundsParts.push('UNBOUNDED PRECEDING');
3049
- boundsParts.push('AND CURRENT ROW');
3165
+ else if (frameOptions & FRAMEOPTION_START_UNBOUNDED_FOLLOWING) {
3166
+ start = 'UNBOUNDED FOLLOWING';
3050
3167
  }
3051
- else if (frameOptions === 18453) {
3052
- if (node.startOffset && node.endOffset) {
3053
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
3054
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
3055
- }
3168
+ else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW) {
3169
+ start = 'CURRENT ROW';
3056
3170
  }
3057
- else if (frameOptions === 1557) {
3058
- boundsParts.push('CURRENT ROW');
3059
- boundsParts.push('AND CURRENT ROW');
3171
+ else if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING && node.startOffset) {
3172
+ start = `${this.visit(node.startOffset, context)} PRECEDING`;
3060
3173
  }
3061
- else if (frameOptions === 16917) {
3062
- boundsParts.push('CURRENT ROW');
3063
- if (node.endOffset) {
3064
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
3065
- }
3174
+ else if (frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING && node.startOffset) {
3175
+ start = `${this.visit(node.startOffset, context)} FOLLOWING`;
3066
3176
  }
3067
- else if (frameOptions === 1058) {
3068
- return null;
3177
+ let end = null;
3178
+ if (frameOptions & FRAMEOPTION_END_UNBOUNDED_PRECEDING) {
3179
+ end = 'UNBOUNDED PRECEDING';
3069
3180
  }
3070
- else {
3071
- // Handle start bound - prioritize explicit offset values over bit flags
3072
- if (node.startOffset) {
3073
- if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
3074
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
3075
- }
3076
- else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
3077
- boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
3078
- }
3079
- else {
3080
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
3081
- }
3082
- }
3083
- else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
3084
- boundsParts.push('UNBOUNDED PRECEDING');
3085
- }
3086
- else if (frameOptions & 0x20) { // FRAMEOPTION_START_CURRENT_ROW
3087
- boundsParts.push('CURRENT ROW');
3088
- }
3089
- // Handle end bound - prioritize explicit offset values over bit flags
3090
- if (node.endOffset) {
3091
- if (boundsParts.length > 0) {
3092
- if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
3093
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
3094
- }
3095
- else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
3096
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
3097
- }
3098
- else {
3099
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
3100
- }
3101
- }
3102
- }
3103
- else if (frameOptions & 0x80) { // FRAMEOPTION_END_UNBOUNDED_FOLLOWING
3104
- if (boundsParts.length > 0) {
3105
- boundsParts.push('AND UNBOUNDED FOLLOWING');
3106
- }
3107
- }
3108
- else if (frameOptions & 0x100) { // FRAMEOPTION_END_CURRENT_ROW
3109
- if (boundsParts.length > 0) {
3110
- boundsParts.push('AND CURRENT ROW');
3111
- }
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'}`);
3112
3196
  }
3113
- else if (boundsParts.length > 0) {
3114
- boundsParts.push('AND CURRENT ROW');
3197
+ else {
3198
+ frameParts.push(start);
3115
3199
  }
3116
3200
  }
3117
- if (boundsParts.length > 0) {
3118
- frameParts.push('BETWEEN');
3119
- frameParts.push(boundsParts.join(' '));
3201
+ if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW) {
3202
+ frameParts.push('EXCLUDE CURRENT ROW');
3203
+ }
3204
+ else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP) {
3205
+ frameParts.push('EXCLUDE GROUP');
3206
+ }
3207
+ else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES) {
3208
+ frameParts.push('EXCLUDE TIES');
3120
3209
  }
3121
3210
  return frameParts.join(' ');
3122
3211
  }
@@ -3194,8 +3283,35 @@ export class Deparser {
3194
3283
  if (node.ctequery) {
3195
3284
  output.push(context.parens(this.visit(node.ctequery, context)));
3196
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
+ }
3197
3304
  return output.join(' ');
3198
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
+ }
3199
3315
  ParamRef(node, context) {
3200
3316
  return `$${node.number}`;
3201
3317
  }
@@ -3445,7 +3561,7 @@ export class Deparser {
3445
3561
  output.push('IF NOT EXISTS');
3446
3562
  }
3447
3563
  if (node.idxname) {
3448
- output.push(QuoteUtils.quote(node.idxname));
3564
+ output.push(QuoteUtils.quoteIdentifier(node.idxname));
3449
3565
  }
3450
3566
  output.push('ON');
3451
3567
  if (node.relation) {
@@ -3464,9 +3580,8 @@ export class Deparser {
3464
3580
  output.push('INCLUDE');
3465
3581
  output.push(context.parens(includeStrs.join(', ')));
3466
3582
  }
3467
- if (node.whereClause) {
3468
- output.push('WHERE');
3469
- output.push(this.visit(node.whereClause, context));
3583
+ if (node.nulls_not_distinct) {
3584
+ output.push('NULLS NOT DISTINCT');
3470
3585
  }
3471
3586
  if (node.options && node.options.length > 0) {
3472
3587
  const indexContext = context.spawn('IndexStmt');
@@ -3474,19 +3589,20 @@ export class Deparser {
3474
3589
  output.push('WITH');
3475
3590
  output.push(context.parens(optionStrs.join(', ')));
3476
3591
  }
3477
- if (node.nulls_not_distinct) {
3478
- output.push('NULLS NOT DISTINCT');
3479
- }
3480
3592
  if (node.tableSpace) {
3481
3593
  output.push('TABLESPACE');
3482
- output.push(QuoteUtils.quote(node.tableSpace));
3594
+ output.push(QuoteUtils.quoteIdentifier(node.tableSpace));
3595
+ }
3596
+ if (node.whereClause) {
3597
+ output.push('WHERE');
3598
+ output.push(this.visit(node.whereClause, context));
3483
3599
  }
3484
3600
  return output.join(' ');
3485
3601
  }
3486
3602
  IndexElem(node, context) {
3487
3603
  const output = [];
3488
3604
  if (node.name) {
3489
- output.push(QuoteUtils.quote(node.name));
3605
+ output.push(QuoteUtils.quoteIdentifier(node.name));
3490
3606
  }
3491
3607
  else if (node.expr) {
3492
3608
  output.push(context.parens(this.visit(node.expr, context)));
@@ -3504,7 +3620,7 @@ export class Deparser {
3504
3620
  const opclassOpts = ListUtils.unwrapList(node.opclassopts).map(opt => {
3505
3621
  if (opt.DefElem && opt.DefElem.arg && this.getNodeType(opt.DefElem.arg) === 'String') {
3506
3622
  const stringData = this.getNodeData(opt.DefElem.arg);
3507
- return `${opt.DefElem.defname}='${stringData.sval}'`;
3623
+ return `${QuoteUtils.quoteIdentifierAfterDot(opt.DefElem.defname)}=${QuoteUtils.escape(stringData.sval)}`;
3508
3624
  }
3509
3625
  return this.visit(opt, context.spawn('IndexElem'));
3510
3626
  });
@@ -3537,7 +3653,7 @@ export class Deparser {
3537
3653
  PartitionElem(node, context) {
3538
3654
  const output = [];
3539
3655
  if (node.name) {
3540
- output.push(QuoteUtils.quote(node.name));
3656
+ output.push(QuoteUtils.quoteIdentifier(node.name));
3541
3657
  }
3542
3658
  else if (node.expr) {
3543
3659
  output.push(context.parens(this.visit(node.expr, context)));
@@ -3555,12 +3671,12 @@ export class Deparser {
3555
3671
  }
3556
3672
  PartitionCmd(node, context) {
3557
3673
  const output = [];
3674
+ if (node.name) {
3675
+ output.push(this.RangeVar(node.name, context));
3676
+ }
3558
3677
  if (node.concurrent) {
3559
3678
  output.push('CONCURRENTLY');
3560
3679
  }
3561
- if (node.name) {
3562
- output.push(this.visit(node.name, context));
3563
- }
3564
3680
  if (node.bound) {
3565
3681
  if (node.bound.strategy === 'l' && node.bound.listdatums) {
3566
3682
  output.push('FOR VALUES IN');
@@ -3585,9 +3701,9 @@ export class Deparser {
3585
3701
  output.push(`(${upperValues})`);
3586
3702
  }
3587
3703
  }
3588
- else if (node.bound.strategy === 'h' && node.bound.modulus !== undefined && node.bound.remainder !== undefined) {
3704
+ else if (node.bound.strategy === 'h') {
3589
3705
  output.push('FOR VALUES WITH');
3590
- output.push(`(modulus ${node.bound.modulus}, remainder ${node.bound.remainder})`);
3706
+ output.push(`(MODULUS ${node.bound.modulus ?? 0}, REMAINDER ${node.bound.remainder ?? 0})`);
3591
3707
  }
3592
3708
  else if (node.bound.is_default) {
3593
3709
  output.push('DEFAULT');
@@ -3718,12 +3834,7 @@ export class Deparser {
3718
3834
  if (context.isPretty()) {
3719
3835
  // For complex JOIN conditions, format with proper indentation
3720
3836
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3721
- if (this.containsMultilineStringLiteral(qualsStr)) {
3722
- output.push(` ON ${qualsStr}`);
3723
- }
3724
- else {
3725
- output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3726
- }
3837
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3727
3838
  }
3728
3839
  else {
3729
3840
  output.push(` ON ${qualsStr}`);
@@ -3787,37 +3898,37 @@ export class Deparser {
3787
3898
  case 'TRANS_STMT_SAVEPOINT':
3788
3899
  output.push('SAVEPOINT');
3789
3900
  if (node.savepoint_name) {
3790
- output.push(QuoteUtils.quote(node.savepoint_name));
3901
+ output.push(QuoteUtils.quoteIdentifier(node.savepoint_name));
3791
3902
  }
3792
3903
  break;
3793
3904
  case 'TRANS_STMT_RELEASE':
3794
3905
  output.push('RELEASE SAVEPOINT');
3795
3906
  if (node.savepoint_name) {
3796
- output.push(QuoteUtils.quote(node.savepoint_name));
3907
+ output.push(QuoteUtils.quoteIdentifier(node.savepoint_name));
3797
3908
  }
3798
3909
  break;
3799
3910
  case 'TRANS_STMT_ROLLBACK_TO':
3800
3911
  output.push('ROLLBACK TO');
3801
3912
  if (node.savepoint_name) {
3802
- output.push(QuoteUtils.quote(node.savepoint_name));
3913
+ output.push(QuoteUtils.quoteIdentifier(node.savepoint_name));
3803
3914
  }
3804
3915
  break;
3805
3916
  case 'TRANS_STMT_PREPARE':
3806
3917
  output.push('PREPARE TRANSACTION');
3807
3918
  if (node.gid) {
3808
- output.push(`'${node.gid}'`);
3919
+ output.push(QuoteUtils.escape(node.gid));
3809
3920
  }
3810
3921
  break;
3811
3922
  case 'TRANS_STMT_COMMIT_PREPARED':
3812
3923
  output.push('COMMIT PREPARED');
3813
3924
  if (node.gid) {
3814
- output.push(`'${node.gid}'`);
3925
+ output.push(QuoteUtils.escape(node.gid));
3815
3926
  }
3816
3927
  break;
3817
3928
  case 'TRANS_STMT_ROLLBACK_PREPARED':
3818
3929
  output.push('ROLLBACK PREPARED');
3819
3930
  if (node.gid) {
3820
- output.push(`'${node.gid}'`);
3931
+ output.push(QuoteUtils.escape(node.gid));
3821
3932
  }
3822
3933
  break;
3823
3934
  default:
@@ -3884,28 +3995,39 @@ export class Deparser {
3884
3995
  switch (node.kind) {
3885
3996
  case 'VAR_SET_VALUE':
3886
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
+ }
3887
4003
  const args = node.args ? ListUtils.unwrapList(node.args).map(arg => {
3888
4004
  const nodeData = this.getNodeData(arg);
3889
4005
  if (nodeData.sval !== undefined) {
3890
4006
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3891
- if (svalValue === '' || svalValue.includes(' ') || svalValue.includes('-') || /[A-Z]/.test(svalValue) || /^\d/.test(svalValue) || svalValue.includes('.') || svalValue.includes('$') || svalValue.toLowerCase() === 'all' || /^[+-]\d/.test(svalValue)) {
3892
- 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);
3893
4009
  }
3894
4010
  return svalValue;
3895
4011
  }
3896
4012
  return this.visit(arg, context);
3897
4013
  }).join(', ') : '';
4014
+ if (node.jumble_args && node.name === 'timezone') {
4015
+ return `SET ${localPrefix}TIME ZONE ${args}`;
4016
+ }
3898
4017
  // Handle args - always include TO clause if args exist (even if empty string)
3899
- const paramName = node.name && (node.name.includes('.') || node.name.includes('-') || /[A-Z]/.test(node.name)) ? `"${node.name}"` : node.name;
4018
+ const paramName = QuoteUtils.quoteIdentifier(node.name);
3900
4019
  if (!node.args || node.args.length === 0) {
3901
4020
  return `SET ${localPrefix}${paramName}`;
3902
4021
  }
3903
4022
  return `SET ${localPrefix}${paramName} TO ${args}`;
3904
4023
  case 'VAR_SET_DEFAULT':
3905
- const defaultParamName = node.name && (node.name.includes('.') || node.name.includes('-') || /[A-Z]/.test(node.name)) ? `"${node.name}"` : node.name;
4024
+ if (node.jumble_args && node.name === 'timezone') {
4025
+ return `SET ${node.is_local ? 'LOCAL ' : ''}TIME ZONE DEFAULT`;
4026
+ }
4027
+ const defaultParamName = QuoteUtils.quoteIdentifier(node.name);
3906
4028
  return `SET ${defaultParamName} TO DEFAULT`;
3907
4029
  case 'VAR_SET_CURRENT':
3908
- const currentParamName = node.name && (node.name.includes('.') || node.name.includes('-') || /[A-Z]/.test(node.name)) ? `"${node.name}"` : node.name;
4030
+ const currentParamName = QuoteUtils.quoteIdentifier(node.name);
3909
4031
  return `SET ${currentParamName} FROM CURRENT`;
3910
4032
  case 'VAR_SET_MULTI':
3911
4033
  if (node.name === 'TRANSACTION' || node.name === 'SESSION CHARACTERISTICS') {
@@ -3979,7 +4101,7 @@ export class Deparser {
3979
4101
  return `SET ${assignments}`;
3980
4102
  }
3981
4103
  case 'VAR_RESET':
3982
- const resetParamName = node.name && (node.name.includes('.') || node.name.includes('-') || /[A-Z]/.test(node.name)) ? `"${node.name}"` : node.name;
4104
+ const resetParamName = QuoteUtils.quoteIdentifier(node.name);
3983
4105
  return `RESET ${resetParamName}`;
3984
4106
  case 'VAR_RESET_ALL':
3985
4107
  return 'RESET ALL';
@@ -3999,7 +4121,7 @@ export class Deparser {
3999
4121
  output.push('IF NOT EXISTS');
4000
4122
  }
4001
4123
  if (node.schemaname) {
4002
- output.push(node.schemaname);
4124
+ output.push(QuoteUtils.quoteIdentifier(node.schemaname));
4003
4125
  }
4004
4126
  if (node.authrole) {
4005
4127
  output.push('AUTHORIZATION');
@@ -4206,7 +4328,7 @@ export class Deparser {
4206
4328
  if (objList && objList.List && objList.List.items) {
4207
4329
  const items = objList.List.items.map((item) => {
4208
4330
  if (item.String && item.String.sval) {
4209
- return item.String.sval;
4331
+ return QuoteUtils.quoteIdentifier(item.String.sval);
4210
4332
  }
4211
4333
  return this.visit(item, context);
4212
4334
  });
@@ -4248,7 +4370,7 @@ export class Deparser {
4248
4370
  if (objList && objList.List && objList.List.items) {
4249
4371
  const items = objList.List.items.map((item) => {
4250
4372
  if (item.String && item.String.sval) {
4251
- return QuoteUtils.quote(item.String.sval);
4373
+ return QuoteUtils.quoteIdentifier(item.String.sval);
4252
4374
  }
4253
4375
  return this.visit(item, context);
4254
4376
  }).filter((name) => name && name.trim());
@@ -4282,13 +4404,13 @@ export class Deparser {
4282
4404
  if (items.length === 2) {
4283
4405
  const accessMethod = items[0];
4284
4406
  const objectName = items[1];
4285
- return `${QuoteUtils.quote(objectName)} USING ${accessMethod}`;
4407
+ return `${QuoteUtils.quoteIdentifier(objectName)} USING ${accessMethod}`;
4286
4408
  }
4287
4409
  else if (items.length === 3) {
4288
4410
  const accessMethod = items[0];
4289
4411
  const schemaName = items[1];
4290
4412
  const objectName = items[2];
4291
- return `${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(objectName)} USING ${accessMethod}`;
4413
+ return `${QuoteUtils.quoteQualifiedIdentifier(schemaName, objectName)} USING ${accessMethod}`;
4292
4414
  }
4293
4415
  return items.join('.');
4294
4416
  }
@@ -4331,7 +4453,7 @@ export class Deparser {
4331
4453
  if (objList && objList.List && objList.List.items) {
4332
4454
  const items = objList.List.items.map((item) => {
4333
4455
  if (item.String && item.String.sval) {
4334
- return QuoteUtils.quote(item.String.sval);
4456
+ return QuoteUtils.quoteIdentifier(item.String.sval);
4335
4457
  }
4336
4458
  return this.visit(item, context);
4337
4459
  }).filter((name) => name && name.trim());
@@ -4379,7 +4501,7 @@ export class Deparser {
4379
4501
  PLAssignStmt(node, context) {
4380
4502
  const output = [];
4381
4503
  if (node.name) {
4382
- let nameWithIndirection = QuoteUtils.quote(node.name);
4504
+ let nameWithIndirection = QuoteUtils.quoteIdentifier(node.name);
4383
4505
  if (node.indirection && node.indirection.length > 0) {
4384
4506
  const indirectionStr = node.indirection
4385
4507
  .map((ind) => this.visit(ind, context))
@@ -4429,10 +4551,10 @@ export class Deparser {
4429
4551
  output.push('PROGRAM');
4430
4552
  }
4431
4553
  if (node.filename) {
4432
- output.push(`'${node.filename}'`);
4554
+ output.push(QuoteUtils.escape(node.filename));
4433
4555
  }
4434
4556
  else {
4435
- output.push('STDIN');
4557
+ output.push(node.is_from ? 'STDIN' : 'STDOUT');
4436
4558
  }
4437
4559
  if (node.options && node.options.length > 0) {
4438
4560
  output.push('WITH');
@@ -4528,7 +4650,7 @@ export class Deparser {
4528
4650
  const parts = [];
4529
4651
  const indentedParts = [];
4530
4652
  if (colDefData.colname) {
4531
- parts.push(QuoteUtils.quote(colDefData.colname));
4653
+ parts.push(QuoteUtils.quoteIdentifier(colDefData.colname));
4532
4654
  }
4533
4655
  if (colDefData.typeName) {
4534
4656
  parts.push(this.TypeName(colDefData.typeName, context));
@@ -4586,7 +4708,7 @@ export class Deparser {
4586
4708
  else {
4587
4709
  const parts = [];
4588
4710
  if (colDefData.colname) {
4589
- parts.push(QuoteUtils.quote(colDefData.colname));
4711
+ parts.push(QuoteUtils.quoteIdentifier(colDefData.colname));
4590
4712
  }
4591
4713
  if (colDefData.typeName) {
4592
4714
  parts.push(this.TypeName(colDefData.typeName, context));
@@ -4640,7 +4762,7 @@ export class Deparser {
4640
4762
  }
4641
4763
  }
4642
4764
  if (node.name) {
4643
- output.push(QuoteUtils.quote(node.name));
4765
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4644
4766
  }
4645
4767
  if (node.behavior === 'DROP_CASCADE') {
4646
4768
  output.push('CASCADE');
@@ -4657,13 +4779,16 @@ export class Deparser {
4657
4779
  output.push('ALTER COLUMN');
4658
4780
  }
4659
4781
  if (node.name) {
4660
- output.push(QuoteUtils.quote(node.name));
4782
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4661
4783
  }
4662
4784
  output.push('TYPE');
4663
4785
  if (node.def) {
4664
4786
  const nodeData = this.getNodeData(node.def);
4665
4787
  if (nodeData && nodeData.typeName) {
4666
4788
  output.push(this.TypeName(nodeData.typeName, context));
4789
+ if (nodeData.collClause) {
4790
+ output.push(this.CollateClause(nodeData.collClause, context));
4791
+ }
4667
4792
  // Handle USING clause (stored in raw_default for ALTER COLUMN TYPE)
4668
4793
  if (nodeData.raw_default) {
4669
4794
  output.push('USING');
@@ -4684,7 +4809,7 @@ export class Deparser {
4684
4809
  case 'AT_SetTableSpace':
4685
4810
  output.push('SET TABLESPACE');
4686
4811
  if (node.name) {
4687
- output.push(QuoteUtils.quote(node.name));
4812
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4688
4813
  }
4689
4814
  break;
4690
4815
  case 'AT_AddConstraint':
@@ -4702,7 +4827,7 @@ export class Deparser {
4702
4827
  output.push('DROP CONSTRAINT');
4703
4828
  }
4704
4829
  if (node.name) {
4705
- output.push(QuoteUtils.quote(node.name));
4830
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4706
4831
  }
4707
4832
  if (node.behavior === 'DROP_CASCADE') {
4708
4833
  output.push('CASCADE');
@@ -4740,7 +4865,7 @@ export class Deparser {
4740
4865
  case 'AT_ColumnDefault':
4741
4866
  output.push('ALTER COLUMN');
4742
4867
  if (node.name) {
4743
- output.push(QuoteUtils.quote(node.name));
4868
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4744
4869
  }
4745
4870
  if (node.def) {
4746
4871
  output.push('SET DEFAULT');
@@ -4753,7 +4878,7 @@ export class Deparser {
4753
4878
  case 'AT_SetStorage':
4754
4879
  output.push('ALTER COLUMN');
4755
4880
  if (node.name) {
4756
- output.push(QuoteUtils.quote(node.name));
4881
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4757
4882
  }
4758
4883
  output.push('SET STORAGE');
4759
4884
  if (node.def) {
@@ -4764,7 +4889,7 @@ export class Deparser {
4764
4889
  case 'AT_ClusterOn':
4765
4890
  output.push('CLUSTER ON');
4766
4891
  if (node.name) {
4767
- output.push(QuoteUtils.quote(node.name));
4892
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4768
4893
  }
4769
4894
  break;
4770
4895
  case 'AT_DropCluster':
@@ -4791,21 +4916,21 @@ export class Deparser {
4791
4916
  case 'AT_SetNotNull':
4792
4917
  output.push('ALTER COLUMN');
4793
4918
  if (node.name) {
4794
- output.push(QuoteUtils.quote(node.name));
4919
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4795
4920
  }
4796
4921
  output.push('SET NOT NULL');
4797
4922
  break;
4798
4923
  case 'AT_DropNotNull':
4799
4924
  output.push('ALTER COLUMN');
4800
4925
  if (node.name) {
4801
- output.push(QuoteUtils.quote(node.name));
4926
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4802
4927
  }
4803
4928
  output.push('DROP NOT NULL');
4804
4929
  break;
4805
4930
  case 'AT_SetStatistics':
4806
4931
  output.push('ALTER COLUMN');
4807
4932
  if (node.name) {
4808
- output.push(QuoteUtils.quote(node.name));
4933
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4809
4934
  }
4810
4935
  else if (node.num !== undefined && node.num !== null) {
4811
4936
  output.push(node.num.toString());
@@ -4818,7 +4943,7 @@ export class Deparser {
4818
4943
  case 'AT_SetOptions':
4819
4944
  output.push('ALTER COLUMN');
4820
4945
  if (node.name) {
4821
- output.push(QuoteUtils.quote(node.name));
4946
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4822
4947
  }
4823
4948
  output.push('SET');
4824
4949
  if (node.def) {
@@ -4835,7 +4960,7 @@ export class Deparser {
4835
4960
  case 'AT_ResetOptions':
4836
4961
  output.push('ALTER COLUMN');
4837
4962
  if (node.name) {
4838
- output.push(QuoteUtils.quote(node.name));
4963
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4839
4964
  }
4840
4965
  output.push('RESET');
4841
4966
  if (node.def) {
@@ -4852,7 +4977,7 @@ export class Deparser {
4852
4977
  case 'AT_SetCompression':
4853
4978
  output.push('ALTER COLUMN');
4854
4979
  if (node.name) {
4855
- output.push(QuoteUtils.quote(node.name));
4980
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4856
4981
  }
4857
4982
  output.push('SET COMPRESSION');
4858
4983
  if (node.def) {
@@ -4862,31 +4987,31 @@ export class Deparser {
4862
4987
  case 'AT_ValidateConstraint':
4863
4988
  output.push('VALIDATE CONSTRAINT');
4864
4989
  if (node.name) {
4865
- output.push(QuoteUtils.quote(node.name));
4990
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4866
4991
  }
4867
4992
  break;
4868
4993
  case 'AT_EnableTrig':
4869
4994
  output.push('ENABLE TRIGGER');
4870
4995
  if (node.name) {
4871
- output.push(QuoteUtils.quote(node.name));
4996
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4872
4997
  }
4873
4998
  break;
4874
4999
  case 'AT_EnableAlwaysTrig':
4875
5000
  output.push('ENABLE ALWAYS TRIGGER');
4876
5001
  if (node.name) {
4877
- output.push(QuoteUtils.quote(node.name));
5002
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4878
5003
  }
4879
5004
  break;
4880
5005
  case 'AT_EnableReplicaTrig':
4881
5006
  output.push('ENABLE REPLICA TRIGGER');
4882
5007
  if (node.name) {
4883
- output.push(QuoteUtils.quote(node.name));
5008
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4884
5009
  }
4885
5010
  break;
4886
5011
  case 'AT_DisableTrig':
4887
5012
  output.push('DISABLE TRIGGER');
4888
5013
  if (node.name) {
4889
- output.push(QuoteUtils.quote(node.name));
5014
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4890
5015
  }
4891
5016
  break;
4892
5017
  case 'AT_EnableTrigAll':
@@ -4904,31 +5029,31 @@ export class Deparser {
4904
5029
  case 'AT_EnableRule':
4905
5030
  output.push('ENABLE RULE');
4906
5031
  if (node.name) {
4907
- output.push(QuoteUtils.quote(node.name));
5032
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4908
5033
  }
4909
5034
  break;
4910
5035
  case 'AT_EnableAlwaysRule':
4911
5036
  output.push('ENABLE ALWAYS RULE');
4912
5037
  if (node.name) {
4913
- output.push(QuoteUtils.quote(node.name));
5038
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4914
5039
  }
4915
5040
  break;
4916
5041
  case 'AT_EnableReplicaRule':
4917
5042
  output.push('ENABLE REPLICA RULE');
4918
5043
  if (node.name) {
4919
- output.push(QuoteUtils.quote(node.name));
5044
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4920
5045
  }
4921
5046
  break;
4922
5047
  case 'AT_DisableRule':
4923
5048
  output.push('DISABLE RULE');
4924
5049
  if (node.name) {
4925
- output.push(QuoteUtils.quote(node.name));
5050
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4926
5051
  }
4927
5052
  break;
4928
5053
  case 'AT_SetAccessMethod':
4929
5054
  output.push('SET ACCESS METHOD');
4930
5055
  if (node.name) {
4931
- output.push(QuoteUtils.quote(node.name));
5056
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4932
5057
  }
4933
5058
  else {
4934
5059
  // Handle DEFAULT access method case
@@ -4982,7 +5107,7 @@ export class Deparser {
4982
5107
  case 'AT_CookedColumnDefault':
4983
5108
  output.push('ALTER COLUMN');
4984
5109
  if (node.name) {
4985
- output.push(QuoteUtils.quote(node.name));
5110
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4986
5111
  }
4987
5112
  if (node.def) {
4988
5113
  output.push('SET DEFAULT');
@@ -4995,26 +5120,23 @@ export class Deparser {
4995
5120
  case 'AT_SetExpression':
4996
5121
  output.push('ALTER COLUMN');
4997
5122
  if (node.name) {
4998
- output.push(QuoteUtils.quote(node.name));
5123
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4999
5124
  }
5000
5125
  output.push('SET EXPRESSION');
5001
5126
  if (node.def) {
5002
- output.push(this.visit(node.def, context));
5127
+ output.push('AS');
5128
+ output.push(context.parens(this.visit(node.def, context)));
5003
5129
  }
5004
5130
  break;
5005
5131
  case 'AT_DropExpression':
5006
5132
  output.push('ALTER COLUMN');
5007
5133
  if (node.name) {
5008
- output.push(QuoteUtils.quote(node.name));
5134
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5009
5135
  }
5010
5136
  output.push('DROP EXPRESSION');
5011
- break;
5012
- case 'AT_CheckNotNull':
5013
- output.push('ALTER COLUMN');
5014
- if (node.name) {
5015
- output.push(QuoteUtils.quote(node.name));
5137
+ if (node.missing_ok) {
5138
+ output.push('IF EXISTS');
5016
5139
  }
5017
- output.push('SET NOT NULL');
5018
5140
  break;
5019
5141
  case 'AT_AddIndex':
5020
5142
  output.push('ADD');
@@ -5041,23 +5163,28 @@ export class Deparser {
5041
5163
  }
5042
5164
  break;
5043
5165
  case 'AT_AlterConstraint':
5044
- output.push('ALTER CONSTRAINT');
5045
- if (node.def && this.getNodeType(node.def) === 'Constraint') {
5046
- const constraintData = this.getNodeData(node.def);
5047
- if (constraintData.conname) {
5048
- output.push(QuoteUtils.quote(constraintData.conname));
5049
- if (constraintData.deferrable !== undefined) {
5050
- output.push(constraintData.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
5051
- }
5052
- if (constraintData.initdeferred !== undefined) {
5053
- 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
+ }
5054
5181
  }
5055
5182
  }
5056
- }
5057
- else if (node.name) {
5058
- output.push(QuoteUtils.quote(node.name));
5059
- if (node.def) {
5060
- 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
+ }
5061
5188
  }
5062
5189
  }
5063
5190
  break;
@@ -5076,7 +5203,7 @@ export class Deparser {
5076
5203
  case 'AT_AlterColumnGenericOptions':
5077
5204
  output.push('ALTER COLUMN');
5078
5205
  if (node.name) {
5079
- output.push(QuoteUtils.quote(node.name));
5206
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5080
5207
  }
5081
5208
  output.push('OPTIONS');
5082
5209
  if (node.def) {
@@ -5130,7 +5257,7 @@ export class Deparser {
5130
5257
  case 'AT_AddIdentity':
5131
5258
  output.push('ALTER COLUMN');
5132
5259
  if (node.name) {
5133
- output.push(QuoteUtils.quote(node.name));
5260
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5134
5261
  }
5135
5262
  output.push('ADD');
5136
5263
  if (node.def) {
@@ -5140,24 +5267,20 @@ export class Deparser {
5140
5267
  case 'AT_SetIdentity':
5141
5268
  output.push('ALTER COLUMN');
5142
5269
  if (node.name) {
5143
- output.push(QuoteUtils.quote(node.name));
5270
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5144
5271
  }
5145
- output.push('SET');
5146
5272
  if (node.def) {
5147
- output.push(this.visit(node.def, context));
5273
+ output.push(this.formatIdentityColumnOptions(node.def, context));
5148
5274
  }
5149
5275
  break;
5150
5276
  case 'AT_DropIdentity':
5151
5277
  output.push('ALTER COLUMN');
5152
5278
  if (node.name) {
5153
- output.push(QuoteUtils.quote(node.name));
5279
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5154
5280
  }
5155
5281
  output.push('DROP IDENTITY');
5156
- if (node.behavior === 'DROP_CASCADE') {
5157
- output.push('CASCADE');
5158
- }
5159
- else if (node.behavior === 'DROP_RESTRICT') {
5160
- output.push('RESTRICT');
5282
+ if (node.missing_ok) {
5283
+ output.push('IF EXISTS');
5161
5284
  }
5162
5285
  break;
5163
5286
  case 'AT_ReAddStatistics':
@@ -5184,7 +5307,8 @@ export class Deparser {
5184
5307
  output.push('FUNCTION');
5185
5308
  }
5186
5309
  if (node.funcname && node.funcname.length > 0) {
5187
- const funcName = node.funcname.map((name) => this.visit(name, context)).join('.');
5310
+ const funcnameParts = node.funcname.map((name) => name.String?.sval || name.String?.str || '').filter((s) => s);
5311
+ const funcName = QuoteUtils.quoteDottedName(funcnameParts);
5188
5312
  if (node.parameters && node.parameters.length > 0) {
5189
5313
  const params = node.parameters
5190
5314
  .filter((param) => {
@@ -5193,7 +5317,13 @@ export class Deparser {
5193
5317
  })
5194
5318
  .map((param) => this.visit(param, context));
5195
5319
  if (params.length > 0) {
5196
- output.push(funcName + '(' + params.join(', ') + ')');
5320
+ if (context.isPretty()) {
5321
+ const formattedParams = params.map(p => context.indent(p)).join(',' + context.newline());
5322
+ output.push(funcName + '(' + context.newline() + formattedParams + context.newline() + ')');
5323
+ }
5324
+ else {
5325
+ output.push(funcName + '(' + params.join(', ') + ')');
5326
+ }
5197
5327
  }
5198
5328
  else {
5199
5329
  output.push(funcName + '()');
@@ -5208,15 +5338,21 @@ export class Deparser {
5208
5338
  return paramData.mode === 'FUNC_PARAM_TABLE';
5209
5339
  });
5210
5340
  if (hasTableParams) {
5211
- output.push('RETURNS TABLE (');
5212
5341
  const tableParams = node.parameters
5213
5342
  .filter((param) => {
5214
5343
  const paramData = this.getNodeData(param);
5215
5344
  return paramData.mode === 'FUNC_PARAM_TABLE';
5216
5345
  })
5217
5346
  .map((param) => this.visit(param, context));
5218
- output.push(tableParams.join(', '));
5219
- output.push(')');
5347
+ if (context.isPretty()) {
5348
+ const formattedTableParams = tableParams.map(p => context.indent(p)).join(',' + context.newline());
5349
+ output.push('RETURNS TABLE (' + context.newline() + formattedTableParams + context.newline() + ')');
5350
+ }
5351
+ else {
5352
+ output.push('RETURNS TABLE (');
5353
+ output.push(tableParams.join(', '));
5354
+ output.push(')');
5355
+ }
5220
5356
  }
5221
5357
  else if (node.returnType) {
5222
5358
  output.push('RETURNS');
@@ -5238,6 +5374,7 @@ export class Deparser {
5238
5374
  if (bodyType === 'List') {
5239
5375
  const statements = ListUtils.unwrapList(node.sql_body);
5240
5376
  if (statements.length === 0 || (statements.length === 1 && Object.keys(statements[0]).length === 0)) {
5377
+ // empty body: nothing to emit
5241
5378
  }
5242
5379
  else {
5243
5380
  // Handle nested List structure in BEGIN ATOMIC blocks
@@ -5289,7 +5426,7 @@ export class Deparser {
5289
5426
  }
5290
5427
  }
5291
5428
  if (node.name) {
5292
- output.push(QuoteUtils.quote(node.name));
5429
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5293
5430
  }
5294
5431
  if (node.argType) {
5295
5432
  output.push(this.TypeName(node.argType, context));
@@ -5361,7 +5498,7 @@ export class Deparser {
5361
5498
  output.push('ROLE');
5362
5499
  }
5363
5500
  if (node.role) {
5364
- const roleName = Deparser.needsQuotes(node.role) ? `"${node.role}"` : node.role;
5501
+ const roleName = QuoteUtils.quoteIdentifier(node.role);
5365
5502
  output.push(roleName);
5366
5503
  }
5367
5504
  if (node.options) {
@@ -5385,25 +5522,28 @@ export class Deparser {
5385
5522
  if (node.arg && this.getNodeType(node.arg) === 'List') {
5386
5523
  const listData = this.getNodeData(node.arg);
5387
5524
  const listItems = ListUtils.unwrapList(listData.items);
5525
+ const preservedName = this.preserveOperatorDefElemCase(node.defname);
5388
5526
  if (listItems.length === 1 && listItems[0].String) {
5389
- const preservedName = this.preserveOperatorDefElemCase(node.defname);
5390
5527
  return `${preservedName} = ${listItems[0].String.sval}`;
5391
5528
  }
5529
+ if (listItems.length > 1 && listItems.every((item) => item.String)) {
5530
+ return `${preservedName} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
5531
+ }
5392
5532
  }
5393
5533
  }
5394
5534
  // Handle IndexElem opclassopts - MUST be first to preserve string types
5395
5535
  if (context.parentNodeTypes.includes('IndexElem')) {
5396
5536
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5397
5537
  const stringData = this.getNodeData(node.arg);
5398
- return `${node.defname}='${stringData.sval}'`;
5538
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${QuoteUtils.escape(stringData.sval)}`;
5399
5539
  }
5400
- 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')))}`;
5401
5541
  }
5402
5542
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
5403
5543
  if (context.parentNodeTypes.includes('DefineStmt') &&
5404
5544
  ['hashes', 'merges'].includes(node.defname.toLowerCase()) && !node.arg) {
5405
5545
  if (node.defname !== node.defname.toLowerCase() && node.defname !== node.defname.toUpperCase()) {
5406
- return `"${node.defname}"`;
5546
+ return QuoteUtils.quoteIdentifier(node.defname);
5407
5547
  }
5408
5548
  return node.defname.charAt(0).toUpperCase() + node.defname.slice(1).toLowerCase();
5409
5549
  }
@@ -5422,12 +5562,8 @@ export class Deparser {
5422
5562
  const defElemContext = context.spawn('DefElem');
5423
5563
  const argValue = this.visit(node.arg, defElemContext);
5424
5564
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5425
- const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
5426
- ? `'${argValue}'`
5427
- : argValue;
5428
- const quotedDefname = node.defname.includes(' ') || node.defname.includes('-') || Deparser.needsQuotes(node.defname)
5429
- ? `"${node.defname}"`
5430
- : node.defname;
5565
+ const finalValue = this.quoteDefElemArg(node.arg, argValue);
5566
+ const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5431
5567
  if (node.defaction === 'DEFELEM_ADD') {
5432
5568
  return `ADD ${quotedDefname} ${finalValue}`;
5433
5569
  }
@@ -5439,9 +5575,7 @@ export class Deparser {
5439
5575
  }
5440
5576
  return `${quotedDefname} ${finalValue}`;
5441
5577
  }
5442
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5443
- ? `'${argValue}'`
5444
- : argValue;
5578
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5445
5579
  if (node.defaction === 'DEFELEM_ADD') {
5446
5580
  return `ADD ${node.defname} ${quotedValue}`;
5447
5581
  }
@@ -5451,9 +5585,7 @@ export class Deparser {
5451
5585
  else if (node.defaction === 'DEFELEM_SET') {
5452
5586
  return `SET ${node.defname} ${quotedValue}`;
5453
5587
  }
5454
- const quotedDefname = node.defname.includes(' ') || node.defname.includes('-')
5455
- ? `"${node.defname}"`
5456
- : node.defname;
5588
+ const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5457
5589
  return `${quotedDefname} ${quotedValue}`;
5458
5590
  }
5459
5591
  else if (node.defaction === 'DEFELEM_DROP') {
@@ -5475,9 +5607,7 @@ export class Deparser {
5475
5607
  }
5476
5608
  const defElemContext = context.spawn('DefElem');
5477
5609
  const argValue = this.visit(node.arg, defElemContext);
5478
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5479
- ? `'${argValue}'`
5480
- : argValue;
5610
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5481
5611
  return `PASSWORD ${quotedValue}`;
5482
5612
  }
5483
5613
  }
@@ -5500,6 +5630,9 @@ export class Deparser {
5500
5630
  if (listItems.length === 1 && listItems[0].String) {
5501
5631
  return `${node.defname} = ${listItems[0].String.sval}`;
5502
5632
  }
5633
+ if (listItems.length > 1 && listItems.every((item) => item.String)) {
5634
+ return `${node.defname} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
5635
+ }
5503
5636
  }
5504
5637
  }
5505
5638
  if (context.parentNodeTypes.includes('CreatedbStmt') || context.parentNodeTypes.includes('DropdbStmt')) {
@@ -5513,9 +5646,7 @@ export class Deparser {
5513
5646
  const quotedValue = typeof argValue === 'string'
5514
5647
  ? QuoteUtils.escape(argValue)
5515
5648
  : argValue;
5516
- const quotedDefname = node.defname.includes(' ') || node.defname.includes('-')
5517
- ? `"${node.defname}"`
5518
- : node.defname;
5649
+ const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5519
5650
  return `${quotedDefname} ${quotedValue}`;
5520
5651
  }
5521
5652
  if (context.parentNodeTypes.includes('CreateRoleStmt') || context.parentNodeTypes.includes('AlterRoleStmt')) {
@@ -5544,9 +5675,7 @@ export class Deparser {
5544
5675
  }
5545
5676
  }
5546
5677
  if (node.defname === 'validUntil') {
5547
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5548
- ? `'${argValue}'`
5549
- : argValue;
5678
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5550
5679
  return `VALID UNTIL ${quotedValue}`;
5551
5680
  }
5552
5681
  if (node.defname === 'adminmembers') {
@@ -5585,15 +5714,15 @@ export class Deparser {
5585
5714
  if (node.arg && this.getNodeType(node.arg) === 'List') {
5586
5715
  const listData = this.getNodeData(node.arg);
5587
5716
  const listItems = ListUtils.unwrapList(listData.items);
5717
+ if (listItems.length === 1 && listItems[0].String?.sval === 'none') {
5718
+ return 'OWNED BY NONE';
5719
+ }
5588
5720
  const parts = listItems.map(item => {
5589
5721
  const itemData = this.getNodeData(item);
5590
5722
  if (this.getNodeType(item) === 'String') {
5591
5723
  // Check if this identifier needs quotes to preserve case
5592
5724
  const value = itemData.sval;
5593
- if (Deparser.needsQuotes(value)) {
5594
- return `"${value}"`;
5595
- }
5596
- return value;
5725
+ return QuoteUtils.quoteIdentifier(value);
5597
5726
  }
5598
5727
  return this.visit(item, context);
5599
5728
  });
@@ -5726,10 +5855,8 @@ export class Deparser {
5726
5855
  if (context.parentNodeTypes.includes('AlterExtensionStmt')) {
5727
5856
  if (node.defname === 'new_version') {
5728
5857
  // argValue is unquoted due to DefElem context, so we need to quote it
5729
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5730
- ? `'${argValue}'`
5731
- : argValue;
5732
- return `UPDATE TO ${quotedValue}`;
5858
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5859
+ return `TO ${quotedValue}`;
5733
5860
  }
5734
5861
  if (node.defname === 'schema') {
5735
5862
  return `SET SCHEMA ${argValue}`;
@@ -5749,28 +5876,37 @@ export class Deparser {
5749
5876
  if (node.defname === 'cascade') {
5750
5877
  return argValue === 'true' ? 'CASCADE' : '';
5751
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
+ }
5752
5885
  return `${node.defname.toUpperCase()} ${argValue}`;
5753
5886
  }
5754
5887
  // Handle IndexStmt WITH clause options - no quotes, compact formatting
5755
5888
  if (context.parentNodeTypes.includes('IndexStmt')) {
5756
- return `${node.defname}=${argValue}`;
5889
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5757
5890
  }
5758
5891
  // Handle IndexElem opclassopts - preserve string values as strings
5759
5892
  if (context.parentNodeTypes.includes('IndexElem')) {
5760
5893
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5761
5894
  const stringData = this.getNodeData(node.arg);
5762
- return `${node.defname}='${stringData.sval}'`;
5895
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${QuoteUtils.escape(stringData.sval)}`;
5763
5896
  }
5764
- return `${node.defname}=${argValue}`;
5897
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5765
5898
  }
5766
5899
  // Handle CreateStmt table options - no quotes, compact formatting
5767
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);
5768
5904
  // For numeric values, use the raw value without quotes
5769
5905
  if (node.arg && this.getNodeType(node.arg) === 'Integer') {
5770
5906
  const integerData = this.getNodeData(node.arg);
5771
- return `${node.defname}=${integerData.ival}`;
5907
+ return `${relOptName}=${formatInteger(integerData.ival)}`;
5772
5908
  }
5773
- return `${node.defname}=${argValue}`;
5909
+ return `${relOptName}=${this.formatOptionValue(node.arg, argValue)}`;
5774
5910
  }
5775
5911
  // Handle CreateEventTrigStmt WHEN clause - use IN syntax for List arguments
5776
5912
  if (context.parentNodeTypes.includes('CreateEventTrigStmt')) {
@@ -5780,7 +5916,7 @@ export class Deparser {
5780
5916
  const values = listItems.map(item => {
5781
5917
  if (this.getNodeType(item) === 'String') {
5782
5918
  const stringData = this.getNodeData(item);
5783
- return `'${stringData.sval || ''}'`;
5919
+ return QuoteUtils.escape(stringData.sval || '');
5784
5920
  }
5785
5921
  return this.visit(item, context);
5786
5922
  });
@@ -5795,7 +5931,7 @@ export class Deparser {
5795
5931
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
5796
5932
  return `${optionName} = ${argValue}`;
5797
5933
  }
5798
- return `${optionName} = ${argValue}`;
5934
+ return `${optionName} = ${this.formatOptionValue(node.arg, argValue)}`;
5799
5935
  }
5800
5936
  // Handle ViewStmt WITH options - don't quote numeric values
5801
5937
  if (context.parentNodeTypes.includes('ViewStmt')) {
@@ -5809,15 +5945,27 @@ export class Deparser {
5809
5945
  }
5810
5946
  // Handle CopyStmt WITH clause options - uppercase format without quotes
5811
5947
  if (context.parentNodeTypes.includes('CopyStmt')) {
5812
- if (node.defname === 'format' && node.arg && this.getNodeType(node.arg) === 'String') {
5813
- const stringData = this.getNodeData(node.arg);
5814
- return `FORMAT ${stringData.sval.toUpperCase()}`;
5948
+ const optName = node.defname.toUpperCase();
5949
+ if (!node.arg) {
5950
+ return optName;
5815
5951
  }
5816
- // Handle other COPY options with uppercase defname
5817
- if (node.arg) {
5818
- return `${node.defname.toUpperCase()} ${argValue}`;
5952
+ const copyArgType = this.getNodeType(node.arg);
5953
+ if (copyArgType === 'Boolean' && this.getNodeData(node.arg).boolval !== false) {
5954
+ return optName;
5819
5955
  }
5820
- return node.defname.toUpperCase();
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(', ')})`;
5960
+ }
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}`;
5821
5969
  }
5822
5970
  // Handle CREATE OPERATOR and CREATE TYPE context
5823
5971
  if (context.parentNodeTypes.includes('DefineStmt')) {
@@ -5841,13 +5989,13 @@ export class Deparser {
5841
5989
  // Handle boolean flags (no arguments) - preserve quoted case
5842
5990
  if (['hashes', 'merges'].includes(node.defname.toLowerCase())) {
5843
5991
  if (node.defname !== node.defname.toLowerCase() && node.defname !== node.defname.toUpperCase()) {
5844
- return `"${node.defname}"`;
5992
+ return QuoteUtils.quoteIdentifier(node.defname);
5845
5993
  }
5846
5994
  return preservedName.toUpperCase();
5847
5995
  }
5848
5996
  // Handle CREATE AGGREGATE quoted identifiers - preserve quotes when needed
5849
- if (Deparser.needsQuotes(node.defname)) {
5850
- const quotedDefname = `"${node.defname}"`;
5997
+ const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5998
+ if (quotedDefname !== node.defname) {
5851
5999
  if (node.arg) {
5852
6000
  if (this.getNodeType(node.arg) === 'String') {
5853
6001
  const stringData = this.getNodeData(node.arg);
@@ -5856,7 +6004,7 @@ export class Deparser {
5856
6004
  return `${quotedDefname} = ${stringData.sval}`;
5857
6005
  }
5858
6006
  // Regular string literals get single quotes
5859
- return `${quotedDefname} = '${stringData.sval}'`;
6007
+ return `${quotedDefname} = ${QuoteUtils.escape(stringData.sval)}`;
5860
6008
  }
5861
6009
  return `${quotedDefname} = ${argValue}`;
5862
6010
  }
@@ -5877,7 +6025,7 @@ export class Deparser {
5877
6025
  return `${node.defname} = ${stringData.sval}`;
5878
6026
  }
5879
6027
  // Regular string literals get single quotes
5880
- return `${node.defname} = '${stringData.sval}'`;
6028
+ return `${node.defname} = ${QuoteUtils.escape(stringData.sval)}`;
5881
6029
  }
5882
6030
  if (node.arg && this.getNodeType(node.arg) === 'Boolean') {
5883
6031
  const boolData = this.getNodeData(node.arg);
@@ -5898,6 +6046,10 @@ export class Deparser {
5898
6046
  return `${node.defname} = ${argValue}`;
5899
6047
  }
5900
6048
  }
6049
+ const argType = this.getNodeType(node.arg);
6050
+ if (argType === 'TypeName' || argType === 'Integer' || argType === 'Float' || argType === 'Boolean') {
6051
+ return `${node.defname} = ${argValue}`;
6052
+ }
5901
6053
  const quotedValue = typeof argValue === 'string'
5902
6054
  ? QuoteUtils.escape(argValue)
5903
6055
  : argValue;
@@ -5907,7 +6059,7 @@ export class Deparser {
5907
6059
  if (context.parentNodeTypes.includes('DefineStmt') && !node.arg) {
5908
6060
  // Check if the original defname appears to be quoted (mixed case that's not all upper/lower)
5909
6061
  if (node.defname !== node.defname.toLowerCase() && node.defname !== node.defname.toUpperCase()) {
5910
- return `"${node.defname}"`;
6062
+ return QuoteUtils.quoteIdentifier(node.defname);
5911
6063
  }
5912
6064
  }
5913
6065
  return node.defname.toUpperCase();
@@ -5923,7 +6075,7 @@ export class Deparser {
5923
6075
  }
5924
6076
  if (node.location) {
5925
6077
  output.push('LOCATION');
5926
- output.push(`'${node.location}'`);
6078
+ output.push(QuoteUtils.escape(node.location));
5927
6079
  }
5928
6080
  if (node.options && node.options.length > 0) {
5929
6081
  output.push('WITH');
@@ -5987,6 +6139,7 @@ export class Deparser {
5987
6139
  if (node.extname) {
5988
6140
  output.push(this.quoteIfNeeded(node.extname));
5989
6141
  }
6142
+ output.push('UPDATE');
5990
6143
  if (node.options && node.options.length > 0) {
5991
6144
  const extContext = context.spawn('AlterExtensionStmt');
5992
6145
  const options = ListUtils.unwrapList(node.options)
@@ -6091,7 +6244,7 @@ export class Deparser {
6091
6244
  case 'REPLICA_IDENTITY_INDEX':
6092
6245
  output.push('USING', 'INDEX');
6093
6246
  if (node.name) {
6094
- output.push(QuoteUtils.quote(node.name));
6247
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6095
6248
  }
6096
6249
  break;
6097
6250
  default:
@@ -6150,7 +6303,7 @@ export class Deparser {
6150
6303
  output.push('IF', 'EXISTS');
6151
6304
  }
6152
6305
  if (node.name) {
6153
- output.push(QuoteUtils.quote(node.name));
6306
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6154
6307
  }
6155
6308
  if (node.behavior === 'DROP_CASCADE') {
6156
6309
  output.push('CASCADE');
@@ -6159,7 +6312,7 @@ export class Deparser {
6159
6312
  case 'AT_ValidateConstraint':
6160
6313
  output.push('VALIDATE', 'CONSTRAINT');
6161
6314
  if (node.name) {
6162
- output.push(QuoteUtils.quote(node.name));
6315
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6163
6316
  }
6164
6317
  break;
6165
6318
  case 'C':
@@ -6176,7 +6329,7 @@ export class Deparser {
6176
6329
  output.push('IF', 'EXISTS');
6177
6330
  }
6178
6331
  if (node.name) {
6179
- output.push(QuoteUtils.quote(node.name));
6332
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6180
6333
  }
6181
6334
  if (node.behavior === 'DROP_CASCADE') {
6182
6335
  output.push('CASCADE');
@@ -6185,7 +6338,7 @@ export class Deparser {
6185
6338
  case 'V':
6186
6339
  output.push('VALIDATE', 'CONSTRAINT');
6187
6340
  if (node.name) {
6188
- output.push(QuoteUtils.quote(node.name));
6341
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6189
6342
  }
6190
6343
  break;
6191
6344
  case 'O':
@@ -6256,7 +6409,7 @@ export class Deparser {
6256
6409
  }
6257
6410
  if (node.payload !== null && node.payload !== undefined) {
6258
6411
  output.push(',');
6259
- output.push(`'${node.payload}'`);
6412
+ output.push(QuoteUtils.escape(node.payload));
6260
6413
  }
6261
6414
  return output.join(' ');
6262
6415
  }
@@ -6284,7 +6437,7 @@ export class Deparser {
6284
6437
  if (!node.filename) {
6285
6438
  throw new Error('LoadStmt requires filename');
6286
6439
  }
6287
- return `LOAD '${node.filename}'`;
6440
+ return `LOAD ${QuoteUtils.escape(node.filename)}`;
6288
6441
  }
6289
6442
  DiscardStmt(node, context) {
6290
6443
  switch (node.target) {
@@ -6300,6 +6453,13 @@ export class Deparser {
6300
6453
  throw new Error(`Unsupported DiscardStmt target: ${node.target}`);
6301
6454
  }
6302
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
+ }
6303
6463
  CommentStmt(node, context) {
6304
6464
  const output = ['COMMENT ON'];
6305
6465
  if (node.objtype) {
@@ -6331,6 +6491,9 @@ export class Deparser {
6331
6491
  case 'OBJECT_TABCONSTRAINT':
6332
6492
  output.push('CONSTRAINT');
6333
6493
  break;
6494
+ case 'OBJECT_DOMCONSTRAINT':
6495
+ output.push('CONSTRAINT');
6496
+ break;
6334
6497
  case 'OBJECT_TRIGGER':
6335
6498
  output.push('TRIGGER');
6336
6499
  break;
@@ -6407,23 +6570,30 @@ export class Deparser {
6407
6570
  output.push(objectParts.join('.'));
6408
6571
  }
6409
6572
  }
6410
- else if (node.objtype === 'OBJECT_TRIGGER') {
6573
+ else if (node.objtype === 'OBJECT_DOMCONSTRAINT') {
6411
6574
  if (objectParts.length === 2) {
6412
- const [table, trigger] = objectParts;
6413
- 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]);
6414
6585
  output.push('ON');
6415
- output.push(table);
6586
+ output.push(objectParts.slice(0, -1).join('.'));
6416
6587
  }
6417
6588
  else {
6418
6589
  output.push(objectParts.join('.'));
6419
6590
  }
6420
6591
  }
6421
6592
  else if (node.objtype === 'OBJECT_RULE') {
6422
- if (objectParts.length === 2) {
6423
- const [table, rule] = objectParts;
6424
- output.push(rule);
6593
+ if (objectParts.length >= 2) {
6594
+ output.push(objectParts[objectParts.length - 1]);
6425
6595
  output.push('ON');
6426
- output.push(table);
6596
+ output.push(objectParts.slice(0, -1).join('.'));
6427
6597
  }
6428
6598
  else {
6429
6599
  output.push(objectParts.join('.'));
@@ -6434,8 +6604,7 @@ export class Deparser {
6434
6604
  // For operators, we need to handle ObjectWithArgs structure
6435
6605
  if (node.object && node.object.ObjectWithArgs) {
6436
6606
  const objWithArgs = node.object.ObjectWithArgs;
6437
- let operatorName = objWithArgs.objname && objWithArgs.objname[0] && objWithArgs.objname[0].String
6438
- ? objWithArgs.objname[0].String.sval : 'unknown';
6607
+ let operatorName = this.qualifiedOperatorName(objWithArgs.objname);
6439
6608
  if (operatorName.startsWith('"') && operatorName.endsWith('"')) {
6440
6609
  operatorName = operatorName.slice(1, -1);
6441
6610
  }
@@ -6473,7 +6642,13 @@ export class Deparser {
6473
6642
  }
6474
6643
  else if (node.objtype === 'OBJECT_POLICY') {
6475
6644
  // Handle POLICY syntax: COMMENT ON POLICY policy_name ON table_name IS 'comment'
6476
- 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) {
6477
6652
  const [table, policy] = objectParts;
6478
6653
  output.push(policy);
6479
6654
  output.push('ON');
@@ -6496,6 +6671,9 @@ export class Deparser {
6496
6671
  output.push(objectParts.join('.'));
6497
6672
  }
6498
6673
  }
6674
+ else if (node.objtype === 'OBJECT_CAST' && objectParts.length === 2) {
6675
+ output.push(`(${objectParts[0]} AS ${objectParts[1]})`);
6676
+ }
6499
6677
  else {
6500
6678
  output.push(objectParts.join('.'));
6501
6679
  }
@@ -6504,8 +6682,7 @@ export class Deparser {
6504
6682
  else if (node.objtype === 'OBJECT_OPERATOR' && node.object && node.object.ObjectWithArgs) {
6505
6683
  // Handle direct ObjectWithArgs for OPERATOR syntax: COMMENT ON OPERATOR -(NONE, integer) IS 'comment'
6506
6684
  const objWithArgs = node.object.ObjectWithArgs;
6507
- let operatorName = objWithArgs.objname && objWithArgs.objname[0] && objWithArgs.objname[0].String
6508
- ? objWithArgs.objname[0].String.sval : 'unknown';
6685
+ let operatorName = this.qualifiedOperatorName(objWithArgs.objname);
6509
6686
  // Remove quotes from operator name if present
6510
6687
  if (operatorName.startsWith('"') && operatorName.endsWith('"')) {
6511
6688
  operatorName = operatorName.slice(1, -1);
@@ -6574,7 +6751,7 @@ export class Deparser {
6574
6751
  const output = [];
6575
6752
  const initialParts = ['CREATE', 'POLICY'];
6576
6753
  if (node.policy_name) {
6577
- initialParts.push(QuoteUtils.quote(node.policy_name));
6754
+ initialParts.push(QuoteUtils.quoteIdentifier(node.policy_name));
6578
6755
  }
6579
6756
  output.push(initialParts.join(' '));
6580
6757
  // Add ON clause on new line in pretty mode
@@ -6651,7 +6828,7 @@ export class Deparser {
6651
6828
  AlterPolicyStmt(node, context) {
6652
6829
  const output = ['ALTER', 'POLICY'];
6653
6830
  if (node.policy_name) {
6654
- output.push(QuoteUtils.quote(node.policy_name));
6831
+ output.push(QuoteUtils.quoteIdentifier(node.policy_name));
6655
6832
  }
6656
6833
  if (node.table) {
6657
6834
  output.push('ON');
@@ -6687,7 +6864,7 @@ export class Deparser {
6687
6864
  }
6688
6865
  output.push('SERVER');
6689
6866
  if (node.servername) {
6690
- output.push(`"${node.servername}"`);
6867
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
6691
6868
  }
6692
6869
  if (node.options && node.options.length > 0) {
6693
6870
  output.push('OPTIONS');
@@ -6735,10 +6912,10 @@ export class Deparser {
6735
6912
  CreatePublicationStmt(node, context) {
6736
6913
  const output = ['CREATE', 'PUBLICATION'];
6737
6914
  if (node.pubname) {
6738
- output.push(`"${node.pubname}"`);
6915
+ output.push(QuoteUtils.quoteIdentifier(node.pubname));
6739
6916
  }
6740
6917
  if (node.pubobjects && node.pubobjects.length > 0) {
6741
- output.push('FOR', 'TABLE');
6918
+ output.push('FOR');
6742
6919
  const tables = ListUtils.unwrapList(node.pubobjects).map(table => this.visit(table, context));
6743
6920
  output.push(tables.join(', '));
6744
6921
  }
@@ -6755,11 +6932,11 @@ export class Deparser {
6755
6932
  CreateSubscriptionStmt(node, context) {
6756
6933
  const output = ['CREATE', 'SUBSCRIPTION'];
6757
6934
  if (node.subname) {
6758
- output.push(`"${node.subname}"`);
6935
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
6759
6936
  }
6760
6937
  output.push('CONNECTION');
6761
6938
  if (node.conninfo) {
6762
- output.push(`'${node.conninfo}'`);
6939
+ output.push(QuoteUtils.escape(node.conninfo));
6763
6940
  }
6764
6941
  output.push('PUBLICATION');
6765
6942
  if (node.publication && node.publication.length > 0) {
@@ -6776,10 +6953,11 @@ export class Deparser {
6776
6953
  AlterPublicationStmt(node, context) {
6777
6954
  const output = ['ALTER', 'PUBLICATION'];
6778
6955
  if (node.pubname) {
6779
- output.push(`"${node.pubname}"`);
6956
+ output.push(QuoteUtils.quoteIdentifier(node.pubname));
6780
6957
  }
6781
- if (node.action) {
6782
- switch (node.action) {
6958
+ const hasObjects = node.pubobjects && node.pubobjects.length > 0;
6959
+ if (hasObjects) {
6960
+ switch (node.action || 'AP_AddObjects') {
6783
6961
  case 'AP_AddObjects':
6784
6962
  output.push('ADD');
6785
6963
  break;
@@ -6792,17 +6970,11 @@ export class Deparser {
6792
6970
  default:
6793
6971
  throw new Error(`Unsupported AlterPublicationStmt action: ${node.action}`);
6794
6972
  }
6795
- }
6796
- if (node.for_all_tables) {
6797
- output.push('FOR ALL TABLES');
6798
- }
6799
- else if (node.pubobjects && node.pubobjects.length > 0) {
6800
- output.push('FOR TABLE');
6801
6973
  const objects = ListUtils.unwrapList(node.pubobjects).map(obj => this.visit(obj, context));
6802
6974
  output.push(objects.join(', '));
6803
6975
  }
6804
- if (node.options && node.options.length > 0) {
6805
- output.push('WITH');
6976
+ else if (node.options && node.options.length > 0) {
6977
+ output.push('SET');
6806
6978
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context));
6807
6979
  output.push(`(${options.join(', ')})`);
6808
6980
  }
@@ -6811,44 +6983,69 @@ export class Deparser {
6811
6983
  AlterSubscriptionStmt(node, context) {
6812
6984
  const output = ['ALTER', 'SUBSCRIPTION'];
6813
6985
  if (node.subname) {
6814
- output.push(`"${node.subname}"`);
6815
- }
6816
- if (node.kind) {
6817
- switch (node.kind) {
6986
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
6987
+ }
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') {
6818
6996
  case 'ALTER_SUBSCRIPTION_OPTIONS':
6819
6997
  output.push('SET');
6998
+ if (options)
6999
+ output.push(options);
6820
7000
  break;
6821
7001
  case 'ALTER_SUBSCRIPTION_CONNECTION':
6822
7002
  output.push('CONNECTION');
6823
7003
  if (node.conninfo) {
6824
- output.push(`'${node.conninfo}'`);
7004
+ output.push(QuoteUtils.escape(node.conninfo));
6825
7005
  }
6826
7006
  break;
6827
7007
  case 'ALTER_SUBSCRIPTION_SET_PUBLICATION':
6828
7008
  output.push('SET PUBLICATION');
6829
- if (node.publication && node.publication.length > 0) {
6830
- const publications = ListUtils.unwrapList(node.publication).map(pub => this.visit(pub, context));
6831
- output.push(publications.join(', '));
6832
- }
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);
6833
7027
  break;
6834
7028
  case 'ALTER_SUBSCRIPTION_REFRESH':
6835
7029
  output.push('REFRESH PUBLICATION');
7030
+ if (options)
7031
+ output.push('WITH', options);
6836
7032
  break;
6837
- case 'ALTER_SUBSCRIPTION_ENABLED':
6838
- 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');
6839
7038
  break;
7039
+ }
6840
7040
  case 'ALTER_SUBSCRIPTION_SKIP':
6841
7041
  output.push('SKIP');
7042
+ if (options)
7043
+ output.push(options);
6842
7044
  break;
6843
7045
  default:
6844
7046
  throw new Error(`Unsupported AlterSubscriptionStmt kind: ${node.kind}`);
6845
7047
  }
6846
7048
  }
6847
- if (node.options && node.options.length > 0) {
6848
- output.push('WITH');
6849
- const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context));
6850
- output.push(`(${options.join(', ')})`);
6851
- }
6852
7049
  return output.join(' ');
6853
7050
  }
6854
7051
  DropSubscriptionStmt(node, context) {
@@ -6857,7 +7054,7 @@ export class Deparser {
6857
7054
  output.push('IF EXISTS');
6858
7055
  }
6859
7056
  if (node.subname) {
6860
- output.push(`"${node.subname}"`);
7057
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
6861
7058
  }
6862
7059
  if (node.behavior) {
6863
7060
  switch (node.behavior) {
@@ -7102,7 +7299,7 @@ export class Deparser {
7102
7299
  ClosePortalStmt(node, context) {
7103
7300
  const output = ['CLOSE'];
7104
7301
  if (node.portalname) {
7105
- output.push(QuoteUtils.quote(node.portalname));
7302
+ output.push(QuoteUtils.quoteIdentifier(node.portalname));
7106
7303
  }
7107
7304
  else {
7108
7305
  output.push('ALL');
@@ -7160,7 +7357,7 @@ export class Deparser {
7160
7357
  output.push('ALL');
7161
7358
  }
7162
7359
  if (node.portalname) {
7163
- output.push(QuoteUtils.quote(node.portalname));
7360
+ output.push(QuoteUtils.quoteIdentifier(node.portalname));
7164
7361
  }
7165
7362
  return output.join(' ');
7166
7363
  }
@@ -7230,7 +7427,7 @@ export class Deparser {
7230
7427
  AlterFdwStmt(node, context) {
7231
7428
  const output = ['ALTER', 'FOREIGN', 'DATA', 'WRAPPER'];
7232
7429
  if (node.fdwname) {
7233
- output.push(QuoteUtils.quote(node.fdwname));
7430
+ output.push(QuoteUtils.quoteIdentifier(node.fdwname));
7234
7431
  }
7235
7432
  if (node.func_options && node.func_options.length > 0) {
7236
7433
  const fdwContext = context.spawn('AlterFdwStmt');
@@ -7251,7 +7448,7 @@ export class Deparser {
7251
7448
  output.push('IF', 'NOT', 'EXISTS');
7252
7449
  }
7253
7450
  if (node.servername) {
7254
- output.push(QuoteUtils.quote(node.servername));
7451
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
7255
7452
  }
7256
7453
  if (node.servertype) {
7257
7454
  output.push('TYPE', QuoteUtils.escape(node.servertype));
@@ -7260,7 +7457,7 @@ export class Deparser {
7260
7457
  output.push('VERSION', QuoteUtils.escape(node.version));
7261
7458
  }
7262
7459
  if (node.fdwname) {
7263
- output.push('FOREIGN', 'DATA', 'WRAPPER', QuoteUtils.quote(node.fdwname));
7460
+ output.push('FOREIGN', 'DATA', 'WRAPPER', QuoteUtils.quoteIdentifier(node.fdwname));
7264
7461
  }
7265
7462
  if (node.options && node.options.length > 0) {
7266
7463
  output.push('OPTIONS');
@@ -7275,7 +7472,7 @@ export class Deparser {
7275
7472
  AlterForeignServerStmt(node, context) {
7276
7473
  const output = ['ALTER', 'SERVER'];
7277
7474
  if (node.servername) {
7278
- output.push(QuoteUtils.quote(node.servername));
7475
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
7279
7476
  }
7280
7477
  if (node.version) {
7281
7478
  output.push('VERSION', QuoteUtils.escape(node.version));
@@ -7300,7 +7497,7 @@ export class Deparser {
7300
7497
  }
7301
7498
  output.push('SERVER');
7302
7499
  if (node.servername) {
7303
- output.push(QuoteUtils.quote(node.servername));
7500
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
7304
7501
  }
7305
7502
  if (node.options && node.options.length > 0) {
7306
7503
  output.push('OPTIONS');
@@ -7324,14 +7521,14 @@ export class Deparser {
7324
7521
  }
7325
7522
  output.push('SERVER');
7326
7523
  if (node.servername) {
7327
- output.push(QuoteUtils.quote(node.servername));
7524
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
7328
7525
  }
7329
7526
  return output.join(' ');
7330
7527
  }
7331
7528
  ImportForeignSchemaStmt(node, context) {
7332
7529
  const output = ['IMPORT', 'FOREIGN', 'SCHEMA'];
7333
7530
  if (node.remote_schema) {
7334
- output.push(QuoteUtils.quote(node.remote_schema));
7531
+ output.push(QuoteUtils.quoteIdentifier(node.remote_schema));
7335
7532
  }
7336
7533
  if (node.list_type) {
7337
7534
  switch (node.list_type) {
@@ -7357,11 +7554,11 @@ export class Deparser {
7357
7554
  }
7358
7555
  output.push('FROM', 'SERVER');
7359
7556
  if (node.server_name) {
7360
- output.push(QuoteUtils.quote(node.server_name));
7557
+ output.push(QuoteUtils.quoteIdentifier(node.server_name));
7361
7558
  }
7362
7559
  output.push('INTO');
7363
7560
  if (node.local_schema) {
7364
- output.push(QuoteUtils.quote(node.local_schema));
7561
+ output.push(QuoteUtils.quoteIdentifier(node.local_schema));
7365
7562
  }
7366
7563
  if (node.options && node.options.length > 0) {
7367
7564
  const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
@@ -7375,7 +7572,7 @@ export class Deparser {
7375
7572
  if (node.relation) {
7376
7573
  output.push(this.RangeVar(node.relation, context));
7377
7574
  if (node.indexname) {
7378
- output.push('USING', `"${node.indexname}"`);
7575
+ output.push('USING', QuoteUtils.quoteIdentifier(node.indexname));
7379
7576
  }
7380
7577
  }
7381
7578
  if (node.params && node.params.length > 0) {
@@ -7439,7 +7636,7 @@ export class Deparser {
7439
7636
  output.push(this.RangeVar(node.relation, context));
7440
7637
  }
7441
7638
  if (node.name) {
7442
- output.push(`"${node.name}"`);
7639
+ output.push(QuoteUtils.quoteIdentifier(node.name));
7443
7640
  }
7444
7641
  return output.join(' ');
7445
7642
  }
@@ -7479,7 +7676,7 @@ export class Deparser {
7479
7676
  if (!node.dbname) {
7480
7677
  throw new Error('CreatedbStmt requires dbname');
7481
7678
  }
7482
- output.push(`"${node.dbname}"`);
7679
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
7483
7680
  if (node.options && node.options.length > 0) {
7484
7681
  const options = ListUtils.unwrapList(node.options)
7485
7682
  .map(option => this.visit(option, context))
@@ -7496,7 +7693,7 @@ export class Deparser {
7496
7693
  if (!node.dbname) {
7497
7694
  throw new Error('DropdbStmt requires dbname');
7498
7695
  }
7499
- output.push(`"${node.dbname}"`);
7696
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
7500
7697
  if (node.options && node.options.length > 0) {
7501
7698
  const options = ListUtils.unwrapList(node.options)
7502
7699
  .map(option => this.visit(option, context))
@@ -7594,7 +7791,7 @@ export class Deparser {
7594
7791
  case 'OBJECT_POLICY':
7595
7792
  output.push('POLICY');
7596
7793
  if (node.subname) {
7597
- output.push(QuoteUtils.quote(node.subname));
7794
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
7598
7795
  }
7599
7796
  break;
7600
7797
  case 'OBJECT_PUBLICATION':
@@ -7652,7 +7849,7 @@ export class Deparser {
7652
7849
  }
7653
7850
  // Handle OBJECT_RULE special case: rule_name ON table_name format
7654
7851
  if (node.renameType === 'OBJECT_RULE' && node.subname && node.relation) {
7655
- output.push(QuoteUtils.quote(node.subname));
7852
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
7656
7853
  output.push('ON');
7657
7854
  output.push(this.RangeVar(node.relation, context));
7658
7855
  }
@@ -7671,12 +7868,21 @@ export class Deparser {
7671
7868
  if (items.length === 2) {
7672
7869
  const accessMethod = items[0].String?.sval || '';
7673
7870
  const objectName = items[1].String?.sval || '';
7674
- output.push(`${QuoteUtils.quote(objectName)} USING ${accessMethod}`);
7871
+ output.push(`${QuoteUtils.quoteIdentifier(objectName)} USING ${accessMethod}`);
7675
7872
  }
7676
7873
  else {
7677
7874
  output.push(this.visit(node.object, context));
7678
7875
  }
7679
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
+ }
7680
7886
  else if (node.renameType === 'OBJECT_SCHEMA' && node.object.List) {
7681
7887
  // Handle schema names - extract from List structure
7682
7888
  const items = ListUtils.unwrapList(node.object);
@@ -7692,19 +7898,19 @@ export class Deparser {
7692
7898
  }
7693
7899
  }
7694
7900
  if (node.renameType === 'OBJECT_COLUMN' && node.subname) {
7695
- output.push('RENAME COLUMN', `"${node.subname}"`, 'TO');
7901
+ output.push('RENAME COLUMN', QuoteUtils.quoteIdentifier(node.subname), 'TO');
7696
7902
  }
7697
7903
  else if (node.renameType === 'OBJECT_DOMCONSTRAINT' && node.subname) {
7698
- output.push('RENAME CONSTRAINT', `"${node.subname}"`, 'TO');
7904
+ output.push('RENAME CONSTRAINT', QuoteUtils.quoteIdentifier(node.subname), 'TO');
7699
7905
  }
7700
7906
  else if (node.renameType === 'OBJECT_TABCONSTRAINT' && node.subname) {
7701
- output.push('RENAME CONSTRAINT', `"${node.subname}"`, 'TO');
7907
+ output.push('RENAME CONSTRAINT', QuoteUtils.quoteIdentifier(node.subname), 'TO');
7702
7908
  }
7703
7909
  else if (node.renameType === 'OBJECT_ATTRIBUTE' && node.subname) {
7704
- output.push('RENAME ATTRIBUTE', `"${node.subname}"`, 'TO');
7910
+ output.push('RENAME ATTRIBUTE', QuoteUtils.quoteIdentifier(node.subname), 'TO');
7705
7911
  }
7706
7912
  else if (node.renameType === 'OBJECT_ROLE' && node.subname) {
7707
- output.push(`"${node.subname}"`, 'RENAME TO');
7913
+ output.push(QuoteUtils.quoteIdentifier(node.subname), 'RENAME TO');
7708
7914
  }
7709
7915
  else if (node.renameType === 'OBJECT_SCHEMA' && node.subname) {
7710
7916
  output.push(this.quoteIfNeeded(node.subname), 'RENAME TO');
@@ -7718,7 +7924,7 @@ export class Deparser {
7718
7924
  if (!node.newname) {
7719
7925
  throw new Error('RenameStmt requires newname');
7720
7926
  }
7721
- output.push(QuoteUtils.quote(node.newname));
7927
+ output.push(QuoteUtils.quoteIdentifier(node.newname));
7722
7928
  // Handle CASCADE/RESTRICT behavior for RENAME operations
7723
7929
  if (node.behavior === 'DROP_CASCADE') {
7724
7930
  output.push('CASCADE');
@@ -7741,7 +7947,7 @@ export class Deparser {
7741
7947
  if (items.length === 2) {
7742
7948
  const accessMethod = items[0].String?.sval || '';
7743
7949
  const objectName = items[1].String?.sval || '';
7744
- output.push(`${QuoteUtils.quote(objectName)} USING ${accessMethod}`);
7950
+ output.push(`${QuoteUtils.quoteIdentifier(objectName)} USING ${accessMethod}`);
7745
7951
  }
7746
7952
  else {
7747
7953
  output.push(this.visit(node.object, context));
@@ -7816,6 +8022,9 @@ export class Deparser {
7816
8022
  else if (node.objtype === 'OBJECT_LANGUAGE') {
7817
8023
  output.push('LANGUAGE');
7818
8024
  }
8025
+ else if (node.objtype === 'OBJECT_SEQUENCE') {
8026
+ output.push('SEQUENCE');
8027
+ }
7819
8028
  else if (node.objtype === 'OBJECT_FUNCTION') {
7820
8029
  output.push('FUNCTION');
7821
8030
  }
@@ -7841,8 +8050,11 @@ export class Deparser {
7841
8050
  output.push('DATABASE');
7842
8051
  }
7843
8052
  if (node.objects && node.objects.length > 0) {
8053
+ const qualifiedKinds = ['OBJECT_TYPE', 'OBJECT_DOMAIN'];
7844
8054
  const objects = ListUtils.unwrapList(node.objects)
7845
- .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))
7846
8058
  .join(', ');
7847
8059
  output.push(objects);
7848
8060
  }
@@ -7881,8 +8093,11 @@ export class Deparser {
7881
8093
  break;
7882
8094
  default:
7883
8095
  if (node.objects && node.objects.length > 0) {
8096
+ const qualifiedKinds = ['OBJECT_TYPE', 'OBJECT_DOMAIN'];
7884
8097
  const objects = ListUtils.unwrapList(node.objects)
7885
- .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))
7886
8101
  .join(', ');
7887
8102
  output.push(objects);
7888
8103
  }
@@ -7902,6 +8117,9 @@ export class Deparser {
7902
8117
  if (node.grant_option && node.is_grant) {
7903
8118
  output.push('WITH GRANT OPTION');
7904
8119
  }
8120
+ if (node.grantor) {
8121
+ output.push('GRANTED BY', this.RoleSpec(node.grantor, context));
8122
+ }
7905
8123
  // Only add behavior clauses for REVOKE statements, not for GRANT statements in ALTER DEFAULT PRIVILEGES
7906
8124
  if (!node.is_grant) {
7907
8125
  if (node.behavior === 'DROP_CASCADE') {
@@ -7915,57 +8133,34 @@ export class Deparser {
7915
8133
  }
7916
8134
  GrantRoleStmt(node, context) {
7917
8135
  const output = [];
7918
- // Check for inherit, admin, and set options first to place them correctly
7919
- let hasInheritOption = false;
7920
- let hasAdminOption = false;
7921
- let hasSetOption = false;
7922
- let inheritValue;
7923
- let adminValue;
7924
- let setValue;
7925
- if (node.opt && node.opt.length > 0) {
7926
- const options = ListUtils.unwrapList(node.opt);
7927
- const inheritOption = options.find(opt => opt.DefElem && opt.DefElem.defname === 'inherit');
7928
- const adminOption = options.find(opt => (opt.String && opt.String.sval === 'admin') ||
7929
- (opt.DefElem && opt.DefElem.defname === 'admin'));
7930
- const setOption = options.find(opt => opt.DefElem && opt.DefElem.defname === 'set');
7931
- if (inheritOption && inheritOption.DefElem) {
7932
- hasInheritOption = true;
7933
- inheritValue = inheritOption.DefElem.arg?.Boolean?.boolval;
7934
- }
7935
- if (adminOption) {
7936
- hasAdminOption = true;
7937
- if (adminOption.DefElem && adminOption.DefElem.arg) {
7938
- adminValue = adminOption.DefElem.arg.Boolean?.boolval;
7939
- }
7940
- }
7941
- if (setOption && setOption.DefElem) {
7942
- hasSetOption = true;
7943
- setValue = setOption.DefElem.arg?.Boolean?.boolval;
7944
- }
7945
- }
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(', ');
7946
8155
  if (node.is_grant) {
7947
- output.push('GRANT');
8156
+ output.push('GRANT', roles, 'TO');
7948
8157
  }
7949
8158
  else {
7950
8159
  output.push('REVOKE');
7951
- if (hasInheritOption) {
7952
- output.push('INHERIT OPTION FOR');
7953
- }
7954
- else if (hasAdminOption) {
7955
- output.push('ADMIN OPTION FOR');
8160
+ if (options.length > 0) {
8161
+ output.push(`${options[0].defname.toUpperCase()} OPTION FOR`);
7956
8162
  }
7957
- }
7958
- if (node.granted_roles && node.granted_roles.length > 0) {
7959
- const roles = ListUtils.unwrapList(node.granted_roles)
7960
- .map(role => this.visit(role, context))
7961
- .join(', ');
7962
- output.push(roles);
7963
- }
7964
- if (node.is_grant) {
7965
- output.push('TO');
7966
- }
7967
- else {
7968
- output.push('FROM');
8163
+ output.push(roles, 'FROM');
7969
8164
  }
7970
8165
  if (node.grantee_roles && node.grantee_roles.length > 0) {
7971
8166
  const grantees = ListUtils.unwrapList(node.grantee_roles)
@@ -7973,45 +8168,22 @@ export class Deparser {
7973
8168
  .join(', ');
7974
8169
  output.push(grantees);
7975
8170
  }
7976
- if (node.is_grant) {
7977
- const withOptions = [];
7978
- if (hasAdminOption) {
7979
- if (adminValue === true) {
7980
- withOptions.push('ADMIN OPTION');
7981
- }
7982
- else if (adminValue === false) {
7983
- withOptions.push('ADMIN FALSE');
7984
- }
7985
- else {
7986
- withOptions.push('ADMIN OPTION');
7987
- }
7988
- }
7989
- if (hasInheritOption) {
7990
- if (inheritValue === true) {
7991
- withOptions.push('INHERIT OPTION');
7992
- }
7993
- else if (inheritValue === false) {
7994
- withOptions.push('INHERIT FALSE');
7995
- }
7996
- }
7997
- if (hasSetOption) {
7998
- if (setValue === true) {
7999
- withOptions.push('SET TRUE');
8000
- }
8001
- else if (setValue === false) {
8002
- withOptions.push('SET FALSE');
8003
- }
8004
- }
8005
- if (withOptions.length > 0) {
8006
- output.push('WITH', withOptions.join(', '));
8007
- }
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');
8008
8180
  }
8009
8181
  return output.join(' ');
8010
8182
  }
8011
8183
  SecLabelStmt(node, context) {
8012
8184
  const output = ['SECURITY LABEL'];
8013
8185
  if (node.provider) {
8014
- output.push('FOR', `"${node.provider}"`);
8186
+ output.push('FOR', QuoteUtils.quoteIdentifier(node.provider));
8015
8187
  }
8016
8188
  output.push('ON');
8017
8189
  if (node.objtype) {
@@ -8039,11 +8211,21 @@ export class Deparser {
8039
8211
  }
8040
8212
  }
8041
8213
  if (node.object) {
8042
- 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
+ }
8043
8225
  }
8044
8226
  output.push('IS');
8045
8227
  if (node.label) {
8046
- output.push(`'${node.label}'`);
8228
+ output.push(QuoteUtils.escape(node.label));
8047
8229
  }
8048
8230
  else {
8049
8231
  output.push('NULL');
@@ -8100,10 +8282,10 @@ export class Deparser {
8100
8282
  output.push(conversionName);
8101
8283
  }
8102
8284
  if (node.for_encoding_name) {
8103
- output.push('FOR', `'${node.for_encoding_name}'`);
8285
+ output.push('FOR', QuoteUtils.escape(node.for_encoding_name));
8104
8286
  }
8105
8287
  if (node.to_encoding_name) {
8106
- output.push('TO', `'${node.to_encoding_name}'`);
8288
+ output.push('TO', QuoteUtils.escape(node.to_encoding_name));
8107
8289
  }
8108
8290
  if (node.func_name && node.func_name.length > 0) {
8109
8291
  output.push('FROM');
@@ -8164,7 +8346,7 @@ export class Deparser {
8164
8346
  }
8165
8347
  output.push('LANGUAGE');
8166
8348
  if (node.plname) {
8167
- output.push(QuoteUtils.quote(node.plname));
8349
+ output.push(QuoteUtils.quoteIdentifier(node.plname));
8168
8350
  }
8169
8351
  if (node.plhandler && node.plhandler.length > 0) {
8170
8352
  output.push('HANDLER');
@@ -8200,7 +8382,7 @@ export class Deparser {
8200
8382
  }
8201
8383
  output.push('LANGUAGE');
8202
8384
  if (node.lang) {
8203
- output.push(QuoteUtils.quote(node.lang));
8385
+ output.push(QuoteUtils.quoteIdentifier(node.lang));
8204
8386
  }
8205
8387
  output.push('(');
8206
8388
  const transforms = [];
@@ -8228,7 +8410,7 @@ export class Deparser {
8228
8410
  }
8229
8411
  output.push('TRIGGER');
8230
8412
  if (node.trigname) {
8231
- output.push(QuoteUtils.quote(node.trigname));
8413
+ output.push(QuoteUtils.quoteIdentifier(node.trigname));
8232
8414
  }
8233
8415
  if (context.isPretty()) {
8234
8416
  const components = [];
@@ -8266,6 +8448,9 @@ export class Deparser {
8266
8448
  .join(' ');
8267
8449
  components.push(context.indent('REFERENCING ' + transitionClauses));
8268
8450
  }
8451
+ if (node.constrrel) {
8452
+ components.push(context.indent('FROM ' + this.RangeVar(node.constrrel, context)));
8453
+ }
8269
8454
  if (node.deferrable) {
8270
8455
  components.push(context.indent('DEFERRABLE'));
8271
8456
  }
@@ -8287,7 +8472,7 @@ export class Deparser {
8287
8472
  const funcName = ListUtils.unwrapList(node.funcname)
8288
8473
  .map(name => this.visit(name, context))
8289
8474
  .join('.');
8290
- executeStr += ' PROCEDURE ' + funcName;
8475
+ executeStr += ' FUNCTION ' + funcName;
8291
8476
  }
8292
8477
  if (node.args && node.args.length > 0) {
8293
8478
  const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
@@ -8392,14 +8577,14 @@ export class Deparser {
8392
8577
  output.push('OLD TABLE AS');
8393
8578
  }
8394
8579
  if (node.name) {
8395
- output.push(QuoteUtils.quote(node.name));
8580
+ output.push(QuoteUtils.quoteIdentifier(node.name));
8396
8581
  }
8397
8582
  return output.join(' ');
8398
8583
  }
8399
8584
  CreateEventTrigStmt(node, context) {
8400
8585
  const output = ['CREATE EVENT TRIGGER'];
8401
8586
  if (node.trigname) {
8402
- output.push(QuoteUtils.quote(node.trigname));
8587
+ output.push(QuoteUtils.quoteIdentifier(node.trigname));
8403
8588
  }
8404
8589
  output.push('ON');
8405
8590
  if (node.eventname) {
@@ -8425,7 +8610,7 @@ export class Deparser {
8425
8610
  AlterEventTrigStmt(node, context) {
8426
8611
  const output = ['ALTER EVENT TRIGGER'];
8427
8612
  if (node.trigname) {
8428
- output.push(QuoteUtils.quote(node.trigname));
8613
+ output.push(QuoteUtils.quoteIdentifier(node.trigname));
8429
8614
  }
8430
8615
  if (node.tgenabled) {
8431
8616
  switch (node.tgenabled) {
@@ -8545,6 +8730,74 @@ export class Deparser {
8545
8730
  .join(' ');
8546
8731
  output.push(whenClauses);
8547
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
+ }
8548
8801
  return output.join(' ');
8549
8802
  }
8550
8803
  AlterTableMoveAllStmt(node, context) {
@@ -8560,11 +8813,11 @@ export class Deparser {
8560
8813
  }
8561
8814
  output.push('ALL', 'IN', 'TABLESPACE');
8562
8815
  if (node.orig_tablespacename) {
8563
- output.push(QuoteUtils.quote(node.orig_tablespacename));
8816
+ output.push(QuoteUtils.quoteIdentifier(node.orig_tablespacename));
8564
8817
  }
8565
8818
  output.push('SET', 'TABLESPACE');
8566
8819
  if (node.new_tablespacename) {
8567
- output.push(QuoteUtils.quote(node.new_tablespacename));
8820
+ output.push(QuoteUtils.quoteIdentifier(node.new_tablespacename));
8568
8821
  }
8569
8822
  if (node.nowait) {
8570
8823
  output.push('NOWAIT');
@@ -8588,10 +8841,10 @@ export class Deparser {
8588
8841
  const sequenceName = [];
8589
8842
  const seq = node.sequence;
8590
8843
  if (seq.schemaname) {
8591
- sequenceName.push(QuoteUtils.quote(seq.schemaname));
8844
+ sequenceName.push(QuoteUtils.quoteIdentifier(seq.schemaname));
8592
8845
  }
8593
8846
  if (seq.relname) {
8594
- sequenceName.push(QuoteUtils.quote(seq.relname));
8847
+ sequenceName.push(QuoteUtils.quoteIdentifier(seq.relname));
8595
8848
  }
8596
8849
  output.push(sequenceName.join('.'));
8597
8850
  }
@@ -8625,10 +8878,10 @@ export class Deparser {
8625
8878
  const sequenceName = [];
8626
8879
  const seq = node.sequence;
8627
8880
  if (seq.schemaname) {
8628
- sequenceName.push(QuoteUtils.quote(seq.schemaname));
8881
+ sequenceName.push(QuoteUtils.quoteIdentifier(seq.schemaname));
8629
8882
  }
8630
8883
  if (seq.relname) {
8631
- sequenceName.push(QuoteUtils.quote(seq.relname));
8884
+ sequenceName.push(QuoteUtils.quoteIdentifier(seq.relname));
8632
8885
  }
8633
8886
  output.push(sequenceName.join('.'));
8634
8887
  }
@@ -8913,6 +9166,13 @@ export class Deparser {
8913
9166
  output.push('USING');
8914
9167
  output.push(node.into.accessMethod);
8915
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
+ }
8916
9176
  if (node.into && node.into.onCommit && node.into.onCommit !== 'ONCOMMIT_NOOP') {
8917
9177
  output.push('ON COMMIT');
8918
9178
  switch (node.into.onCommit) {
@@ -8927,17 +9187,13 @@ export class Deparser {
8927
9187
  break;
8928
9188
  }
8929
9189
  }
9190
+ if (node.into && node.into.tableSpaceName) {
9191
+ output.push('TABLESPACE', QuoteUtils.quoteIdentifier(node.into.tableSpaceName));
9192
+ }
8930
9193
  output.push('AS');
8931
9194
  if (node.query) {
8932
9195
  output.push(this.visit(node.query, context));
8933
9196
  }
8934
- if (node.into && node.into.options && node.into.options.length > 0) {
8935
- output.push('WITH');
8936
- const options = ListUtils.unwrapList(node.into.options)
8937
- .map(option => this.visit(option, context))
8938
- .join(', ');
8939
- output.push(`(${options})`);
8940
- }
8941
9197
  if (node.into && node.into.skipData) {
8942
9198
  output.push('WITH NO DATA');
8943
9199
  }
@@ -8975,7 +9231,7 @@ export class Deparser {
8975
9231
  }
8976
9232
  aliasname(node, context) {
8977
9233
  if (typeof node === 'string') {
8978
- return QuoteUtils.quote(node);
9234
+ return QuoteUtils.quoteIdentifier(node);
8979
9235
  }
8980
9236
  return this.visit(node, context);
8981
9237
  }
@@ -9007,18 +9263,16 @@ export class Deparser {
9007
9263
  const defName = defElem.defname;
9008
9264
  const defValue = defElem.arg;
9009
9265
  if (defName && defValue) {
9010
- let preservedDefName;
9011
- if (Deparser.needsQuotes(defName)) {
9012
- preservedDefName = `"${defName}"`;
9013
- }
9014
- else {
9015
- preservedDefName = this.preserveOperatorDefElemCase(defName);
9016
- }
9266
+ const quotedDefName = QuoteUtils.quoteIdentifier(defName);
9267
+ const preservedDefName = quotedDefName !== defName ? quotedDefName : this.preserveOperatorDefElemCase(defName);
9017
9268
  if ((defName.toLowerCase() === 'commutator' || defName.toLowerCase() === 'negator') && defValue.List) {
9018
9269
  const listItems = ListUtils.unwrapList(defValue.List.items);
9019
9270
  if (listItems.length === 1 && listItems[0].String) {
9020
9271
  return `${preservedDefName} = ${listItems[0].String.sval}`;
9021
9272
  }
9273
+ if (listItems.length > 1 && listItems.every(item => item.String)) {
9274
+ return `${preservedDefName} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
9275
+ }
9022
9276
  }
9023
9277
  // For commutator/negator, we already handled them above
9024
9278
  if ((defName.toLowerCase() === 'commutator' || defName.toLowerCase() === 'negator')) {
@@ -9029,7 +9283,7 @@ export class Deparser {
9029
9283
  else if (defName && !defValue) {
9030
9284
  // Handle boolean flags like HASHES, MERGES - preserve original case
9031
9285
  if (defName === 'Hashes' || defName === 'Merges') {
9032
- return `"${defName}"`;
9286
+ return QuoteUtils.quoteIdentifier(defName);
9033
9287
  }
9034
9288
  return this.preserveOperatorDefElemCase(defName).toUpperCase();
9035
9289
  }
@@ -9168,16 +9422,11 @@ export class Deparser {
9168
9422
  const defName = defElem.defname;
9169
9423
  const defValue = defElem.arg;
9170
9424
  if (defName && defValue) {
9171
- let preservedDefName;
9172
- if (Deparser.needsQuotes(defName)) {
9173
- preservedDefName = `"${defName}"`;
9174
- }
9175
- else {
9176
- preservedDefName = defName;
9177
- }
9425
+ const quotedDefName = QuoteUtils.quoteIdentifier(defName);
9426
+ const preservedDefName = quotedDefName !== defName ? quotedDefName : defName;
9178
9427
  // Handle String arguments with single quotes for string literals
9179
9428
  if (defValue.String) {
9180
- return `${preservedDefName} = '${defValue.String.sval}'`;
9429
+ return `${preservedDefName} = ${QuoteUtils.escape(defValue.String.sval)}`;
9181
9430
  }
9182
9431
  return `${preservedDefName} = ${this.visit(defValue, context)}`;
9183
9432
  }
@@ -9300,7 +9549,7 @@ export class Deparser {
9300
9549
  // For CREATE COLLATION, ensure String nodes are quoted as string literals
9301
9550
  let valueStr;
9302
9551
  if (defValue.String) {
9303
- valueStr = `'${defValue.String.sval}'`;
9552
+ valueStr = QuoteUtils.escape(defValue.String.sval);
9304
9553
  }
9305
9554
  else {
9306
9555
  valueStr = this.visit(defValue, context);
@@ -9329,7 +9578,7 @@ export class Deparser {
9329
9578
  AlterDatabaseStmt(node, context) {
9330
9579
  const output = ['ALTER', 'DATABASE'];
9331
9580
  if (node.dbname) {
9332
- output.push(QuoteUtils.quote(node.dbname));
9581
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
9333
9582
  }
9334
9583
  if (node.options && node.options.length > 0) {
9335
9584
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context));
@@ -9340,7 +9589,7 @@ export class Deparser {
9340
9589
  AlterDatabaseRefreshCollStmt(node, context) {
9341
9590
  const output = ['ALTER', 'DATABASE'];
9342
9591
  if (node.dbname) {
9343
- output.push(QuoteUtils.quote(node.dbname));
9592
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
9344
9593
  }
9345
9594
  output.push('REFRESH', 'COLLATION', 'VERSION');
9346
9595
  return output.join(' ');
@@ -9348,7 +9597,7 @@ export class Deparser {
9348
9597
  AlterDatabaseSetStmt(node, context) {
9349
9598
  const output = ['ALTER', 'DATABASE'];
9350
9599
  if (node.dbname) {
9351
- output.push(QuoteUtils.quote(node.dbname));
9600
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
9352
9601
  }
9353
9602
  if (node.setstmt) {
9354
9603
  const setClause = this.VariableSetStmt(node.setstmt, context);
@@ -9359,7 +9608,7 @@ export class Deparser {
9359
9608
  DeclareCursorStmt(node, context) {
9360
9609
  const output = ['DECLARE'];
9361
9610
  if (node.portalname) {
9362
- output.push(QuoteUtils.quote(node.portalname));
9611
+ output.push(QuoteUtils.quoteIdentifier(node.portalname));
9363
9612
  }
9364
9613
  // Handle cursor options before CURSOR keyword
9365
9614
  const cursorOptions = [];
@@ -9401,7 +9650,7 @@ export class Deparser {
9401
9650
  else if (node.pubobjtype === 'PUBLICATIONOBJ_TABLES_IN_SCHEMA') {
9402
9651
  output.push('TABLES IN SCHEMA');
9403
9652
  if (node.name) {
9404
- output.push(QuoteUtils.quote(node.name));
9653
+ output.push(QuoteUtils.quoteIdentifier(node.name));
9405
9654
  }
9406
9655
  }
9407
9656
  else if (node.pubobjtype === 'PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA') {
@@ -9420,14 +9669,14 @@ export class Deparser {
9420
9669
  }
9421
9670
  if (node.whereClause) {
9422
9671
  output.push('WHERE');
9423
- output.push(this.visit(node.whereClause, context));
9672
+ output.push(`(${this.visit(node.whereClause, context)})`);
9424
9673
  }
9425
9674
  return output.join(' ');
9426
9675
  }
9427
9676
  CreateAmStmt(node, context) {
9428
9677
  const output = ['CREATE', 'ACCESS', 'METHOD'];
9429
9678
  if (node.amname) {
9430
- output.push(QuoteUtils.quote(node.amname));
9679
+ output.push(QuoteUtils.quoteIdentifier(node.amname));
9431
9680
  }
9432
9681
  output.push('TYPE');
9433
9682
  switch (node.amtype) {
@@ -9487,7 +9736,7 @@ export class Deparser {
9487
9736
  }
9488
9737
  }
9489
9738
  if (node.tableSpaceName) {
9490
- output.push('TABLESPACE', QuoteUtils.quote(node.tableSpaceName));
9739
+ output.push('TABLESPACE', QuoteUtils.quoteIdentifier(node.tableSpaceName));
9491
9740
  }
9492
9741
  return output.join(' ');
9493
9742
  }
@@ -9567,6 +9816,23 @@ export class Deparser {
9567
9816
  }
9568
9817
  return output.join(' ');
9569
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
+ }
9570
9836
  Var(node, context) {
9571
9837
  if (node.varno && node.varattno) {
9572
9838
  return `$${node.varno}.${node.varattno}`;
@@ -9606,7 +9872,7 @@ export class Deparser {
9606
9872
  }
9607
9873
  if (node.rowexpr) {
9608
9874
  output.push(',');
9609
- output.push(`'${this.visit(node.rowexpr, context)}'`);
9875
+ output.push(this.visit(node.rowexpr, context));
9610
9876
  }
9611
9877
  if (node.colexprs && node.colexprs.length > 0) {
9612
9878
  output.push('COLUMNS');
@@ -9623,29 +9889,44 @@ export class Deparser {
9623
9889
  if (node.lateral) {
9624
9890
  output.push('LATERAL');
9625
9891
  }
9626
- if (node.docexpr) {
9627
- 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(', ')}),`);
9628
9900
  }
9629
9901
  if (node.rowexpr) {
9630
- output.push('PASSING');
9631
- 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));
9632
9907
  }
9633
9908
  if (node.columns && node.columns.length > 0) {
9634
- output.push('COLUMNS');
9635
- const columns = ListUtils.unwrapList(node.columns)
9909
+ args.push('COLUMNS');
9910
+ args.push(ListUtils.unwrapList(node.columns)
9636
9911
  .map(col => this.visit(col, context))
9637
- .join(', ');
9638
- output.push(`(${columns})`);
9912
+ .join(', '));
9639
9913
  }
9914
+ output.push(`XMLTABLE(${args.join(' ')})`);
9640
9915
  if (node.alias) {
9641
9916
  output.push(this.Alias(node.alias, context));
9642
9917
  }
9643
9918
  return output.join(' ');
9644
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
+ }
9645
9926
  RangeTableFuncCol(node, context) {
9646
9927
  const output = [];
9647
9928
  if (node.colname) {
9648
- output.push(QuoteUtils.quote(node.colname));
9929
+ output.push(QuoteUtils.quoteIdentifier(node.colname));
9649
9930
  }
9650
9931
  if (node.for_ordinality) {
9651
9932
  output.push('FOR ORDINALITY');
@@ -9655,32 +9936,193 @@ export class Deparser {
9655
9936
  }
9656
9937
  if (node.colexpr) {
9657
9938
  output.push('PATH');
9658
- output.push(`'${this.visit(node.colexpr, context)}'`);
9939
+ output.push(this.visit(node.colexpr, context));
9659
9940
  }
9660
9941
  if (node.coldefexpr) {
9661
9942
  output.push('DEFAULT');
9662
9943
  output.push(this.visit(node.coldefexpr, context));
9663
9944
  }
9945
+ if (node.is_not_null) {
9946
+ output.push('NOT NULL');
9947
+ }
9664
9948
  return output.join(' ');
9665
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
+ }
9666
10023
  JsonArrayQueryConstructor(node, context) {
9667
- const output = ['JSON_ARRAYAGG'];
10024
+ const args = [];
9668
10025
  if (node.query) {
9669
- 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)})`;
9670
10042
  }
9671
- if (node.format) {
9672
- output.push('FORMAT JSON');
10043
+ if (ctor.over) {
10044
+ result += this.formatOverClause(ctor.over, context);
9673
10045
  }
9674
- if (node.output) {
9675
- 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));
9676
10066
  }
9677
- if (node.absent_on_null) {
9678
- 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(', ')}`);
9679
10071
  }
9680
- else {
9681
- 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));
9682
10082
  }
9683
- 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(' ');
9684
10126
  }
9685
10127
  RangeFunction(node, context) {
9686
10128
  const output = [];
@@ -9791,10 +10233,10 @@ export class Deparser {
9791
10233
  if (node.op === 'IS_XMLPI') {
9792
10234
  if (node.name && node.args && node.args.length > 0) {
9793
10235
  const argStrs = ListUtils.unwrapList(node.args).map(arg => this.visit(arg, context));
9794
- return `xmlpi(name ${QuoteUtils.quote(node.name)}, ${argStrs.join(', ')})`;
10236
+ return `xmlpi(name ${QuoteUtils.quoteIdentifier(node.name)}, ${argStrs.join(', ')})`;
9795
10237
  }
9796
10238
  else if (node.name) {
9797
- return `xmlpi(name ${QuoteUtils.quote(node.name)})`;
10239
+ return `xmlpi(name ${QuoteUtils.quoteIdentifier(node.name)})`;
9798
10240
  }
9799
10241
  else {
9800
10242
  return 'XMLPI()';
@@ -9809,7 +10251,7 @@ export class Deparser {
9809
10251
  output.push('XMLELEMENT');
9810
10252
  const elementParts = [];
9811
10253
  if (node.name) {
9812
- elementParts.push(`NAME ${QuoteUtils.quote(node.name)}`);
10254
+ elementParts.push(`NAME ${QuoteUtils.quoteIdentifier(node.name)}`);
9813
10255
  }
9814
10256
  if (node.named_args && node.named_args.length > 0) {
9815
10257
  const namedArgStrs = ListUtils.unwrapList(node.named_args).map(arg => this.visit(arg, context));
@@ -9874,6 +10316,7 @@ export class Deparser {
9874
10316
  rootParts.push('STANDALONE NO VALUE');
9875
10317
  }
9876
10318
  else if (standaloneArg.A_Const.ival.ival === 3) {
10319
+ // STANDALONE omitted
9877
10320
  }
9878
10321
  else if (Object.keys(standaloneArg.A_Const.ival).length === 0) {
9879
10322
  rootParts.push('STANDALONE YES');
@@ -9909,7 +10352,7 @@ export class Deparser {
9909
10352
  // Handle name and args for operations that don't have special handling
9910
10353
  if (node.op !== 'IS_XMLELEMENT' && node.op !== 'IS_XMLPARSE' && node.op !== 'IS_XMLROOT' && node.op !== 'IS_DOCUMENT') {
9911
10354
  if (node.name) {
9912
- const quotedName = QuoteUtils.quote(node.name);
10355
+ const quotedName = QuoteUtils.quoteIdentifier(node.name);
9913
10356
  output.push(`NAME ${quotedName}`);
9914
10357
  }
9915
10358
  if (node.args && node.args.length > 0) {
@@ -9930,26 +10373,26 @@ export class Deparser {
9930
10373
  }
9931
10374
  schemaname(node, context) {
9932
10375
  if (typeof node === 'string') {
9933
- return QuoteUtils.quote(node);
10376
+ return QuoteUtils.quoteIdentifier(node);
9934
10377
  }
9935
10378
  if (node && node.String && node.String.sval) {
9936
- return QuoteUtils.quote(node.String.sval);
10379
+ return QuoteUtils.quoteIdentifier(node.String.sval);
9937
10380
  }
9938
10381
  // Handle other node types without recursion
9939
10382
  if (node && typeof node === 'object') {
9940
10383
  if (node.sval !== undefined) {
9941
- return QuoteUtils.quote(node.sval);
10384
+ return QuoteUtils.quoteIdentifier(node.sval);
9942
10385
  }
9943
10386
  // Handle List nodes that might contain schema names
9944
10387
  if (node.List && Array.isArray(node.List.items)) {
9945
10388
  const items = node.List.items;
9946
10389
  if (items.length > 0 && items[0].String && items[0].String.sval) {
9947
- return QuoteUtils.quote(items[0].String.sval);
10390
+ return QuoteUtils.quoteIdentifier(items[0].String.sval);
9948
10391
  }
9949
10392
  }
9950
10393
  // For other complex nodes, try to extract string value without recursion
9951
10394
  if (node.val !== undefined) {
9952
- return QuoteUtils.quote(node.val);
10395
+ return QuoteUtils.quoteIdentifier(node.val);
9953
10396
  }
9954
10397
  return '';
9955
10398
  }
@@ -10012,7 +10455,7 @@ export class Deparser {
10012
10455
  }
10013
10456
  output.push('RULE');
10014
10457
  if (node.rulename) {
10015
- output.push(QuoteUtils.quote(node.rulename));
10458
+ output.push(QuoteUtils.quoteIdentifier(node.rulename));
10016
10459
  }
10017
10460
  output.push('AS ON');
10018
10461
  if (node.event) {
@@ -10079,42 +10522,42 @@ export class Deparser {
10079
10522
  }
10080
10523
  relname(node, context) {
10081
10524
  if (typeof node === 'string') {
10082
- return QuoteUtils.quote(node);
10525
+ return QuoteUtils.quoteIdentifier(node);
10083
10526
  }
10084
10527
  if (node && node.String && node.String.sval) {
10085
- return QuoteUtils.quote(node.String.sval);
10528
+ return QuoteUtils.quoteIdentifier(node.String.sval);
10086
10529
  }
10087
10530
  if (node && typeof node === 'object' && node.relname) {
10088
- return QuoteUtils.quote(node.relname);
10531
+ return QuoteUtils.quoteIdentifier(node.relname);
10089
10532
  }
10090
10533
  return this.visit(node, context);
10091
10534
  }
10092
10535
  rel(node, context) {
10093
10536
  if (typeof node === 'string') {
10094
- return QuoteUtils.quote(node);
10537
+ return QuoteUtils.quoteIdentifier(node);
10095
10538
  }
10096
10539
  if (node && node.String && node.String.sval) {
10097
- return QuoteUtils.quote(node.String.sval);
10540
+ return QuoteUtils.quoteIdentifier(node.String.sval);
10098
10541
  }
10099
10542
  if (node && node.RangeVar) {
10100
10543
  return this.RangeVar(node.RangeVar, context);
10101
10544
  }
10102
10545
  if (node && typeof node === 'object' && node.relname) {
10103
- return QuoteUtils.quote(node.relname);
10546
+ return QuoteUtils.quoteIdentifier(node.relname);
10104
10547
  }
10105
10548
  return this.visit(node, context);
10106
10549
  }
10107
10550
  objname(node, context) {
10108
10551
  if (typeof node === 'string') {
10109
- return QuoteUtils.quote(node);
10552
+ return QuoteUtils.quoteIdentifier(node);
10110
10553
  }
10111
10554
  if (node && node.String && node.String.sval) {
10112
- return QuoteUtils.quote(node.String.sval);
10555
+ return QuoteUtils.quoteIdentifier(node.String.sval);
10113
10556
  }
10114
10557
  if (Array.isArray(node)) {
10115
10558
  const parts = node.map(part => {
10116
10559
  if (part && part.String && part.String.sval) {
10117
- return QuoteUtils.quote(part.String.sval);
10560
+ return QuoteUtils.quoteIdentifier(part.String.sval);
10118
10561
  }
10119
10562
  return this.visit(part, context);
10120
10563
  });
@@ -10185,7 +10628,7 @@ export class Deparser {
10185
10628
  CurrentOfExpr(node, context) {
10186
10629
  const output = ['CURRENT OF'];
10187
10630
  if (node.cursor_name) {
10188
- output.push(QuoteUtils.quote(node.cursor_name));
10631
+ output.push(QuoteUtils.quoteIdentifier(node.cursor_name));
10189
10632
  }
10190
10633
  if (node.cursor_param > 0) {
10191
10634
  output.push(`$${node.cursor_param}`);
@@ -10268,7 +10711,7 @@ export class Deparser {
10268
10711
  if (items.length === 2) {
10269
10712
  const schemaName = items[0].String?.sval || '';
10270
10713
  const domainName = items[1].String?.sval || '';
10271
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(domainName)}`);
10714
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, domainName));
10272
10715
  }
10273
10716
  else {
10274
10717
  output.push(this.visit(node.object, context));
@@ -10280,7 +10723,7 @@ export class Deparser {
10280
10723
  if (items.length === 2) {
10281
10724
  const schemaName = items[0].String?.sval || '';
10282
10725
  const typeName = items[1].String?.sval || '';
10283
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(typeName)}`);
10726
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, typeName));
10284
10727
  }
10285
10728
  else {
10286
10729
  output.push(this.visit(node.object, context));
@@ -10292,7 +10735,7 @@ export class Deparser {
10292
10735
  if (items.length === 2) {
10293
10736
  const schemaName = items[0].String?.sval || '';
10294
10737
  const conversionName = items[1].String?.sval || '';
10295
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(conversionName)}`);
10738
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, conversionName));
10296
10739
  }
10297
10740
  else {
10298
10741
  output.push(this.visit(node.object, context));
@@ -10304,7 +10747,7 @@ export class Deparser {
10304
10747
  if (items.length === 2) {
10305
10748
  const schemaName = items[0].String?.sval || '';
10306
10749
  const parserName = items[1].String?.sval || '';
10307
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(parserName)}`);
10750
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, parserName));
10308
10751
  }
10309
10752
  else {
10310
10753
  output.push(this.visit(node.object, context));
@@ -10316,7 +10759,7 @@ export class Deparser {
10316
10759
  if (items.length === 2) {
10317
10760
  const schemaName = items[0].String?.sval || '';
10318
10761
  const configName = items[1].String?.sval || '';
10319
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(configName)}`);
10762
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, configName));
10320
10763
  }
10321
10764
  else {
10322
10765
  output.push(this.visit(node.object, context));
@@ -10328,7 +10771,7 @@ export class Deparser {
10328
10771
  if (items.length === 2) {
10329
10772
  const schemaName = items[0].String?.sval || '';
10330
10773
  const templateName = items[1].String?.sval || '';
10331
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(templateName)}`);
10774
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, templateName));
10332
10775
  }
10333
10776
  else {
10334
10777
  output.push(this.visit(node.object, context));
@@ -10340,7 +10783,7 @@ export class Deparser {
10340
10783
  if (items.length === 2) {
10341
10784
  const schemaName = items[0].String?.sval || '';
10342
10785
  const dictionaryName = items[1].String?.sval || '';
10343
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(dictionaryName)}`);
10786
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, dictionaryName));
10344
10787
  }
10345
10788
  else {
10346
10789
  output.push(this.visit(node.object, context));
@@ -10352,13 +10795,13 @@ export class Deparser {
10352
10795
  if (items.length === 2) {
10353
10796
  const accessMethod = items[0].String?.sval || '';
10354
10797
  const opClassName = items[1].String?.sval || '';
10355
- output.push(`${QuoteUtils.quote(opClassName)} USING ${accessMethod}`);
10798
+ output.push(`${QuoteUtils.quoteIdentifier(opClassName)} USING ${accessMethod}`);
10356
10799
  }
10357
10800
  else if (items.length === 3) {
10358
10801
  const accessMethod = items[0].String?.sval || '';
10359
10802
  const schemaName = items[1].String?.sval || '';
10360
10803
  const opClassName = items[2].String?.sval || '';
10361
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(opClassName)} USING ${accessMethod}`);
10804
+ output.push(`${QuoteUtils.quoteQualifiedIdentifier(schemaName, opClassName)} USING ${accessMethod}`);
10362
10805
  }
10363
10806
  else {
10364
10807
  output.push(this.visit(node.object, context));
@@ -10370,13 +10813,13 @@ export class Deparser {
10370
10813
  if (items.length === 2) {
10371
10814
  const accessMethod = items[0].String?.sval || '';
10372
10815
  const opFamilyName = items[1].String?.sval || '';
10373
- output.push(`${QuoteUtils.quote(opFamilyName)} USING ${accessMethod}`);
10816
+ output.push(`${QuoteUtils.quoteIdentifier(opFamilyName)} USING ${accessMethod}`);
10374
10817
  }
10375
10818
  else if (items.length === 3) {
10376
10819
  const accessMethod = items[0].String?.sval || '';
10377
10820
  const schemaName = items[1].String?.sval || '';
10378
10821
  const opFamilyName = items[2].String?.sval || '';
10379
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(opFamilyName)} USING ${accessMethod}`);
10822
+ output.push(`${QuoteUtils.quoteQualifiedIdentifier(schemaName, opFamilyName)} USING ${accessMethod}`);
10380
10823
  }
10381
10824
  else {
10382
10825
  output.push(this.visit(node.object, context));
@@ -10388,7 +10831,7 @@ export class Deparser {
10388
10831
  }
10389
10832
  output.push('SET SCHEMA');
10390
10833
  if (node.newschema) {
10391
- output.push(QuoteUtils.quote(node.newschema));
10834
+ output.push(QuoteUtils.quoteIdentifier(node.newschema));
10392
10835
  }
10393
10836
  return output.join(' ');
10394
10837
  }
@@ -10457,7 +10900,7 @@ export class Deparser {
10457
10900
  }
10458
10901
  if (node.servername) {
10459
10902
  output.push('SERVER');
10460
- output.push(QuoteUtils.quote(node.servername));
10903
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
10461
10904
  }
10462
10905
  if (node.options && node.options.length > 0) {
10463
10906
  const foreignTableContext = context.spawn('CreateForeignTableStmt');
@@ -10466,8 +10909,4 @@ export class Deparser {
10466
10909
  }
10467
10910
  return output.join(' ');
10468
10911
  }
10469
- containsMultilineStringLiteral(content) {
10470
- const stringLiteralRegex = /'[^']*\n[^']*'/g;
10471
- return stringLiteralRegex.test(content);
10472
- }
10473
10912
  }