pgsql-deparser 17.18.4 → 18.0.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.
package/deparser.d.ts CHANGED
@@ -80,6 +80,10 @@ export declare class Deparser implements DeparserVisitor {
80
80
  DeleteStmt(node: t.DeleteStmt, context: DeparserContext): string;
81
81
  WithClause(node: t.WithClause, context: DeparserContext): string;
82
82
  ResTarget(node: t.ResTarget, context: DeparserContext): string;
83
+ ATAlterConstraint(node: t.ATAlterConstraint, context: DeparserContext): string;
84
+ deparseKeyAttrs(attrs: t.Node[], withPeriod: boolean | undefined, context: DeparserContext): string;
85
+ ReturningClause(node: t.ReturningClause, context: DeparserContext): string;
86
+ ReturningOption(node: t.ReturningOption, context: DeparserContext): string;
83
87
  deparseReturningList(list: t.Node[], context: DeparserContext): string;
84
88
  BoolExpr(node: t.BoolExpr, context: DeparserContext): string;
85
89
  FuncCall(node: t.FuncCall, context: DeparserContext): string;
@@ -247,6 +251,8 @@ export declare class Deparser implements DeparserVisitor {
247
251
  CreateOpFamilyStmt(node: t.CreateOpFamilyStmt, context: DeparserContext): string;
248
252
  AlterOpFamilyStmt(node: t.AlterOpFamilyStmt, context: DeparserContext): string;
249
253
  MergeStmt(node: t.MergeStmt, context: DeparserContext): string;
254
+ MergeSupportFunc(node: t.MergeSupportFunc, context: DeparserContext): string;
255
+ MergeWhenClause(node: t.MergeWhenClause, context: DeparserContext): string;
250
256
  AlterTableMoveAllStmt(node: t.AlterTableMoveAllStmt, context: DeparserContext): string;
251
257
  CreateSeqStmt(node: t.CreateSeqStmt, context: DeparserContext): string;
252
258
  AlterSeqStmt(node: t.AlterSeqStmt, context: DeparserContext): string;
package/deparser.js CHANGED
@@ -1045,11 +1045,8 @@ class Deparser {
1045
1045
  output.push('DO NOTHING');
1046
1046
  }
1047
1047
  }
1048
- if (node.returningList) {
1049
- output.push('RETURNING');
1050
- const returningList = list_utils_1.ListUtils.unwrapList(node.returningList);
1051
- const returns = returningList.map(ret => this.visit(ret, context));
1052
- output.push(returns.join(', '));
1048
+ if (node.returningClause) {
1049
+ output.push(this.ReturningClause(node.returningClause, context));
1053
1050
  }
1054
1051
  return output.join(' ');
1055
1052
  }
@@ -1106,9 +1103,8 @@ class Deparser {
1106
1103
  output.push('WHERE');
1107
1104
  output.push(this.visit(node.whereClause, context));
1108
1105
  }
1109
- if (node.returningList) {
1110
- output.push('RETURNING');
1111
- output.push(this.deparseReturningList(node.returningList, context));
1106
+ if (node.returningClause) {
1107
+ output.push(this.ReturningClause(node.returningClause, context));
1112
1108
  }
1113
1109
  return output.join(' ');
1114
1110
  }
@@ -1159,13 +1155,12 @@ class Deparser {
1159
1155
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1160
1156
  }
1161
1157
  }
1162
- if (node.returningList) {
1163
- output.push('RETURNING');
1158
+ if (node.returningClause) {
1164
1159
  try {
1165
- output.push(this.deparseReturningList(node.returningList, context));
1160
+ output.push(this.ReturningClause(node.returningClause, context));
1166
1161
  }
1167
1162
  catch (error) {
1168
- console.warn(`Error processing returningList in DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1163
+ console.warn(`Error processing returningClause in DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1169
1164
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1170
1165
  }
1171
1166
  }
@@ -1243,6 +1238,48 @@ class Deparser {
1243
1238
  }
1244
1239
  return output.join(' ');
1245
1240
  }
1241
+ ATAlterConstraint(node, context) {
1242
+ const output = ['ALTER CONSTRAINT'];
1243
+ if (node.conname) {
1244
+ output.push(quotes_1.QuoteUtils.quoteIdentifier(node.conname));
1245
+ }
1246
+ if (node.alterEnforceability) {
1247
+ output.push(node.is_enforced ? 'ENFORCED' : 'NOT ENFORCED');
1248
+ }
1249
+ if (node.alterDeferrability) {
1250
+ output.push(node.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
1251
+ if (node.initdeferred) {
1252
+ output.push('INITIALLY DEFERRED');
1253
+ }
1254
+ }
1255
+ if (node.alterInheritability) {
1256
+ output.push(node.noinherit ? 'NO INHERIT' : 'INHERIT');
1257
+ }
1258
+ return output.join(' ');
1259
+ }
1260
+ deparseKeyAttrs(attrs, withPeriod, context) {
1261
+ const items = list_utils_1.ListUtils.unwrapList(attrs).map(attr => this.visit(attr, context));
1262
+ if (withPeriod && items.length > 0) {
1263
+ items[items.length - 1] = `PERIOD ${items[items.length - 1]}`;
1264
+ }
1265
+ return items.join(', ');
1266
+ }
1267
+ ReturningClause(node, context) {
1268
+ const output = ['RETURNING'];
1269
+ if (node.options && node.options.length > 0) {
1270
+ const options = list_utils_1.ListUtils.unwrapList(node.options)
1271
+ .map(option => this.visit(option, context));
1272
+ output.push(`WITH (${options.join(', ')})`);
1273
+ }
1274
+ if (node.exprs && node.exprs.length > 0) {
1275
+ output.push(this.deparseReturningList(node.exprs, context));
1276
+ }
1277
+ return output.join(' ');
1278
+ }
1279
+ ReturningOption(node, context) {
1280
+ const keyword = node.option === 'RETURNING_OPTION_NEW' ? 'NEW' : 'OLD';
1281
+ return `${keyword} AS ${quotes_1.QuoteUtils.quoteIdentifier(node.value)}`;
1282
+ }
1246
1283
  deparseReturningList(list, context) {
1247
1284
  return list_utils_1.ListUtils.unwrapList(list)
1248
1285
  .filter(item => item != null && this.getNodeType(item) !== 'undefined')
@@ -2539,7 +2576,7 @@ class Deparser {
2539
2576
  Constraint(node, context) {
2540
2577
  const output = [];
2541
2578
  // Handle constraint name if present
2542
- if (node.conname && (node.contype === 'CONSTR_CHECK' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_FOREIGN')) {
2579
+ if (node.conname && (node.contype === 'CONSTR_CHECK' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_FOREIGN' || node.contype === 'CONSTR_NOTNULL')) {
2543
2580
  output.push('CONSTRAINT');
2544
2581
  output.push(quotes_1.QuoteUtils.quoteIdentifier(node.conname));
2545
2582
  }
@@ -2549,6 +2586,18 @@ class Deparser {
2549
2586
  break;
2550
2587
  case 'CONSTR_NOTNULL':
2551
2588
  output.push('NOT NULL');
2589
+ if (node.keys && node.keys.length > 0 && !context.isColumnConstraint) {
2590
+ const keyList = list_utils_1.ListUtils.unwrapList(node.keys)
2591
+ .map(key => this.visit(key, context))
2592
+ .join(', ');
2593
+ output.push(keyList);
2594
+ }
2595
+ if (node.is_no_inherit) {
2596
+ output.push('NO INHERIT');
2597
+ }
2598
+ if (node.skip_validation) {
2599
+ output.push('NOT VALID');
2600
+ }
2552
2601
  break;
2553
2602
  case 'CONSTR_DEFAULT':
2554
2603
  output.push('DEFAULT');
@@ -2577,8 +2626,13 @@ class Deparser {
2577
2626
  output.push(context.parens(this.visit(node.raw_expr, context)));
2578
2627
  }
2579
2628
  }
2580
- // Handle NOT VALID for check constraints
2581
- if (node.skip_validation) {
2629
+ // PG18: constraints are enforced unless explicitly NOT ENFORCED
2630
+ // (libpg-query omits false booleans, so undefined means not enforced)
2631
+ if (node.is_enforced !== true && !context.isDomainConstraint) {
2632
+ output.push('NOT ENFORCED');
2633
+ }
2634
+ else if (node.skip_validation) {
2635
+ // NOT ENFORCED implies NOT VALID, so only emit NOT VALID when enforced
2582
2636
  output.push('NOT VALID');
2583
2637
  }
2584
2638
  // Handle NO INHERIT for check constraints - only for table constraints, not domain constraints
@@ -2598,7 +2652,7 @@ class Deparser {
2598
2652
  if (node.raw_expr) {
2599
2653
  output.push(context.parens(this.visit(node.raw_expr, context)));
2600
2654
  }
2601
- output.push('STORED');
2655
+ output.push(node.generated_kind === 'v' ? 'VIRTUAL' : 'STORED');
2602
2656
  break;
2603
2657
  case 'CONSTR_IDENTITY':
2604
2658
  output.push('GENERATED');
@@ -2674,10 +2728,12 @@ class Deparser {
2674
2728
  case 'CONSTR_PRIMARY':
2675
2729
  output.push('PRIMARY KEY');
2676
2730
  if (node.keys && node.keys.length > 0) {
2677
- const keyList = list_utils_1.ListUtils.unwrapList(node.keys)
2678
- .map(key => this.visit(key, context))
2679
- .join(', ');
2680
- output.push(`(${keyList})`);
2731
+ const keyItems = list_utils_1.ListUtils.unwrapList(node.keys)
2732
+ .map(key => this.visit(key, context));
2733
+ if (node.without_overlaps) {
2734
+ keyItems[keyItems.length - 1] += ' WITHOUT OVERLAPS';
2735
+ }
2736
+ output.push(`(${keyItems.join(', ')})`);
2681
2737
  }
2682
2738
  if (node.indexname) {
2683
2739
  output.push('USING INDEX');
@@ -2695,10 +2751,12 @@ class Deparser {
2695
2751
  output.push('NULLS NOT DISTINCT');
2696
2752
  }
2697
2753
  if (node.keys && node.keys.length > 0) {
2698
- const keyList = list_utils_1.ListUtils.unwrapList(node.keys)
2699
- .map(key => this.visit(key, context))
2700
- .join(', ');
2701
- output.push(`(${keyList})`);
2754
+ const keyItems = list_utils_1.ListUtils.unwrapList(node.keys)
2755
+ .map(key => this.visit(key, context));
2756
+ if (node.without_overlaps) {
2757
+ keyItems[keyItems.length - 1] += ' WITHOUT OVERLAPS';
2758
+ }
2759
+ output.push(`(${keyItems.join(', ')})`);
2702
2760
  }
2703
2761
  if (node.indexname) {
2704
2762
  output.push('USING INDEX');
@@ -2711,20 +2769,14 @@ class Deparser {
2711
2769
  if (context.isPretty()) {
2712
2770
  output.push('\n' + context.indent('FOREIGN KEY'));
2713
2771
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2714
- const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2715
- .map(attr => this.visit(attr, context))
2716
- .join(', ');
2717
- output.push(`(${fkAttrs})`);
2772
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2718
2773
  }
2719
2774
  output.push('\n' + context.indent('REFERENCES'));
2720
2775
  }
2721
2776
  else {
2722
2777
  output.push('FOREIGN KEY');
2723
2778
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2724
- const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2725
- .map(attr => this.visit(attr, context))
2726
- .join(', ');
2727
- output.push(`(${fkAttrs})`);
2779
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2728
2780
  }
2729
2781
  output.push('REFERENCES');
2730
2782
  }
@@ -2747,9 +2799,7 @@ class Deparser {
2747
2799
  }
2748
2800
  }
2749
2801
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2750
- const pkAttrs = list_utils_1.ListUtils.unwrapList(node.pk_attrs)
2751
- .map(attr => this.visit(attr, context))
2752
- .join(', ');
2802
+ const pkAttrs = this.deparseKeyAttrs(node.pk_attrs, node.pk_with_period, context);
2753
2803
  if (context.isPretty() && !context.isColumnConstraint) {
2754
2804
  const lastIndex = output.length - 1;
2755
2805
  if (lastIndex >= 0) {
@@ -2828,8 +2878,18 @@ class Deparser {
2828
2878
  output.push(deleteClause.replace('ON DELETE ', ''));
2829
2879
  }
2830
2880
  }
2831
- // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2832
- if (node.skip_validation && !context.isDomainConstraint) {
2881
+ // PG18: constraints are enforced unless explicitly NOT ENFORCED
2882
+ // (libpg-query omits false booleans, so undefined means not enforced)
2883
+ if (node.is_enforced !== true && !context.isDomainConstraint) {
2884
+ if (context.isPretty() && !context.isColumnConstraint) {
2885
+ output.push('\n' + context.indent('NOT ENFORCED'));
2886
+ }
2887
+ else {
2888
+ output.push('NOT ENFORCED');
2889
+ }
2890
+ }
2891
+ else if (node.skip_validation && !context.isDomainConstraint) {
2892
+ // NOT ENFORCED implies NOT VALID, so only emit NOT VALID when enforced
2833
2893
  if (context.isPretty() && !context.isColumnConstraint) {
2834
2894
  output.push('\n' + context.indent('NOT VALID'));
2835
2895
  }
@@ -2850,6 +2910,12 @@ class Deparser {
2850
2910
  case 'CONSTR_ATTR_IMMEDIATE':
2851
2911
  output.push('INITIALLY IMMEDIATE');
2852
2912
  break;
2913
+ case 'CONSTR_ATTR_ENFORCED':
2914
+ output.push('ENFORCED');
2915
+ break;
2916
+ case 'CONSTR_ATTR_NOT_ENFORCED':
2917
+ output.push('NOT ENFORCED');
2918
+ break;
2853
2919
  case 'CONSTR_EXCLUSION':
2854
2920
  output.push('EXCLUDE');
2855
2921
  if (node.access_method) {
@@ -3893,6 +3959,11 @@ class Deparser {
3893
3959
  switch (node.kind) {
3894
3960
  case 'VAR_SET_VALUE':
3895
3961
  const localPrefix = node.is_local ? 'LOCAL ' : '';
3962
+ if (node.jumble_args && node.name === 'xmloption' && node.args) {
3963
+ const xmlValue = this.getNodeData(list_utils_1.ListUtils.unwrapList(node.args)[0]);
3964
+ const xmlOption = (typeof xmlValue.sval === 'object' ? xmlValue.sval.sval : xmlValue.sval) || '';
3965
+ return `SET ${localPrefix}XML OPTION ${xmlOption.toUpperCase()}`;
3966
+ }
3896
3967
  const args = node.args ? list_utils_1.ListUtils.unwrapList(node.args).map(arg => {
3897
3968
  const nodeData = this.getNodeData(arg);
3898
3969
  if (nodeData.sval !== undefined) {
@@ -3904,6 +3975,9 @@ class Deparser {
3904
3975
  }
3905
3976
  return this.visit(arg, context);
3906
3977
  }).join(', ') : '';
3978
+ if (node.jumble_args && node.name === 'timezone') {
3979
+ return `SET ${localPrefix}TIME ZONE ${args}`;
3980
+ }
3907
3981
  // Handle args - always include TO clause if args exist (even if empty string)
3908
3982
  const paramName = quotes_1.QuoteUtils.quoteIdentifier(node.name);
3909
3983
  if (!node.args || node.args.length === 0) {
@@ -3911,6 +3985,9 @@ class Deparser {
3911
3985
  }
3912
3986
  return `SET ${localPrefix}${paramName} TO ${args}`;
3913
3987
  case 'VAR_SET_DEFAULT':
3988
+ if (node.jumble_args && node.name === 'timezone') {
3989
+ return `SET ${node.is_local ? 'LOCAL ' : ''}TIME ZONE DEFAULT`;
3990
+ }
3914
3991
  const defaultParamName = quotes_1.QuoteUtils.quoteIdentifier(node.name);
3915
3992
  return `SET ${defaultParamName} TO DEFAULT`;
3916
3993
  case 'VAR_SET_CURRENT':
@@ -5022,13 +5099,6 @@ class Deparser {
5022
5099
  output.push('IF EXISTS');
5023
5100
  }
5024
5101
  break;
5025
- case 'AT_CheckNotNull':
5026
- output.push('ALTER COLUMN');
5027
- if (node.name) {
5028
- output.push(quotes_1.QuoteUtils.quoteIdentifier(node.name));
5029
- }
5030
- output.push('SET NOT NULL');
5031
- break;
5032
5102
  case 'AT_AddIndex':
5033
5103
  output.push('ADD');
5034
5104
  if (node.def) {
@@ -5054,23 +5124,28 @@ class Deparser {
5054
5124
  }
5055
5125
  break;
5056
5126
  case 'AT_AlterConstraint':
5057
- output.push('ALTER CONSTRAINT');
5058
- if (node.def && this.getNodeType(node.def) === 'Constraint') {
5059
- const constraintData = this.getNodeData(node.def);
5060
- if (constraintData.conname) {
5061
- output.push(quotes_1.QuoteUtils.quoteIdentifier(constraintData.conname));
5062
- if (constraintData.deferrable !== undefined) {
5063
- output.push(constraintData.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
5064
- }
5065
- if (constraintData.initdeferred !== undefined) {
5066
- output.push(constraintData.initdeferred ? 'INITIALLY DEFERRED' : 'INITIALLY IMMEDIATE');
5127
+ if (node.def && this.getNodeType(node.def) === 'ATAlterConstraint') {
5128
+ output.push(this.ATAlterConstraint(this.getNodeData(node.def), context));
5129
+ }
5130
+ else {
5131
+ output.push('ALTER CONSTRAINT');
5132
+ if (node.def && this.getNodeType(node.def) === 'Constraint') {
5133
+ const constraintData = this.getNodeData(node.def);
5134
+ if (constraintData.conname) {
5135
+ output.push(quotes_1.QuoteUtils.quoteIdentifier(constraintData.conname));
5136
+ if (constraintData.deferrable !== undefined) {
5137
+ output.push(constraintData.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
5138
+ }
5139
+ if (constraintData.initdeferred !== undefined) {
5140
+ output.push(constraintData.initdeferred ? 'INITIALLY DEFERRED' : 'INITIALLY IMMEDIATE');
5141
+ }
5067
5142
  }
5068
5143
  }
5069
- }
5070
- else if (node.name) {
5071
- output.push(quotes_1.QuoteUtils.quoteIdentifier(node.name));
5072
- if (node.def) {
5073
- output.push(this.visit(node.def, context));
5144
+ else if (node.name) {
5145
+ output.push(quotes_1.QuoteUtils.quoteIdentifier(node.name));
5146
+ if (node.def) {
5147
+ output.push(this.visit(node.def, context));
5148
+ }
5074
5149
  }
5075
5150
  }
5076
5151
  break;
@@ -8056,7 +8131,17 @@ class Deparser {
8056
8131
  }
8057
8132
  }
8058
8133
  if (node.object) {
8059
- output.push(this.visit(node.object, context));
8134
+ // Qualified names arrive as a List of String nodes and must be
8135
+ // dot-joined (visiting the List directly would comma-join them).
8136
+ if (typeof node.object === 'object' && 'List' in node.object) {
8137
+ const list = node.object.List;
8138
+ const objectParts = list_utils_1.ListUtils.unwrapList(list.items)
8139
+ .map(item => this.visit(item, context));
8140
+ output.push(objectParts.join('.'));
8141
+ }
8142
+ else {
8143
+ output.push(this.visit(node.object, context));
8144
+ }
8060
8145
  }
8061
8146
  output.push('IS');
8062
8147
  if (node.label) {
@@ -8562,6 +8647,75 @@ class Deparser {
8562
8647
  .join(' ');
8563
8648
  output.push(whenClauses);
8564
8649
  }
8650
+ if (node.returningClause) {
8651
+ output.push(this.ReturningClause(node.returningClause, context));
8652
+ }
8653
+ return output.join(' ');
8654
+ }
8655
+ MergeSupportFunc(node, context) {
8656
+ return 'merge_action()';
8657
+ }
8658
+ MergeWhenClause(node, context) {
8659
+ const output = ['WHEN'];
8660
+ switch (node.matchKind) {
8661
+ case 'MERGE_WHEN_MATCHED':
8662
+ output.push('MATCHED');
8663
+ break;
8664
+ case 'MERGE_WHEN_NOT_MATCHED_BY_SOURCE':
8665
+ output.push('NOT MATCHED BY SOURCE');
8666
+ break;
8667
+ case 'MERGE_WHEN_NOT_MATCHED_BY_TARGET':
8668
+ output.push('NOT MATCHED');
8669
+ break;
8670
+ }
8671
+ if (node.condition) {
8672
+ output.push('AND');
8673
+ output.push(this.visit(node.condition, context));
8674
+ }
8675
+ output.push('THEN');
8676
+ switch (node.commandType) {
8677
+ case 'CMD_UPDATE': {
8678
+ output.push('UPDATE SET');
8679
+ const assignments = list_utils_1.ListUtils.unwrapList(node.targetList)
8680
+ .map(target => this.visit(target, context.spawn('UpdateStmt', { update: true })))
8681
+ .join(', ');
8682
+ output.push(assignments);
8683
+ break;
8684
+ }
8685
+ case 'CMD_INSERT': {
8686
+ output.push('INSERT');
8687
+ const targetList = list_utils_1.ListUtils.unwrapList(node.targetList);
8688
+ if (targetList.length > 0) {
8689
+ const columns = targetList
8690
+ .map(target => quotes_1.QuoteUtils.quoteIdentifier(target.ResTarget?.name))
8691
+ .join(', ');
8692
+ output.push(`(${columns})`);
8693
+ }
8694
+ if (node.override === 'OVERRIDING_USER_VALUE') {
8695
+ output.push('OVERRIDING USER VALUE');
8696
+ }
8697
+ else if (node.override === 'OVERRIDING_SYSTEM_VALUE') {
8698
+ output.push('OVERRIDING SYSTEM VALUE');
8699
+ }
8700
+ const values = list_utils_1.ListUtils.unwrapList(node.values);
8701
+ if (values.length > 0) {
8702
+ const valueItems = values
8703
+ .map(value => this.visit(value, context))
8704
+ .join(', ');
8705
+ output.push(`VALUES (${valueItems})`);
8706
+ }
8707
+ else {
8708
+ output.push('DEFAULT VALUES');
8709
+ }
8710
+ break;
8711
+ }
8712
+ case 'CMD_DELETE':
8713
+ output.push('DELETE');
8714
+ break;
8715
+ case 'CMD_NOTHING':
8716
+ output.push('DO NOTHING');
8717
+ break;
8718
+ }
8565
8719
  return output.join(' ');
8566
8720
  }
8567
8721
  AlterTableMoveAllStmt(node, context) {
package/esm/deparser.js CHANGED
@@ -1042,11 +1042,8 @@ export class Deparser {
1042
1042
  output.push('DO NOTHING');
1043
1043
  }
1044
1044
  }
1045
- if (node.returningList) {
1046
- output.push('RETURNING');
1047
- const returningList = ListUtils.unwrapList(node.returningList);
1048
- const returns = returningList.map(ret => this.visit(ret, context));
1049
- output.push(returns.join(', '));
1045
+ if (node.returningClause) {
1046
+ output.push(this.ReturningClause(node.returningClause, context));
1050
1047
  }
1051
1048
  return output.join(' ');
1052
1049
  }
@@ -1103,9 +1100,8 @@ export class Deparser {
1103
1100
  output.push('WHERE');
1104
1101
  output.push(this.visit(node.whereClause, context));
1105
1102
  }
1106
- if (node.returningList) {
1107
- output.push('RETURNING');
1108
- output.push(this.deparseReturningList(node.returningList, context));
1103
+ if (node.returningClause) {
1104
+ output.push(this.ReturningClause(node.returningClause, context));
1109
1105
  }
1110
1106
  return output.join(' ');
1111
1107
  }
@@ -1156,13 +1152,12 @@ export class Deparser {
1156
1152
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1157
1153
  }
1158
1154
  }
1159
- if (node.returningList) {
1160
- output.push('RETURNING');
1155
+ if (node.returningClause) {
1161
1156
  try {
1162
- output.push(this.deparseReturningList(node.returningList, context));
1157
+ output.push(this.ReturningClause(node.returningClause, context));
1163
1158
  }
1164
1159
  catch (error) {
1165
- console.warn(`Error processing returningList in DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1160
+ console.warn(`Error processing returningClause in DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1166
1161
  throw new Error(`Error deparsing DeleteStmt: ${error instanceof Error ? error.message : String(error)}`);
1167
1162
  }
1168
1163
  }
@@ -1240,6 +1235,48 @@ export class Deparser {
1240
1235
  }
1241
1236
  return output.join(' ');
1242
1237
  }
1238
+ ATAlterConstraint(node, context) {
1239
+ const output = ['ALTER CONSTRAINT'];
1240
+ if (node.conname) {
1241
+ output.push(QuoteUtils.quoteIdentifier(node.conname));
1242
+ }
1243
+ if (node.alterEnforceability) {
1244
+ output.push(node.is_enforced ? 'ENFORCED' : 'NOT ENFORCED');
1245
+ }
1246
+ if (node.alterDeferrability) {
1247
+ output.push(node.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
1248
+ if (node.initdeferred) {
1249
+ output.push('INITIALLY DEFERRED');
1250
+ }
1251
+ }
1252
+ if (node.alterInheritability) {
1253
+ output.push(node.noinherit ? 'NO INHERIT' : 'INHERIT');
1254
+ }
1255
+ return output.join(' ');
1256
+ }
1257
+ deparseKeyAttrs(attrs, withPeriod, context) {
1258
+ const items = ListUtils.unwrapList(attrs).map(attr => this.visit(attr, context));
1259
+ if (withPeriod && items.length > 0) {
1260
+ items[items.length - 1] = `PERIOD ${items[items.length - 1]}`;
1261
+ }
1262
+ return items.join(', ');
1263
+ }
1264
+ ReturningClause(node, context) {
1265
+ const output = ['RETURNING'];
1266
+ if (node.options && node.options.length > 0) {
1267
+ const options = ListUtils.unwrapList(node.options)
1268
+ .map(option => this.visit(option, context));
1269
+ output.push(`WITH (${options.join(', ')})`);
1270
+ }
1271
+ if (node.exprs && node.exprs.length > 0) {
1272
+ output.push(this.deparseReturningList(node.exprs, context));
1273
+ }
1274
+ return output.join(' ');
1275
+ }
1276
+ ReturningOption(node, context) {
1277
+ const keyword = node.option === 'RETURNING_OPTION_NEW' ? 'NEW' : 'OLD';
1278
+ return `${keyword} AS ${QuoteUtils.quoteIdentifier(node.value)}`;
1279
+ }
1243
1280
  deparseReturningList(list, context) {
1244
1281
  return ListUtils.unwrapList(list)
1245
1282
  .filter(item => item != null && this.getNodeType(item) !== 'undefined')
@@ -2536,7 +2573,7 @@ export class Deparser {
2536
2573
  Constraint(node, context) {
2537
2574
  const output = [];
2538
2575
  // Handle constraint name if present
2539
- if (node.conname && (node.contype === 'CONSTR_CHECK' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_FOREIGN')) {
2576
+ if (node.conname && (node.contype === 'CONSTR_CHECK' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_FOREIGN' || node.contype === 'CONSTR_NOTNULL')) {
2540
2577
  output.push('CONSTRAINT');
2541
2578
  output.push(QuoteUtils.quoteIdentifier(node.conname));
2542
2579
  }
@@ -2546,6 +2583,18 @@ export class Deparser {
2546
2583
  break;
2547
2584
  case 'CONSTR_NOTNULL':
2548
2585
  output.push('NOT NULL');
2586
+ if (node.keys && node.keys.length > 0 && !context.isColumnConstraint) {
2587
+ const keyList = ListUtils.unwrapList(node.keys)
2588
+ .map(key => this.visit(key, context))
2589
+ .join(', ');
2590
+ output.push(keyList);
2591
+ }
2592
+ if (node.is_no_inherit) {
2593
+ output.push('NO INHERIT');
2594
+ }
2595
+ if (node.skip_validation) {
2596
+ output.push('NOT VALID');
2597
+ }
2549
2598
  break;
2550
2599
  case 'CONSTR_DEFAULT':
2551
2600
  output.push('DEFAULT');
@@ -2574,8 +2623,13 @@ export class Deparser {
2574
2623
  output.push(context.parens(this.visit(node.raw_expr, context)));
2575
2624
  }
2576
2625
  }
2577
- // Handle NOT VALID for check constraints
2578
- if (node.skip_validation) {
2626
+ // PG18: constraints are enforced unless explicitly NOT ENFORCED
2627
+ // (libpg-query omits false booleans, so undefined means not enforced)
2628
+ if (node.is_enforced !== true && !context.isDomainConstraint) {
2629
+ output.push('NOT ENFORCED');
2630
+ }
2631
+ else if (node.skip_validation) {
2632
+ // NOT ENFORCED implies NOT VALID, so only emit NOT VALID when enforced
2579
2633
  output.push('NOT VALID');
2580
2634
  }
2581
2635
  // Handle NO INHERIT for check constraints - only for table constraints, not domain constraints
@@ -2595,7 +2649,7 @@ export class Deparser {
2595
2649
  if (node.raw_expr) {
2596
2650
  output.push(context.parens(this.visit(node.raw_expr, context)));
2597
2651
  }
2598
- output.push('STORED');
2652
+ output.push(node.generated_kind === 'v' ? 'VIRTUAL' : 'STORED');
2599
2653
  break;
2600
2654
  case 'CONSTR_IDENTITY':
2601
2655
  output.push('GENERATED');
@@ -2671,10 +2725,12 @@ export class Deparser {
2671
2725
  case 'CONSTR_PRIMARY':
2672
2726
  output.push('PRIMARY KEY');
2673
2727
  if (node.keys && node.keys.length > 0) {
2674
- const keyList = ListUtils.unwrapList(node.keys)
2675
- .map(key => this.visit(key, context))
2676
- .join(', ');
2677
- output.push(`(${keyList})`);
2728
+ const keyItems = ListUtils.unwrapList(node.keys)
2729
+ .map(key => this.visit(key, context));
2730
+ if (node.without_overlaps) {
2731
+ keyItems[keyItems.length - 1] += ' WITHOUT OVERLAPS';
2732
+ }
2733
+ output.push(`(${keyItems.join(', ')})`);
2678
2734
  }
2679
2735
  if (node.indexname) {
2680
2736
  output.push('USING INDEX');
@@ -2692,10 +2748,12 @@ export class Deparser {
2692
2748
  output.push('NULLS NOT DISTINCT');
2693
2749
  }
2694
2750
  if (node.keys && node.keys.length > 0) {
2695
- const keyList = ListUtils.unwrapList(node.keys)
2696
- .map(key => this.visit(key, context))
2697
- .join(', ');
2698
- output.push(`(${keyList})`);
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(', ')})`);
2699
2757
  }
2700
2758
  if (node.indexname) {
2701
2759
  output.push('USING INDEX');
@@ -2708,20 +2766,14 @@ export class Deparser {
2708
2766
  if (context.isPretty()) {
2709
2767
  output.push('\n' + context.indent('FOREIGN KEY'));
2710
2768
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2711
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2712
- .map(attr => this.visit(attr, context))
2713
- .join(', ');
2714
- output.push(`(${fkAttrs})`);
2769
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2715
2770
  }
2716
2771
  output.push('\n' + context.indent('REFERENCES'));
2717
2772
  }
2718
2773
  else {
2719
2774
  output.push('FOREIGN KEY');
2720
2775
  if (node.fk_attrs && node.fk_attrs.length > 0) {
2721
- const fkAttrs = ListUtils.unwrapList(node.fk_attrs)
2722
- .map(attr => this.visit(attr, context))
2723
- .join(', ');
2724
- output.push(`(${fkAttrs})`);
2776
+ output.push(`(${this.deparseKeyAttrs(node.fk_attrs, node.fk_with_period, context)})`);
2725
2777
  }
2726
2778
  output.push('REFERENCES');
2727
2779
  }
@@ -2744,9 +2796,7 @@ export class Deparser {
2744
2796
  }
2745
2797
  }
2746
2798
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2747
- const pkAttrs = ListUtils.unwrapList(node.pk_attrs)
2748
- .map(attr => this.visit(attr, context))
2749
- .join(', ');
2799
+ const pkAttrs = this.deparseKeyAttrs(node.pk_attrs, node.pk_with_period, context);
2750
2800
  if (context.isPretty() && !context.isColumnConstraint) {
2751
2801
  const lastIndex = output.length - 1;
2752
2802
  if (lastIndex >= 0) {
@@ -2825,8 +2875,18 @@ export class Deparser {
2825
2875
  output.push(deleteClause.replace('ON DELETE ', ''));
2826
2876
  }
2827
2877
  }
2828
- // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2829
- if (node.skip_validation && !context.isDomainConstraint) {
2878
+ // PG18: constraints are enforced unless explicitly NOT ENFORCED
2879
+ // (libpg-query omits false booleans, so undefined means not enforced)
2880
+ if (node.is_enforced !== true && !context.isDomainConstraint) {
2881
+ if (context.isPretty() && !context.isColumnConstraint) {
2882
+ output.push('\n' + context.indent('NOT ENFORCED'));
2883
+ }
2884
+ else {
2885
+ output.push('NOT ENFORCED');
2886
+ }
2887
+ }
2888
+ else if (node.skip_validation && !context.isDomainConstraint) {
2889
+ // NOT ENFORCED implies NOT VALID, so only emit NOT VALID when enforced
2830
2890
  if (context.isPretty() && !context.isColumnConstraint) {
2831
2891
  output.push('\n' + context.indent('NOT VALID'));
2832
2892
  }
@@ -2847,6 +2907,12 @@ export class Deparser {
2847
2907
  case 'CONSTR_ATTR_IMMEDIATE':
2848
2908
  output.push('INITIALLY IMMEDIATE');
2849
2909
  break;
2910
+ case 'CONSTR_ATTR_ENFORCED':
2911
+ output.push('ENFORCED');
2912
+ break;
2913
+ case 'CONSTR_ATTR_NOT_ENFORCED':
2914
+ output.push('NOT ENFORCED');
2915
+ break;
2850
2916
  case 'CONSTR_EXCLUSION':
2851
2917
  output.push('EXCLUDE');
2852
2918
  if (node.access_method) {
@@ -3890,6 +3956,11 @@ export class Deparser {
3890
3956
  switch (node.kind) {
3891
3957
  case 'VAR_SET_VALUE':
3892
3958
  const localPrefix = node.is_local ? 'LOCAL ' : '';
3959
+ if (node.jumble_args && node.name === 'xmloption' && node.args) {
3960
+ const xmlValue = this.getNodeData(ListUtils.unwrapList(node.args)[0]);
3961
+ const xmlOption = (typeof xmlValue.sval === 'object' ? xmlValue.sval.sval : xmlValue.sval) || '';
3962
+ return `SET ${localPrefix}XML OPTION ${xmlOption.toUpperCase()}`;
3963
+ }
3893
3964
  const args = node.args ? ListUtils.unwrapList(node.args).map(arg => {
3894
3965
  const nodeData = this.getNodeData(arg);
3895
3966
  if (nodeData.sval !== undefined) {
@@ -3901,6 +3972,9 @@ export class Deparser {
3901
3972
  }
3902
3973
  return this.visit(arg, context);
3903
3974
  }).join(', ') : '';
3975
+ if (node.jumble_args && node.name === 'timezone') {
3976
+ return `SET ${localPrefix}TIME ZONE ${args}`;
3977
+ }
3904
3978
  // Handle args - always include TO clause if args exist (even if empty string)
3905
3979
  const paramName = QuoteUtils.quoteIdentifier(node.name);
3906
3980
  if (!node.args || node.args.length === 0) {
@@ -3908,6 +3982,9 @@ export class Deparser {
3908
3982
  }
3909
3983
  return `SET ${localPrefix}${paramName} TO ${args}`;
3910
3984
  case 'VAR_SET_DEFAULT':
3985
+ if (node.jumble_args && node.name === 'timezone') {
3986
+ return `SET ${node.is_local ? 'LOCAL ' : ''}TIME ZONE DEFAULT`;
3987
+ }
3911
3988
  const defaultParamName = QuoteUtils.quoteIdentifier(node.name);
3912
3989
  return `SET ${defaultParamName} TO DEFAULT`;
3913
3990
  case 'VAR_SET_CURRENT':
@@ -5019,13 +5096,6 @@ export class Deparser {
5019
5096
  output.push('IF EXISTS');
5020
5097
  }
5021
5098
  break;
5022
- case 'AT_CheckNotNull':
5023
- output.push('ALTER COLUMN');
5024
- if (node.name) {
5025
- output.push(QuoteUtils.quoteIdentifier(node.name));
5026
- }
5027
- output.push('SET NOT NULL');
5028
- break;
5029
5099
  case 'AT_AddIndex':
5030
5100
  output.push('ADD');
5031
5101
  if (node.def) {
@@ -5051,23 +5121,28 @@ export class Deparser {
5051
5121
  }
5052
5122
  break;
5053
5123
  case 'AT_AlterConstraint':
5054
- output.push('ALTER CONSTRAINT');
5055
- if (node.def && this.getNodeType(node.def) === 'Constraint') {
5056
- const constraintData = this.getNodeData(node.def);
5057
- if (constraintData.conname) {
5058
- output.push(QuoteUtils.quoteIdentifier(constraintData.conname));
5059
- if (constraintData.deferrable !== undefined) {
5060
- output.push(constraintData.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
5061
- }
5062
- if (constraintData.initdeferred !== undefined) {
5063
- output.push(constraintData.initdeferred ? 'INITIALLY DEFERRED' : 'INITIALLY IMMEDIATE');
5124
+ if (node.def && this.getNodeType(node.def) === 'ATAlterConstraint') {
5125
+ output.push(this.ATAlterConstraint(this.getNodeData(node.def), context));
5126
+ }
5127
+ else {
5128
+ output.push('ALTER CONSTRAINT');
5129
+ if (node.def && this.getNodeType(node.def) === 'Constraint') {
5130
+ const constraintData = this.getNodeData(node.def);
5131
+ if (constraintData.conname) {
5132
+ output.push(QuoteUtils.quoteIdentifier(constraintData.conname));
5133
+ if (constraintData.deferrable !== undefined) {
5134
+ output.push(constraintData.deferrable ? 'DEFERRABLE' : 'NOT DEFERRABLE');
5135
+ }
5136
+ if (constraintData.initdeferred !== undefined) {
5137
+ output.push(constraintData.initdeferred ? 'INITIALLY DEFERRED' : 'INITIALLY IMMEDIATE');
5138
+ }
5064
5139
  }
5065
5140
  }
5066
- }
5067
- else if (node.name) {
5068
- output.push(QuoteUtils.quoteIdentifier(node.name));
5069
- if (node.def) {
5070
- output.push(this.visit(node.def, context));
5141
+ else if (node.name) {
5142
+ output.push(QuoteUtils.quoteIdentifier(node.name));
5143
+ if (node.def) {
5144
+ output.push(this.visit(node.def, context));
5145
+ }
5071
5146
  }
5072
5147
  }
5073
5148
  break;
@@ -8053,7 +8128,17 @@ export class Deparser {
8053
8128
  }
8054
8129
  }
8055
8130
  if (node.object) {
8056
- output.push(this.visit(node.object, context));
8131
+ // Qualified names arrive as a List of String nodes and must be
8132
+ // dot-joined (visiting the List directly would comma-join them).
8133
+ if (typeof node.object === 'object' && 'List' in node.object) {
8134
+ const list = node.object.List;
8135
+ const objectParts = ListUtils.unwrapList(list.items)
8136
+ .map(item => this.visit(item, context));
8137
+ output.push(objectParts.join('.'));
8138
+ }
8139
+ else {
8140
+ output.push(this.visit(node.object, context));
8141
+ }
8057
8142
  }
8058
8143
  output.push('IS');
8059
8144
  if (node.label) {
@@ -8559,6 +8644,75 @@ export class Deparser {
8559
8644
  .join(' ');
8560
8645
  output.push(whenClauses);
8561
8646
  }
8647
+ if (node.returningClause) {
8648
+ output.push(this.ReturningClause(node.returningClause, context));
8649
+ }
8650
+ return output.join(' ');
8651
+ }
8652
+ MergeSupportFunc(node, context) {
8653
+ return 'merge_action()';
8654
+ }
8655
+ MergeWhenClause(node, context) {
8656
+ const output = ['WHEN'];
8657
+ switch (node.matchKind) {
8658
+ case 'MERGE_WHEN_MATCHED':
8659
+ output.push('MATCHED');
8660
+ break;
8661
+ case 'MERGE_WHEN_NOT_MATCHED_BY_SOURCE':
8662
+ output.push('NOT MATCHED BY SOURCE');
8663
+ break;
8664
+ case 'MERGE_WHEN_NOT_MATCHED_BY_TARGET':
8665
+ output.push('NOT MATCHED');
8666
+ break;
8667
+ }
8668
+ if (node.condition) {
8669
+ output.push('AND');
8670
+ output.push(this.visit(node.condition, context));
8671
+ }
8672
+ output.push('THEN');
8673
+ switch (node.commandType) {
8674
+ case 'CMD_UPDATE': {
8675
+ output.push('UPDATE SET');
8676
+ const assignments = ListUtils.unwrapList(node.targetList)
8677
+ .map(target => this.visit(target, context.spawn('UpdateStmt', { update: true })))
8678
+ .join(', ');
8679
+ output.push(assignments);
8680
+ break;
8681
+ }
8682
+ case 'CMD_INSERT': {
8683
+ output.push('INSERT');
8684
+ const targetList = ListUtils.unwrapList(node.targetList);
8685
+ if (targetList.length > 0) {
8686
+ const columns = targetList
8687
+ .map(target => QuoteUtils.quoteIdentifier(target.ResTarget?.name))
8688
+ .join(', ');
8689
+ output.push(`(${columns})`);
8690
+ }
8691
+ if (node.override === 'OVERRIDING_USER_VALUE') {
8692
+ output.push('OVERRIDING USER VALUE');
8693
+ }
8694
+ else if (node.override === 'OVERRIDING_SYSTEM_VALUE') {
8695
+ output.push('OVERRIDING SYSTEM VALUE');
8696
+ }
8697
+ const values = ListUtils.unwrapList(node.values);
8698
+ if (values.length > 0) {
8699
+ const valueItems = values
8700
+ .map(value => this.visit(value, context))
8701
+ .join(', ');
8702
+ output.push(`VALUES (${valueItems})`);
8703
+ }
8704
+ else {
8705
+ output.push('DEFAULT VALUES');
8706
+ }
8707
+ break;
8708
+ }
8709
+ case 'CMD_DELETE':
8710
+ output.push('DELETE');
8711
+ break;
8712
+ case 'CMD_NOTHING':
8713
+ output.push('DO NOTHING');
8714
+ break;
8715
+ }
8562
8716
  return output.join(' ');
8563
8717
  }
8564
8718
  AlterTableMoveAllStmt(node, context) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgsql-deparser",
3
- "version": "17.18.4",
3
+ "version": "18.0.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PostgreSQL AST Deparser",
6
6
  "main": "index.js",
@@ -53,12 +53,12 @@
53
53
  "database"
54
54
  ],
55
55
  "devDependencies": {
56
- "libpg-query": "17.7.3",
56
+ "libpg-query": "18.1.1",
57
57
  "makage": "^0.1.8"
58
58
  },
59
59
  "dependencies": {
60
- "@pgsql/quotes": "17.1.0",
61
- "@pgsql/types": "^17.6.2"
60
+ "@pgsql/quotes": "18.0.0",
61
+ "@pgsql/types": "^18.0.0"
62
62
  },
63
- "gitHead": "987c0c66fee20025b193cb497d1ceb8832dc4ed2"
63
+ "gitHead": "f700ee16d18aeacd2788eeb4410d73928cd11a5f"
64
64
  }