pgsql-deparser 14.1.0 → 14.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,133 @@ 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;
217
+ }
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}$`;
228
+ }
229
+ /**
230
+ * Maps ObjectType enum values to their corresponding SQL keywords
231
+ * Used by AlterOwnerStmt, AlterObjectSchemaStmt, and other statements that need object type keywords
232
+ */
233
+ getObjectTypeKeyword(objectType) {
234
+ switch (objectType) {
235
+ case 'OBJECT_TABLE':
236
+ return 'TABLE';
237
+ case 'OBJECT_VIEW':
238
+ return 'VIEW';
239
+ case 'OBJECT_INDEX':
240
+ return 'INDEX';
241
+ case 'OBJECT_SEQUENCE':
242
+ return 'SEQUENCE';
243
+ case 'OBJECT_FUNCTION':
244
+ return 'FUNCTION';
245
+ case 'OBJECT_PROCEDURE':
246
+ return 'PROCEDURE';
247
+ case 'OBJECT_SCHEMA':
248
+ return 'SCHEMA';
249
+ case 'OBJECT_DATABASE':
250
+ return 'DATABASE';
251
+ case 'OBJECT_DOMAIN':
252
+ return 'DOMAIN';
253
+ case 'OBJECT_AGGREGATE':
254
+ return 'AGGREGATE';
255
+ case 'OBJECT_CONVERSION':
256
+ return 'CONVERSION';
257
+ case 'OBJECT_LANGUAGE':
258
+ return 'LANGUAGE';
259
+ case 'OBJECT_OPERATOR':
260
+ return 'OPERATOR';
261
+ case 'OBJECT_OPFAMILY':
262
+ return 'OPERATOR FAMILY';
263
+ case 'OBJECT_OPCLASS':
264
+ return 'OPERATOR CLASS';
265
+ case 'OBJECT_TSDICTIONARY':
266
+ return 'TEXT SEARCH DICTIONARY';
267
+ case 'OBJECT_TSCONFIGURATION':
268
+ return 'TEXT SEARCH CONFIGURATION';
269
+ case 'OBJECT_EVENT_TRIGGER':
270
+ return 'EVENT TRIGGER';
271
+ case 'OBJECT_FDW':
272
+ return 'FOREIGN DATA WRAPPER';
273
+ case 'OBJECT_FOREIGN_SERVER':
274
+ return 'SERVER';
275
+ case 'OBJECT_TYPE':
276
+ return 'TYPE';
277
+ case 'OBJECT_COLLATION':
278
+ return 'COLLATION';
279
+ case 'OBJECT_PUBLICATION':
280
+ return 'PUBLICATION';
281
+ case 'OBJECT_ACCESS_METHOD':
282
+ return 'ACCESS METHOD';
283
+ case 'OBJECT_AMOP':
284
+ return 'OPERATOR CLASS';
285
+ case 'OBJECT_AMPROC':
286
+ return 'OPERATOR CLASS';
287
+ case 'OBJECT_ATTRIBUTE':
288
+ return 'ATTRIBUTE';
289
+ case 'OBJECT_CAST':
290
+ return 'CAST';
291
+ case 'OBJECT_COLUMN':
292
+ return 'COLUMN';
293
+ case 'OBJECT_DEFAULT':
294
+ return 'DEFAULT';
295
+ case 'OBJECT_DEFACL':
296
+ return 'DEFAULT PRIVILEGES';
297
+ case 'OBJECT_DOMCONSTRAINT':
298
+ return 'DOMAIN';
299
+ case 'OBJECT_EXTENSION':
300
+ return 'EXTENSION';
301
+ case 'OBJECT_FOREIGN_TABLE':
302
+ return 'FOREIGN TABLE';
303
+ case 'OBJECT_LARGEOBJECT':
304
+ return 'LARGE OBJECT';
305
+ case 'OBJECT_MATVIEW':
306
+ return 'MATERIALIZED VIEW';
307
+ case 'OBJECT_PARAMETER_ACL':
308
+ return 'PARAMETER';
309
+ case 'OBJECT_POLICY':
310
+ return 'POLICY';
311
+ case 'OBJECT_PUBLICATION_NAMESPACE':
312
+ return 'PUBLICATION';
313
+ case 'OBJECT_PUBLICATION_REL':
314
+ return 'PUBLICATION';
315
+ case 'OBJECT_ROLE':
316
+ return 'ROLE';
317
+ case 'OBJECT_ROUTINE':
318
+ return 'ROUTINE';
319
+ case 'OBJECT_RULE':
320
+ return 'RULE';
321
+ case 'OBJECT_STATISTIC_EXT':
322
+ return 'STATISTICS';
323
+ case 'OBJECT_SUBSCRIPTION':
324
+ return 'SUBSCRIPTION';
325
+ case 'OBJECT_TABCONSTRAINT':
326
+ return 'CONSTRAINT';
327
+ case 'OBJECT_TABLESPACE':
328
+ return 'TABLESPACE';
329
+ case 'OBJECT_TRANSFORM':
330
+ return 'TRANSFORM';
331
+ case 'OBJECT_TRIGGER':
332
+ return 'TRIGGER';
333
+ case 'OBJECT_TSPARSER':
334
+ return 'TEXT SEARCH PARSER';
335
+ case 'OBJECT_TSTEMPLATE':
336
+ return 'TEXT SEARCH TEMPLATE';
337
+ case 'OBJECT_USER_MAPPING':
338
+ return 'USER MAPPING';
339
+ default:
340
+ throw new Error(`Unsupported objectType: ${objectType}`);
168
341
  }
169
- return delimiter;
170
342
  }
171
343
  deparse(node, context) {
172
344
  if (node == null) {
@@ -329,12 +501,7 @@ export class Deparser {
329
501
  // Check if single target is complex - if so, use multiline format
330
502
  if (this.isComplexSelectTarget(targetNode)) {
331
503
  output.push('SELECT' + distinctPart);
332
- if (this.containsMultilineStringLiteral(target)) {
333
- output.push(target);
334
- }
335
- else {
336
- output.push(context.indent(target));
337
- }
504
+ output.push(context.indent(target));
338
505
  }
339
506
  else {
340
507
  output.push('SELECT' + distinctPart + ' ' + target);
@@ -344,9 +511,6 @@ export class Deparser {
344
511
  const targetStrings = targetList
345
512
  .map(e => {
346
513
  const targetStr = this.visit(e, context.spawn('SelectStmt', { select: true }));
347
- if (this.containsMultilineStringLiteral(targetStr)) {
348
- return targetStr;
349
- }
350
514
  return context.indent(targetStr);
351
515
  });
352
516
  const formattedTargets = targetStrings.join(',' + context.newline());
@@ -398,9 +562,6 @@ export class Deparser {
398
562
  return context.parens(values.join(', '));
399
563
  });
400
564
  const indentedTuples = lists.map(tuple => {
401
- if (this.containsMultilineStringLiteral(tuple)) {
402
- return tuple;
403
- }
404
565
  return context.indent(tuple);
405
566
  });
406
567
  output.push(indentedTuples.join(',\n'));
@@ -420,9 +581,6 @@ export class Deparser {
420
581
  const groupItems = groupList
421
582
  .map(e => {
422
583
  const groupStr = this.visit(e, context.spawn('SelectStmt', { group: true, indentLevel: context.indentLevel + 1 }));
423
- if (this.containsMultilineStringLiteral(groupStr)) {
424
- return groupStr;
425
- }
426
584
  return context.indent(groupStr);
427
585
  })
428
586
  .join(',' + context.newline());
@@ -441,12 +599,7 @@ export class Deparser {
441
599
  if (context.isPretty()) {
442
600
  output.push('HAVING');
443
601
  const havingStr = this.visit(node.havingClause, context);
444
- if (this.containsMultilineStringLiteral(havingStr)) {
445
- output.push(havingStr);
446
- }
447
- else {
448
- output.push(context.indent(havingStr));
449
- }
602
+ output.push(context.indent(havingStr));
450
603
  }
451
604
  else {
452
605
  output.push('HAVING');
@@ -467,9 +620,6 @@ export class Deparser {
467
620
  const sortItems = sortList
468
621
  .map(e => {
469
622
  const sortStr = this.visit(e, context.spawn('SelectStmt', { sort: true, indentLevel: context.indentLevel + 1 }));
470
- if (this.containsMultilineStringLiteral(sortStr)) {
471
- return sortStr;
472
- }
473
623
  return context.indent(sortStr);
474
624
  })
475
625
  .join(',' + context.newline());
@@ -545,10 +695,15 @@ export class Deparser {
545
695
  return context.format([leftExpr, operator, rightExpr]);
546
696
  }
547
697
  else if (rexpr) {
548
- return context.format([
549
- this.deparseOperatorName(name, context),
550
- this.visit(rexpr, context)
551
- ]);
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]);
552
707
  }
553
708
  break;
554
709
  case 'AEXPR_OP_ANY':
@@ -728,15 +883,15 @@ export class Deparser {
728
883
  getOperatorPrecedence(operator) {
729
884
  const precedence = {
730
885
  '||': 1, // string concatenation
731
- 'OR': 2, // logical OR
732
- 'AND': 3, // logical AND
733
- 'NOT': 4, // logical NOT
734
- 'IS': 5, // IS NULL, IS NOT NULL, etc.
735
- 'IN': 5, // IN, NOT IN
736
- 'BETWEEN': 5, // BETWEEN, NOT BETWEEN
737
- 'LIKE': 5, // LIKE, ILIKE, SIMILAR TO
738
- 'ILIKE': 5,
739
- '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,
740
895
  '<': 6, // comparison operators
741
896
  '<=': 6,
742
897
  '>': 6,
@@ -866,6 +1021,12 @@ export class Deparser {
866
1021
  output.push(context.parens(columnNames.join(', ')));
867
1022
  }
868
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
+ }
869
1030
  if (node.selectStmt) {
870
1031
  output.push(this.visit(node.selectStmt, context));
871
1032
  }
@@ -908,7 +1069,14 @@ export class Deparser {
908
1069
  else {
909
1070
  const updateContext = context.spawn('UpdateStmt', { update: true });
910
1071
  const targets = targetList.map(target => this.visit(target, updateContext));
911
- 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
+ }
912
1080
  }
913
1081
  }
914
1082
  if (node.onConflictClause.whereClause) {
@@ -920,11 +1088,8 @@ export class Deparser {
920
1088
  output.push('DO NOTHING');
921
1089
  }
922
1090
  }
923
- if (node.returningList) {
924
- output.push('RETURNING');
925
- const returningList = ListUtils.unwrapList(node.returningList);
926
- const returns = returningList.map(ret => this.visit(ret, context));
927
- output.push(returns.join(', '));
1091
+ if (node.returningClause) {
1092
+ output.push(this.ReturningClause(node.returningClause, context));
928
1093
  }
929
1094
  return output.join(' ');
930
1095
  }
@@ -938,38 +1103,8 @@ export class Deparser {
938
1103
  output.push(this.RangeVar(node.relation, context));
939
1104
  }
940
1105
  output.push('SET');
941
- const targetList = ListUtils.unwrapList(node.targetList);
942
- if (targetList && targetList.length) {
943
- const firstTarget = targetList[0];
944
- const processedTargets = new Set();
945
- const assignmentParts = [];
946
- for (let i = 0; i < targetList.length; i++) {
947
- if (processedTargets.has(i))
948
- continue;
949
- const target = targetList[i];
950
- const multiAssignRef = target.ResTarget?.val?.MultiAssignRef;
951
- if (multiAssignRef) {
952
- const relatedTargets = [];
953
- for (let j = i; j < targetList.length; j++) {
954
- const otherTarget = targetList[j];
955
- const otherMultiAssignRef = otherTarget.ResTarget?.val?.MultiAssignRef;
956
- if (otherMultiAssignRef &&
957
- JSON.stringify(otherMultiAssignRef.source) === JSON.stringify(multiAssignRef.source)) {
958
- relatedTargets.push(otherTarget);
959
- processedTargets.add(j);
960
- }
961
- }
962
- const names = relatedTargets.map(t => t.ResTarget.name);
963
- const multiAssignment = `${context.parens(names.join(', '))} = ${this.visit(multiAssignRef.source, context)}`;
964
- assignmentParts.push(multiAssignment);
965
- }
966
- else {
967
- // Handle regular single-column assignment
968
- assignmentParts.push(this.visit(target, context.spawn('UpdateStmt', { update: true })));
969
- processedTargets.add(i);
970
- }
971
- }
972
- output.push(assignmentParts.join(','));
1106
+ if (node.targetList && node.targetList.length > 0) {
1107
+ output.push(this.formatUpdateAssignments(node.targetList, context));
973
1108
  }
974
1109
  if (node.fromClause) {
975
1110
  output.push('FROM');
@@ -981,12 +1116,41 @@ export class Deparser {
981
1116
  output.push('WHERE');
982
1117
  output.push(this.visit(node.whereClause, context));
983
1118
  }
984
- if (node.returningList) {
985
- output.push('RETURNING');
986
- output.push(this.deparseReturningList(node.returningList, context));
1119
+ if (node.returningClause) {
1120
+ output.push(this.ReturningClause(node.returningClause, context));
987
1121
  }
988
1122
  return output.join(' ');
989
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
+ }
990
1154
  DeleteStmt(node, context) {
991
1155
  try {
992
1156
  const output = [];
@@ -1034,13 +1198,12 @@ export class Deparser {
1034
1198
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1035
1199
  }
1036
1200
  }
1037
- if (node.returningList) {
1038
- output.push('RETURNING');
1201
+ if (node.returningClause) {
1039
1202
  try {
1040
- output.push(this.deparseReturningList(node.returningList, context));
1203
+ output.push(this.ReturningClause(node.returningClause, context));
1041
1204
  }
1042
1205
  catch (error) {
1043
- 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)}`);
1044
1207
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1045
1208
  }
1046
1209
  }
@@ -1061,9 +1224,6 @@ export class Deparser {
1061
1224
  const cteStrings = ctes.map((cte, index) => {
1062
1225
  const cteStr = this.visit(cte, context);
1063
1226
  const prefix = index === 0 ? context.newline() : ',' + context.newline();
1064
- if (this.containsMultilineStringLiteral(cteStr)) {
1065
- return prefix + cteStr;
1066
- }
1067
1227
  return prefix + context.indent(cteStr);
1068
1228
  });
1069
1229
  output.push(cteStrings.join(''));
@@ -1078,12 +1238,12 @@ export class Deparser {
1078
1238
  ResTarget(node, context) {
1079
1239
  const output = [];
1080
1240
  if (context.update && node.name) {
1081
- output.push(QuoteUtils.quote(node.name));
1241
+ output.push(QuoteUtils.quoteIdentifier(node.name));
1082
1242
  // Handle indirection (array indexing, field access, etc.)
1083
1243
  if (node.indirection && node.indirection.length > 0) {
1084
1244
  const indirectionStrs = ListUtils.unwrapList(node.indirection).map(item => {
1085
1245
  if (item.String) {
1086
- return `.${QuoteUtils.quote(item.String.sval || item.String.str)}`;
1246
+ return `.${QuoteUtils.quoteIdentifierAfterDot(item.String.sval || item.String.str)}`;
1087
1247
  }
1088
1248
  return this.visit(item, context);
1089
1249
  });
@@ -1095,12 +1255,12 @@ export class Deparser {
1095
1255
  }
1096
1256
  }
1097
1257
  else if (context.insertColumns && node.name) {
1098
- output.push(QuoteUtils.quote(node.name));
1258
+ output.push(QuoteUtils.quoteIdentifier(node.name));
1099
1259
  // Handle indirection for INSERT column lists (e.g., q.c1.r)
1100
1260
  if (node.indirection && node.indirection.length > 0) {
1101
1261
  const indirectionStrs = ListUtils.unwrapList(node.indirection).map(item => {
1102
1262
  if (item.String) {
1103
- return `.${QuoteUtils.quote(item.String.sval || item.String.str)}`;
1263
+ return `.${QuoteUtils.quoteIdentifierAfterDot(item.String.sval || item.String.str)}`;
1104
1264
  }
1105
1265
  return this.visit(item, context);
1106
1266
  });
@@ -1113,11 +1273,53 @@ export class Deparser {
1113
1273
  }
1114
1274
  if (node.name) {
1115
1275
  output.push('AS');
1116
- output.push(QuoteUtils.quote(node.name));
1276
+ output.push(QuoteUtils.quoteIdentifier(node.name));
1277
+ }
1278
+ }
1279
+ return output.join(' ');
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');
1117
1293
  }
1118
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
+ }
1119
1317
  return output.join(' ');
1120
1318
  }
1319
+ ReturningOption(node, context) {
1320
+ const keyword = node.option === 'RETURNING_OPTION_NEW' ? 'NEW' : 'OLD';
1321
+ return `${keyword} AS ${QuoteUtils.quoteIdentifier(node.value)}`;
1322
+ }
1121
1323
  deparseReturningList(list, context) {
1122
1324
  return ListUtils.unwrapList(list)
1123
1325
  .filter(item => item != null && this.getNodeType(item) !== 'undefined')
@@ -1127,7 +1329,7 @@ export class Deparser {
1127
1329
  if (this.getNodeType(item) === 'ResTarget') {
1128
1330
  const resTarget = this.getNodeData(item);
1129
1331
  const val = resTarget.val ? this.visit(resTarget.val, context) : '';
1130
- const alias = resTarget.name ? ` AS ${QuoteUtils.quote(resTarget.name)}` : '';
1332
+ const alias = resTarget.name ? ` AS ${QuoteUtils.quoteIdentifier(resTarget.name)}` : '';
1131
1333
  return val + alias;
1132
1334
  }
1133
1335
  else {
@@ -1182,7 +1384,8 @@ export class Deparser {
1182
1384
  FuncCall(node, context) {
1183
1385
  const funcname = ListUtils.unwrapList(node.funcname);
1184
1386
  const args = ListUtils.unwrapList(node.args);
1185
- 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);
1186
1389
  // Handle special SQL syntax functions like XMLEXISTS and EXTRACT
1187
1390
  if (node.funcformat === 'COERCE_SQL_SYNTAX' && name === 'pg_catalog.xmlexists' && args.length >= 2) {
1188
1391
  const xpath = this.visit(args[0], context);
@@ -1191,8 +1394,15 @@ export class Deparser {
1191
1394
  }
1192
1395
  // Handle EXTRACT function with SQL syntax
1193
1396
  if (node.funcformat === 'COERCE_SQL_SYNTAX' && name === 'pg_catalog.extract' && args.length >= 2) {
1194
- const field = this.visit(args[0], context);
1397
+ let field = this.visit(args[0], context);
1195
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
+ }
1196
1406
  return `EXTRACT(${field} FROM ${source})`;
1197
1407
  }
1198
1408
  // Handle TRIM function with SQL syntax (TRIM TRAILING/LEADING/BOTH)
@@ -1352,43 +1562,41 @@ export class Deparser {
1352
1562
  result += ` FILTER (WHERE ${this.visit(node.agg_filter, context)})`;
1353
1563
  }
1354
1564
  if (node.over) {
1355
- // Handle named window references first
1356
- if (node.over.name) {
1357
- result += ` OVER ${node.over.name}`;
1358
- }
1359
- else {
1360
- const windowParts = [];
1361
- if (node.over.partitionClause) {
1362
- const partitions = ListUtils.unwrapList(node.over.partitionClause);
1363
- const partitionStrs = partitions.map(p => this.visit(p, context));
1364
- windowParts.push(`PARTITION BY ${partitionStrs.join(', ')}`);
1365
- }
1366
- if (node.over.orderClause) {
1367
- const orders = ListUtils.unwrapList(node.over.orderClause);
1368
- const orderStrs = orders.map(o => this.visit(o, context));
1369
- windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
1370
- }
1371
- // Handle window frame specifications using the dedicated formatWindowFrame method
1372
- const frameClause = this.formatWindowFrame(node.over, context.spawn('FuncCall'));
1373
- if (frameClause) {
1374
- windowParts.push(frameClause);
1375
- }
1376
- if (windowParts.length > 0) {
1377
- if (context.isPretty() && windowParts.length > 1) {
1378
- const formattedParts = windowParts.map(part => context.indent(part));
1379
- result += ` OVER (${context.newline()}${formattedParts.join(context.newline())}${context.newline()})`;
1380
- }
1381
- else {
1382
- result += ` OVER (${windowParts.join(' ')})`;
1383
- }
1384
- }
1385
- else {
1386
- result += ` OVER ()`;
1387
- }
1388
- }
1565
+ result += this.formatOverClause(node.over, context);
1389
1566
  }
1390
1567
  return result;
1391
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
+ }
1392
1600
  FuncExpr(node, context) {
1393
1601
  const funcName = `func_${node.funcid}`;
1394
1602
  const args = node.args ? ListUtils.unwrapList(node.args).map(arg => this.visit(arg, context)).join(', ') : '';
@@ -1399,39 +1607,39 @@ export class Deparser {
1399
1607
  if (nodeAny.ival !== undefined) {
1400
1608
  if (typeof nodeAny.ival === 'object' && nodeAny.ival !== null) {
1401
1609
  if (nodeAny.ival.ival !== undefined) {
1402
- return nodeAny.ival.ival.toString();
1610
+ return formatInteger(nodeAny.ival.ival);
1403
1611
  }
1404
1612
  else if (Object.keys(nodeAny.ival).length === 0) {
1405
1613
  return '0';
1406
1614
  }
1407
1615
  else {
1408
- return nodeAny.ival.toString();
1616
+ return formatInteger(nodeAny.ival);
1409
1617
  }
1410
1618
  }
1411
1619
  else if (nodeAny.ival === null) {
1412
1620
  return 'NULL';
1413
1621
  }
1414
1622
  else {
1415
- return nodeAny.ival.toString();
1623
+ return formatInteger(nodeAny.ival);
1416
1624
  }
1417
1625
  }
1418
1626
  else if (nodeAny.fval !== undefined) {
1419
1627
  if (typeof nodeAny.fval === 'object' && nodeAny.fval !== null) {
1420
1628
  if (nodeAny.fval.fval !== undefined) {
1421
- return nodeAny.fval.fval.toString();
1629
+ return formatNumeric(nodeAny.fval.fval);
1422
1630
  }
1423
1631
  else if (Object.keys(nodeAny.fval).length === 0) {
1424
1632
  return '0.0';
1425
1633
  }
1426
1634
  else {
1427
- return nodeAny.fval.toString();
1635
+ return formatNumeric(nodeAny.fval);
1428
1636
  }
1429
1637
  }
1430
1638
  else if (nodeAny.fval === null) {
1431
1639
  return 'NULL';
1432
1640
  }
1433
1641
  else {
1434
- return nodeAny.fval.toString();
1642
+ return formatNumeric(nodeAny.fval);
1435
1643
  }
1436
1644
  }
1437
1645
  else if (nodeAny.sval !== undefined) {
@@ -1478,15 +1686,7 @@ export class Deparser {
1478
1686
  else if (nodeAny.bsval !== undefined) {
1479
1687
  if (typeof nodeAny.bsval === 'object' && nodeAny.bsval !== null) {
1480
1688
  if (nodeAny.bsval.bsval !== undefined) {
1481
- const bsval = nodeAny.bsval.bsval;
1482
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1483
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1484
- return `x'${bsval.substring(1)}'`;
1485
- }
1486
- if (bsval.startsWith('b')) {
1487
- return `b'${bsval.substring(1)}'`;
1488
- }
1489
- return `b'${bsval}'`;
1689
+ return formatBitString(nodeAny.bsval.bsval);
1490
1690
  }
1491
1691
  else if (Object.keys(nodeAny.bsval).length === 0) {
1492
1692
  return "''";
@@ -1499,23 +1699,15 @@ export class Deparser {
1499
1699
  return 'NULL';
1500
1700
  }
1501
1701
  else {
1502
- const bsval = nodeAny.bsval;
1503
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1504
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1505
- return `x'${bsval.substring(1)}'`;
1506
- }
1507
- if (bsval.startsWith('b')) {
1508
- return `b'${bsval.substring(1)}'`;
1509
- }
1510
- return `b'${bsval}'`;
1702
+ return formatBitString(nodeAny.bsval);
1511
1703
  }
1512
1704
  }
1513
1705
  if (nodeAny.val) {
1514
1706
  if (nodeAny.val.Integer?.ival !== undefined) {
1515
- return nodeAny.val.Integer.ival.toString();
1707
+ return formatInteger(nodeAny.val.Integer.ival);
1516
1708
  }
1517
1709
  else if (nodeAny.val.Float?.fval !== undefined) {
1518
- return nodeAny.val.Float.fval.toString();
1710
+ return formatNumeric(nodeAny.val.Float.fval);
1519
1711
  }
1520
1712
  else if (nodeAny.val.String?.sval !== undefined) {
1521
1713
  return QuoteUtils.escape(nodeAny.val.String.sval);
@@ -1524,7 +1716,7 @@ export class Deparser {
1524
1716
  return nodeAny.val.Boolean.boolval ? 'true' : 'false';
1525
1717
  }
1526
1718
  else if (nodeAny.val.BitString?.bsval !== undefined) {
1527
- return nodeAny.val.BitString.bsval;
1719
+ return formatBitString(nodeAny.val.BitString.bsval);
1528
1720
  }
1529
1721
  }
1530
1722
  if (nodeAny.isnull === true) {
@@ -1536,15 +1728,15 @@ export class Deparser {
1536
1728
  }
1537
1729
  if (nodeAny.Integer !== undefined) {
1538
1730
  if (typeof nodeAny.Integer === 'object' && nodeAny.Integer.ival !== undefined) {
1539
- return nodeAny.Integer.ival.toString();
1731
+ return formatInteger(nodeAny.Integer.ival);
1540
1732
  }
1541
- return nodeAny.Integer.toString();
1733
+ return formatInteger(nodeAny.Integer);
1542
1734
  }
1543
1735
  if (nodeAny.Float !== undefined) {
1544
1736
  if (typeof nodeAny.Float === 'object' && nodeAny.Float.fval !== undefined) {
1545
- return nodeAny.Float.fval.toString();
1737
+ return formatNumeric(nodeAny.Float.fval);
1546
1738
  }
1547
- return nodeAny.Float.toString();
1739
+ return formatNumeric(nodeAny.Float);
1548
1740
  }
1549
1741
  if (nodeAny.String !== undefined) {
1550
1742
  if (typeof nodeAny.String === 'object' && nodeAny.String.sval !== undefined) {
@@ -1564,7 +1756,7 @@ export class Deparser {
1564
1756
  const fields = ListUtils.unwrapList(node.fields);
1565
1757
  return fields.map(field => {
1566
1758
  if (field.String) {
1567
- return QuoteUtils.quote(field.String.sval || field.String.str);
1759
+ return QuoteUtils.quoteIdentifier(field.String.sval || field.String.str);
1568
1760
  }
1569
1761
  else if (field.A_Star) {
1570
1762
  return '*';
@@ -1641,8 +1833,7 @@ export class Deparser {
1641
1833
  }
1642
1834
  return output.join(' ');
1643
1835
  }
1644
- const quotedTypeName = QuoteUtils.quote(typeName);
1645
- let result = mods(quotedTypeName, args);
1836
+ let result = mods(QuoteUtils.quoteIdentifierTypeName(typeName), args);
1646
1837
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1647
1838
  result += formatArrayBounds(node.arrayBounds);
1648
1839
  }
@@ -1738,8 +1929,9 @@ export class Deparser {
1738
1929
  return output.join(' ');
1739
1930
  }
1740
1931
  }
1741
- const quotedNames = names.map((name) => QuoteUtils.quote(name));
1742
- 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);
1743
1935
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1744
1936
  result += formatArrayBounds(node.arrayBounds);
1745
1937
  }
@@ -1777,17 +1969,17 @@ export class Deparser {
1777
1969
  }
1778
1970
  let tableName = '';
1779
1971
  if (node.catalogname) {
1780
- tableName = QuoteUtils.quote(node.catalogname);
1972
+ tableName = QuoteUtils.quoteIdentifier(node.catalogname);
1781
1973
  if (node.schemaname) {
1782
- tableName += '.' + QuoteUtils.quote(node.schemaname);
1974
+ tableName += '.' + QuoteUtils.quoteIdentifierAfterDot(node.schemaname);
1783
1975
  }
1784
- tableName += '.' + QuoteUtils.quote(node.relname);
1976
+ tableName += '.' + QuoteUtils.quoteIdentifierAfterDot(node.relname);
1785
1977
  }
1786
1978
  else if (node.schemaname) {
1787
- tableName = QuoteUtils.quote(node.schemaname) + '.' + QuoteUtils.quote(node.relname);
1979
+ tableName = QuoteUtils.quoteQualifiedIdentifier(node.schemaname, node.relname);
1788
1980
  }
1789
1981
  else {
1790
- tableName = QuoteUtils.quote(node.relname);
1982
+ tableName = QuoteUtils.quoteIdentifier(node.relname);
1791
1983
  }
1792
1984
  output.push(tableName);
1793
1985
  if (node.alias) {
@@ -1941,7 +2133,7 @@ export class Deparser {
1941
2133
  if (size != null) {
1942
2134
  return 'char';
1943
2135
  }
1944
- return 'pg_catalog.bpchar';
2136
+ return 'bpchar';
1945
2137
  case 'varchar':
1946
2138
  return 'varchar';
1947
2139
  case 'numeric':
@@ -1955,7 +2147,7 @@ export class Deparser {
1955
2147
  case 'int8':
1956
2148
  return 'bigint';
1957
2149
  case 'real':
1958
- return 'pg_catalog.float4';
2150
+ return 'float4';
1959
2151
  case 'time':
1960
2152
  return 'time';
1961
2153
  case 'timestamp':
@@ -1965,7 +2157,7 @@ export class Deparser {
1965
2157
  case 'bit':
1966
2158
  return 'bit';
1967
2159
  default:
1968
- return `pg_catalog.${typeName}`;
2160
+ return typeName;
1969
2161
  }
1970
2162
  }
1971
2163
  isPgCatalogType(typeName) {
@@ -2006,14 +2198,16 @@ export class Deparser {
2006
2198
  A_Indirection(node, context) {
2007
2199
  let argStr = this.visit(node.arg, context);
2008
2200
  const argType = this.getNodeType(node.arg);
2009
- 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') {
2010
2204
  argStr = `(${argStr})`;
2011
2205
  }
2012
2206
  const output = [argStr];
2013
2207
  const indirection = ListUtils.unwrapList(node.indirection);
2014
2208
  for (const subnode of indirection) {
2015
2209
  if (subnode.String || subnode.A_Star) {
2016
- 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);
2017
2211
  output.push(`.${value}`);
2018
2212
  }
2019
2213
  else {
@@ -2034,21 +2228,11 @@ export class Deparser {
2034
2228
  if (context.isPretty() && args.length > 0) {
2035
2229
  for (const arg of args) {
2036
2230
  const whenClause = this.visit(arg, context);
2037
- if (this.containsMultilineStringLiteral(whenClause)) {
2038
- output.push(context.newline() + whenClause);
2039
- }
2040
- else {
2041
- output.push(context.newline() + context.indent(whenClause));
2042
- }
2231
+ output.push(context.newline() + context.indent(whenClause));
2043
2232
  }
2044
2233
  if (node.defresult) {
2045
2234
  const elseResult = this.visit(node.defresult, context);
2046
- if (this.containsMultilineStringLiteral(elseResult)) {
2047
- output.push(context.newline() + 'ELSE ' + elseResult);
2048
- }
2049
- else {
2050
- output.push(context.newline() + context.indent('ELSE ' + elseResult));
2051
- }
2235
+ output.push(context.newline() + context.indent('ELSE ' + elseResult));
2052
2236
  }
2053
2237
  output.push(context.newline() + 'END');
2054
2238
  return output.join(' ');
@@ -2089,10 +2273,13 @@ export class Deparser {
2089
2273
  const isFunctionCall = argType === 'FuncCall';
2090
2274
  if (isSimpleArgument || isFunctionCall) {
2091
2275
  // For simple arguments, avoid :: syntax if they have complex structure
2092
- if (isSimpleArgument && (arg.includes('(') || arg.startsWith('-'))) {
2093
- }
2094
- else {
2276
+ const shouldUseCastSyntax = isSimpleArgument && (arg.includes('(') || arg.startsWith('-'));
2277
+ if (!shouldUseCastSyntax) {
2095
2278
  const cleanTypeName = typeName.replace('pg_catalog.', '');
2279
+ // Wrap FuncCall arguments in parentheses to prevent operator precedence issues
2280
+ if (isFunctionCall) {
2281
+ return `${context.parens(arg)}::${cleanTypeName}`;
2282
+ }
2096
2283
  return `${arg}::${cleanTypeName}`;
2097
2284
  }
2098
2285
  }
@@ -2155,45 +2342,21 @@ export class Deparser {
2155
2342
  }
2156
2343
  return output.join(' ');
2157
2344
  }
2158
- static RESERVED_WORDS = new Set([
2159
- 'all', 'analyse', 'analyze', 'and', 'any', 'array', 'as', 'asc', 'asymmetric', 'both',
2160
- 'case', 'cast', 'check', 'collate', 'column', 'constraint', 'create', 'current_catalog',
2161
- 'current_date', 'current_role', 'current_time', 'current_timestamp', 'current_user',
2162
- 'default', 'deferrable', 'desc', 'distinct', 'do', 'else', 'end', 'except', 'false',
2163
- 'fetch', 'for', 'foreign', 'from', 'grant', 'group', 'having', 'in', 'initially',
2164
- 'intersect', 'into', 'lateral', 'leading', 'limit', 'localtime', 'localtimestamp',
2165
- 'not', 'null', 'offset', 'on', 'only', 'or', 'order', 'placing', 'primary',
2166
- 'references', 'returning', 'select', 'session_user', 'some', 'symmetric', 'table',
2167
- 'then', 'to', 'trailing', 'true', 'union', 'unique', 'user', 'using', 'variadic',
2168
- 'when', 'where', 'window', 'with'
2169
- ]);
2170
- static needsQuotes(value) {
2171
- if (!value)
2172
- return false;
2173
- const needsQuotesRegex = /[a-z]+[\W\w]*[A-Z]+|[A-Z]+[\W\w]*[a-z]+|\W/;
2174
- const isAllUppercase = /^[A-Z]+$/.test(value);
2175
- return needsQuotesRegex.test(value) ||
2176
- Deparser.RESERVED_WORDS.has(value.toLowerCase()) ||
2177
- isAllUppercase;
2178
- }
2179
2345
  quoteIfNeeded(value) {
2180
- if (Deparser.needsQuotes(value)) {
2181
- return `"${value}"`;
2182
- }
2183
- return value;
2346
+ return QuoteUtils.quoteIdentifier(value);
2184
2347
  }
2185
2348
  preserveOperatorDefElemCase(defName) {
2186
2349
  const caseMap = {
2187
- 'leftarg': 'Leftarg',
2188
- 'rightarg': 'Rightarg',
2189
- 'procedure': 'Procedure',
2190
- 'function': 'Function',
2191
- 'commutator': 'Commutator',
2192
- 'negator': 'Negator',
2193
- 'restrict': 'Restrict',
2194
- 'join': 'Join',
2195
- 'hashes': 'Hashes',
2196
- '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'
2197
2360
  };
2198
2361
  return caseMap[defName.toLowerCase()] || defName;
2199
2362
  }
@@ -2213,27 +2376,19 @@ export class Deparser {
2213
2376
  return value; // Don't quote pure operator symbols like "=" or "-"
2214
2377
  }
2215
2378
  }
2216
- return Deparser.needsQuotes(value) ? `"${value}"` : value;
2379
+ return QuoteUtils.quoteIdentifier(value);
2217
2380
  }
2218
2381
  Integer(node, context) {
2219
- return node.ival?.toString() || '0';
2382
+ return node.ival === undefined || node.ival === null ? '0' : formatInteger(node.ival);
2220
2383
  }
2221
2384
  Float(node, context) {
2222
- return node.fval || '0.0';
2385
+ return node.fval ? formatNumeric(node.fval) : '0.0';
2223
2386
  }
2224
2387
  Boolean(node, context) {
2225
2388
  return node.boolval ? 'true' : 'false';
2226
2389
  }
2227
2390
  BitString(node, context) {
2228
- // Check if this is a hexadecimal bit string (starts with x)
2229
- if (node.bsval.startsWith('x')) {
2230
- return `x'${node.bsval.substring(1)}'`;
2231
- }
2232
- if (node.bsval.startsWith('b')) {
2233
- return `b'${node.bsval.substring(1)}'`;
2234
- }
2235
- // Fallback for raw values without prefix
2236
- return `b'${node.bsval}'`;
2391
+ return formatBitString(node.bsval);
2237
2392
  }
2238
2393
  Null(node, context) {
2239
2394
  return 'NULL';
@@ -2272,7 +2427,7 @@ export class Deparser {
2272
2427
  output.push(context.parens(elementStrs.join(', ')));
2273
2428
  }
2274
2429
  }
2275
- else if (node.tableElts) {
2430
+ else if (node.tableElts && !node.partbound) {
2276
2431
  const elements = ListUtils.unwrapList(node.tableElts);
2277
2432
  const elementStrs = elements.map(el => {
2278
2433
  return this.deparse(el, context.spawn('CreateStmt'));
@@ -2300,7 +2455,16 @@ export class Deparser {
2300
2455
  const inherits = ListUtils.unwrapList(node.inhRelations);
2301
2456
  const inheritStrs = inherits.map(rel => this.visit(rel, context));
2302
2457
  output.push(inheritStrs[0]);
2303
- 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) {
2304
2468
  output.push('FOR VALUES IN');
2305
2469
  const listValues = ListUtils.unwrapList(node.partbound.listdatums)
2306
2470
  .map(datum => this.visit(datum, context))
@@ -2323,13 +2487,9 @@ export class Deparser {
2323
2487
  output.push(`(${upperValues})`);
2324
2488
  }
2325
2489
  }
2326
- else if (node.partbound.strategy === 'h' && node.partbound.modulus !== undefined) {
2490
+ else if (node.partbound.strategy === 'h') {
2327
2491
  output.push('FOR VALUES WITH');
2328
- const remainder = node.partbound.remainder !== undefined ? node.partbound.remainder : 0;
2329
- output.push(`(MODULUS ${node.partbound.modulus}, REMAINDER ${remainder})`);
2330
- }
2331
- else if (node.partbound.is_default) {
2332
- output.push('DEFAULT');
2492
+ output.push(`(MODULUS ${node.partbound.modulus ?? 0}, REMAINDER ${node.partbound.remainder ?? 0})`);
2333
2493
  }
2334
2494
  }
2335
2495
  else if (node.inhRelations) {
@@ -2390,11 +2550,17 @@ export class Deparser {
2390
2550
  ColumnDef(node, context) {
2391
2551
  const output = [];
2392
2552
  if (node.colname) {
2393
- output.push(QuoteUtils.quote(node.colname));
2553
+ output.push(QuoteUtils.quoteIdentifier(node.colname));
2394
2554
  }
2395
2555
  if (node.typeName) {
2396
2556
  output.push(this.TypeName(node.typeName, context));
2397
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
+ }
2398
2564
  if (node.fdwoptions && node.fdwoptions.length > 0) {
2399
2565
  output.push('OPTIONS');
2400
2566
  const columnContext = context.spawn('ColumnDef');
@@ -2429,9 +2595,9 @@ export class Deparser {
2429
2595
  Constraint(node, context) {
2430
2596
  const output = [];
2431
2597
  // Handle constraint name if present
2432
- 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')) {
2433
2599
  output.push('CONSTRAINT');
2434
- output.push(QuoteUtils.quote(node.conname));
2600
+ output.push(QuoteUtils.quoteIdentifier(node.conname));
2435
2601
  }
2436
2602
  switch (node.contype) {
2437
2603
  case 'CONSTR_NULL':
@@ -2439,11 +2605,24 @@ export class Deparser {
2439
2605
  break;
2440
2606
  case 'CONSTR_NOTNULL':
2441
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
+ }
2442
2620
  break;
2443
2621
  case 'CONSTR_DEFAULT':
2444
2622
  output.push('DEFAULT');
2445
2623
  if (node.raw_expr) {
2446
- 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);
2447
2626
  }
2448
2627
  break;
2449
2628
  case 'CONSTR_CHECK':
@@ -2467,8 +2646,13 @@ export class Deparser {
2467
2646
  output.push(context.parens(this.visit(node.raw_expr, context)));
2468
2647
  }
2469
2648
  }
2470
- // Handle NOT VALID for check constraints
2471
- 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
2472
2656
  output.push('NOT VALID');
2473
2657
  }
2474
2658
  // Handle NO INHERIT for check constraints - only for table constraints, not domain constraints
@@ -2488,7 +2672,7 @@ export class Deparser {
2488
2672
  if (node.raw_expr) {
2489
2673
  output.push(context.parens(this.visit(node.raw_expr, context)));
2490
2674
  }
2491
- output.push('STORED');
2675
+ output.push(node.generated_kind === 'v' ? 'VIRTUAL' : 'STORED');
2492
2676
  break;
2493
2677
  case 'CONSTR_IDENTITY':
2494
2678
  output.push('GENERATED');
@@ -2564,10 +2748,12 @@ export class Deparser {
2564
2748
  case 'CONSTR_PRIMARY':
2565
2749
  output.push('PRIMARY KEY');
2566
2750
  if (node.keys && node.keys.length > 0) {
2567
- const keyList = ListUtils.unwrapList(node.keys)
2568
- .map(key => this.visit(key, context))
2569
- .join(', ');
2570
- 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(', ')})`);
2571
2757
  }
2572
2758
  if (node.indexname) {
2573
2759
  output.push('USING INDEX');
@@ -2585,10 +2771,12 @@ export class Deparser {
2585
2771
  output.push('NULLS NOT DISTINCT');
2586
2772
  }
2587
2773
  if (node.keys && node.keys.length > 0) {
2588
- const keyList = ListUtils.unwrapList(node.keys)
2589
- .map(key => this.visit(key, context))
2590
- .join(', ');
2591
- 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(', ')})`);
2592
2780
  }
2593
2781
  if (node.indexname) {
2594
2782
  output.push('USING INDEX');
@@ -2601,20 +2789,14 @@ export class Deparser {
2601
2789
  if (context.isPretty()) {
2602
2790
  output.push('\n' + context.indent('FOREIGN KEY'));
2603
2791
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2604
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2605
- .map(attr => this.visit(attr, context))
2606
- .join(', ');
2607
- output.push(`(${fkAttrs})`);
2792
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2608
2793
  }
2609
2794
  output.push('\n' + context.indent('REFERENCES'));
2610
2795
  }
2611
2796
  else {
2612
2797
  output.push('FOREIGN KEY');
2613
2798
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2614
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2615
- .map(attr => this.visit(attr, context))
2616
- .join(', ');
2617
- output.push(`(${fkAttrs})`);
2799
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2618
2800
  }
2619
2801
  output.push('REFERENCES');
2620
2802
  }
@@ -2637,9 +2819,7 @@ export class Deparser {
2637
2819
  }
2638
2820
  }
2639
2821
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2640
- const pkAttrs = ListUtils.unwrapList(node.pk_attrs)
2641
- .map(attr => this.visit(attr, context))
2642
- .join(', ');
2822
+ const pkAttrs = this.deparseKeyAttrs(node.pk_attrs, node.pk_with_period, context);
2643
2823
  if (context.isPretty() && !context.isColumnConstraint) {
2644
2824
  const lastIndex = output.length - 1;
2645
2825
  if (lastIndex >= 0) {
@@ -2710,6 +2890,15 @@ export class Deparser {
2710
2890
  deleteClause += 'SET DEFAULT';
2711
2891
  break;
2712
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
+ }
2713
2902
  if (context.isPretty()) {
2714
2903
  output.push('\n' + context.indent(deleteClause));
2715
2904
  }
@@ -2718,8 +2907,18 @@ export class Deparser {
2718
2907
  output.push(deleteClause.replace('ON DELETE ', ''));
2719
2908
  }
2720
2909
  }
2721
- // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2722
- 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
2723
2922
  if (context.isPretty() && !context.isColumnConstraint) {
2724
2923
  output.push('\n' + context.indent('NOT VALID'));
2725
2924
  }
@@ -2740,6 +2939,12 @@ export class Deparser {
2740
2939
  case 'CONSTR_ATTR_IMMEDIATE':
2741
2940
  output.push('INITIALLY IMMEDIATE');
2742
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;
2743
2948
  case 'CONSTR_EXCLUSION':
2744
2949
  output.push('EXCLUDE');
2745
2950
  if (node.access_method) {
@@ -2774,10 +2979,14 @@ export class Deparser {
2774
2979
  });
2775
2980
  output.push(`(${exclusionElements.join(', ')})`);
2776
2981
  }
2982
+ if (node.where_clause) {
2983
+ output.push('WHERE');
2984
+ output.push(context.parens(this.visit(node.where_clause, context)));
2985
+ }
2777
2986
  break;
2778
2987
  }
2779
2988
  // Handle deferrable constraints for all constraint types that support it
2780
- 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') {
2781
2990
  if (node.deferrable) {
2782
2991
  if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2783
2992
  output.push('\n' + context.indent('DEFERRABLE'));
@@ -2806,6 +3015,9 @@ export class Deparser {
2806
3015
  output.push('NOT DEFERRABLE');
2807
3016
  }
2808
3017
  }
3018
+ else if (node.initdeferred) {
3019
+ output.push('INITIALLY DEFERRED');
3020
+ }
2809
3021
  }
2810
3022
  if (context.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2811
3023
  let result = '';
@@ -2871,6 +3083,9 @@ export class Deparser {
2871
3083
  output.push(node.name);
2872
3084
  }
2873
3085
  const windowParts = [];
3086
+ if (node.refname) {
3087
+ windowParts.push(node.refname);
3088
+ }
2874
3089
  if (node.partitionClause) {
2875
3090
  const partitions = ListUtils.unwrapList(node.partitionClause);
2876
3091
  const partitionStrs = partitions.map(p => this.visit(p, context));
@@ -2881,12 +3096,9 @@ export class Deparser {
2881
3096
  const orderStrs = orders.map(o => this.visit(o, context));
2882
3097
  windowParts.push(`ORDER BY ${orderStrs.join(', ')}`);
2883
3098
  }
2884
- // Only add frame clause if frameOptions indicates non-default framing
2885
- if (node.frameOptions && node.frameOptions !== 1058) {
2886
- const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
2887
- if (frameClause) {
2888
- windowParts.push(frameClause);
2889
- }
3099
+ const frameClause = this.formatWindowFrame(node, context.spawn('WindowDef'));
3100
+ if (frameClause) {
3101
+ windowParts.push(frameClause);
2890
3102
  }
2891
3103
  if (windowParts.length > 0) {
2892
3104
  if (node.name) {
@@ -2903,103 +3115,97 @@ export class Deparser {
2903
3115
  }
2904
3116
  return output.join(' ');
2905
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
+ }
2906
3145
  formatWindowFrame(node, context) {
2907
- if (!node.frameOptions)
3146
+ if (!node.frameOptions || !(node.frameOptions & FRAMEOPTION_NONDEFAULT))
2908
3147
  return null;
2909
3148
  const frameOptions = node.frameOptions;
2910
3149
  const frameParts = [];
2911
- if (frameOptions & 0x01) { // FRAMEOPTION_NONDEFAULT
2912
- if (frameOptions & 0x02) { // FRAMEOPTION_RANGE
2913
- frameParts.push('RANGE');
2914
- }
2915
- else if (frameOptions & 0x04) { // FRAMEOPTION_ROWS
2916
- frameParts.push('ROWS');
2917
- }
2918
- else if (frameOptions & 0x08) { // FRAMEOPTION_GROUPS
2919
- frameParts.push('GROUPS');
2920
- }
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');
2921
3158
  }
2922
3159
  if (frameParts.length === 0)
2923
3160
  return null;
2924
- const boundsParts = [];
2925
- // Handle specific frameOptions values that have known mappings
2926
- if (frameOptions === 789) {
2927
- boundsParts.push('CURRENT ROW');
2928
- boundsParts.push('AND UNBOUNDED FOLLOWING');
3161
+ let start = null;
3162
+ if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) {
3163
+ start = 'UNBOUNDED PRECEDING';
2929
3164
  }
2930
- else if (frameOptions === 1077) {
2931
- boundsParts.push('UNBOUNDED PRECEDING');
2932
- boundsParts.push('AND CURRENT ROW');
3165
+ else if (frameOptions & FRAMEOPTION_START_UNBOUNDED_FOLLOWING) {
3166
+ start = 'UNBOUNDED FOLLOWING';
2933
3167
  }
2934
- else if (frameOptions === 18453) {
2935
- if (node.startOffset && node.endOffset) {
2936
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2937
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2938
- }
3168
+ else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW) {
3169
+ start = 'CURRENT ROW';
2939
3170
  }
2940
- else if (frameOptions === 1557) {
2941
- boundsParts.push('CURRENT ROW');
2942
- boundsParts.push('AND CURRENT ROW');
3171
+ else if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING && node.startOffset) {
3172
+ start = `${this.visit(node.startOffset, context)} PRECEDING`;
2943
3173
  }
2944
- else if (frameOptions === 16917) {
2945
- boundsParts.push('CURRENT ROW');
2946
- if (node.endOffset) {
2947
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2948
- }
3174
+ else if (frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING && node.startOffset) {
3175
+ start = `${this.visit(node.startOffset, context)} FOLLOWING`;
2949
3176
  }
2950
- else if (frameOptions === 1058) {
2951
- return null;
3177
+ let end = null;
3178
+ if (frameOptions & FRAMEOPTION_END_UNBOUNDED_PRECEDING) {
3179
+ end = 'UNBOUNDED PRECEDING';
2952
3180
  }
2953
- else {
2954
- // Handle start bound - prioritize explicit offset values over bit flags
2955
- if (node.startOffset) {
2956
- if (frameOptions & 0x400) { // FRAMEOPTION_START_VALUE_PRECEDING
2957
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2958
- }
2959
- else if (frameOptions & 0x800) { // FRAMEOPTION_START_VALUE_FOLLOWING
2960
- boundsParts.push(`${this.visit(node.startOffset, context)} FOLLOWING`);
2961
- }
2962
- else {
2963
- boundsParts.push(`${this.visit(node.startOffset, context)} PRECEDING`);
2964
- }
2965
- }
2966
- else if (frameOptions & 0x10) { // FRAMEOPTION_START_UNBOUNDED_PRECEDING
2967
- boundsParts.push('UNBOUNDED PRECEDING');
2968
- }
2969
- else if (frameOptions & 0x20) { // FRAMEOPTION_START_CURRENT_ROW
2970
- boundsParts.push('CURRENT ROW');
2971
- }
2972
- // Handle end bound - prioritize explicit offset values over bit flags
2973
- if (node.endOffset) {
2974
- if (boundsParts.length > 0) {
2975
- if (frameOptions & 0x1000) { // FRAMEOPTION_END_VALUE_PRECEDING
2976
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} PRECEDING`);
2977
- }
2978
- else if (frameOptions & 0x2000) { // FRAMEOPTION_END_VALUE_FOLLOWING
2979
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2980
- }
2981
- else {
2982
- boundsParts.push(`AND ${this.visit(node.endOffset, context)} FOLLOWING`);
2983
- }
2984
- }
2985
- }
2986
- else if (frameOptions & 0x80) { // FRAMEOPTION_END_UNBOUNDED_FOLLOWING
2987
- if (boundsParts.length > 0) {
2988
- boundsParts.push('AND UNBOUNDED FOLLOWING');
2989
- }
2990
- }
2991
- else if (frameOptions & 0x100) { // FRAMEOPTION_END_CURRENT_ROW
2992
- if (boundsParts.length > 0) {
2993
- boundsParts.push('AND CURRENT ROW');
2994
- }
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'}`);
2995
3196
  }
2996
- else if (boundsParts.length > 0) {
2997
- boundsParts.push('AND CURRENT ROW');
3197
+ else {
3198
+ frameParts.push(start);
2998
3199
  }
2999
3200
  }
3000
- if (boundsParts.length > 0) {
3001
- frameParts.push('BETWEEN');
3002
- 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');
3003
3209
  }
3004
3210
  return frameParts.join(' ');
3005
3211
  }
@@ -3077,8 +3283,35 @@ export class Deparser {
3077
3283
  if (node.ctequery) {
3078
3284
  output.push(context.parens(this.visit(node.ctequery, context)));
3079
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
+ }
3080
3304
  return output.join(' ');
3081
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
+ }
3082
3315
  ParamRef(node, context) {
3083
3316
  return `$${node.number}`;
3084
3317
  }
@@ -3328,7 +3561,7 @@ export class Deparser {
3328
3561
  output.push('IF NOT EXISTS');
3329
3562
  }
3330
3563
  if (node.idxname) {
3331
- output.push(QuoteUtils.quote(node.idxname));
3564
+ output.push(QuoteUtils.quoteIdentifier(node.idxname));
3332
3565
  }
3333
3566
  output.push('ON');
3334
3567
  if (node.relation) {
@@ -3347,9 +3580,8 @@ export class Deparser {
3347
3580
  output.push('INCLUDE');
3348
3581
  output.push(context.parens(includeStrs.join(', ')));
3349
3582
  }
3350
- if (node.whereClause) {
3351
- output.push('WHERE');
3352
- output.push(this.visit(node.whereClause, context));
3583
+ if (node.nulls_not_distinct) {
3584
+ output.push('NULLS NOT DISTINCT');
3353
3585
  }
3354
3586
  if (node.options && node.options.length > 0) {
3355
3587
  const indexContext = context.spawn('IndexStmt');
@@ -3357,19 +3589,20 @@ export class Deparser {
3357
3589
  output.push('WITH');
3358
3590
  output.push(context.parens(optionStrs.join(', ')));
3359
3591
  }
3360
- if (node.nulls_not_distinct) {
3361
- output.push('NULLS NOT DISTINCT');
3362
- }
3363
3592
  if (node.tableSpace) {
3364
3593
  output.push('TABLESPACE');
3365
- output.push(QuoteUtils.quote(node.tableSpace));
3594
+ output.push(QuoteUtils.quoteIdentifier(node.tableSpace));
3366
3595
  }
3367
- return output.join(' ');
3368
- }
3596
+ if (node.whereClause) {
3597
+ output.push('WHERE');
3598
+ output.push(this.visit(node.whereClause, context));
3599
+ }
3600
+ return output.join(' ');
3601
+ }
3369
3602
  IndexElem(node, context) {
3370
3603
  const output = [];
3371
3604
  if (node.name) {
3372
- output.push(QuoteUtils.quote(node.name));
3605
+ output.push(QuoteUtils.quoteIdentifier(node.name));
3373
3606
  }
3374
3607
  else if (node.expr) {
3375
3608
  output.push(context.parens(this.visit(node.expr, context)));
@@ -3387,7 +3620,7 @@ export class Deparser {
3387
3620
  const opclassOpts = ListUtils.unwrapList(node.opclassopts).map(opt => {
3388
3621
  if (opt.DefElem && opt.DefElem.arg && this.getNodeType(opt.DefElem.arg) === 'String') {
3389
3622
  const stringData = this.getNodeData(opt.DefElem.arg);
3390
- return `${opt.DefElem.defname}='${stringData.sval}'`;
3623
+ return `${QuoteUtils.quoteIdentifierAfterDot(opt.DefElem.defname)}=${QuoteUtils.escape(stringData.sval)}`;
3391
3624
  }
3392
3625
  return this.visit(opt, context.spawn('IndexElem'));
3393
3626
  });
@@ -3420,7 +3653,7 @@ export class Deparser {
3420
3653
  PartitionElem(node, context) {
3421
3654
  const output = [];
3422
3655
  if (node.name) {
3423
- output.push(QuoteUtils.quote(node.name));
3656
+ output.push(QuoteUtils.quoteIdentifier(node.name));
3424
3657
  }
3425
3658
  else if (node.expr) {
3426
3659
  output.push(context.parens(this.visit(node.expr, context)));
@@ -3438,12 +3671,12 @@ export class Deparser {
3438
3671
  }
3439
3672
  PartitionCmd(node, context) {
3440
3673
  const output = [];
3674
+ if (node.name) {
3675
+ output.push(this.RangeVar(node.name, context));
3676
+ }
3441
3677
  if (node.concurrent) {
3442
3678
  output.push('CONCURRENTLY');
3443
3679
  }
3444
- if (node.name) {
3445
- output.push(this.visit(node.name, context));
3446
- }
3447
3680
  if (node.bound) {
3448
3681
  if (node.bound.strategy === 'l' && node.bound.listdatums) {
3449
3682
  output.push('FOR VALUES IN');
@@ -3468,9 +3701,9 @@ export class Deparser {
3468
3701
  output.push(`(${upperValues})`);
3469
3702
  }
3470
3703
  }
3471
- else if (node.bound.strategy === 'h' && node.bound.modulus !== undefined && node.bound.remainder !== undefined) {
3704
+ else if (node.bound.strategy === 'h') {
3472
3705
  output.push('FOR VALUES WITH');
3473
- output.push(`(modulus ${node.bound.modulus}, remainder ${node.bound.remainder})`);
3706
+ output.push(`(MODULUS ${node.bound.modulus ?? 0}, REMAINDER ${node.bound.remainder ?? 0})`);
3474
3707
  }
3475
3708
  else if (node.bound.is_default) {
3476
3709
  output.push('DEFAULT');
@@ -3601,12 +3834,7 @@ export class Deparser {
3601
3834
  if (context.isPretty()) {
3602
3835
  // For complex JOIN conditions, format with proper indentation
3603
3836
  if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3604
- if (this.containsMultilineStringLiteral(qualsStr)) {
3605
- output.push(` ON ${qualsStr}`);
3606
- }
3607
- else {
3608
- output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3609
- }
3837
+ output.push(` ON${context.newline()}${context.indent(qualsStr)}`);
3610
3838
  }
3611
3839
  else {
3612
3840
  output.push(` ON ${qualsStr}`);
@@ -3670,37 +3898,37 @@ export class Deparser {
3670
3898
  case 'TRANS_STMT_SAVEPOINT':
3671
3899
  output.push('SAVEPOINT');
3672
3900
  if (node.savepoint_name) {
3673
- output.push(QuoteUtils.quote(node.savepoint_name));
3901
+ output.push(QuoteUtils.quoteIdentifier(node.savepoint_name));
3674
3902
  }
3675
3903
  break;
3676
3904
  case 'TRANS_STMT_RELEASE':
3677
3905
  output.push('RELEASE SAVEPOINT');
3678
3906
  if (node.savepoint_name) {
3679
- output.push(QuoteUtils.quote(node.savepoint_name));
3907
+ output.push(QuoteUtils.quoteIdentifier(node.savepoint_name));
3680
3908
  }
3681
3909
  break;
3682
3910
  case 'TRANS_STMT_ROLLBACK_TO':
3683
3911
  output.push('ROLLBACK TO');
3684
3912
  if (node.savepoint_name) {
3685
- output.push(QuoteUtils.quote(node.savepoint_name));
3913
+ output.push(QuoteUtils.quoteIdentifier(node.savepoint_name));
3686
3914
  }
3687
3915
  break;
3688
3916
  case 'TRANS_STMT_PREPARE':
3689
3917
  output.push('PREPARE TRANSACTION');
3690
3918
  if (node.gid) {
3691
- output.push(`'${node.gid}'`);
3919
+ output.push(QuoteUtils.escape(node.gid));
3692
3920
  }
3693
3921
  break;
3694
3922
  case 'TRANS_STMT_COMMIT_PREPARED':
3695
3923
  output.push('COMMIT PREPARED');
3696
3924
  if (node.gid) {
3697
- output.push(`'${node.gid}'`);
3925
+ output.push(QuoteUtils.escape(node.gid));
3698
3926
  }
3699
3927
  break;
3700
3928
  case 'TRANS_STMT_ROLLBACK_PREPARED':
3701
3929
  output.push('ROLLBACK PREPARED');
3702
3930
  if (node.gid) {
3703
- output.push(`'${node.gid}'`);
3931
+ output.push(QuoteUtils.escape(node.gid));
3704
3932
  }
3705
3933
  break;
3706
3934
  default:
@@ -3767,28 +3995,39 @@ export class Deparser {
3767
3995
  switch (node.kind) {
3768
3996
  case 'VAR_SET_VALUE':
3769
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
+ }
3770
4003
  const args = node.args ? ListUtils.unwrapList(node.args).map(arg => {
3771
4004
  const nodeData = this.getNodeData(arg);
3772
4005
  if (nodeData.sval !== undefined) {
3773
4006
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3774
- if (svalValue === '' || svalValue.includes(' ') || svalValue.includes('-') || /[A-Z]/.test(svalValue) || /^\d/.test(svalValue) || svalValue.includes('.') || svalValue.includes('$') || svalValue.toLowerCase() === 'all' || /^[+-]\d/.test(svalValue)) {
3775
- 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);
3776
4009
  }
3777
4010
  return svalValue;
3778
4011
  }
3779
4012
  return this.visit(arg, context);
3780
4013
  }).join(', ') : '';
4014
+ if (node.jumble_args && node.name === 'timezone') {
4015
+ return `SET ${localPrefix}TIME ZONE ${args}`;
4016
+ }
3781
4017
  // Handle args - always include TO clause if args exist (even if empty string)
3782
- 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);
3783
4019
  if (!node.args || node.args.length === 0) {
3784
4020
  return `SET ${localPrefix}${paramName}`;
3785
4021
  }
3786
4022
  return `SET ${localPrefix}${paramName} TO ${args}`;
3787
4023
  case 'VAR_SET_DEFAULT':
3788
- 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);
3789
4028
  return `SET ${defaultParamName} TO DEFAULT`;
3790
4029
  case 'VAR_SET_CURRENT':
3791
- 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);
3792
4031
  return `SET ${currentParamName} FROM CURRENT`;
3793
4032
  case 'VAR_SET_MULTI':
3794
4033
  if (node.name === 'TRANSACTION' || node.name === 'SESSION CHARACTERISTICS') {
@@ -3862,7 +4101,7 @@ export class Deparser {
3862
4101
  return `SET ${assignments}`;
3863
4102
  }
3864
4103
  case 'VAR_RESET':
3865
- 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);
3866
4105
  return `RESET ${resetParamName}`;
3867
4106
  case 'VAR_RESET_ALL':
3868
4107
  return 'RESET ALL';
@@ -3882,7 +4121,7 @@ export class Deparser {
3882
4121
  output.push('IF NOT EXISTS');
3883
4122
  }
3884
4123
  if (node.schemaname) {
3885
- output.push(node.schemaname);
4124
+ output.push(QuoteUtils.quoteIdentifier(node.schemaname));
3886
4125
  }
3887
4126
  if (node.authrole) {
3888
4127
  output.push('AUTHORIZATION');
@@ -4089,7 +4328,7 @@ export class Deparser {
4089
4328
  if (objList && objList.List && objList.List.items) {
4090
4329
  const items = objList.List.items.map((item) => {
4091
4330
  if (item.String && item.String.sval) {
4092
- return item.String.sval;
4331
+ return QuoteUtils.quoteIdentifier(item.String.sval);
4093
4332
  }
4094
4333
  return this.visit(item, context);
4095
4334
  });
@@ -4131,7 +4370,7 @@ export class Deparser {
4131
4370
  if (objList && objList.List && objList.List.items) {
4132
4371
  const items = objList.List.items.map((item) => {
4133
4372
  if (item.String && item.String.sval) {
4134
- return QuoteUtils.quote(item.String.sval);
4373
+ return QuoteUtils.quoteIdentifier(item.String.sval);
4135
4374
  }
4136
4375
  return this.visit(item, context);
4137
4376
  }).filter((name) => name && name.trim());
@@ -4165,13 +4404,13 @@ export class Deparser {
4165
4404
  if (items.length === 2) {
4166
4405
  const accessMethod = items[0];
4167
4406
  const objectName = items[1];
4168
- return `${QuoteUtils.quote(objectName)} USING ${accessMethod}`;
4407
+ return `${QuoteUtils.quoteIdentifier(objectName)} USING ${accessMethod}`;
4169
4408
  }
4170
4409
  else if (items.length === 3) {
4171
4410
  const accessMethod = items[0];
4172
4411
  const schemaName = items[1];
4173
4412
  const objectName = items[2];
4174
- return `${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(objectName)} USING ${accessMethod}`;
4413
+ return `${QuoteUtils.quoteQualifiedIdentifier(schemaName, objectName)} USING ${accessMethod}`;
4175
4414
  }
4176
4415
  return items.join('.');
4177
4416
  }
@@ -4214,7 +4453,7 @@ export class Deparser {
4214
4453
  if (objList && objList.List && objList.List.items) {
4215
4454
  const items = objList.List.items.map((item) => {
4216
4455
  if (item.String && item.String.sval) {
4217
- return QuoteUtils.quote(item.String.sval);
4456
+ return QuoteUtils.quoteIdentifier(item.String.sval);
4218
4457
  }
4219
4458
  return this.visit(item, context);
4220
4459
  }).filter((name) => name && name.trim());
@@ -4262,7 +4501,7 @@ export class Deparser {
4262
4501
  PLAssignStmt(node, context) {
4263
4502
  const output = [];
4264
4503
  if (node.name) {
4265
- let nameWithIndirection = QuoteUtils.quote(node.name);
4504
+ let nameWithIndirection = QuoteUtils.quoteIdentifier(node.name);
4266
4505
  if (node.indirection && node.indirection.length > 0) {
4267
4506
  const indirectionStr = node.indirection
4268
4507
  .map((ind) => this.visit(ind, context))
@@ -4312,10 +4551,10 @@ export class Deparser {
4312
4551
  output.push('PROGRAM');
4313
4552
  }
4314
4553
  if (node.filename) {
4315
- output.push(`'${node.filename}'`);
4554
+ output.push(QuoteUtils.escape(node.filename));
4316
4555
  }
4317
4556
  else {
4318
- output.push('STDIN');
4557
+ output.push(node.is_from ? 'STDIN' : 'STDOUT');
4319
4558
  }
4320
4559
  if (node.options && node.options.length > 0) {
4321
4560
  output.push('WITH');
@@ -4411,7 +4650,7 @@ export class Deparser {
4411
4650
  const parts = [];
4412
4651
  const indentedParts = [];
4413
4652
  if (colDefData.colname) {
4414
- parts.push(QuoteUtils.quote(colDefData.colname));
4653
+ parts.push(QuoteUtils.quoteIdentifier(colDefData.colname));
4415
4654
  }
4416
4655
  if (colDefData.typeName) {
4417
4656
  parts.push(this.TypeName(colDefData.typeName, context));
@@ -4469,7 +4708,7 @@ export class Deparser {
4469
4708
  else {
4470
4709
  const parts = [];
4471
4710
  if (colDefData.colname) {
4472
- parts.push(QuoteUtils.quote(colDefData.colname));
4711
+ parts.push(QuoteUtils.quoteIdentifier(colDefData.colname));
4473
4712
  }
4474
4713
  if (colDefData.typeName) {
4475
4714
  parts.push(this.TypeName(colDefData.typeName, context));
@@ -4523,7 +4762,7 @@ export class Deparser {
4523
4762
  }
4524
4763
  }
4525
4764
  if (node.name) {
4526
- output.push(QuoteUtils.quote(node.name));
4765
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4527
4766
  }
4528
4767
  if (node.behavior === 'DROP_CASCADE') {
4529
4768
  output.push('CASCADE');
@@ -4540,13 +4779,16 @@ export class Deparser {
4540
4779
  output.push('ALTER COLUMN');
4541
4780
  }
4542
4781
  if (node.name) {
4543
- output.push(QuoteUtils.quote(node.name));
4782
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4544
4783
  }
4545
4784
  output.push('TYPE');
4546
4785
  if (node.def) {
4547
4786
  const nodeData = this.getNodeData(node.def);
4548
4787
  if (nodeData && nodeData.typeName) {
4549
4788
  output.push(this.TypeName(nodeData.typeName, context));
4789
+ if (nodeData.collClause) {
4790
+ output.push(this.CollateClause(nodeData.collClause, context));
4791
+ }
4550
4792
  // Handle USING clause (stored in raw_default for ALTER COLUMN TYPE)
4551
4793
  if (nodeData.raw_default) {
4552
4794
  output.push('USING');
@@ -4567,7 +4809,7 @@ export class Deparser {
4567
4809
  case 'AT_SetTableSpace':
4568
4810
  output.push('SET TABLESPACE');
4569
4811
  if (node.name) {
4570
- output.push(QuoteUtils.quote(node.name));
4812
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4571
4813
  }
4572
4814
  break;
4573
4815
  case 'AT_AddConstraint':
@@ -4585,7 +4827,7 @@ export class Deparser {
4585
4827
  output.push('DROP CONSTRAINT');
4586
4828
  }
4587
4829
  if (node.name) {
4588
- output.push(QuoteUtils.quote(node.name));
4830
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4589
4831
  }
4590
4832
  if (node.behavior === 'DROP_CASCADE') {
4591
4833
  output.push('CASCADE');
@@ -4623,7 +4865,7 @@ export class Deparser {
4623
4865
  case 'AT_ColumnDefault':
4624
4866
  output.push('ALTER COLUMN');
4625
4867
  if (node.name) {
4626
- output.push(QuoteUtils.quote(node.name));
4868
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4627
4869
  }
4628
4870
  if (node.def) {
4629
4871
  output.push('SET DEFAULT');
@@ -4636,7 +4878,7 @@ export class Deparser {
4636
4878
  case 'AT_SetStorage':
4637
4879
  output.push('ALTER COLUMN');
4638
4880
  if (node.name) {
4639
- output.push(QuoteUtils.quote(node.name));
4881
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4640
4882
  }
4641
4883
  output.push('SET STORAGE');
4642
4884
  if (node.def) {
@@ -4647,7 +4889,7 @@ export class Deparser {
4647
4889
  case 'AT_ClusterOn':
4648
4890
  output.push('CLUSTER ON');
4649
4891
  if (node.name) {
4650
- output.push(QuoteUtils.quote(node.name));
4892
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4651
4893
  }
4652
4894
  break;
4653
4895
  case 'AT_DropCluster':
@@ -4674,21 +4916,21 @@ export class Deparser {
4674
4916
  case 'AT_SetNotNull':
4675
4917
  output.push('ALTER COLUMN');
4676
4918
  if (node.name) {
4677
- output.push(QuoteUtils.quote(node.name));
4919
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4678
4920
  }
4679
4921
  output.push('SET NOT NULL');
4680
4922
  break;
4681
4923
  case 'AT_DropNotNull':
4682
4924
  output.push('ALTER COLUMN');
4683
4925
  if (node.name) {
4684
- output.push(QuoteUtils.quote(node.name));
4926
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4685
4927
  }
4686
4928
  output.push('DROP NOT NULL');
4687
4929
  break;
4688
4930
  case 'AT_SetStatistics':
4689
4931
  output.push('ALTER COLUMN');
4690
4932
  if (node.name) {
4691
- output.push(QuoteUtils.quote(node.name));
4933
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4692
4934
  }
4693
4935
  else if (node.num !== undefined && node.num !== null) {
4694
4936
  output.push(node.num.toString());
@@ -4701,7 +4943,7 @@ export class Deparser {
4701
4943
  case 'AT_SetOptions':
4702
4944
  output.push('ALTER COLUMN');
4703
4945
  if (node.name) {
4704
- output.push(QuoteUtils.quote(node.name));
4946
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4705
4947
  }
4706
4948
  output.push('SET');
4707
4949
  if (node.def) {
@@ -4718,7 +4960,7 @@ export class Deparser {
4718
4960
  case 'AT_ResetOptions':
4719
4961
  output.push('ALTER COLUMN');
4720
4962
  if (node.name) {
4721
- output.push(QuoteUtils.quote(node.name));
4963
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4722
4964
  }
4723
4965
  output.push('RESET');
4724
4966
  if (node.def) {
@@ -4735,7 +4977,7 @@ export class Deparser {
4735
4977
  case 'AT_SetCompression':
4736
4978
  output.push('ALTER COLUMN');
4737
4979
  if (node.name) {
4738
- output.push(QuoteUtils.quote(node.name));
4980
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4739
4981
  }
4740
4982
  output.push('SET COMPRESSION');
4741
4983
  if (node.def) {
@@ -4745,31 +4987,31 @@ export class Deparser {
4745
4987
  case 'AT_ValidateConstraint':
4746
4988
  output.push('VALIDATE CONSTRAINT');
4747
4989
  if (node.name) {
4748
- output.push(QuoteUtils.quote(node.name));
4990
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4749
4991
  }
4750
4992
  break;
4751
4993
  case 'AT_EnableTrig':
4752
4994
  output.push('ENABLE TRIGGER');
4753
4995
  if (node.name) {
4754
- output.push(QuoteUtils.quote(node.name));
4996
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4755
4997
  }
4756
4998
  break;
4757
4999
  case 'AT_EnableAlwaysTrig':
4758
5000
  output.push('ENABLE ALWAYS TRIGGER');
4759
5001
  if (node.name) {
4760
- output.push(QuoteUtils.quote(node.name));
5002
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4761
5003
  }
4762
5004
  break;
4763
5005
  case 'AT_EnableReplicaTrig':
4764
5006
  output.push('ENABLE REPLICA TRIGGER');
4765
5007
  if (node.name) {
4766
- output.push(QuoteUtils.quote(node.name));
5008
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4767
5009
  }
4768
5010
  break;
4769
5011
  case 'AT_DisableTrig':
4770
5012
  output.push('DISABLE TRIGGER');
4771
5013
  if (node.name) {
4772
- output.push(QuoteUtils.quote(node.name));
5014
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4773
5015
  }
4774
5016
  break;
4775
5017
  case 'AT_EnableTrigAll':
@@ -4787,31 +5029,31 @@ export class Deparser {
4787
5029
  case 'AT_EnableRule':
4788
5030
  output.push('ENABLE RULE');
4789
5031
  if (node.name) {
4790
- output.push(QuoteUtils.quote(node.name));
5032
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4791
5033
  }
4792
5034
  break;
4793
5035
  case 'AT_EnableAlwaysRule':
4794
5036
  output.push('ENABLE ALWAYS RULE');
4795
5037
  if (node.name) {
4796
- output.push(QuoteUtils.quote(node.name));
5038
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4797
5039
  }
4798
5040
  break;
4799
5041
  case 'AT_EnableReplicaRule':
4800
5042
  output.push('ENABLE REPLICA RULE');
4801
5043
  if (node.name) {
4802
- output.push(QuoteUtils.quote(node.name));
5044
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4803
5045
  }
4804
5046
  break;
4805
5047
  case 'AT_DisableRule':
4806
5048
  output.push('DISABLE RULE');
4807
5049
  if (node.name) {
4808
- output.push(QuoteUtils.quote(node.name));
5050
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4809
5051
  }
4810
5052
  break;
4811
5053
  case 'AT_SetAccessMethod':
4812
5054
  output.push('SET ACCESS METHOD');
4813
5055
  if (node.name) {
4814
- output.push(QuoteUtils.quote(node.name));
5056
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4815
5057
  }
4816
5058
  else {
4817
5059
  // Handle DEFAULT access method case
@@ -4865,7 +5107,7 @@ export class Deparser {
4865
5107
  case 'AT_CookedColumnDefault':
4866
5108
  output.push('ALTER COLUMN');
4867
5109
  if (node.name) {
4868
- output.push(QuoteUtils.quote(node.name));
5110
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4869
5111
  }
4870
5112
  if (node.def) {
4871
5113
  output.push('SET DEFAULT');
@@ -4878,26 +5120,23 @@ export class Deparser {
4878
5120
  case 'AT_SetExpression':
4879
5121
  output.push('ALTER COLUMN');
4880
5122
  if (node.name) {
4881
- output.push(QuoteUtils.quote(node.name));
5123
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4882
5124
  }
4883
5125
  output.push('SET EXPRESSION');
4884
5126
  if (node.def) {
4885
- output.push(this.visit(node.def, context));
5127
+ output.push('AS');
5128
+ output.push(context.parens(this.visit(node.def, context)));
4886
5129
  }
4887
5130
  break;
4888
5131
  case 'AT_DropExpression':
4889
5132
  output.push('ALTER COLUMN');
4890
5133
  if (node.name) {
4891
- output.push(QuoteUtils.quote(node.name));
5134
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4892
5135
  }
4893
5136
  output.push('DROP EXPRESSION');
4894
- break;
4895
- case 'AT_CheckNotNull':
4896
- output.push('ALTER COLUMN');
4897
- if (node.name) {
4898
- output.push(QuoteUtils.quote(node.name));
5137
+ if (node.missing_ok) {
5138
+ output.push('IF EXISTS');
4899
5139
  }
4900
- output.push('SET NOT NULL');
4901
5140
  break;
4902
5141
  case 'AT_AddIndex':
4903
5142
  output.push('ADD');
@@ -4924,23 +5163,28 @@ export class Deparser {
4924
5163
  }
4925
5164
  break;
4926
5165
  case 'AT_AlterConstraint':
4927
- output.push('ALTER CONSTRAINT');
4928
- if (node.def && this.getNodeType(node.def) === 'Constraint') {
4929
- const constraintData = this.getNodeData(node.def);
4930
- if (constraintData.conname) {
4931
- output.push(QuoteUtils.quote(constraintData.conname));
4932
- if (constraintData.deferrable !== undefined) {
4933
- output.push(constraintData.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
4934
- }
4935
- if (constraintData.initdeferred !== undefined) {
4936
- 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
+ }
4937
5181
  }
4938
5182
  }
4939
- }
4940
- else if (node.name) {
4941
- output.push(QuoteUtils.quote(node.name));
4942
- if (node.def) {
4943
- 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
+ }
4944
5188
  }
4945
5189
  }
4946
5190
  break;
@@ -4959,7 +5203,7 @@ export class Deparser {
4959
5203
  case 'AT_AlterColumnGenericOptions':
4960
5204
  output.push('ALTER COLUMN');
4961
5205
  if (node.name) {
4962
- output.push(QuoteUtils.quote(node.name));
5206
+ output.push(QuoteUtils.quoteIdentifier(node.name));
4963
5207
  }
4964
5208
  output.push('OPTIONS');
4965
5209
  if (node.def) {
@@ -5013,7 +5257,7 @@ export class Deparser {
5013
5257
  case 'AT_AddIdentity':
5014
5258
  output.push('ALTER COLUMN');
5015
5259
  if (node.name) {
5016
- output.push(QuoteUtils.quote(node.name));
5260
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5017
5261
  }
5018
5262
  output.push('ADD');
5019
5263
  if (node.def) {
@@ -5023,24 +5267,20 @@ export class Deparser {
5023
5267
  case 'AT_SetIdentity':
5024
5268
  output.push('ALTER COLUMN');
5025
5269
  if (node.name) {
5026
- output.push(QuoteUtils.quote(node.name));
5270
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5027
5271
  }
5028
- output.push('SET');
5029
5272
  if (node.def) {
5030
- output.push(this.visit(node.def, context));
5273
+ output.push(this.formatIdentityColumnOptions(node.def, context));
5031
5274
  }
5032
5275
  break;
5033
5276
  case 'AT_DropIdentity':
5034
5277
  output.push('ALTER COLUMN');
5035
5278
  if (node.name) {
5036
- output.push(QuoteUtils.quote(node.name));
5279
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5037
5280
  }
5038
5281
  output.push('DROP IDENTITY');
5039
- if (node.behavior === 'DROP_CASCADE') {
5040
- output.push('CASCADE');
5041
- }
5042
- else if (node.behavior === 'DROP_RESTRICT') {
5043
- output.push('RESTRICT');
5282
+ if (node.missing_ok) {
5283
+ output.push('IF EXISTS');
5044
5284
  }
5045
5285
  break;
5046
5286
  case 'AT_ReAddStatistics':
@@ -5067,7 +5307,8 @@ export class Deparser {
5067
5307
  output.push('FUNCTION');
5068
5308
  }
5069
5309
  if (node.funcname && node.funcname.length > 0) {
5070
- 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);
5071
5312
  if (node.parameters && node.parameters.length > 0) {
5072
5313
  const params = node.parameters
5073
5314
  .filter((param) => {
@@ -5076,7 +5317,13 @@ export class Deparser {
5076
5317
  })
5077
5318
  .map((param) => this.visit(param, context));
5078
5319
  if (params.length > 0) {
5079
- 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
+ }
5080
5327
  }
5081
5328
  else {
5082
5329
  output.push(funcName + '()');
@@ -5091,15 +5338,21 @@ export class Deparser {
5091
5338
  return paramData.mode === 'FUNC_PARAM_TABLE';
5092
5339
  });
5093
5340
  if (hasTableParams) {
5094
- output.push('RETURNS TABLE (');
5095
5341
  const tableParams = node.parameters
5096
5342
  .filter((param) => {
5097
5343
  const paramData = this.getNodeData(param);
5098
5344
  return paramData.mode === 'FUNC_PARAM_TABLE';
5099
5345
  })
5100
5346
  .map((param) => this.visit(param, context));
5101
- output.push(tableParams.join(', '));
5102
- 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
+ }
5103
5356
  }
5104
5357
  else if (node.returnType) {
5105
5358
  output.push('RETURNS');
@@ -5121,6 +5374,7 @@ export class Deparser {
5121
5374
  if (bodyType === 'List') {
5122
5375
  const statements = ListUtils.unwrapList(node.sql_body);
5123
5376
  if (statements.length === 0 || (statements.length === 1 && Object.keys(statements[0]).length === 0)) {
5377
+ // empty body: nothing to emit
5124
5378
  }
5125
5379
  else {
5126
5380
  // Handle nested List structure in BEGIN ATOMIC blocks
@@ -5172,7 +5426,7 @@ export class Deparser {
5172
5426
  }
5173
5427
  }
5174
5428
  if (node.name) {
5175
- output.push(QuoteUtils.quote(node.name));
5429
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5176
5430
  }
5177
5431
  if (node.argType) {
5178
5432
  output.push(this.TypeName(node.argType, context));
@@ -5244,7 +5498,7 @@ export class Deparser {
5244
5498
  output.push('ROLE');
5245
5499
  }
5246
5500
  if (node.role) {
5247
- const roleName = Deparser.needsQuotes(node.role) ? `"${node.role}"` : node.role;
5501
+ const roleName = QuoteUtils.quoteIdentifier(node.role);
5248
5502
  output.push(roleName);
5249
5503
  }
5250
5504
  if (node.options) {
@@ -5268,25 +5522,28 @@ export class Deparser {
5268
5522
  if (node.arg && this.getNodeType(node.arg) === 'List') {
5269
5523
  const listData = this.getNodeData(node.arg);
5270
5524
  const listItems = ListUtils.unwrapList(listData.items);
5525
+ const preservedName = this.preserveOperatorDefElemCase(node.defname);
5271
5526
  if (listItems.length === 1 && listItems[0].String) {
5272
- const preservedName = this.preserveOperatorDefElemCase(node.defname);
5273
5527
  return `${preservedName} = ${listItems[0].String.sval}`;
5274
5528
  }
5529
+ if (listItems.length > 1 && listItems.every((item) => item.String)) {
5530
+ return `${preservedName} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
5531
+ }
5275
5532
  }
5276
5533
  }
5277
5534
  // Handle IndexElem opclassopts - MUST be first to preserve string types
5278
5535
  if (context.parentNodeTypes.includes('IndexElem')) {
5279
5536
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5280
5537
  const stringData = this.getNodeData(node.arg);
5281
- return `${node.defname}='${stringData.sval}'`;
5538
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${QuoteUtils.escape(stringData.sval)}`;
5282
5539
  }
5283
- 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')))}`;
5284
5541
  }
5285
5542
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
5286
5543
  if (context.parentNodeTypes.includes('DefineStmt') &&
5287
5544
  ['hashes', 'merges'].includes(node.defname.toLowerCase()) && !node.arg) {
5288
5545
  if (node.defname !== node.defname.toLowerCase() && node.defname !== node.defname.toUpperCase()) {
5289
- return `"${node.defname}"`;
5546
+ return QuoteUtils.quoteIdentifier(node.defname);
5290
5547
  }
5291
5548
  return node.defname.charAt(0).toUpperCase() + node.defname.slice(1).toLowerCase();
5292
5549
  }
@@ -5305,12 +5562,8 @@ export class Deparser {
5305
5562
  const defElemContext = context.spawn('DefElem');
5306
5563
  const argValue = this.visit(node.arg, defElemContext);
5307
5564
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5308
- const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
5309
- ? `'${argValue}'`
5310
- : argValue;
5311
- const quotedDefname = node.defname.includes(' ') || node.defname.includes('-') || Deparser.needsQuotes(node.defname)
5312
- ? `"${node.defname}"`
5313
- : node.defname;
5565
+ const finalValue = this.quoteDefElemArg(node.arg, argValue);
5566
+ const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5314
5567
  if (node.defaction === 'DEFELEM_ADD') {
5315
5568
  return `ADD ${quotedDefname} ${finalValue}`;
5316
5569
  }
@@ -5322,9 +5575,7 @@ export class Deparser {
5322
5575
  }
5323
5576
  return `${quotedDefname} ${finalValue}`;
5324
5577
  }
5325
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5326
- ? `'${argValue}'`
5327
- : argValue;
5578
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5328
5579
  if (node.defaction === 'DEFELEM_ADD') {
5329
5580
  return `ADD ${node.defname} ${quotedValue}`;
5330
5581
  }
@@ -5334,9 +5585,7 @@ export class Deparser {
5334
5585
  else if (node.defaction === 'DEFELEM_SET') {
5335
5586
  return `SET ${node.defname} ${quotedValue}`;
5336
5587
  }
5337
- const quotedDefname = node.defname.includes(' ') || node.defname.includes('-')
5338
- ? `"${node.defname}"`
5339
- : node.defname;
5588
+ const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5340
5589
  return `${quotedDefname} ${quotedValue}`;
5341
5590
  }
5342
5591
  else if (node.defaction === 'DEFELEM_DROP') {
@@ -5358,9 +5607,7 @@ export class Deparser {
5358
5607
  }
5359
5608
  const defElemContext = context.spawn('DefElem');
5360
5609
  const argValue = this.visit(node.arg, defElemContext);
5361
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5362
- ? `'${argValue}'`
5363
- : argValue;
5610
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5364
5611
  return `PASSWORD ${quotedValue}`;
5365
5612
  }
5366
5613
  }
@@ -5383,6 +5630,9 @@ export class Deparser {
5383
5630
  if (listItems.length === 1 && listItems[0].String) {
5384
5631
  return `${node.defname} = ${listItems[0].String.sval}`;
5385
5632
  }
5633
+ if (listItems.length > 1 && listItems.every((item) => item.String)) {
5634
+ return `${node.defname} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
5635
+ }
5386
5636
  }
5387
5637
  }
5388
5638
  if (context.parentNodeTypes.includes('CreatedbStmt') || context.parentNodeTypes.includes('DropdbStmt')) {
@@ -5396,9 +5646,7 @@ export class Deparser {
5396
5646
  const quotedValue = typeof argValue === 'string'
5397
5647
  ? QuoteUtils.escape(argValue)
5398
5648
  : argValue;
5399
- const quotedDefname = node.defname.includes(' ') || node.defname.includes('-')
5400
- ? `"${node.defname}"`
5401
- : node.defname;
5649
+ const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5402
5650
  return `${quotedDefname} ${quotedValue}`;
5403
5651
  }
5404
5652
  if (context.parentNodeTypes.includes('CreateRoleStmt') || context.parentNodeTypes.includes('AlterRoleStmt')) {
@@ -5427,9 +5675,7 @@ export class Deparser {
5427
5675
  }
5428
5676
  }
5429
5677
  if (node.defname === 'validUntil') {
5430
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5431
- ? `'${argValue}'`
5432
- : argValue;
5678
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5433
5679
  return `VALID UNTIL ${quotedValue}`;
5434
5680
  }
5435
5681
  if (node.defname === 'adminmembers') {
@@ -5468,15 +5714,15 @@ export class Deparser {
5468
5714
  if (node.arg && this.getNodeType(node.arg) === 'List') {
5469
5715
  const listData = this.getNodeData(node.arg);
5470
5716
  const listItems = ListUtils.unwrapList(listData.items);
5717
+ if (listItems.length === 1 && listItems[0].String?.sval === 'none') {
5718
+ return 'OWNED BY NONE';
5719
+ }
5471
5720
  const parts = listItems.map(item => {
5472
5721
  const itemData = this.getNodeData(item);
5473
5722
  if (this.getNodeType(item) === 'String') {
5474
5723
  // Check if this identifier needs quotes to preserve case
5475
5724
  const value = itemData.sval;
5476
- if (Deparser.needsQuotes(value)) {
5477
- return `"${value}"`;
5478
- }
5479
- return value;
5725
+ return QuoteUtils.quoteIdentifier(value);
5480
5726
  }
5481
5727
  return this.visit(item, context);
5482
5728
  });
@@ -5607,11 +5853,13 @@ export class Deparser {
5607
5853
  if (context.parentNodeTypes.includes('CreateExtensionStmt') || context.parentNodeTypes.includes('AlterExtensionStmt') || context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5608
5854
  // AlterExtensionStmt specific cases
5609
5855
  if (context.parentNodeTypes.includes('AlterExtensionStmt')) {
5610
- if (node.defname === 'to') {
5611
- return `TO ${argValue}`;
5856
+ if (node.defname === 'new_version') {
5857
+ // argValue is unquoted due to DefElem context, so we need to quote it
5858
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5859
+ return `TO ${quotedValue}`;
5612
5860
  }
5613
5861
  if (node.defname === 'schema') {
5614
- return `SCHEMA ${argValue}`;
5862
+ return `SET SCHEMA ${argValue}`;
5615
5863
  }
5616
5864
  }
5617
5865
  // CreateFdwStmt specific cases
@@ -5628,28 +5876,37 @@ export class Deparser {
5628
5876
  if (node.defname === 'cascade') {
5629
5877
  return argValue === 'true' ? 'CASCADE' : '';
5630
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
+ }
5631
5885
  return `${node.defname.toUpperCase()} ${argValue}`;
5632
5886
  }
5633
5887
  // Handle IndexStmt WITH clause options - no quotes, compact formatting
5634
5888
  if (context.parentNodeTypes.includes('IndexStmt')) {
5635
- return `${node.defname}=${argValue}`;
5889
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5636
5890
  }
5637
5891
  // Handle IndexElem opclassopts - preserve string values as strings
5638
5892
  if (context.parentNodeTypes.includes('IndexElem')) {
5639
5893
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5640
5894
  const stringData = this.getNodeData(node.arg);
5641
- return `${node.defname}='${stringData.sval}'`;
5895
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${QuoteUtils.escape(stringData.sval)}`;
5642
5896
  }
5643
- return `${node.defname}=${argValue}`;
5897
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5644
5898
  }
5645
5899
  // Handle CreateStmt table options - no quotes, compact formatting
5646
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);
5647
5904
  // For numeric values, use the raw value without quotes
5648
5905
  if (node.arg && this.getNodeType(node.arg) === 'Integer') {
5649
5906
  const integerData = this.getNodeData(node.arg);
5650
- return `${node.defname}=${integerData.ival}`;
5907
+ return `${relOptName}=${formatInteger(integerData.ival)}`;
5651
5908
  }
5652
- return `${node.defname}=${argValue}`;
5909
+ return `${relOptName}=${this.formatOptionValue(node.arg, argValue)}`;
5653
5910
  }
5654
5911
  // Handle CreateEventTrigStmt WHEN clause - use IN syntax for List arguments
5655
5912
  if (context.parentNodeTypes.includes('CreateEventTrigStmt')) {
@@ -5659,7 +5916,7 @@ export class Deparser {
5659
5916
  const values = listItems.map(item => {
5660
5917
  if (this.getNodeType(item) === 'String') {
5661
5918
  const stringData = this.getNodeData(item);
5662
- return `'${stringData.sval || ''}'`;
5919
+ return QuoteUtils.escape(stringData.sval || '');
5663
5920
  }
5664
5921
  return this.visit(item, context);
5665
5922
  });
@@ -5674,7 +5931,7 @@ export class Deparser {
5674
5931
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
5675
5932
  return `${optionName} = ${argValue}`;
5676
5933
  }
5677
- return `${optionName} = ${argValue}`;
5934
+ return `${optionName} = ${this.formatOptionValue(node.arg, argValue)}`;
5678
5935
  }
5679
5936
  // Handle ViewStmt WITH options - don't quote numeric values
5680
5937
  if (context.parentNodeTypes.includes('ViewStmt')) {
@@ -5688,15 +5945,27 @@ export class Deparser {
5688
5945
  }
5689
5946
  // Handle CopyStmt WITH clause options - uppercase format without quotes
5690
5947
  if (context.parentNodeTypes.includes('CopyStmt')) {
5691
- if (node.defname === 'format' && node.arg && this.getNodeType(node.arg) === 'String') {
5692
- const stringData = this.getNodeData(node.arg);
5693
- return `FORMAT ${stringData.sval.toUpperCase()}`;
5948
+ const optName = node.defname.toUpperCase();
5949
+ if (!node.arg) {
5950
+ return optName;
5694
5951
  }
5695
- // Handle other COPY options with uppercase defname
5696
- if (node.arg) {
5697
- 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;
5698
5955
  }
5699
- 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}`;
5700
5969
  }
5701
5970
  // Handle CREATE OPERATOR and CREATE TYPE context
5702
5971
  if (context.parentNodeTypes.includes('DefineStmt')) {
@@ -5720,13 +5989,13 @@ export class Deparser {
5720
5989
  // Handle boolean flags (no arguments) - preserve quoted case
5721
5990
  if (['hashes', 'merges'].includes(node.defname.toLowerCase())) {
5722
5991
  if (node.defname !== node.defname.toLowerCase() && node.defname !== node.defname.toUpperCase()) {
5723
- return `"${node.defname}"`;
5992
+ return QuoteUtils.quoteIdentifier(node.defname);
5724
5993
  }
5725
5994
  return preservedName.toUpperCase();
5726
5995
  }
5727
5996
  // Handle CREATE AGGREGATE quoted identifiers - preserve quotes when needed
5728
- if (Deparser.needsQuotes(node.defname)) {
5729
- const quotedDefname = `"${node.defname}"`;
5997
+ const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5998
+ if (quotedDefname !== node.defname) {
5730
5999
  if (node.arg) {
5731
6000
  if (this.getNodeType(node.arg) === 'String') {
5732
6001
  const stringData = this.getNodeData(node.arg);
@@ -5735,7 +6004,7 @@ export class Deparser {
5735
6004
  return `${quotedDefname} = ${stringData.sval}`;
5736
6005
  }
5737
6006
  // Regular string literals get single quotes
5738
- return `${quotedDefname} = '${stringData.sval}'`;
6007
+ return `${quotedDefname} = ${QuoteUtils.escape(stringData.sval)}`;
5739
6008
  }
5740
6009
  return `${quotedDefname} = ${argValue}`;
5741
6010
  }
@@ -5756,7 +6025,7 @@ export class Deparser {
5756
6025
  return `${node.defname} = ${stringData.sval}`;
5757
6026
  }
5758
6027
  // Regular string literals get single quotes
5759
- return `${node.defname} = '${stringData.sval}'`;
6028
+ return `${node.defname} = ${QuoteUtils.escape(stringData.sval)}`;
5760
6029
  }
5761
6030
  if (node.arg && this.getNodeType(node.arg) === 'Boolean') {
5762
6031
  const boolData = this.getNodeData(node.arg);
@@ -5777,6 +6046,10 @@ export class Deparser {
5777
6046
  return `${node.defname} = ${argValue}`;
5778
6047
  }
5779
6048
  }
6049
+ const argType = this.getNodeType(node.arg);
6050
+ if (argType === 'TypeName' || argType === 'Integer' || argType === 'Float' || argType === 'Boolean') {
6051
+ return `${node.defname} = ${argValue}`;
6052
+ }
5780
6053
  const quotedValue = typeof argValue === 'string'
5781
6054
  ? QuoteUtils.escape(argValue)
5782
6055
  : argValue;
@@ -5786,7 +6059,7 @@ export class Deparser {
5786
6059
  if (context.parentNodeTypes.includes('DefineStmt') && !node.arg) {
5787
6060
  // Check if the original defname appears to be quoted (mixed case that's not all upper/lower)
5788
6061
  if (node.defname !== node.defname.toLowerCase() && node.defname !== node.defname.toUpperCase()) {
5789
- return `"${node.defname}"`;
6062
+ return QuoteUtils.quoteIdentifier(node.defname);
5790
6063
  }
5791
6064
  }
5792
6065
  return node.defname.toUpperCase();
@@ -5802,7 +6075,7 @@ export class Deparser {
5802
6075
  }
5803
6076
  if (node.location) {
5804
6077
  output.push('LOCATION');
5805
- output.push(`'${node.location}'`);
6078
+ output.push(QuoteUtils.escape(node.location));
5806
6079
  }
5807
6080
  if (node.options && node.options.length > 0) {
5808
6081
  output.push('WITH');
@@ -5866,6 +6139,7 @@ export class Deparser {
5866
6139
  if (node.extname) {
5867
6140
  output.push(this.quoteIfNeeded(node.extname));
5868
6141
  }
6142
+ output.push('UPDATE');
5869
6143
  if (node.options && node.options.length > 0) {
5870
6144
  const extContext = context.spawn('AlterExtensionStmt');
5871
6145
  const options = ListUtils.unwrapList(node.options)
@@ -5875,6 +6149,34 @@ export class Deparser {
5875
6149
  }
5876
6150
  return output.join(' ');
5877
6151
  }
6152
+ AlterExtensionContentsStmt(node, context) {
6153
+ const output = ['ALTER', 'EXTENSION'];
6154
+ if (node.extname) {
6155
+ output.push(this.quoteIfNeeded(node.extname));
6156
+ }
6157
+ // Handle action: 1 = ADD, -1 = DROP
6158
+ if (node.action === 1) {
6159
+ output.push('ADD');
6160
+ }
6161
+ else if (node.action === -1) {
6162
+ output.push('DROP');
6163
+ }
6164
+ // Add object type keyword
6165
+ if (node.objtype) {
6166
+ try {
6167
+ output.push(this.getObjectTypeKeyword(node.objtype));
6168
+ }
6169
+ catch {
6170
+ // Fallback to the raw objtype if not supported
6171
+ output.push(node.objtype.toString());
6172
+ }
6173
+ }
6174
+ // Add the object itself
6175
+ if (node.object) {
6176
+ output.push(this.visit(node.object, context));
6177
+ }
6178
+ return output.join(' ');
6179
+ }
5878
6180
  CreateFdwStmt(node, context) {
5879
6181
  const output = ['CREATE', 'FOREIGN', 'DATA', 'WRAPPER'];
5880
6182
  if (node.fdwname) {
@@ -5942,7 +6244,7 @@ export class Deparser {
5942
6244
  case 'REPLICA_IDENTITY_INDEX':
5943
6245
  output.push('USING', 'INDEX');
5944
6246
  if (node.name) {
5945
- output.push(QuoteUtils.quote(node.name));
6247
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5946
6248
  }
5947
6249
  break;
5948
6250
  default:
@@ -6001,7 +6303,7 @@ export class Deparser {
6001
6303
  output.push('IF', 'EXISTS');
6002
6304
  }
6003
6305
  if (node.name) {
6004
- output.push(QuoteUtils.quote(node.name));
6306
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6005
6307
  }
6006
6308
  if (node.behavior === 'DROP_CASCADE') {
6007
6309
  output.push('CASCADE');
@@ -6010,7 +6312,7 @@ export class Deparser {
6010
6312
  case 'AT_ValidateConstraint':
6011
6313
  output.push('VALIDATE', 'CONSTRAINT');
6012
6314
  if (node.name) {
6013
- output.push(QuoteUtils.quote(node.name));
6315
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6014
6316
  }
6015
6317
  break;
6016
6318
  case 'C':
@@ -6027,7 +6329,7 @@ export class Deparser {
6027
6329
  output.push('IF', 'EXISTS');
6028
6330
  }
6029
6331
  if (node.name) {
6030
- output.push(QuoteUtils.quote(node.name));
6332
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6031
6333
  }
6032
6334
  if (node.behavior === 'DROP_CASCADE') {
6033
6335
  output.push('CASCADE');
@@ -6036,7 +6338,7 @@ export class Deparser {
6036
6338
  case 'V':
6037
6339
  output.push('VALIDATE', 'CONSTRAINT');
6038
6340
  if (node.name) {
6039
- output.push(QuoteUtils.quote(node.name));
6341
+ output.push(QuoteUtils.quoteIdentifier(node.name));
6040
6342
  }
6041
6343
  break;
6042
6344
  case 'O':
@@ -6107,7 +6409,7 @@ export class Deparser {
6107
6409
  }
6108
6410
  if (node.payload !== null && node.payload !== undefined) {
6109
6411
  output.push(',');
6110
- output.push(`'${node.payload}'`);
6412
+ output.push(QuoteUtils.escape(node.payload));
6111
6413
  }
6112
6414
  return output.join(' ');
6113
6415
  }
@@ -6135,7 +6437,7 @@ export class Deparser {
6135
6437
  if (!node.filename) {
6136
6438
  throw new Error('LoadStmt requires filename');
6137
6439
  }
6138
- return `LOAD '${node.filename}'`;
6440
+ return `LOAD ${QuoteUtils.escape(node.filename)}`;
6139
6441
  }
6140
6442
  DiscardStmt(node, context) {
6141
6443
  switch (node.target) {
@@ -6151,6 +6453,13 @@ export class Deparser {
6151
6453
  throw new Error(`Unsupported DiscardStmt target: ${node.target}`);
6152
6454
  }
6153
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
+ }
6154
6463
  CommentStmt(node, context) {
6155
6464
  const output = ['COMMENT ON'];
6156
6465
  if (node.objtype) {
@@ -6182,6 +6491,9 @@ export class Deparser {
6182
6491
  case 'OBJECT_TABCONSTRAINT':
6183
6492
  output.push('CONSTRAINT');
6184
6493
  break;
6494
+ case 'OBJECT_DOMCONSTRAINT':
6495
+ output.push('CONSTRAINT');
6496
+ break;
6185
6497
  case 'OBJECT_TRIGGER':
6186
6498
  output.push('TRIGGER');
6187
6499
  break;
@@ -6258,23 +6570,30 @@ export class Deparser {
6258
6570
  output.push(objectParts.join('.'));
6259
6571
  }
6260
6572
  }
6261
- else if (node.objtype === 'OBJECT_TRIGGER') {
6573
+ else if (node.objtype === 'OBJECT_DOMCONSTRAINT') {
6262
6574
  if (objectParts.length === 2) {
6263
- const [table, trigger] = objectParts;
6264
- 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]);
6265
6585
  output.push('ON');
6266
- output.push(table);
6586
+ output.push(objectParts.slice(0, -1).join('.'));
6267
6587
  }
6268
6588
  else {
6269
6589
  output.push(objectParts.join('.'));
6270
6590
  }
6271
6591
  }
6272
6592
  else if (node.objtype === 'OBJECT_RULE') {
6273
- if (objectParts.length === 2) {
6274
- const [table, rule] = objectParts;
6275
- output.push(rule);
6593
+ if (objectParts.length >= 2) {
6594
+ output.push(objectParts[objectParts.length - 1]);
6276
6595
  output.push('ON');
6277
- output.push(table);
6596
+ output.push(objectParts.slice(0, -1).join('.'));
6278
6597
  }
6279
6598
  else {
6280
6599
  output.push(objectParts.join('.'));
@@ -6285,8 +6604,7 @@ export class Deparser {
6285
6604
  // For operators, we need to handle ObjectWithArgs structure
6286
6605
  if (node.object && node.object.ObjectWithArgs) {
6287
6606
  const objWithArgs = node.object.ObjectWithArgs;
6288
- let operatorName = objWithArgs.objname && objWithArgs.objname[0] && objWithArgs.objname[0].String
6289
- ? objWithArgs.objname[0].String.sval : 'unknown';
6607
+ let operatorName = this.qualifiedOperatorName(objWithArgs.objname);
6290
6608
  if (operatorName.startsWith('"') && operatorName.endsWith('"')) {
6291
6609
  operatorName = operatorName.slice(1, -1);
6292
6610
  }
@@ -6324,7 +6642,13 @@ export class Deparser {
6324
6642
  }
6325
6643
  else if (node.objtype === 'OBJECT_POLICY') {
6326
6644
  // Handle POLICY syntax: COMMENT ON POLICY policy_name ON table_name IS 'comment'
6327
- 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) {
6328
6652
  const [table, policy] = objectParts;
6329
6653
  output.push(policy);
6330
6654
  output.push('ON');
@@ -6347,6 +6671,9 @@ export class Deparser {
6347
6671
  output.push(objectParts.join('.'));
6348
6672
  }
6349
6673
  }
6674
+ else if (node.objtype === 'OBJECT_CAST' && objectParts.length === 2) {
6675
+ output.push(`(${objectParts[0]} AS ${objectParts[1]})`);
6676
+ }
6350
6677
  else {
6351
6678
  output.push(objectParts.join('.'));
6352
6679
  }
@@ -6355,8 +6682,7 @@ export class Deparser {
6355
6682
  else if (node.objtype === 'OBJECT_OPERATOR' && node.object && node.object.ObjectWithArgs) {
6356
6683
  // Handle direct ObjectWithArgs for OPERATOR syntax: COMMENT ON OPERATOR -(NONE, integer) IS 'comment'
6357
6684
  const objWithArgs = node.object.ObjectWithArgs;
6358
- let operatorName = objWithArgs.objname && objWithArgs.objname[0] && objWithArgs.objname[0].String
6359
- ? objWithArgs.objname[0].String.sval : 'unknown';
6685
+ let operatorName = this.qualifiedOperatorName(objWithArgs.objname);
6360
6686
  // Remove quotes from operator name if present
6361
6687
  if (operatorName.startsWith('"') && operatorName.endsWith('"')) {
6362
6688
  operatorName = operatorName.slice(1, -1);
@@ -6425,7 +6751,7 @@ export class Deparser {
6425
6751
  const output = [];
6426
6752
  const initialParts = ['CREATE', 'POLICY'];
6427
6753
  if (node.policy_name) {
6428
- initialParts.push(QuoteUtils.quote(node.policy_name));
6754
+ initialParts.push(QuoteUtils.quoteIdentifier(node.policy_name));
6429
6755
  }
6430
6756
  output.push(initialParts.join(' '));
6431
6757
  // Add ON clause on new line in pretty mode
@@ -6502,7 +6828,7 @@ export class Deparser {
6502
6828
  AlterPolicyStmt(node, context) {
6503
6829
  const output = ['ALTER', 'POLICY'];
6504
6830
  if (node.policy_name) {
6505
- output.push(QuoteUtils.quote(node.policy_name));
6831
+ output.push(QuoteUtils.quoteIdentifier(node.policy_name));
6506
6832
  }
6507
6833
  if (node.table) {
6508
6834
  output.push('ON');
@@ -6538,7 +6864,7 @@ export class Deparser {
6538
6864
  }
6539
6865
  output.push('SERVER');
6540
6866
  if (node.servername) {
6541
- output.push(`"${node.servername}"`);
6867
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
6542
6868
  }
6543
6869
  if (node.options && node.options.length > 0) {
6544
6870
  output.push('OPTIONS');
@@ -6586,10 +6912,10 @@ export class Deparser {
6586
6912
  CreatePublicationStmt(node, context) {
6587
6913
  const output = ['CREATE', 'PUBLICATION'];
6588
6914
  if (node.pubname) {
6589
- output.push(`"${node.pubname}"`);
6915
+ output.push(QuoteUtils.quoteIdentifier(node.pubname));
6590
6916
  }
6591
6917
  if (node.pubobjects && node.pubobjects.length > 0) {
6592
- output.push('FOR', 'TABLE');
6918
+ output.push('FOR');
6593
6919
  const tables = ListUtils.unwrapList(node.pubobjects).map(table => this.visit(table, context));
6594
6920
  output.push(tables.join(', '));
6595
6921
  }
@@ -6606,11 +6932,11 @@ export class Deparser {
6606
6932
  CreateSubscriptionStmt(node, context) {
6607
6933
  const output = ['CREATE', 'SUBSCRIPTION'];
6608
6934
  if (node.subname) {
6609
- output.push(`"${node.subname}"`);
6935
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
6610
6936
  }
6611
6937
  output.push('CONNECTION');
6612
6938
  if (node.conninfo) {
6613
- output.push(`'${node.conninfo}'`);
6939
+ output.push(QuoteUtils.escape(node.conninfo));
6614
6940
  }
6615
6941
  output.push('PUBLICATION');
6616
6942
  if (node.publication && node.publication.length > 0) {
@@ -6627,10 +6953,11 @@ export class Deparser {
6627
6953
  AlterPublicationStmt(node, context) {
6628
6954
  const output = ['ALTER', 'PUBLICATION'];
6629
6955
  if (node.pubname) {
6630
- output.push(`"${node.pubname}"`);
6956
+ output.push(QuoteUtils.quoteIdentifier(node.pubname));
6631
6957
  }
6632
- if (node.action) {
6633
- switch (node.action) {
6958
+ const hasObjects = node.pubobjects && node.pubobjects.length > 0;
6959
+ if (hasObjects) {
6960
+ switch (node.action || 'AP_AddObjects') {
6634
6961
  case 'AP_AddObjects':
6635
6962
  output.push('ADD');
6636
6963
  break;
@@ -6643,17 +6970,11 @@ export class Deparser {
6643
6970
  default:
6644
6971
  throw new Error(`Unsupported AlterPublicationStmt action: ${node.action}`);
6645
6972
  }
6646
- }
6647
- if (node.for_all_tables) {
6648
- output.push('FOR ALL TABLES');
6649
- }
6650
- else if (node.pubobjects && node.pubobjects.length > 0) {
6651
- output.push('FOR TABLE');
6652
6973
  const objects = ListUtils.unwrapList(node.pubobjects).map(obj => this.visit(obj, context));
6653
6974
  output.push(objects.join(', '));
6654
6975
  }
6655
- if (node.options && node.options.length > 0) {
6656
- output.push('WITH');
6976
+ else if (node.options && node.options.length > 0) {
6977
+ output.push('SET');
6657
6978
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context));
6658
6979
  output.push(`(${options.join(', ')})`);
6659
6980
  }
@@ -6662,44 +6983,69 @@ export class Deparser {
6662
6983
  AlterSubscriptionStmt(node, context) {
6663
6984
  const output = ['ALTER', 'SUBSCRIPTION'];
6664
6985
  if (node.subname) {
6665
- output.push(`"${node.subname}"`);
6666
- }
6667
- if (node.kind) {
6668
- 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') {
6669
6996
  case 'ALTER_SUBSCRIPTION_OPTIONS':
6670
6997
  output.push('SET');
6998
+ if (options)
6999
+ output.push(options);
6671
7000
  break;
6672
7001
  case 'ALTER_SUBSCRIPTION_CONNECTION':
6673
7002
  output.push('CONNECTION');
6674
7003
  if (node.conninfo) {
6675
- output.push(`'${node.conninfo}'`);
7004
+ output.push(QuoteUtils.escape(node.conninfo));
6676
7005
  }
6677
7006
  break;
6678
7007
  case 'ALTER_SUBSCRIPTION_SET_PUBLICATION':
6679
7008
  output.push('SET PUBLICATION');
6680
- if (node.publication && node.publication.length > 0) {
6681
- const publications = ListUtils.unwrapList(node.publication).map(pub => this.visit(pub, context));
6682
- output.push(publications.join(', '));
6683
- }
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);
6684
7027
  break;
6685
7028
  case 'ALTER_SUBSCRIPTION_REFRESH':
6686
7029
  output.push('REFRESH PUBLICATION');
7030
+ if (options)
7031
+ output.push('WITH', options);
6687
7032
  break;
6688
- case 'ALTER_SUBSCRIPTION_ENABLED':
6689
- 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');
6690
7038
  break;
7039
+ }
6691
7040
  case 'ALTER_SUBSCRIPTION_SKIP':
6692
7041
  output.push('SKIP');
7042
+ if (options)
7043
+ output.push(options);
6693
7044
  break;
6694
7045
  default:
6695
7046
  throw new Error(`Unsupported AlterSubscriptionStmt kind: ${node.kind}`);
6696
7047
  }
6697
7048
  }
6698
- if (node.options && node.options.length > 0) {
6699
- output.push('WITH');
6700
- const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context));
6701
- output.push(`(${options.join(', ')})`);
6702
- }
6703
7049
  return output.join(' ');
6704
7050
  }
6705
7051
  DropSubscriptionStmt(node, context) {
@@ -6708,7 +7054,7 @@ export class Deparser {
6708
7054
  output.push('IF EXISTS');
6709
7055
  }
6710
7056
  if (node.subname) {
6711
- output.push(`"${node.subname}"`);
7057
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
6712
7058
  }
6713
7059
  if (node.behavior) {
6714
7060
  switch (node.behavior) {
@@ -6953,7 +7299,7 @@ export class Deparser {
6953
7299
  ClosePortalStmt(node, context) {
6954
7300
  const output = ['CLOSE'];
6955
7301
  if (node.portalname) {
6956
- output.push(QuoteUtils.quote(node.portalname));
7302
+ output.push(QuoteUtils.quoteIdentifier(node.portalname));
6957
7303
  }
6958
7304
  else {
6959
7305
  output.push('ALL');
@@ -7011,7 +7357,7 @@ export class Deparser {
7011
7357
  output.push('ALL');
7012
7358
  }
7013
7359
  if (node.portalname) {
7014
- output.push(QuoteUtils.quote(node.portalname));
7360
+ output.push(QuoteUtils.quoteIdentifier(node.portalname));
7015
7361
  }
7016
7362
  return output.join(' ');
7017
7363
  }
@@ -7081,7 +7427,7 @@ export class Deparser {
7081
7427
  AlterFdwStmt(node, context) {
7082
7428
  const output = ['ALTER', 'FOREIGN', 'DATA', 'WRAPPER'];
7083
7429
  if (node.fdwname) {
7084
- output.push(QuoteUtils.quote(node.fdwname));
7430
+ output.push(QuoteUtils.quoteIdentifier(node.fdwname));
7085
7431
  }
7086
7432
  if (node.func_options && node.func_options.length > 0) {
7087
7433
  const fdwContext = context.spawn('AlterFdwStmt');
@@ -7102,7 +7448,7 @@ export class Deparser {
7102
7448
  output.push('IF', 'NOT', 'EXISTS');
7103
7449
  }
7104
7450
  if (node.servername) {
7105
- output.push(QuoteUtils.quote(node.servername));
7451
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
7106
7452
  }
7107
7453
  if (node.servertype) {
7108
7454
  output.push('TYPE', QuoteUtils.escape(node.servertype));
@@ -7111,7 +7457,7 @@ export class Deparser {
7111
7457
  output.push('VERSION', QuoteUtils.escape(node.version));
7112
7458
  }
7113
7459
  if (node.fdwname) {
7114
- output.push('FOREIGN', 'DATA', 'WRAPPER', QuoteUtils.quote(node.fdwname));
7460
+ output.push('FOREIGN', 'DATA', 'WRAPPER', QuoteUtils.quoteIdentifier(node.fdwname));
7115
7461
  }
7116
7462
  if (node.options && node.options.length > 0) {
7117
7463
  output.push('OPTIONS');
@@ -7126,7 +7472,7 @@ export class Deparser {
7126
7472
  AlterForeignServerStmt(node, context) {
7127
7473
  const output = ['ALTER', 'SERVER'];
7128
7474
  if (node.servername) {
7129
- output.push(QuoteUtils.quote(node.servername));
7475
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
7130
7476
  }
7131
7477
  if (node.version) {
7132
7478
  output.push('VERSION', QuoteUtils.escape(node.version));
@@ -7151,7 +7497,7 @@ export class Deparser {
7151
7497
  }
7152
7498
  output.push('SERVER');
7153
7499
  if (node.servername) {
7154
- output.push(QuoteUtils.quote(node.servername));
7500
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
7155
7501
  }
7156
7502
  if (node.options && node.options.length > 0) {
7157
7503
  output.push('OPTIONS');
@@ -7175,14 +7521,14 @@ export class Deparser {
7175
7521
  }
7176
7522
  output.push('SERVER');
7177
7523
  if (node.servername) {
7178
- output.push(QuoteUtils.quote(node.servername));
7524
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
7179
7525
  }
7180
7526
  return output.join(' ');
7181
7527
  }
7182
7528
  ImportForeignSchemaStmt(node, context) {
7183
7529
  const output = ['IMPORT', 'FOREIGN', 'SCHEMA'];
7184
7530
  if (node.remote_schema) {
7185
- output.push(QuoteUtils.quote(node.remote_schema));
7531
+ output.push(QuoteUtils.quoteIdentifier(node.remote_schema));
7186
7532
  }
7187
7533
  if (node.list_type) {
7188
7534
  switch (node.list_type) {
@@ -7208,11 +7554,11 @@ export class Deparser {
7208
7554
  }
7209
7555
  output.push('FROM', 'SERVER');
7210
7556
  if (node.server_name) {
7211
- output.push(QuoteUtils.quote(node.server_name));
7557
+ output.push(QuoteUtils.quoteIdentifier(node.server_name));
7212
7558
  }
7213
7559
  output.push('INTO');
7214
7560
  if (node.local_schema) {
7215
- output.push(QuoteUtils.quote(node.local_schema));
7561
+ output.push(QuoteUtils.quoteIdentifier(node.local_schema));
7216
7562
  }
7217
7563
  if (node.options && node.options.length > 0) {
7218
7564
  const importSchemaContext = context.spawn('ImportForeignSchemaStmt');
@@ -7226,7 +7572,7 @@ export class Deparser {
7226
7572
  if (node.relation) {
7227
7573
  output.push(this.RangeVar(node.relation, context));
7228
7574
  if (node.indexname) {
7229
- output.push('USING', `"${node.indexname}"`);
7575
+ output.push('USING', QuoteUtils.quoteIdentifier(node.indexname));
7230
7576
  }
7231
7577
  }
7232
7578
  if (node.params && node.params.length > 0) {
@@ -7290,7 +7636,7 @@ export class Deparser {
7290
7636
  output.push(this.RangeVar(node.relation, context));
7291
7637
  }
7292
7638
  if (node.name) {
7293
- output.push(`"${node.name}"`);
7639
+ output.push(QuoteUtils.quoteIdentifier(node.name));
7294
7640
  }
7295
7641
  return output.join(' ');
7296
7642
  }
@@ -7330,7 +7676,7 @@ export class Deparser {
7330
7676
  if (!node.dbname) {
7331
7677
  throw new Error('CreatedbStmt requires dbname');
7332
7678
  }
7333
- output.push(`"${node.dbname}"`);
7679
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
7334
7680
  if (node.options && node.options.length > 0) {
7335
7681
  const options = ListUtils.unwrapList(node.options)
7336
7682
  .map(option => this.visit(option, context))
@@ -7347,7 +7693,7 @@ export class Deparser {
7347
7693
  if (!node.dbname) {
7348
7694
  throw new Error('DropdbStmt requires dbname');
7349
7695
  }
7350
- output.push(`"${node.dbname}"`);
7696
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
7351
7697
  if (node.options && node.options.length > 0) {
7352
7698
  const options = ListUtils.unwrapList(node.options)
7353
7699
  .map(option => this.visit(option, context))
@@ -7445,7 +7791,7 @@ export class Deparser {
7445
7791
  case 'OBJECT_POLICY':
7446
7792
  output.push('POLICY');
7447
7793
  if (node.subname) {
7448
- output.push(QuoteUtils.quote(node.subname));
7794
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
7449
7795
  }
7450
7796
  break;
7451
7797
  case 'OBJECT_PUBLICATION':
@@ -7503,7 +7849,7 @@ export class Deparser {
7503
7849
  }
7504
7850
  // Handle OBJECT_RULE special case: rule_name ON table_name format
7505
7851
  if (node.renameType === 'OBJECT_RULE' && node.subname && node.relation) {
7506
- output.push(QuoteUtils.quote(node.subname));
7852
+ output.push(QuoteUtils.quoteIdentifier(node.subname));
7507
7853
  output.push('ON');
7508
7854
  output.push(this.RangeVar(node.relation, context));
7509
7855
  }
@@ -7522,12 +7868,21 @@ export class Deparser {
7522
7868
  if (items.length === 2) {
7523
7869
  const accessMethod = items[0].String?.sval || '';
7524
7870
  const objectName = items[1].String?.sval || '';
7525
- output.push(`${QuoteUtils.quote(objectName)} USING ${accessMethod}`);
7871
+ output.push(`${QuoteUtils.quoteIdentifier(objectName)} USING ${accessMethod}`);
7526
7872
  }
7527
7873
  else {
7528
7874
  output.push(this.visit(node.object, context));
7529
7875
  }
7530
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
+ }
7531
7886
  else if (node.renameType === 'OBJECT_SCHEMA' && node.object.List) {
7532
7887
  // Handle schema names - extract from List structure
7533
7888
  const items = ListUtils.unwrapList(node.object);
@@ -7543,19 +7898,19 @@ export class Deparser {
7543
7898
  }
7544
7899
  }
7545
7900
  if (node.renameType === 'OBJECT_COLUMN' && node.subname) {
7546
- output.push('RENAME COLUMN', `"${node.subname}"`, 'TO');
7901
+ output.push('RENAME COLUMN', QuoteUtils.quoteIdentifier(node.subname), 'TO');
7547
7902
  }
7548
7903
  else if (node.renameType === 'OBJECT_DOMCONSTRAINT' && node.subname) {
7549
- output.push('RENAME CONSTRAINT', `"${node.subname}"`, 'TO');
7904
+ output.push('RENAME CONSTRAINT', QuoteUtils.quoteIdentifier(node.subname), 'TO');
7550
7905
  }
7551
7906
  else if (node.renameType === 'OBJECT_TABCONSTRAINT' && node.subname) {
7552
- output.push('RENAME CONSTRAINT', `"${node.subname}"`, 'TO');
7907
+ output.push('RENAME CONSTRAINT', QuoteUtils.quoteIdentifier(node.subname), 'TO');
7553
7908
  }
7554
7909
  else if (node.renameType === 'OBJECT_ATTRIBUTE' && node.subname) {
7555
- output.push('RENAME ATTRIBUTE', `"${node.subname}"`, 'TO');
7910
+ output.push('RENAME ATTRIBUTE', QuoteUtils.quoteIdentifier(node.subname), 'TO');
7556
7911
  }
7557
7912
  else if (node.renameType === 'OBJECT_ROLE' && node.subname) {
7558
- output.push(`"${node.subname}"`, 'RENAME TO');
7913
+ output.push(QuoteUtils.quoteIdentifier(node.subname), 'RENAME TO');
7559
7914
  }
7560
7915
  else if (node.renameType === 'OBJECT_SCHEMA' && node.subname) {
7561
7916
  output.push(this.quoteIfNeeded(node.subname), 'RENAME TO');
@@ -7569,7 +7924,7 @@ export class Deparser {
7569
7924
  if (!node.newname) {
7570
7925
  throw new Error('RenameStmt requires newname');
7571
7926
  }
7572
- output.push(QuoteUtils.quote(node.newname));
7927
+ output.push(QuoteUtils.quoteIdentifier(node.newname));
7573
7928
  // Handle CASCADE/RESTRICT behavior for RENAME operations
7574
7929
  if (node.behavior === 'DROP_CASCADE') {
7575
7930
  output.push('CASCADE');
@@ -7581,76 +7936,7 @@ export class Deparser {
7581
7936
  if (!node.objectType) {
7582
7937
  throw new Error('AlterOwnerStmt requires objectType');
7583
7938
  }
7584
- switch (node.objectType) {
7585
- case 'OBJECT_TABLE':
7586
- output.push('TABLE');
7587
- break;
7588
- case 'OBJECT_VIEW':
7589
- output.push('VIEW');
7590
- break;
7591
- case 'OBJECT_INDEX':
7592
- output.push('INDEX');
7593
- break;
7594
- case 'OBJECT_SEQUENCE':
7595
- output.push('SEQUENCE');
7596
- break;
7597
- case 'OBJECT_FUNCTION':
7598
- output.push('FUNCTION');
7599
- break;
7600
- case 'OBJECT_PROCEDURE':
7601
- output.push('PROCEDURE');
7602
- break;
7603
- case 'OBJECT_SCHEMA':
7604
- output.push('SCHEMA');
7605
- break;
7606
- case 'OBJECT_DATABASE':
7607
- output.push('DATABASE');
7608
- break;
7609
- case 'OBJECT_DOMAIN':
7610
- output.push('DOMAIN');
7611
- break;
7612
- case 'OBJECT_AGGREGATE':
7613
- output.push('AGGREGATE');
7614
- break;
7615
- case 'OBJECT_CONVERSION':
7616
- output.push('CONVERSION');
7617
- break;
7618
- case 'OBJECT_LANGUAGE':
7619
- output.push('LANGUAGE');
7620
- break;
7621
- case 'OBJECT_OPERATOR':
7622
- output.push('OPERATOR');
7623
- break;
7624
- case 'OBJECT_OPFAMILY':
7625
- output.push('OPERATOR FAMILY');
7626
- break;
7627
- case 'OBJECT_OPCLASS':
7628
- output.push('OPERATOR CLASS');
7629
- break;
7630
- case 'OBJECT_TSDICTIONARY':
7631
- output.push('TEXT SEARCH DICTIONARY');
7632
- break;
7633
- case 'OBJECT_TSCONFIGURATION':
7634
- output.push('TEXT SEARCH CONFIGURATION');
7635
- break;
7636
- case 'OBJECT_EVENT_TRIGGER':
7637
- output.push('EVENT TRIGGER');
7638
- break;
7639
- case 'OBJECT_FDW':
7640
- output.push('FOREIGN DATA WRAPPER');
7641
- break;
7642
- case 'OBJECT_FOREIGN_SERVER':
7643
- output.push('SERVER');
7644
- break;
7645
- case 'OBJECT_TYPE':
7646
- output.push('TYPE');
7647
- break;
7648
- case 'OBJECT_COLLATION':
7649
- output.push('COLLATION');
7650
- break;
7651
- default:
7652
- throw new Error(`Unsupported AlterOwnerStmt objectType: ${node.objectType}`);
7653
- }
7939
+ output.push(this.getObjectTypeKeyword(node.objectType));
7654
7940
  if (node.relation) {
7655
7941
  output.push(this.RangeVar(node.relation, context));
7656
7942
  }
@@ -7661,7 +7947,7 @@ export class Deparser {
7661
7947
  if (items.length === 2) {
7662
7948
  const accessMethod = items[0].String?.sval || '';
7663
7949
  const objectName = items[1].String?.sval || '';
7664
- output.push(`${QuoteUtils.quote(objectName)} USING ${accessMethod}`);
7950
+ output.push(`${QuoteUtils.quoteIdentifier(objectName)} USING ${accessMethod}`);
7665
7951
  }
7666
7952
  else {
7667
7953
  output.push(this.visit(node.object, context));
@@ -7736,6 +8022,9 @@ export class Deparser {
7736
8022
  else if (node.objtype === 'OBJECT_LANGUAGE') {
7737
8023
  output.push('LANGUAGE');
7738
8024
  }
8025
+ else if (node.objtype === 'OBJECT_SEQUENCE') {
8026
+ output.push('SEQUENCE');
8027
+ }
7739
8028
  else if (node.objtype === 'OBJECT_FUNCTION') {
7740
8029
  output.push('FUNCTION');
7741
8030
  }
@@ -7761,8 +8050,11 @@ export class Deparser {
7761
8050
  output.push('DATABASE');
7762
8051
  }
7763
8052
  if (node.objects && node.objects.length > 0) {
8053
+ const qualifiedKinds = ['OBJECT_TYPE', 'OBJECT_DOMAIN'];
7764
8054
  const objects = ListUtils.unwrapList(node.objects)
7765
- .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))
7766
8058
  .join(', ');
7767
8059
  output.push(objects);
7768
8060
  }
@@ -7801,8 +8093,11 @@ export class Deparser {
7801
8093
  break;
7802
8094
  default:
7803
8095
  if (node.objects && node.objects.length > 0) {
8096
+ const qualifiedKinds = ['OBJECT_TYPE', 'OBJECT_DOMAIN'];
7804
8097
  const objects = ListUtils.unwrapList(node.objects)
7805
- .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))
7806
8101
  .join(', ');
7807
8102
  output.push(objects);
7808
8103
  }
@@ -7822,6 +8117,9 @@ export class Deparser {
7822
8117
  if (node.grant_option && node.is_grant) {
7823
8118
  output.push('WITH GRANT OPTION');
7824
8119
  }
8120
+ if (node.grantor) {
8121
+ output.push('GRANTED BY', this.RoleSpec(node.grantor, context));
8122
+ }
7825
8123
  // Only add behavior clauses for REVOKE statements, not for GRANT statements in ALTER DEFAULT PRIVILEGES
7826
8124
  if (!node.is_grant) {
7827
8125
  if (node.behavior === 'DROP_CASCADE') {
@@ -7835,57 +8133,34 @@ export class Deparser {
7835
8133
  }
7836
8134
  GrantRoleStmt(node, context) {
7837
8135
  const output = [];
7838
- // Check for inherit, admin, and set options first to place them correctly
7839
- let hasInheritOption = false;
7840
- let hasAdminOption = false;
7841
- let hasSetOption = false;
7842
- let inheritValue;
7843
- let adminValue;
7844
- let setValue;
7845
- if (node.opt && node.opt.length > 0) {
7846
- const options = ListUtils.unwrapList(node.opt);
7847
- const inheritOption = options.find(opt => opt.DefElem && opt.DefElem.defname === 'inherit');
7848
- const adminOption = options.find(opt => (opt.String && opt.String.sval === 'admin') ||
7849
- (opt.DefElem && opt.DefElem.defname === 'admin'));
7850
- const setOption = options.find(opt => opt.DefElem && opt.DefElem.defname === 'set');
7851
- if (inheritOption && inheritOption.DefElem) {
7852
- hasInheritOption = true;
7853
- inheritValue = inheritOption.DefElem.arg?.Boolean?.boolval;
7854
- }
7855
- if (adminOption) {
7856
- hasAdminOption = true;
7857
- if (adminOption.DefElem && adminOption.DefElem.arg) {
7858
- adminValue = adminOption.DefElem.arg.Boolean?.boolval;
7859
- }
7860
- }
7861
- if (setOption && setOption.DefElem) {
7862
- hasSetOption = true;
7863
- setValue = setOption.DefElem.arg?.Boolean?.boolval;
7864
- }
7865
- }
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(', ');
7866
8155
  if (node.is_grant) {
7867
- output.push('GRANT');
8156
+ output.push('GRANT', roles, 'TO');
7868
8157
  }
7869
8158
  else {
7870
8159
  output.push('REVOKE');
7871
- if (hasInheritOption) {
7872
- output.push('INHERIT OPTION FOR');
7873
- }
7874
- else if (hasAdminOption) {
7875
- output.push('ADMIN OPTION FOR');
8160
+ if (options.length > 0) {
8161
+ output.push(`${options[0].defname.toUpperCase()} OPTION FOR`);
7876
8162
  }
7877
- }
7878
- if (node.granted_roles && node.granted_roles.length > 0) {
7879
- const roles = ListUtils.unwrapList(node.granted_roles)
7880
- .map(role => this.visit(role, context))
7881
- .join(', ');
7882
- output.push(roles);
7883
- }
7884
- if (node.is_grant) {
7885
- output.push('TO');
7886
- }
7887
- else {
7888
- output.push('FROM');
8163
+ output.push(roles, 'FROM');
7889
8164
  }
7890
8165
  if (node.grantee_roles && node.grantee_roles.length > 0) {
7891
8166
  const grantees = ListUtils.unwrapList(node.grantee_roles)
@@ -7893,45 +8168,22 @@ export class Deparser {
7893
8168
  .join(', ');
7894
8169
  output.push(grantees);
7895
8170
  }
7896
- if (node.is_grant) {
7897
- const withOptions = [];
7898
- if (hasAdminOption) {
7899
- if (adminValue === true) {
7900
- withOptions.push('ADMIN OPTION');
7901
- }
7902
- else if (adminValue === false) {
7903
- withOptions.push('ADMIN FALSE');
7904
- }
7905
- else {
7906
- withOptions.push('ADMIN OPTION');
7907
- }
7908
- }
7909
- if (hasInheritOption) {
7910
- if (inheritValue === true) {
7911
- withOptions.push('INHERIT OPTION');
7912
- }
7913
- else if (inheritValue === false) {
7914
- withOptions.push('INHERIT FALSE');
7915
- }
7916
- }
7917
- if (hasSetOption) {
7918
- if (setValue === true) {
7919
- withOptions.push('SET TRUE');
7920
- }
7921
- else if (setValue === false) {
7922
- withOptions.push('SET FALSE');
7923
- }
7924
- }
7925
- if (withOptions.length > 0) {
7926
- output.push('WITH', withOptions.join(', '));
7927
- }
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');
7928
8180
  }
7929
8181
  return output.join(' ');
7930
8182
  }
7931
8183
  SecLabelStmt(node, context) {
7932
8184
  const output = ['SECURITY LABEL'];
7933
8185
  if (node.provider) {
7934
- output.push('FOR', `"${node.provider}"`);
8186
+ output.push('FOR', QuoteUtils.quoteIdentifier(node.provider));
7935
8187
  }
7936
8188
  output.push('ON');
7937
8189
  if (node.objtype) {
@@ -7959,11 +8211,21 @@ export class Deparser {
7959
8211
  }
7960
8212
  }
7961
8213
  if (node.object) {
7962
- 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
+ }
7963
8225
  }
7964
8226
  output.push('IS');
7965
8227
  if (node.label) {
7966
- output.push(`'${node.label}'`);
8228
+ output.push(QuoteUtils.escape(node.label));
7967
8229
  }
7968
8230
  else {
7969
8231
  output.push('NULL');
@@ -8020,10 +8282,10 @@ export class Deparser {
8020
8282
  output.push(conversionName);
8021
8283
  }
8022
8284
  if (node.for_encoding_name) {
8023
- output.push('FOR', `'${node.for_encoding_name}'`);
8285
+ output.push('FOR', QuoteUtils.escape(node.for_encoding_name));
8024
8286
  }
8025
8287
  if (node.to_encoding_name) {
8026
- output.push('TO', `'${node.to_encoding_name}'`);
8288
+ output.push('TO', QuoteUtils.escape(node.to_encoding_name));
8027
8289
  }
8028
8290
  if (node.func_name && node.func_name.length > 0) {
8029
8291
  output.push('FROM');
@@ -8084,7 +8346,7 @@ export class Deparser {
8084
8346
  }
8085
8347
  output.push('LANGUAGE');
8086
8348
  if (node.plname) {
8087
- output.push(QuoteUtils.quote(node.plname));
8349
+ output.push(QuoteUtils.quoteIdentifier(node.plname));
8088
8350
  }
8089
8351
  if (node.plhandler && node.plhandler.length > 0) {
8090
8352
  output.push('HANDLER');
@@ -8120,7 +8382,7 @@ export class Deparser {
8120
8382
  }
8121
8383
  output.push('LANGUAGE');
8122
8384
  if (node.lang) {
8123
- output.push(QuoteUtils.quote(node.lang));
8385
+ output.push(QuoteUtils.quoteIdentifier(node.lang));
8124
8386
  }
8125
8387
  output.push('(');
8126
8388
  const transforms = [];
@@ -8148,7 +8410,7 @@ export class Deparser {
8148
8410
  }
8149
8411
  output.push('TRIGGER');
8150
8412
  if (node.trigname) {
8151
- output.push(QuoteUtils.quote(node.trigname));
8413
+ output.push(QuoteUtils.quoteIdentifier(node.trigname));
8152
8414
  }
8153
8415
  if (context.isPretty()) {
8154
8416
  const components = [];
@@ -8186,6 +8448,9 @@ export class Deparser {
8186
8448
  .join(' ');
8187
8449
  components.push(context.indent('REFERENCING ' + transitionClauses));
8188
8450
  }
8451
+ if (node.constrrel) {
8452
+ components.push(context.indent('FROM ' + this.RangeVar(node.constrrel, context)));
8453
+ }
8189
8454
  if (node.deferrable) {
8190
8455
  components.push(context.indent('DEFERRABLE'));
8191
8456
  }
@@ -8207,7 +8472,7 @@ export class Deparser {
8207
8472
  const funcName = ListUtils.unwrapList(node.funcname)
8208
8473
  .map(name => this.visit(name, context))
8209
8474
  .join('.');
8210
- executeStr += ' PROCEDURE ' + funcName;
8475
+ executeStr += ' FUNCTION ' + funcName;
8211
8476
  }
8212
8477
  if (node.args && node.args.length > 0) {
8213
8478
  const argContext = context.spawn('CreateTrigStmt', { isStringLiteral: true });
@@ -8312,14 +8577,14 @@ export class Deparser {
8312
8577
  output.push('OLD TABLE AS');
8313
8578
  }
8314
8579
  if (node.name) {
8315
- output.push(QuoteUtils.quote(node.name));
8580
+ output.push(QuoteUtils.quoteIdentifier(node.name));
8316
8581
  }
8317
8582
  return output.join(' ');
8318
8583
  }
8319
8584
  CreateEventTrigStmt(node, context) {
8320
8585
  const output = ['CREATE EVENT TRIGGER'];
8321
8586
  if (node.trigname) {
8322
- output.push(QuoteUtils.quote(node.trigname));
8587
+ output.push(QuoteUtils.quoteIdentifier(node.trigname));
8323
8588
  }
8324
8589
  output.push('ON');
8325
8590
  if (node.eventname) {
@@ -8345,7 +8610,7 @@ export class Deparser {
8345
8610
  AlterEventTrigStmt(node, context) {
8346
8611
  const output = ['ALTER EVENT TRIGGER'];
8347
8612
  if (node.trigname) {
8348
- output.push(QuoteUtils.quote(node.trigname));
8613
+ output.push(QuoteUtils.quoteIdentifier(node.trigname));
8349
8614
  }
8350
8615
  if (node.tgenabled) {
8351
8616
  switch (node.tgenabled) {
@@ -8465,6 +8730,74 @@ export class Deparser {
8465
8730
  .join(' ');
8466
8731
  output.push(whenClauses);
8467
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
+ }
8468
8801
  return output.join(' ');
8469
8802
  }
8470
8803
  AlterTableMoveAllStmt(node, context) {
@@ -8480,11 +8813,11 @@ export class Deparser {
8480
8813
  }
8481
8814
  output.push('ALL', 'IN', 'TABLESPACE');
8482
8815
  if (node.orig_tablespacename) {
8483
- output.push(QuoteUtils.quote(node.orig_tablespacename));
8816
+ output.push(QuoteUtils.quoteIdentifier(node.orig_tablespacename));
8484
8817
  }
8485
8818
  output.push('SET', 'TABLESPACE');
8486
8819
  if (node.new_tablespacename) {
8487
- output.push(QuoteUtils.quote(node.new_tablespacename));
8820
+ output.push(QuoteUtils.quoteIdentifier(node.new_tablespacename));
8488
8821
  }
8489
8822
  if (node.nowait) {
8490
8823
  output.push('NOWAIT');
@@ -8508,10 +8841,10 @@ export class Deparser {
8508
8841
  const sequenceName = [];
8509
8842
  const seq = node.sequence;
8510
8843
  if (seq.schemaname) {
8511
- sequenceName.push(QuoteUtils.quote(seq.schemaname));
8844
+ sequenceName.push(QuoteUtils.quoteIdentifier(seq.schemaname));
8512
8845
  }
8513
8846
  if (seq.relname) {
8514
- sequenceName.push(QuoteUtils.quote(seq.relname));
8847
+ sequenceName.push(QuoteUtils.quoteIdentifier(seq.relname));
8515
8848
  }
8516
8849
  output.push(sequenceName.join('.'));
8517
8850
  }
@@ -8545,10 +8878,10 @@ export class Deparser {
8545
8878
  const sequenceName = [];
8546
8879
  const seq = node.sequence;
8547
8880
  if (seq.schemaname) {
8548
- sequenceName.push(QuoteUtils.quote(seq.schemaname));
8881
+ sequenceName.push(QuoteUtils.quoteIdentifier(seq.schemaname));
8549
8882
  }
8550
8883
  if (seq.relname) {
8551
- sequenceName.push(QuoteUtils.quote(seq.relname));
8884
+ sequenceName.push(QuoteUtils.quoteIdentifier(seq.relname));
8552
8885
  }
8553
8886
  output.push(sequenceName.join('.'));
8554
8887
  }
@@ -8833,6 +9166,13 @@ export class Deparser {
8833
9166
  output.push('USING');
8834
9167
  output.push(node.into.accessMethod);
8835
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
+ }
8836
9176
  if (node.into && node.into.onCommit && node.into.onCommit !== 'ONCOMMIT_NOOP') {
8837
9177
  output.push('ON COMMIT');
8838
9178
  switch (node.into.onCommit) {
@@ -8847,17 +9187,13 @@ export class Deparser {
8847
9187
  break;
8848
9188
  }
8849
9189
  }
9190
+ if (node.into && node.into.tableSpaceName) {
9191
+ output.push('TABLESPACE', QuoteUtils.quoteIdentifier(node.into.tableSpaceName));
9192
+ }
8850
9193
  output.push('AS');
8851
9194
  if (node.query) {
8852
9195
  output.push(this.visit(node.query, context));
8853
9196
  }
8854
- if (node.into && node.into.options && node.into.options.length > 0) {
8855
- output.push('WITH');
8856
- const options = ListUtils.unwrapList(node.into.options)
8857
- .map(option => this.visit(option, context))
8858
- .join(', ');
8859
- output.push(`(${options})`);
8860
- }
8861
9197
  if (node.into && node.into.skipData) {
8862
9198
  output.push('WITH NO DATA');
8863
9199
  }
@@ -8895,7 +9231,7 @@ export class Deparser {
8895
9231
  }
8896
9232
  aliasname(node, context) {
8897
9233
  if (typeof node === 'string') {
8898
- return QuoteUtils.quote(node);
9234
+ return QuoteUtils.quoteIdentifier(node);
8899
9235
  }
8900
9236
  return this.visit(node, context);
8901
9237
  }
@@ -8927,18 +9263,16 @@ export class Deparser {
8927
9263
  const defName = defElem.defname;
8928
9264
  const defValue = defElem.arg;
8929
9265
  if (defName && defValue) {
8930
- let preservedDefName;
8931
- if (Deparser.needsQuotes(defName)) {
8932
- preservedDefName = `"${defName}"`;
8933
- }
8934
- else {
8935
- preservedDefName = this.preserveOperatorDefElemCase(defName);
8936
- }
9266
+ const quotedDefName = QuoteUtils.quoteIdentifier(defName);
9267
+ const preservedDefName = quotedDefName !== defName ? quotedDefName : this.preserveOperatorDefElemCase(defName);
8937
9268
  if ((defName.toLowerCase() === 'commutator' || defName.toLowerCase() === 'negator') && defValue.List) {
8938
9269
  const listItems = ListUtils.unwrapList(defValue.List.items);
8939
9270
  if (listItems.length === 1 && listItems[0].String) {
8940
9271
  return `${preservedDefName} = ${listItems[0].String.sval}`;
8941
9272
  }
9273
+ if (listItems.length > 1 && listItems.every(item => item.String)) {
9274
+ return `${preservedDefName} = OPERATOR(${this.qualifiedOperatorName(listItems)})`;
9275
+ }
8942
9276
  }
8943
9277
  // For commutator/negator, we already handled them above
8944
9278
  if ((defName.toLowerCase() === 'commutator' || defName.toLowerCase() === 'negator')) {
@@ -8949,7 +9283,7 @@ export class Deparser {
8949
9283
  else if (defName && !defValue) {
8950
9284
  // Handle boolean flags like HASHES, MERGES - preserve original case
8951
9285
  if (defName === 'Hashes' || defName === 'Merges') {
8952
- return `"${defName}"`;
9286
+ return QuoteUtils.quoteIdentifier(defName);
8953
9287
  }
8954
9288
  return this.preserveOperatorDefElemCase(defName).toUpperCase();
8955
9289
  }
@@ -9088,16 +9422,11 @@ export class Deparser {
9088
9422
  const defName = defElem.defname;
9089
9423
  const defValue = defElem.arg;
9090
9424
  if (defName && defValue) {
9091
- let preservedDefName;
9092
- if (Deparser.needsQuotes(defName)) {
9093
- preservedDefName = `"${defName}"`;
9094
- }
9095
- else {
9096
- preservedDefName = defName;
9097
- }
9425
+ const quotedDefName = QuoteUtils.quoteIdentifier(defName);
9426
+ const preservedDefName = quotedDefName !== defName ? quotedDefName : defName;
9098
9427
  // Handle String arguments with single quotes for string literals
9099
9428
  if (defValue.String) {
9100
- return `${preservedDefName} = '${defValue.String.sval}'`;
9429
+ return `${preservedDefName} = ${QuoteUtils.escape(defValue.String.sval)}`;
9101
9430
  }
9102
9431
  return `${preservedDefName} = ${this.visit(defValue, context)}`;
9103
9432
  }
@@ -9220,7 +9549,7 @@ export class Deparser {
9220
9549
  // For CREATE COLLATION, ensure String nodes are quoted as string literals
9221
9550
  let valueStr;
9222
9551
  if (defValue.String) {
9223
- valueStr = `'${defValue.String.sval}'`;
9552
+ valueStr = QuoteUtils.escape(defValue.String.sval);
9224
9553
  }
9225
9554
  else {
9226
9555
  valueStr = this.visit(defValue, context);
@@ -9249,7 +9578,7 @@ export class Deparser {
9249
9578
  AlterDatabaseStmt(node, context) {
9250
9579
  const output = ['ALTER', 'DATABASE'];
9251
9580
  if (node.dbname) {
9252
- output.push(QuoteUtils.quote(node.dbname));
9581
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
9253
9582
  }
9254
9583
  if (node.options && node.options.length > 0) {
9255
9584
  const options = ListUtils.unwrapList(node.options).map(opt => this.visit(opt, context));
@@ -9260,7 +9589,7 @@ export class Deparser {
9260
9589
  AlterDatabaseRefreshCollStmt(node, context) {
9261
9590
  const output = ['ALTER', 'DATABASE'];
9262
9591
  if (node.dbname) {
9263
- output.push(QuoteUtils.quote(node.dbname));
9592
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
9264
9593
  }
9265
9594
  output.push('REFRESH', 'COLLATION', 'VERSION');
9266
9595
  return output.join(' ');
@@ -9268,7 +9597,7 @@ export class Deparser {
9268
9597
  AlterDatabaseSetStmt(node, context) {
9269
9598
  const output = ['ALTER', 'DATABASE'];
9270
9599
  if (node.dbname) {
9271
- output.push(QuoteUtils.quote(node.dbname));
9600
+ output.push(QuoteUtils.quoteIdentifier(node.dbname));
9272
9601
  }
9273
9602
  if (node.setstmt) {
9274
9603
  const setClause = this.VariableSetStmt(node.setstmt, context);
@@ -9279,7 +9608,7 @@ export class Deparser {
9279
9608
  DeclareCursorStmt(node, context) {
9280
9609
  const output = ['DECLARE'];
9281
9610
  if (node.portalname) {
9282
- output.push(QuoteUtils.quote(node.portalname));
9611
+ output.push(QuoteUtils.quoteIdentifier(node.portalname));
9283
9612
  }
9284
9613
  // Handle cursor options before CURSOR keyword
9285
9614
  const cursorOptions = [];
@@ -9321,7 +9650,7 @@ export class Deparser {
9321
9650
  else if (node.pubobjtype === 'PUBLICATIONOBJ_TABLES_IN_SCHEMA') {
9322
9651
  output.push('TABLES IN SCHEMA');
9323
9652
  if (node.name) {
9324
- output.push(QuoteUtils.quote(node.name));
9653
+ output.push(QuoteUtils.quoteIdentifier(node.name));
9325
9654
  }
9326
9655
  }
9327
9656
  else if (node.pubobjtype === 'PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA') {
@@ -9340,14 +9669,14 @@ export class Deparser {
9340
9669
  }
9341
9670
  if (node.whereClause) {
9342
9671
  output.push('WHERE');
9343
- output.push(this.visit(node.whereClause, context));
9672
+ output.push(`(${this.visit(node.whereClause, context)})`);
9344
9673
  }
9345
9674
  return output.join(' ');
9346
9675
  }
9347
9676
  CreateAmStmt(node, context) {
9348
9677
  const output = ['CREATE', 'ACCESS', 'METHOD'];
9349
9678
  if (node.amname) {
9350
- output.push(QuoteUtils.quote(node.amname));
9679
+ output.push(QuoteUtils.quoteIdentifier(node.amname));
9351
9680
  }
9352
9681
  output.push('TYPE');
9353
9682
  switch (node.amtype) {
@@ -9407,7 +9736,7 @@ export class Deparser {
9407
9736
  }
9408
9737
  }
9409
9738
  if (node.tableSpaceName) {
9410
- output.push('TABLESPACE', QuoteUtils.quote(node.tableSpaceName));
9739
+ output.push('TABLESPACE', QuoteUtils.quoteIdentifier(node.tableSpaceName));
9411
9740
  }
9412
9741
  return output.join(' ');
9413
9742
  }
@@ -9487,6 +9816,23 @@ export class Deparser {
9487
9816
  }
9488
9817
  return output.join(' ');
9489
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
+ }
9490
9836
  Var(node, context) {
9491
9837
  if (node.varno && node.varattno) {
9492
9838
  return `$${node.varno}.${node.varattno}`;
@@ -9526,7 +9872,7 @@ export class Deparser {
9526
9872
  }
9527
9873
  if (node.rowexpr) {
9528
9874
  output.push(',');
9529
- output.push(`'${this.visit(node.rowexpr, context)}'`);
9875
+ output.push(this.visit(node.rowexpr, context));
9530
9876
  }
9531
9877
  if (node.colexprs && node.colexprs.length > 0) {
9532
9878
  output.push('COLUMNS');
@@ -9543,29 +9889,44 @@ export class Deparser {
9543
9889
  if (node.lateral) {
9544
9890
  output.push('LATERAL');
9545
9891
  }
9546
- if (node.docexpr) {
9547
- 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(', ')}),`);
9548
9900
  }
9549
9901
  if (node.rowexpr) {
9550
- output.push('PASSING');
9551
- 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));
9552
9907
  }
9553
9908
  if (node.columns && node.columns.length > 0) {
9554
- output.push('COLUMNS');
9555
- const columns = ListUtils.unwrapList(node.columns)
9909
+ args.push('COLUMNS');
9910
+ args.push(ListUtils.unwrapList(node.columns)
9556
9911
  .map(col => this.visit(col, context))
9557
- .join(', ');
9558
- output.push(`(${columns})`);
9912
+ .join(', '));
9559
9913
  }
9914
+ output.push(`XMLTABLE(${args.join(' ')})`);
9560
9915
  if (node.alias) {
9561
9916
  output.push(this.Alias(node.alias, context));
9562
9917
  }
9563
9918
  return output.join(' ');
9564
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
+ }
9565
9926
  RangeTableFuncCol(node, context) {
9566
9927
  const output = [];
9567
9928
  if (node.colname) {
9568
- output.push(QuoteUtils.quote(node.colname));
9929
+ output.push(QuoteUtils.quoteIdentifier(node.colname));
9569
9930
  }
9570
9931
  if (node.for_ordinality) {
9571
9932
  output.push('FOR ORDINALITY');
@@ -9575,32 +9936,193 @@ export class Deparser {
9575
9936
  }
9576
9937
  if (node.colexpr) {
9577
9938
  output.push('PATH');
9578
- output.push(`'${this.visit(node.colexpr, context)}'`);
9939
+ output.push(this.visit(node.colexpr, context));
9579
9940
  }
9580
9941
  if (node.coldefexpr) {
9581
9942
  output.push('DEFAULT');
9582
9943
  output.push(this.visit(node.coldefexpr, context));
9583
9944
  }
9945
+ if (node.is_not_null) {
9946
+ output.push('NOT NULL');
9947
+ }
9584
9948
  return output.join(' ');
9585
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
+ }
9586
10023
  JsonArrayQueryConstructor(node, context) {
9587
- const output = ['JSON_ARRAYAGG'];
10024
+ const args = [];
9588
10025
  if (node.query) {
9589
- 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)})`;
9590
10042
  }
9591
- if (node.format) {
9592
- output.push('FORMAT JSON');
10043
+ if (ctor.over) {
10044
+ result += this.formatOverClause(ctor.over, context);
9593
10045
  }
9594
- if (node.output) {
9595
- 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));
9596
10066
  }
9597
- if (node.absent_on_null) {
9598
- 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(', ')}`);
9599
10071
  }
9600
- else {
9601
- 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));
9602
10082
  }
9603
- 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(' ');
9604
10126
  }
9605
10127
  RangeFunction(node, context) {
9606
10128
  const output = [];
@@ -9711,10 +10233,10 @@ export class Deparser {
9711
10233
  if (node.op === 'IS_XMLPI') {
9712
10234
  if (node.name && node.args && node.args.length > 0) {
9713
10235
  const argStrs = ListUtils.unwrapList(node.args).map(arg => this.visit(arg, context));
9714
- return `xmlpi(name ${QuoteUtils.quote(node.name)}, ${argStrs.join(', ')})`;
10236
+ return `xmlpi(name ${QuoteUtils.quoteIdentifier(node.name)}, ${argStrs.join(', ')})`;
9715
10237
  }
9716
10238
  else if (node.name) {
9717
- return `xmlpi(name ${QuoteUtils.quote(node.name)})`;
10239
+ return `xmlpi(name ${QuoteUtils.quoteIdentifier(node.name)})`;
9718
10240
  }
9719
10241
  else {
9720
10242
  return 'XMLPI()';
@@ -9729,7 +10251,7 @@ export class Deparser {
9729
10251
  output.push('XMLELEMENT');
9730
10252
  const elementParts = [];
9731
10253
  if (node.name) {
9732
- elementParts.push(`NAME ${QuoteUtils.quote(node.name)}`);
10254
+ elementParts.push(`NAME ${QuoteUtils.quoteIdentifier(node.name)}`);
9733
10255
  }
9734
10256
  if (node.named_args && node.named_args.length > 0) {
9735
10257
  const namedArgStrs = ListUtils.unwrapList(node.named_args).map(arg => this.visit(arg, context));
@@ -9794,6 +10316,7 @@ export class Deparser {
9794
10316
  rootParts.push('STANDALONE NO VALUE');
9795
10317
  }
9796
10318
  else if (standaloneArg.A_Const.ival.ival === 3) {
10319
+ // STANDALONE omitted
9797
10320
  }
9798
10321
  else if (Object.keys(standaloneArg.A_Const.ival).length === 0) {
9799
10322
  rootParts.push('STANDALONE YES');
@@ -9829,7 +10352,7 @@ export class Deparser {
9829
10352
  // Handle name and args for operations that don't have special handling
9830
10353
  if (node.op !== 'IS_XMLELEMENT' && node.op !== 'IS_XMLPARSE' && node.op !== 'IS_XMLROOT' && node.op !== 'IS_DOCUMENT') {
9831
10354
  if (node.name) {
9832
- const quotedName = QuoteUtils.quote(node.name);
10355
+ const quotedName = QuoteUtils.quoteIdentifier(node.name);
9833
10356
  output.push(`NAME ${quotedName}`);
9834
10357
  }
9835
10358
  if (node.args && node.args.length > 0) {
@@ -9850,26 +10373,26 @@ export class Deparser {
9850
10373
  }
9851
10374
  schemaname(node, context) {
9852
10375
  if (typeof node === 'string') {
9853
- return QuoteUtils.quote(node);
10376
+ return QuoteUtils.quoteIdentifier(node);
9854
10377
  }
9855
10378
  if (node && node.String && node.String.sval) {
9856
- return QuoteUtils.quote(node.String.sval);
10379
+ return QuoteUtils.quoteIdentifier(node.String.sval);
9857
10380
  }
9858
10381
  // Handle other node types without recursion
9859
10382
  if (node && typeof node === 'object') {
9860
10383
  if (node.sval !== undefined) {
9861
- return QuoteUtils.quote(node.sval);
10384
+ return QuoteUtils.quoteIdentifier(node.sval);
9862
10385
  }
9863
10386
  // Handle List nodes that might contain schema names
9864
10387
  if (node.List && Array.isArray(node.List.items)) {
9865
10388
  const items = node.List.items;
9866
10389
  if (items.length > 0 && items[0].String && items[0].String.sval) {
9867
- return QuoteUtils.quote(items[0].String.sval);
10390
+ return QuoteUtils.quoteIdentifier(items[0].String.sval);
9868
10391
  }
9869
10392
  }
9870
10393
  // For other complex nodes, try to extract string value without recursion
9871
10394
  if (node.val !== undefined) {
9872
- return QuoteUtils.quote(node.val);
10395
+ return QuoteUtils.quoteIdentifier(node.val);
9873
10396
  }
9874
10397
  return '';
9875
10398
  }
@@ -9932,7 +10455,7 @@ export class Deparser {
9932
10455
  }
9933
10456
  output.push('RULE');
9934
10457
  if (node.rulename) {
9935
- output.push(QuoteUtils.quote(node.rulename));
10458
+ output.push(QuoteUtils.quoteIdentifier(node.rulename));
9936
10459
  }
9937
10460
  output.push('AS ON');
9938
10461
  if (node.event) {
@@ -9999,42 +10522,42 @@ export class Deparser {
9999
10522
  }
10000
10523
  relname(node, context) {
10001
10524
  if (typeof node === 'string') {
10002
- return QuoteUtils.quote(node);
10525
+ return QuoteUtils.quoteIdentifier(node);
10003
10526
  }
10004
10527
  if (node && node.String && node.String.sval) {
10005
- return QuoteUtils.quote(node.String.sval);
10528
+ return QuoteUtils.quoteIdentifier(node.String.sval);
10006
10529
  }
10007
10530
  if (node && typeof node === 'object' && node.relname) {
10008
- return QuoteUtils.quote(node.relname);
10531
+ return QuoteUtils.quoteIdentifier(node.relname);
10009
10532
  }
10010
10533
  return this.visit(node, context);
10011
10534
  }
10012
10535
  rel(node, context) {
10013
10536
  if (typeof node === 'string') {
10014
- return QuoteUtils.quote(node);
10537
+ return QuoteUtils.quoteIdentifier(node);
10015
10538
  }
10016
10539
  if (node && node.String && node.String.sval) {
10017
- return QuoteUtils.quote(node.String.sval);
10540
+ return QuoteUtils.quoteIdentifier(node.String.sval);
10018
10541
  }
10019
10542
  if (node && node.RangeVar) {
10020
10543
  return this.RangeVar(node.RangeVar, context);
10021
10544
  }
10022
10545
  if (node && typeof node === 'object' && node.relname) {
10023
- return QuoteUtils.quote(node.relname);
10546
+ return QuoteUtils.quoteIdentifier(node.relname);
10024
10547
  }
10025
10548
  return this.visit(node, context);
10026
10549
  }
10027
10550
  objname(node, context) {
10028
10551
  if (typeof node === 'string') {
10029
- return QuoteUtils.quote(node);
10552
+ return QuoteUtils.quoteIdentifier(node);
10030
10553
  }
10031
10554
  if (node && node.String && node.String.sval) {
10032
- return QuoteUtils.quote(node.String.sval);
10555
+ return QuoteUtils.quoteIdentifier(node.String.sval);
10033
10556
  }
10034
10557
  if (Array.isArray(node)) {
10035
10558
  const parts = node.map(part => {
10036
10559
  if (part && part.String && part.String.sval) {
10037
- return QuoteUtils.quote(part.String.sval);
10560
+ return QuoteUtils.quoteIdentifier(part.String.sval);
10038
10561
  }
10039
10562
  return this.visit(part, context);
10040
10563
  });
@@ -10105,7 +10628,7 @@ export class Deparser {
10105
10628
  CurrentOfExpr(node, context) {
10106
10629
  const output = ['CURRENT OF'];
10107
10630
  if (node.cursor_name) {
10108
- output.push(QuoteUtils.quote(node.cursor_name));
10631
+ output.push(QuoteUtils.quoteIdentifier(node.cursor_name));
10109
10632
  }
10110
10633
  if (node.cursor_param > 0) {
10111
10634
  output.push(`$${node.cursor_param}`);
@@ -10168,66 +10691,12 @@ export class Deparser {
10168
10691
  }
10169
10692
  AlterObjectSchemaStmt(node, context) {
10170
10693
  const output = ['ALTER'];
10171
- switch (node.objectType) {
10172
- case 'OBJECT_TABLE':
10173
- output.push('TABLE');
10174
- break;
10175
- case 'OBJECT_VIEW':
10176
- output.push('VIEW');
10177
- break;
10178
- case 'OBJECT_FUNCTION':
10179
- output.push('FUNCTION');
10180
- break;
10181
- case 'OBJECT_TYPE':
10182
- output.push('TYPE');
10183
- break;
10184
- case 'OBJECT_DOMAIN':
10185
- output.push('DOMAIN');
10186
- break;
10187
- case 'OBJECT_SEQUENCE':
10188
- output.push('SEQUENCE');
10189
- break;
10190
- case 'OBJECT_OPCLASS':
10191
- output.push('OPERATOR CLASS');
10192
- break;
10193
- case 'OBJECT_OPFAMILY':
10194
- output.push('OPERATOR FAMILY');
10195
- break;
10196
- case 'OBJECT_OPERATOR':
10197
- output.push('OPERATOR');
10198
- break;
10199
- case 'OBJECT_TYPE':
10200
- output.push('TYPE');
10201
- break;
10202
- case 'OBJECT_COLLATION':
10203
- output.push('COLLATION');
10204
- break;
10205
- case 'OBJECT_CONVERSION':
10206
- output.push('CONVERSION');
10207
- break;
10208
- case 'OBJECT_TSPARSER':
10209
- output.push('TEXT SEARCH PARSER');
10210
- break;
10211
- case 'OBJECT_TSCONFIGURATION':
10212
- output.push('TEXT SEARCH CONFIGURATION');
10213
- break;
10214
- case 'OBJECT_TSTEMPLATE':
10215
- output.push('TEXT SEARCH TEMPLATE');
10216
- break;
10217
- case 'OBJECT_TSDICTIONARY':
10218
- output.push('TEXT SEARCH DICTIONARY');
10219
- break;
10220
- case 'OBJECT_AGGREGATE':
10221
- output.push('AGGREGATE');
10222
- break;
10223
- case 'OBJECT_FOREIGN_TABLE':
10224
- output.push('FOREIGN TABLE');
10225
- break;
10226
- case 'OBJECT_MATVIEW':
10227
- output.push('MATERIALIZED VIEW');
10228
- break;
10229
- default:
10230
- output.push(node.objectType.toString());
10694
+ try {
10695
+ output.push(this.getObjectTypeKeyword(node.objectType));
10696
+ }
10697
+ catch {
10698
+ // Fallback to objectType string if not supported
10699
+ output.push(node.objectType.toString());
10231
10700
  }
10232
10701
  if (node.missing_ok) {
10233
10702
  output.push('IF EXISTS');
@@ -10242,7 +10711,7 @@ export class Deparser {
10242
10711
  if (items.length === 2) {
10243
10712
  const schemaName = items[0].String?.sval || '';
10244
10713
  const domainName = items[1].String?.sval || '';
10245
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(domainName)}`);
10714
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, domainName));
10246
10715
  }
10247
10716
  else {
10248
10717
  output.push(this.visit(node.object, context));
@@ -10254,7 +10723,7 @@ export class Deparser {
10254
10723
  if (items.length === 2) {
10255
10724
  const schemaName = items[0].String?.sval || '';
10256
10725
  const typeName = items[1].String?.sval || '';
10257
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(typeName)}`);
10726
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, typeName));
10258
10727
  }
10259
10728
  else {
10260
10729
  output.push(this.visit(node.object, context));
@@ -10266,7 +10735,7 @@ export class Deparser {
10266
10735
  if (items.length === 2) {
10267
10736
  const schemaName = items[0].String?.sval || '';
10268
10737
  const conversionName = items[1].String?.sval || '';
10269
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(conversionName)}`);
10738
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, conversionName));
10270
10739
  }
10271
10740
  else {
10272
10741
  output.push(this.visit(node.object, context));
@@ -10278,7 +10747,7 @@ export class Deparser {
10278
10747
  if (items.length === 2) {
10279
10748
  const schemaName = items[0].String?.sval || '';
10280
10749
  const parserName = items[1].String?.sval || '';
10281
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(parserName)}`);
10750
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, parserName));
10282
10751
  }
10283
10752
  else {
10284
10753
  output.push(this.visit(node.object, context));
@@ -10290,7 +10759,7 @@ export class Deparser {
10290
10759
  if (items.length === 2) {
10291
10760
  const schemaName = items[0].String?.sval || '';
10292
10761
  const configName = items[1].String?.sval || '';
10293
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(configName)}`);
10762
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, configName));
10294
10763
  }
10295
10764
  else {
10296
10765
  output.push(this.visit(node.object, context));
@@ -10302,7 +10771,7 @@ export class Deparser {
10302
10771
  if (items.length === 2) {
10303
10772
  const schemaName = items[0].String?.sval || '';
10304
10773
  const templateName = items[1].String?.sval || '';
10305
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(templateName)}`);
10774
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, templateName));
10306
10775
  }
10307
10776
  else {
10308
10777
  output.push(this.visit(node.object, context));
@@ -10314,7 +10783,7 @@ export class Deparser {
10314
10783
  if (items.length === 2) {
10315
10784
  const schemaName = items[0].String?.sval || '';
10316
10785
  const dictionaryName = items[1].String?.sval || '';
10317
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(dictionaryName)}`);
10786
+ output.push(QuoteUtils.quoteQualifiedIdentifier(schemaName, dictionaryName));
10318
10787
  }
10319
10788
  else {
10320
10789
  output.push(this.visit(node.object, context));
@@ -10326,13 +10795,13 @@ export class Deparser {
10326
10795
  if (items.length === 2) {
10327
10796
  const accessMethod = items[0].String?.sval || '';
10328
10797
  const opClassName = items[1].String?.sval || '';
10329
- output.push(`${QuoteUtils.quote(opClassName)} USING ${accessMethod}`);
10798
+ output.push(`${QuoteUtils.quoteIdentifier(opClassName)} USING ${accessMethod}`);
10330
10799
  }
10331
10800
  else if (items.length === 3) {
10332
10801
  const accessMethod = items[0].String?.sval || '';
10333
10802
  const schemaName = items[1].String?.sval || '';
10334
10803
  const opClassName = items[2].String?.sval || '';
10335
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(opClassName)} USING ${accessMethod}`);
10804
+ output.push(`${QuoteUtils.quoteQualifiedIdentifier(schemaName, opClassName)} USING ${accessMethod}`);
10336
10805
  }
10337
10806
  else {
10338
10807
  output.push(this.visit(node.object, context));
@@ -10344,13 +10813,13 @@ export class Deparser {
10344
10813
  if (items.length === 2) {
10345
10814
  const accessMethod = items[0].String?.sval || '';
10346
10815
  const opFamilyName = items[1].String?.sval || '';
10347
- output.push(`${QuoteUtils.quote(opFamilyName)} USING ${accessMethod}`);
10816
+ output.push(`${QuoteUtils.quoteIdentifier(opFamilyName)} USING ${accessMethod}`);
10348
10817
  }
10349
10818
  else if (items.length === 3) {
10350
10819
  const accessMethod = items[0].String?.sval || '';
10351
10820
  const schemaName = items[1].String?.sval || '';
10352
10821
  const opFamilyName = items[2].String?.sval || '';
10353
- output.push(`${QuoteUtils.quote(schemaName)}.${QuoteUtils.quote(opFamilyName)} USING ${accessMethod}`);
10822
+ output.push(`${QuoteUtils.quoteQualifiedIdentifier(schemaName, opFamilyName)} USING ${accessMethod}`);
10354
10823
  }
10355
10824
  else {
10356
10825
  output.push(this.visit(node.object, context));
@@ -10362,7 +10831,7 @@ export class Deparser {
10362
10831
  }
10363
10832
  output.push('SET SCHEMA');
10364
10833
  if (node.newschema) {
10365
- output.push(QuoteUtils.quote(node.newschema));
10834
+ output.push(QuoteUtils.quoteIdentifier(node.newschema));
10366
10835
  }
10367
10836
  return output.join(' ');
10368
10837
  }
@@ -10431,7 +10900,7 @@ export class Deparser {
10431
10900
  }
10432
10901
  if (node.servername) {
10433
10902
  output.push('SERVER');
10434
- output.push(QuoteUtils.quote(node.servername));
10903
+ output.push(QuoteUtils.quoteIdentifier(node.servername));
10435
10904
  }
10436
10905
  if (node.options && node.options.length > 0) {
10437
10906
  const foreignTableContext = context.spawn('CreateForeignTableStmt');
@@ -10440,8 +10909,4 @@ export class Deparser {
10440
10909
  }
10441
10910
  return output.join(' ');
10442
10911
  }
10443
- containsMultilineStringLiteral(content) {
10444
- const stringLiteralRegex = /'[^']*\n[^']*'/g;
10445
- return stringLiteralRegex.test(content);
10446
- }
10447
10912
  }