pgsql-deparser 18.3.8 → 18.3.10

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
@@ -8,31 +8,6 @@ export interface DeparserOptions {
8
8
  functionDelimiterFallback?: string;
9
9
  pretty?: boolean;
10
10
  }
11
- /**
12
- * Deparser - Converts PostgreSQL AST nodes back to SQL strings
13
- *
14
- * Entry Points:
15
- * 1. ParseResult (from libpg-query) - The complete parse result
16
- * Structure: { version: number, stmts: RawStmt[] }
17
- * Note: stmts is "repeated RawStmt" in protobuf, so array contains RawStmt
18
- * objects inline (not wrapped as { RawStmt: ... } nodes)
19
- * Example: { version: 170004, stmts: [{ stmt: {...}, stmt_len: 32 }] }
20
- *
21
- * 2. Wrapped ParseResult - When explicitly wrapped as a Node
22
- * Structure: { ParseResult: { version: number, stmts: RawStmt[] } }
23
- *
24
- * 3. Wrapped RawStmt - When explicitly wrapped as a Node
25
- * Structure: { RawStmt: { stmt: Node, stmt_len?: number } }
26
- *
27
- * 4. Array of Nodes - Multiple statements to deparse
28
- * Can be: Node[] (e.g., SelectStmt, InsertStmt, etc.)
29
- *
30
- * 5. Single Node - Individual statement node
31
- * Example: { SelectStmt: {...} }, { InsertStmt: {...} }, etc.
32
- *
33
- * The deparser automatically detects bare ParseResult objects for backward
34
- * compatibility and wraps them internally for consistent processing.
35
- */
36
11
  export declare class Deparser implements DeparserVisitor {
37
12
  private tree;
38
13
  private options;
@@ -285,9 +260,12 @@ export declare class Deparser implements DeparserVisitor {
285
260
  OnConflictExpr(node: t.OnConflictExpr, context: DeparserContext): string;
286
261
  ScanToken(node: t.ScanToken, context: DeparserContext): string;
287
262
  CreateOpClassItem(node: t.CreateOpClassItem, context: DeparserContext): string;
263
+ private quoteDefElemArg;
264
+ private formatOptionValue;
288
265
  Var(node: t.Var, context: DeparserContext): string;
289
266
  TableFunc(node: t.TableFunc, context: DeparserContext): string;
290
267
  RangeTableFunc(node: t.RangeTableFunc, context: DeparserContext): string;
268
+ private formatXmlTableExpr;
291
269
  RangeTableFuncCol(node: t.RangeTableFuncCol, context: DeparserContext): string;
292
270
  formatJsonFormat(format: t.JsonFormat | undefined): string | null;
293
271
  formatJsonOutput(output: t.JsonOutput | undefined, context: DeparserContext): string | null;
package/deparser.js CHANGED
@@ -122,6 +122,31 @@ function isWrappedParseResult(obj) {
122
122
  * The deparser automatically detects bare ParseResult objects for backward
123
123
  * compatibility and wraps them internally for consistent processing.
124
124
  */
125
+ function formatBitString(bsval) {
126
+ const quote = (value) => value.replace(/'/g, "''");
127
+ if (/^x[0-9A-Fa-f]+$/.test(bsval)) {
128
+ return `x'${bsval.substring(1)}'`;
129
+ }
130
+ if (bsval.startsWith('b')) {
131
+ return `b'${quote(bsval.substring(1))}'`;
132
+ }
133
+ return `b'${quote(bsval)}'`;
134
+ }
135
+ 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)*)?)$/;
136
+ function formatInteger(ival) {
137
+ const value = String(ival);
138
+ if (!/^[+-]?\d+$/.test(value)) {
139
+ throw new Error(`Invalid integer literal: ${value}`);
140
+ }
141
+ return value;
142
+ }
143
+ function formatNumeric(fval) {
144
+ const value = String(fval);
145
+ if (!NUMERIC_LITERAL.test(value)) {
146
+ throw new Error(`Invalid numeric literal: ${value}`);
147
+ }
148
+ return value;
149
+ }
125
150
  class Deparser {
126
151
  tree;
127
152
  options;
@@ -1590,39 +1615,39 @@ class Deparser {
1590
1615
  if (nodeAny.ival !== undefined) {
1591
1616
  if (typeof nodeAny.ival === 'object' && nodeAny.ival !== null) {
1592
1617
  if (nodeAny.ival.ival !== undefined) {
1593
- return nodeAny.ival.ival.toString();
1618
+ return formatInteger(nodeAny.ival.ival);
1594
1619
  }
1595
1620
  else if (Object.keys(nodeAny.ival).length === 0) {
1596
1621
  return '0';
1597
1622
  }
1598
1623
  else {
1599
- return nodeAny.ival.toString();
1624
+ return formatInteger(nodeAny.ival);
1600
1625
  }
1601
1626
  }
1602
1627
  else if (nodeAny.ival === null) {
1603
1628
  return 'NULL';
1604
1629
  }
1605
1630
  else {
1606
- return nodeAny.ival.toString();
1631
+ return formatInteger(nodeAny.ival);
1607
1632
  }
1608
1633
  }
1609
1634
  else if (nodeAny.fval !== undefined) {
1610
1635
  if (typeof nodeAny.fval === 'object' && nodeAny.fval !== null) {
1611
1636
  if (nodeAny.fval.fval !== undefined) {
1612
- return nodeAny.fval.fval.toString();
1637
+ return formatNumeric(nodeAny.fval.fval);
1613
1638
  }
1614
1639
  else if (Object.keys(nodeAny.fval).length === 0) {
1615
1640
  return '0.0';
1616
1641
  }
1617
1642
  else {
1618
- return nodeAny.fval.toString();
1643
+ return formatNumeric(nodeAny.fval);
1619
1644
  }
1620
1645
  }
1621
1646
  else if (nodeAny.fval === null) {
1622
1647
  return 'NULL';
1623
1648
  }
1624
1649
  else {
1625
- return nodeAny.fval.toString();
1650
+ return formatNumeric(nodeAny.fval);
1626
1651
  }
1627
1652
  }
1628
1653
  else if (nodeAny.sval !== undefined) {
@@ -1669,15 +1694,7 @@ class Deparser {
1669
1694
  else if (nodeAny.bsval !== undefined) {
1670
1695
  if (typeof nodeAny.bsval === 'object' && nodeAny.bsval !== null) {
1671
1696
  if (nodeAny.bsval.bsval !== undefined) {
1672
- const bsval = nodeAny.bsval.bsval;
1673
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1674
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1675
- return `x'${bsval.substring(1)}'`;
1676
- }
1677
- if (bsval.startsWith('b')) {
1678
- return `b'${bsval.substring(1)}'`;
1679
- }
1680
- return `b'${bsval}'`;
1697
+ return formatBitString(nodeAny.bsval.bsval);
1681
1698
  }
1682
1699
  else if (Object.keys(nodeAny.bsval).length === 0) {
1683
1700
  return "''";
@@ -1690,23 +1707,15 @@ class Deparser {
1690
1707
  return 'NULL';
1691
1708
  }
1692
1709
  else {
1693
- const bsval = nodeAny.bsval;
1694
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1695
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1696
- return `x'${bsval.substring(1)}'`;
1697
- }
1698
- if (bsval.startsWith('b')) {
1699
- return `b'${bsval.substring(1)}'`;
1700
- }
1701
- return `b'${bsval}'`;
1710
+ return formatBitString(nodeAny.bsval);
1702
1711
  }
1703
1712
  }
1704
1713
  if (nodeAny.val) {
1705
1714
  if (nodeAny.val.Integer?.ival !== undefined) {
1706
- return nodeAny.val.Integer.ival.toString();
1715
+ return formatInteger(nodeAny.val.Integer.ival);
1707
1716
  }
1708
1717
  else if (nodeAny.val.Float?.fval !== undefined) {
1709
- return nodeAny.val.Float.fval.toString();
1718
+ return formatNumeric(nodeAny.val.Float.fval);
1710
1719
  }
1711
1720
  else if (nodeAny.val.String?.sval !== undefined) {
1712
1721
  return quotes_1.QuoteUtils.escape(nodeAny.val.String.sval);
@@ -1715,7 +1724,7 @@ class Deparser {
1715
1724
  return nodeAny.val.Boolean.boolval ? 'true' : 'false';
1716
1725
  }
1717
1726
  else if (nodeAny.val.BitString?.bsval !== undefined) {
1718
- return nodeAny.val.BitString.bsval;
1727
+ return formatBitString(nodeAny.val.BitString.bsval);
1719
1728
  }
1720
1729
  }
1721
1730
  if (nodeAny.isnull === true) {
@@ -1727,15 +1736,15 @@ class Deparser {
1727
1736
  }
1728
1737
  if (nodeAny.Integer !== undefined) {
1729
1738
  if (typeof nodeAny.Integer === 'object' && nodeAny.Integer.ival !== undefined) {
1730
- return nodeAny.Integer.ival.toString();
1739
+ return formatInteger(nodeAny.Integer.ival);
1731
1740
  }
1732
- return nodeAny.Integer.toString();
1741
+ return formatInteger(nodeAny.Integer);
1733
1742
  }
1734
1743
  if (nodeAny.Float !== undefined) {
1735
1744
  if (typeof nodeAny.Float === 'object' && nodeAny.Float.fval !== undefined) {
1736
- return nodeAny.Float.fval.toString();
1745
+ return formatNumeric(nodeAny.Float.fval);
1737
1746
  }
1738
- return nodeAny.Float.toString();
1747
+ return formatNumeric(nodeAny.Float);
1739
1748
  }
1740
1749
  if (nodeAny.String !== undefined) {
1741
1750
  if (typeof nodeAny.String === 'object' && nodeAny.String.sval !== undefined) {
@@ -2386,24 +2395,16 @@ class Deparser {
2386
2395
  return quotes_1.QuoteUtils.quoteIdentifier(value);
2387
2396
  }
2388
2397
  Integer(node, context) {
2389
- return node.ival?.toString() || '0';
2398
+ return node.ival === undefined || node.ival === null ? '0' : formatInteger(node.ival);
2390
2399
  }
2391
2400
  Float(node, context) {
2392
- return node.fval || '0.0';
2401
+ return node.fval ? formatNumeric(node.fval) : '0.0';
2393
2402
  }
2394
2403
  Boolean(node, context) {
2395
2404
  return node.boolval ? 'true' : 'false';
2396
2405
  }
2397
2406
  BitString(node, context) {
2398
- // Check if this is a hexadecimal bit string (starts with x)
2399
- if (node.bsval.startsWith('x')) {
2400
- return `x'${node.bsval.substring(1)}'`;
2401
- }
2402
- if (node.bsval.startsWith('b')) {
2403
- return `b'${node.bsval.substring(1)}'`;
2404
- }
2405
- // Fallback for raw values without prefix
2406
- return `b'${node.bsval}'`;
2407
+ return formatBitString(node.bsval);
2407
2408
  }
2408
2409
  Null(node, context) {
2409
2410
  return 'NULL';
@@ -3635,7 +3636,7 @@ class Deparser {
3635
3636
  const opclassOpts = list_utils_1.ListUtils.unwrapList(node.opclassopts).map(opt => {
3636
3637
  if (opt.DefElem && opt.DefElem.arg && this.getNodeType(opt.DefElem.arg) === 'String') {
3637
3638
  const stringData = this.getNodeData(opt.DefElem.arg);
3638
- return `${opt.DefElem.defname}='${stringData.sval}'`;
3639
+ return `${quotes_1.QuoteUtils.quoteIdentifierAfterDot(opt.DefElem.defname)}=${quotes_1.QuoteUtils.escape(stringData.sval)}`;
3639
3640
  }
3640
3641
  return this.visit(opt, context.spawn('IndexElem'));
3641
3642
  });
@@ -3936,19 +3937,19 @@ class Deparser {
3936
3937
  case 'TRANS_STMT_PREPARE':
3937
3938
  output.push('PREPARE TRANSACTION');
3938
3939
  if (node.gid) {
3939
- output.push(`'${node.gid}'`);
3940
+ output.push(quotes_1.QuoteUtils.escape(node.gid));
3940
3941
  }
3941
3942
  break;
3942
3943
  case 'TRANS_STMT_COMMIT_PREPARED':
3943
3944
  output.push('COMMIT PREPARED');
3944
3945
  if (node.gid) {
3945
- output.push(`'${node.gid}'`);
3946
+ output.push(quotes_1.QuoteUtils.escape(node.gid));
3946
3947
  }
3947
3948
  break;
3948
3949
  case 'TRANS_STMT_ROLLBACK_PREPARED':
3949
3950
  output.push('ROLLBACK PREPARED');
3950
3951
  if (node.gid) {
3951
- output.push(`'${node.gid}'`);
3952
+ output.push(quotes_1.QuoteUtils.escape(node.gid));
3952
3953
  }
3953
3954
  break;
3954
3955
  default:
@@ -4024,8 +4025,8 @@ class Deparser {
4024
4025
  const nodeData = this.getNodeData(arg);
4025
4026
  if (nodeData.sval !== undefined) {
4026
4027
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
4027
- if (svalValue === '' || svalValue.includes(' ') || svalValue.includes('-') || /[A-Z]/.test(svalValue) || /^\d/.test(svalValue) || svalValue.includes('.') || svalValue.includes('$') || svalValue.toLowerCase() === 'all' || /^[+-]\d/.test(svalValue)) {
4028
- return `'${svalValue}'`;
4028
+ 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)) {
4029
+ return quotes_1.QuoteUtils.escape(svalValue);
4029
4030
  }
4030
4031
  return svalValue;
4031
4032
  }
@@ -5552,9 +5553,9 @@ class Deparser {
5552
5553
  if (context.parentNodeTypes.includes('IndexElem')) {
5553
5554
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5554
5555
  const stringData = this.getNodeData(node.arg);
5555
- return `${node.defname}='${stringData.sval}'`;
5556
+ return `${quotes_1.QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${quotes_1.QuoteUtils.escape(stringData.sval)}`;
5556
5557
  }
5557
- return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
5558
+ return `${quotes_1.QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, this.visit(node.arg, context.spawn('DefElem')))}`;
5558
5559
  }
5559
5560
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
5560
5561
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -5579,9 +5580,7 @@ class Deparser {
5579
5580
  const defElemContext = context.spawn('DefElem');
5580
5581
  const argValue = this.visit(node.arg, defElemContext);
5581
5582
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5582
- const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
5583
- ? `'${argValue}'`
5584
- : argValue;
5583
+ const finalValue = this.quoteDefElemArg(node.arg, argValue);
5585
5584
  const quotedDefname = quotes_1.QuoteUtils.quoteIdentifier(node.defname);
5586
5585
  if (node.defaction === 'DEFELEM_ADD') {
5587
5586
  return `ADD ${quotedDefname} ${finalValue}`;
@@ -5594,9 +5593,7 @@ class Deparser {
5594
5593
  }
5595
5594
  return `${quotedDefname} ${finalValue}`;
5596
5595
  }
5597
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5598
- ? `'${argValue}'`
5599
- : argValue;
5596
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5600
5597
  if (node.defaction === 'DEFELEM_ADD') {
5601
5598
  return `ADD ${node.defname} ${quotedValue}`;
5602
5599
  }
@@ -5628,9 +5625,7 @@ class Deparser {
5628
5625
  }
5629
5626
  const defElemContext = context.spawn('DefElem');
5630
5627
  const argValue = this.visit(node.arg, defElemContext);
5631
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5632
- ? `'${argValue}'`
5633
- : argValue;
5628
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5634
5629
  return `PASSWORD ${quotedValue}`;
5635
5630
  }
5636
5631
  }
@@ -5695,9 +5690,7 @@ class Deparser {
5695
5690
  }
5696
5691
  }
5697
5692
  if (node.defname === 'validUntil') {
5698
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5699
- ? `'${argValue}'`
5700
- : argValue;
5693
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5701
5694
  return `VALID UNTIL ${quotedValue}`;
5702
5695
  }
5703
5696
  if (node.defname === 'adminmembers') {
@@ -5877,9 +5870,7 @@ class Deparser {
5877
5870
  if (context.parentNodeTypes.includes('AlterExtensionStmt')) {
5878
5871
  if (node.defname === 'new_version') {
5879
5872
  // argValue is unquoted due to DefElem context, so we need to quote it
5880
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5881
- ? `'${argValue}'`
5882
- : argValue;
5873
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5883
5874
  return `UPDATE TO ${quotedValue}`;
5884
5875
  }
5885
5876
  if (node.defname === 'schema') {
@@ -5904,15 +5895,15 @@ class Deparser {
5904
5895
  }
5905
5896
  // Handle IndexStmt WITH clause options - no quotes, compact formatting
5906
5897
  if (context.parentNodeTypes.includes('IndexStmt')) {
5907
- return `${node.defname}=${argValue}`;
5898
+ return `${quotes_1.QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5908
5899
  }
5909
5900
  // Handle IndexElem opclassopts - preserve string values as strings
5910
5901
  if (context.parentNodeTypes.includes('IndexElem')) {
5911
5902
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5912
5903
  const stringData = this.getNodeData(node.arg);
5913
- return `${node.defname}='${stringData.sval}'`;
5904
+ return `${quotes_1.QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${quotes_1.QuoteUtils.escape(stringData.sval)}`;
5914
5905
  }
5915
- return `${node.defname}=${argValue}`;
5906
+ return `${quotes_1.QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5916
5907
  }
5917
5908
  // Handle CreateStmt table options - no quotes, compact formatting
5918
5909
  if (context.parentNodeTypes.includes('CreateStmt')) {
@@ -5922,9 +5913,9 @@ class Deparser {
5922
5913
  // For numeric values, use the raw value without quotes
5923
5914
  if (node.arg && this.getNodeType(node.arg) === 'Integer') {
5924
5915
  const integerData = this.getNodeData(node.arg);
5925
- return `${relOptName}=${integerData.ival}`;
5916
+ return `${relOptName}=${formatInteger(integerData.ival)}`;
5926
5917
  }
5927
- return `${relOptName}=${argValue}`;
5918
+ return `${relOptName}=${this.formatOptionValue(node.arg, argValue)}`;
5928
5919
  }
5929
5920
  // Handle CreateEventTrigStmt WHEN clause - use IN syntax for List arguments
5930
5921
  if (context.parentNodeTypes.includes('CreateEventTrigStmt')) {
@@ -5934,7 +5925,7 @@ class Deparser {
5934
5925
  const values = listItems.map(item => {
5935
5926
  if (this.getNodeType(item) === 'String') {
5936
5927
  const stringData = this.getNodeData(item);
5937
- return `'${stringData.sval || ''}'`;
5928
+ return quotes_1.QuoteUtils.escape(stringData.sval || '');
5938
5929
  }
5939
5930
  return this.visit(item, context);
5940
5931
  });
@@ -5949,7 +5940,7 @@ class Deparser {
5949
5940
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
5950
5941
  return `${optionName} = ${argValue}`;
5951
5942
  }
5952
- return `${optionName} = ${argValue}`;
5943
+ return `${optionName} = ${this.formatOptionValue(node.arg, argValue)}`;
5953
5944
  }
5954
5945
  // Handle ViewStmt WITH options - don't quote numeric values
5955
5946
  if (context.parentNodeTypes.includes('ViewStmt')) {
@@ -6022,7 +6013,7 @@ class Deparser {
6022
6013
  return `${quotedDefname} = ${stringData.sval}`;
6023
6014
  }
6024
6015
  // Regular string literals get single quotes
6025
- return `${quotedDefname} = '${stringData.sval}'`;
6016
+ return `${quotedDefname} = ${quotes_1.QuoteUtils.escape(stringData.sval)}`;
6026
6017
  }
6027
6018
  return `${quotedDefname} = ${argValue}`;
6028
6019
  }
@@ -6043,7 +6034,7 @@ class Deparser {
6043
6034
  return `${node.defname} = ${stringData.sval}`;
6044
6035
  }
6045
6036
  // Regular string literals get single quotes
6046
- return `${node.defname} = '${stringData.sval}'`;
6037
+ return `${node.defname} = ${quotes_1.QuoteUtils.escape(stringData.sval)}`;
6047
6038
  }
6048
6039
  if (node.arg && this.getNodeType(node.arg) === 'Boolean') {
6049
6040
  const boolData = this.getNodeData(node.arg);
@@ -6093,7 +6084,7 @@ class Deparser {
6093
6084
  }
6094
6085
  if (node.location) {
6095
6086
  output.push('LOCATION');
6096
- output.push(`'${node.location}'`);
6087
+ output.push(quotes_1.QuoteUtils.escape(node.location));
6097
6088
  }
6098
6089
  if (node.options && node.options.length > 0) {
6099
6090
  output.push('WITH');
@@ -6426,7 +6417,7 @@ class Deparser {
6426
6417
  }
6427
6418
  if (node.payload !== null && node.payload !== undefined) {
6428
6419
  output.push(',');
6429
- output.push(`'${node.payload}'`);
6420
+ output.push(quotes_1.QuoteUtils.escape(node.payload));
6430
6421
  }
6431
6422
  return output.join(' ');
6432
6423
  }
@@ -6454,7 +6445,7 @@ class Deparser {
6454
6445
  if (!node.filename) {
6455
6446
  throw new Error('LoadStmt requires filename');
6456
6447
  }
6457
- return `LOAD '${node.filename}'`;
6448
+ return `LOAD ${quotes_1.QuoteUtils.escape(node.filename)}`;
6458
6449
  }
6459
6450
  DiscardStmt(node, context) {
6460
6451
  switch (node.target) {
@@ -6941,7 +6932,7 @@ class Deparser {
6941
6932
  }
6942
6933
  output.push('CONNECTION');
6943
6934
  if (node.conninfo) {
6944
- output.push(`'${node.conninfo}'`);
6935
+ output.push(quotes_1.QuoteUtils.escape(node.conninfo));
6945
6936
  }
6946
6937
  output.push('PUBLICATION');
6947
6938
  if (node.publication && node.publication.length > 0) {
@@ -8224,7 +8215,7 @@ class Deparser {
8224
8215
  }
8225
8216
  output.push('IS');
8226
8217
  if (node.label) {
8227
- output.push(`'${node.label}'`);
8218
+ output.push(quotes_1.QuoteUtils.escape(node.label));
8228
8219
  }
8229
8220
  else {
8230
8221
  output.push('NULL');
@@ -8281,10 +8272,10 @@ class Deparser {
8281
8272
  output.push(conversionName);
8282
8273
  }
8283
8274
  if (node.for_encoding_name) {
8284
- output.push('FOR', `'${node.for_encoding_name}'`);
8275
+ output.push('FOR', quotes_1.QuoteUtils.escape(node.for_encoding_name));
8285
8276
  }
8286
8277
  if (node.to_encoding_name) {
8287
- output.push('TO', `'${node.to_encoding_name}'`);
8278
+ output.push('TO', quotes_1.QuoteUtils.escape(node.to_encoding_name));
8288
8279
  }
8289
8280
  if (node.func_name && node.func_name.length > 0) {
8290
8281
  output.push('FROM');
@@ -9419,7 +9410,7 @@ class Deparser {
9419
9410
  const preservedDefName = quotedDefName !== defName ? quotedDefName : defName;
9420
9411
  // Handle String arguments with single quotes for string literals
9421
9412
  if (defValue.String) {
9422
- return `${preservedDefName} = '${defValue.String.sval}'`;
9413
+ return `${preservedDefName} = ${quotes_1.QuoteUtils.escape(defValue.String.sval)}`;
9423
9414
  }
9424
9415
  return `${preservedDefName} = ${this.visit(defValue, context)}`;
9425
9416
  }
@@ -9542,7 +9533,7 @@ class Deparser {
9542
9533
  // For CREATE COLLATION, ensure String nodes are quoted as string literals
9543
9534
  let valueStr;
9544
9535
  if (defValue.String) {
9545
- valueStr = `'${defValue.String.sval}'`;
9536
+ valueStr = quotes_1.QuoteUtils.escape(defValue.String.sval);
9546
9537
  }
9547
9538
  else {
9548
9539
  valueStr = this.visit(defValue, context);
@@ -9809,6 +9800,21 @@ class Deparser {
9809
9800
  }
9810
9801
  return output.join(' ');
9811
9802
  }
9803
+ quoteDefElemArg(arg, argValue) {
9804
+ if (arg && this.getNodeType(arg) === 'String') {
9805
+ return quotes_1.QuoteUtils.escape(this.getNodeData(arg).sval || '');
9806
+ }
9807
+ return typeof argValue === 'string' && !argValue.startsWith("'")
9808
+ ? quotes_1.QuoteUtils.escape(argValue)
9809
+ : argValue;
9810
+ }
9811
+ formatOptionValue(arg, argValue) {
9812
+ if (arg && this.getNodeType(arg) === 'String') {
9813
+ const sval = this.getNodeData(arg).sval || '';
9814
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(sval) ? sval : quotes_1.QuoteUtils.escape(sval);
9815
+ }
9816
+ return argValue;
9817
+ }
9812
9818
  Var(node, context) {
9813
9819
  if (node.varno && node.varattno) {
9814
9820
  return `$${node.varno}.${node.varattno}`;
@@ -9848,7 +9854,7 @@ class Deparser {
9848
9854
  }
9849
9855
  if (node.rowexpr) {
9850
9856
  output.push(',');
9851
- output.push(`'${this.visit(node.rowexpr, context)}'`);
9857
+ output.push(this.visit(node.rowexpr, context));
9852
9858
  }
9853
9859
  if (node.colexprs && node.colexprs.length > 0) {
9854
9860
  output.push('COLUMNS');
@@ -9865,25 +9871,40 @@ class Deparser {
9865
9871
  if (node.lateral) {
9866
9872
  output.push('LATERAL');
9867
9873
  }
9868
- if (node.docexpr) {
9869
- output.push(this.visit(node.docexpr, context));
9874
+ const args = [];
9875
+ if (node.namespaces && node.namespaces.length > 0) {
9876
+ const namespaces = list_utils_1.ListUtils.unwrapList(node.namespaces).map(ns => {
9877
+ const target = this.getNodeData(ns);
9878
+ const uri = this.visit(target.val, context);
9879
+ return target.name ? `${uri} AS ${quotes_1.QuoteUtils.quoteIdentifier(target.name)}` : `DEFAULT ${uri}`;
9880
+ });
9881
+ args.push(`XMLNAMESPACES(${namespaces.join(', ')}),`);
9870
9882
  }
9871
9883
  if (node.rowexpr) {
9872
- output.push('PASSING');
9873
- output.push(this.visit(node.rowexpr, context));
9884
+ args.push(this.formatXmlTableExpr(node.rowexpr, context));
9885
+ }
9886
+ if (node.docexpr) {
9887
+ args.push('PASSING');
9888
+ args.push(this.formatXmlTableExpr(node.docexpr, context));
9874
9889
  }
9875
9890
  if (node.columns && node.columns.length > 0) {
9876
- output.push('COLUMNS');
9877
- const columns = list_utils_1.ListUtils.unwrapList(node.columns)
9891
+ args.push('COLUMNS');
9892
+ args.push(list_utils_1.ListUtils.unwrapList(node.columns)
9878
9893
  .map(col => this.visit(col, context))
9879
- .join(', ');
9880
- output.push(`(${columns})`);
9894
+ .join(', '));
9881
9895
  }
9896
+ output.push(`XMLTABLE(${args.join(' ')})`);
9882
9897
  if (node.alias) {
9883
9898
  output.push(this.Alias(node.alias, context));
9884
9899
  }
9885
9900
  return output.join(' ');
9886
9901
  }
9902
+ formatXmlTableExpr(expr, context) {
9903
+ const value = this.visit(expr, context);
9904
+ return ['A_Const', 'ColumnRef', 'ParamRef', 'FuncCall'].includes(this.getNodeType(expr))
9905
+ ? value
9906
+ : `(${value})`;
9907
+ }
9887
9908
  RangeTableFuncCol(node, context) {
9888
9909
  const output = [];
9889
9910
  if (node.colname) {
@@ -9897,12 +9918,15 @@ class Deparser {
9897
9918
  }
9898
9919
  if (node.colexpr) {
9899
9920
  output.push('PATH');
9900
- output.push(`'${this.visit(node.colexpr, context)}'`);
9921
+ output.push(this.visit(node.colexpr, context));
9901
9922
  }
9902
9923
  if (node.coldefexpr) {
9903
9924
  output.push('DEFAULT');
9904
9925
  output.push(this.visit(node.coldefexpr, context));
9905
9926
  }
9927
+ if (node.is_not_null) {
9928
+ output.push('NOT NULL');
9929
+ }
9906
9930
  return output.join(' ');
9907
9931
  }
9908
9932
  formatJsonFormat(format) {
package/esm/deparser.js CHANGED
@@ -119,6 +119,31 @@ function isWrappedParseResult(obj) {
119
119
  * The deparser automatically detects bare ParseResult objects for backward
120
120
  * compatibility and wraps them internally for consistent processing.
121
121
  */
122
+ function formatBitString(bsval) {
123
+ const quote = (value) => value.replace(/'/g, "''");
124
+ if (/^x[0-9A-Fa-f]+$/.test(bsval)) {
125
+ return `x'${bsval.substring(1)}'`;
126
+ }
127
+ if (bsval.startsWith('b')) {
128
+ return `b'${quote(bsval.substring(1))}'`;
129
+ }
130
+ return `b'${quote(bsval)}'`;
131
+ }
132
+ 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)*)?)$/;
133
+ function formatInteger(ival) {
134
+ const value = String(ival);
135
+ if (!/^[+-]?\d+$/.test(value)) {
136
+ throw new Error(`Invalid integer literal: ${value}`);
137
+ }
138
+ return value;
139
+ }
140
+ function formatNumeric(fval) {
141
+ const value = String(fval);
142
+ if (!NUMERIC_LITERAL.test(value)) {
143
+ throw new Error(`Invalid numeric literal: ${value}`);
144
+ }
145
+ return value;
146
+ }
122
147
  export class Deparser {
123
148
  tree;
124
149
  options;
@@ -1587,39 +1612,39 @@ export class Deparser {
1587
1612
  if (nodeAny.ival !== undefined) {
1588
1613
  if (typeof nodeAny.ival === 'object' && nodeAny.ival !== null) {
1589
1614
  if (nodeAny.ival.ival !== undefined) {
1590
- return nodeAny.ival.ival.toString();
1615
+ return formatInteger(nodeAny.ival.ival);
1591
1616
  }
1592
1617
  else if (Object.keys(nodeAny.ival).length === 0) {
1593
1618
  return '0';
1594
1619
  }
1595
1620
  else {
1596
- return nodeAny.ival.toString();
1621
+ return formatInteger(nodeAny.ival);
1597
1622
  }
1598
1623
  }
1599
1624
  else if (nodeAny.ival === null) {
1600
1625
  return 'NULL';
1601
1626
  }
1602
1627
  else {
1603
- return nodeAny.ival.toString();
1628
+ return formatInteger(nodeAny.ival);
1604
1629
  }
1605
1630
  }
1606
1631
  else if (nodeAny.fval !== undefined) {
1607
1632
  if (typeof nodeAny.fval === 'object' && nodeAny.fval !== null) {
1608
1633
  if (nodeAny.fval.fval !== undefined) {
1609
- return nodeAny.fval.fval.toString();
1634
+ return formatNumeric(nodeAny.fval.fval);
1610
1635
  }
1611
1636
  else if (Object.keys(nodeAny.fval).length === 0) {
1612
1637
  return '0.0';
1613
1638
  }
1614
1639
  else {
1615
- return nodeAny.fval.toString();
1640
+ return formatNumeric(nodeAny.fval);
1616
1641
  }
1617
1642
  }
1618
1643
  else if (nodeAny.fval === null) {
1619
1644
  return 'NULL';
1620
1645
  }
1621
1646
  else {
1622
- return nodeAny.fval.toString();
1647
+ return formatNumeric(nodeAny.fval);
1623
1648
  }
1624
1649
  }
1625
1650
  else if (nodeAny.sval !== undefined) {
@@ -1666,15 +1691,7 @@ export class Deparser {
1666
1691
  else if (nodeAny.bsval !== undefined) {
1667
1692
  if (typeof nodeAny.bsval === 'object' && nodeAny.bsval !== null) {
1668
1693
  if (nodeAny.bsval.bsval !== undefined) {
1669
- const bsval = nodeAny.bsval.bsval;
1670
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1671
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1672
- return `x'${bsval.substring(1)}'`;
1673
- }
1674
- if (bsval.startsWith('b')) {
1675
- return `b'${bsval.substring(1)}'`;
1676
- }
1677
- return `b'${bsval}'`;
1694
+ return formatBitString(nodeAny.bsval.bsval);
1678
1695
  }
1679
1696
  else if (Object.keys(nodeAny.bsval).length === 0) {
1680
1697
  return "''";
@@ -1687,23 +1704,15 @@ export class Deparser {
1687
1704
  return 'NULL';
1688
1705
  }
1689
1706
  else {
1690
- const bsval = nodeAny.bsval;
1691
- // Check if this is a hexadecimal bit string (starts with x and contains only hex digits)
1692
- if (bsval.startsWith('x') && /^x[0-9A-Fa-f]+$/.test(bsval)) {
1693
- return `x'${bsval.substring(1)}'`;
1694
- }
1695
- if (bsval.startsWith('b')) {
1696
- return `b'${bsval.substring(1)}'`;
1697
- }
1698
- return `b'${bsval}'`;
1707
+ return formatBitString(nodeAny.bsval);
1699
1708
  }
1700
1709
  }
1701
1710
  if (nodeAny.val) {
1702
1711
  if (nodeAny.val.Integer?.ival !== undefined) {
1703
- return nodeAny.val.Integer.ival.toString();
1712
+ return formatInteger(nodeAny.val.Integer.ival);
1704
1713
  }
1705
1714
  else if (nodeAny.val.Float?.fval !== undefined) {
1706
- return nodeAny.val.Float.fval.toString();
1715
+ return formatNumeric(nodeAny.val.Float.fval);
1707
1716
  }
1708
1717
  else if (nodeAny.val.String?.sval !== undefined) {
1709
1718
  return QuoteUtils.escape(nodeAny.val.String.sval);
@@ -1712,7 +1721,7 @@ export class Deparser {
1712
1721
  return nodeAny.val.Boolean.boolval ? 'true' : 'false';
1713
1722
  }
1714
1723
  else if (nodeAny.val.BitString?.bsval !== undefined) {
1715
- return nodeAny.val.BitString.bsval;
1724
+ return formatBitString(nodeAny.val.BitString.bsval);
1716
1725
  }
1717
1726
  }
1718
1727
  if (nodeAny.isnull === true) {
@@ -1724,15 +1733,15 @@ export class Deparser {
1724
1733
  }
1725
1734
  if (nodeAny.Integer !== undefined) {
1726
1735
  if (typeof nodeAny.Integer === 'object' && nodeAny.Integer.ival !== undefined) {
1727
- return nodeAny.Integer.ival.toString();
1736
+ return formatInteger(nodeAny.Integer.ival);
1728
1737
  }
1729
- return nodeAny.Integer.toString();
1738
+ return formatInteger(nodeAny.Integer);
1730
1739
  }
1731
1740
  if (nodeAny.Float !== undefined) {
1732
1741
  if (typeof nodeAny.Float === 'object' && nodeAny.Float.fval !== undefined) {
1733
- return nodeAny.Float.fval.toString();
1742
+ return formatNumeric(nodeAny.Float.fval);
1734
1743
  }
1735
- return nodeAny.Float.toString();
1744
+ return formatNumeric(nodeAny.Float);
1736
1745
  }
1737
1746
  if (nodeAny.String !== undefined) {
1738
1747
  if (typeof nodeAny.String === 'object' && nodeAny.String.sval !== undefined) {
@@ -2383,24 +2392,16 @@ export class Deparser {
2383
2392
  return QuoteUtils.quoteIdentifier(value);
2384
2393
  }
2385
2394
  Integer(node, context) {
2386
- return node.ival?.toString() || '0';
2395
+ return node.ival === undefined || node.ival === null ? '0' : formatInteger(node.ival);
2387
2396
  }
2388
2397
  Float(node, context) {
2389
- return node.fval || '0.0';
2398
+ return node.fval ? formatNumeric(node.fval) : '0.0';
2390
2399
  }
2391
2400
  Boolean(node, context) {
2392
2401
  return node.boolval ? 'true' : 'false';
2393
2402
  }
2394
2403
  BitString(node, context) {
2395
- // Check if this is a hexadecimal bit string (starts with x)
2396
- if (node.bsval.startsWith('x')) {
2397
- return `x'${node.bsval.substring(1)}'`;
2398
- }
2399
- if (node.bsval.startsWith('b')) {
2400
- return `b'${node.bsval.substring(1)}'`;
2401
- }
2402
- // Fallback for raw values without prefix
2403
- return `b'${node.bsval}'`;
2404
+ return formatBitString(node.bsval);
2404
2405
  }
2405
2406
  Null(node, context) {
2406
2407
  return 'NULL';
@@ -3632,7 +3633,7 @@ export class Deparser {
3632
3633
  const opclassOpts = ListUtils.unwrapList(node.opclassopts).map(opt => {
3633
3634
  if (opt.DefElem && opt.DefElem.arg && this.getNodeType(opt.DefElem.arg) === 'String') {
3634
3635
  const stringData = this.getNodeData(opt.DefElem.arg);
3635
- return `${opt.DefElem.defname}='${stringData.sval}'`;
3636
+ return `${QuoteUtils.quoteIdentifierAfterDot(opt.DefElem.defname)}=${QuoteUtils.escape(stringData.sval)}`;
3636
3637
  }
3637
3638
  return this.visit(opt, context.spawn('IndexElem'));
3638
3639
  });
@@ -3933,19 +3934,19 @@ export class Deparser {
3933
3934
  case 'TRANS_STMT_PREPARE':
3934
3935
  output.push('PREPARE TRANSACTION');
3935
3936
  if (node.gid) {
3936
- output.push(`'${node.gid}'`);
3937
+ output.push(QuoteUtils.escape(node.gid));
3937
3938
  }
3938
3939
  break;
3939
3940
  case 'TRANS_STMT_COMMIT_PREPARED':
3940
3941
  output.push('COMMIT PREPARED');
3941
3942
  if (node.gid) {
3942
- output.push(`'${node.gid}'`);
3943
+ output.push(QuoteUtils.escape(node.gid));
3943
3944
  }
3944
3945
  break;
3945
3946
  case 'TRANS_STMT_ROLLBACK_PREPARED':
3946
3947
  output.push('ROLLBACK PREPARED');
3947
3948
  if (node.gid) {
3948
- output.push(`'${node.gid}'`);
3949
+ output.push(QuoteUtils.escape(node.gid));
3949
3950
  }
3950
3951
  break;
3951
3952
  default:
@@ -4021,8 +4022,8 @@ export class Deparser {
4021
4022
  const nodeData = this.getNodeData(arg);
4022
4023
  if (nodeData.sval !== undefined) {
4023
4024
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
4024
- if (svalValue === '' || svalValue.includes(' ') || svalValue.includes('-') || /[A-Z]/.test(svalValue) || /^\d/.test(svalValue) || svalValue.includes('.') || svalValue.includes('$') || svalValue.toLowerCase() === 'all' || /^[+-]\d/.test(svalValue)) {
4025
- return `'${svalValue}'`;
4025
+ 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)) {
4026
+ return QuoteUtils.escape(svalValue);
4026
4027
  }
4027
4028
  return svalValue;
4028
4029
  }
@@ -5549,9 +5550,9 @@ export class Deparser {
5549
5550
  if (context.parentNodeTypes.includes('IndexElem')) {
5550
5551
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5551
5552
  const stringData = this.getNodeData(node.arg);
5552
- return `${node.defname}='${stringData.sval}'`;
5553
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${QuoteUtils.escape(stringData.sval)}`;
5553
5554
  }
5554
- return `${node.defname}=${this.visit(node.arg, context.spawn('DefElem'))}`;
5555
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, this.visit(node.arg, context.spawn('DefElem')))}`;
5555
5556
  }
5556
5557
  // Handle CREATE OPERATOR boolean flags - MUST be first to preserve case
5557
5558
  if (context.parentNodeTypes.includes('DefineStmt') &&
@@ -5576,9 +5577,7 @@ export class Deparser {
5576
5577
  const defElemContext = context.spawn('DefElem');
5577
5578
  const argValue = this.visit(node.arg, defElemContext);
5578
5579
  if (context.parentNodeTypes.includes('CreateFdwStmt') || context.parentNodeTypes.includes('AlterFdwStmt')) {
5579
- const finalValue = typeof argValue === 'string' && !argValue.startsWith("'")
5580
- ? `'${argValue}'`
5581
- : argValue;
5580
+ const finalValue = this.quoteDefElemArg(node.arg, argValue);
5582
5581
  const quotedDefname = QuoteUtils.quoteIdentifier(node.defname);
5583
5582
  if (node.defaction === 'DEFELEM_ADD') {
5584
5583
  return `ADD ${quotedDefname} ${finalValue}`;
@@ -5591,9 +5590,7 @@ export class Deparser {
5591
5590
  }
5592
5591
  return `${quotedDefname} ${finalValue}`;
5593
5592
  }
5594
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5595
- ? `'${argValue}'`
5596
- : argValue;
5593
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5597
5594
  if (node.defaction === 'DEFELEM_ADD') {
5598
5595
  return `ADD ${node.defname} ${quotedValue}`;
5599
5596
  }
@@ -5625,9 +5622,7 @@ export class Deparser {
5625
5622
  }
5626
5623
  const defElemContext = context.spawn('DefElem');
5627
5624
  const argValue = this.visit(node.arg, defElemContext);
5628
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5629
- ? `'${argValue}'`
5630
- : argValue;
5625
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5631
5626
  return `PASSWORD ${quotedValue}`;
5632
5627
  }
5633
5628
  }
@@ -5692,9 +5687,7 @@ export class Deparser {
5692
5687
  }
5693
5688
  }
5694
5689
  if (node.defname === 'validUntil') {
5695
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5696
- ? `'${argValue}'`
5697
- : argValue;
5690
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5698
5691
  return `VALID UNTIL ${quotedValue}`;
5699
5692
  }
5700
5693
  if (node.defname === 'adminmembers') {
@@ -5874,9 +5867,7 @@ export class Deparser {
5874
5867
  if (context.parentNodeTypes.includes('AlterExtensionStmt')) {
5875
5868
  if (node.defname === 'new_version') {
5876
5869
  // argValue is unquoted due to DefElem context, so we need to quote it
5877
- const quotedValue = typeof argValue === 'string' && !argValue.startsWith("'")
5878
- ? `'${argValue}'`
5879
- : argValue;
5870
+ const quotedValue = this.quoteDefElemArg(node.arg, argValue);
5880
5871
  return `UPDATE TO ${quotedValue}`;
5881
5872
  }
5882
5873
  if (node.defname === 'schema') {
@@ -5901,15 +5892,15 @@ export class Deparser {
5901
5892
  }
5902
5893
  // Handle IndexStmt WITH clause options - no quotes, compact formatting
5903
5894
  if (context.parentNodeTypes.includes('IndexStmt')) {
5904
- return `${node.defname}=${argValue}`;
5895
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5905
5896
  }
5906
5897
  // Handle IndexElem opclassopts - preserve string values as strings
5907
5898
  if (context.parentNodeTypes.includes('IndexElem')) {
5908
5899
  if (node.arg && this.getNodeType(node.arg) === 'String') {
5909
5900
  const stringData = this.getNodeData(node.arg);
5910
- return `${node.defname}='${stringData.sval}'`;
5901
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${QuoteUtils.escape(stringData.sval)}`;
5911
5902
  }
5912
- return `${node.defname}=${argValue}`;
5903
+ return `${QuoteUtils.quoteIdentifierAfterDot(node.defname)}=${this.formatOptionValue(node.arg, argValue)}`;
5913
5904
  }
5914
5905
  // Handle CreateStmt table options - no quotes, compact formatting
5915
5906
  if (context.parentNodeTypes.includes('CreateStmt')) {
@@ -5919,9 +5910,9 @@ export class Deparser {
5919
5910
  // For numeric values, use the raw value without quotes
5920
5911
  if (node.arg && this.getNodeType(node.arg) === 'Integer') {
5921
5912
  const integerData = this.getNodeData(node.arg);
5922
- return `${relOptName}=${integerData.ival}`;
5913
+ return `${relOptName}=${formatInteger(integerData.ival)}`;
5923
5914
  }
5924
- return `${relOptName}=${argValue}`;
5915
+ return `${relOptName}=${this.formatOptionValue(node.arg, argValue)}`;
5925
5916
  }
5926
5917
  // Handle CreateEventTrigStmt WHEN clause - use IN syntax for List arguments
5927
5918
  if (context.parentNodeTypes.includes('CreateEventTrigStmt')) {
@@ -5931,7 +5922,7 @@ export class Deparser {
5931
5922
  const values = listItems.map(item => {
5932
5923
  if (this.getNodeType(item) === 'String') {
5933
5924
  const stringData = this.getNodeData(item);
5934
- return `'${stringData.sval || ''}'`;
5925
+ return QuoteUtils.escape(stringData.sval || '');
5935
5926
  }
5936
5927
  return this.visit(item, context);
5937
5928
  });
@@ -5946,7 +5937,7 @@ export class Deparser {
5946
5937
  if (node.arg && this.getNodeType(node.arg) === 'TypeName') {
5947
5938
  return `${optionName} = ${argValue}`;
5948
5939
  }
5949
- return `${optionName} = ${argValue}`;
5940
+ return `${optionName} = ${this.formatOptionValue(node.arg, argValue)}`;
5950
5941
  }
5951
5942
  // Handle ViewStmt WITH options - don't quote numeric values
5952
5943
  if (context.parentNodeTypes.includes('ViewStmt')) {
@@ -6019,7 +6010,7 @@ export class Deparser {
6019
6010
  return `${quotedDefname} = ${stringData.sval}`;
6020
6011
  }
6021
6012
  // Regular string literals get single quotes
6022
- return `${quotedDefname} = '${stringData.sval}'`;
6013
+ return `${quotedDefname} = ${QuoteUtils.escape(stringData.sval)}`;
6023
6014
  }
6024
6015
  return `${quotedDefname} = ${argValue}`;
6025
6016
  }
@@ -6040,7 +6031,7 @@ export class Deparser {
6040
6031
  return `${node.defname} = ${stringData.sval}`;
6041
6032
  }
6042
6033
  // Regular string literals get single quotes
6043
- return `${node.defname} = '${stringData.sval}'`;
6034
+ return `${node.defname} = ${QuoteUtils.escape(stringData.sval)}`;
6044
6035
  }
6045
6036
  if (node.arg && this.getNodeType(node.arg) === 'Boolean') {
6046
6037
  const boolData = this.getNodeData(node.arg);
@@ -6090,7 +6081,7 @@ export class Deparser {
6090
6081
  }
6091
6082
  if (node.location) {
6092
6083
  output.push('LOCATION');
6093
- output.push(`'${node.location}'`);
6084
+ output.push(QuoteUtils.escape(node.location));
6094
6085
  }
6095
6086
  if (node.options && node.options.length > 0) {
6096
6087
  output.push('WITH');
@@ -6423,7 +6414,7 @@ export class Deparser {
6423
6414
  }
6424
6415
  if (node.payload !== null && node.payload !== undefined) {
6425
6416
  output.push(',');
6426
- output.push(`'${node.payload}'`);
6417
+ output.push(QuoteUtils.escape(node.payload));
6427
6418
  }
6428
6419
  return output.join(' ');
6429
6420
  }
@@ -6451,7 +6442,7 @@ export class Deparser {
6451
6442
  if (!node.filename) {
6452
6443
  throw new Error('LoadStmt requires filename');
6453
6444
  }
6454
- return `LOAD '${node.filename}'`;
6445
+ return `LOAD ${QuoteUtils.escape(node.filename)}`;
6455
6446
  }
6456
6447
  DiscardStmt(node, context) {
6457
6448
  switch (node.target) {
@@ -6938,7 +6929,7 @@ export class Deparser {
6938
6929
  }
6939
6930
  output.push('CONNECTION');
6940
6931
  if (node.conninfo) {
6941
- output.push(`'${node.conninfo}'`);
6932
+ output.push(QuoteUtils.escape(node.conninfo));
6942
6933
  }
6943
6934
  output.push('PUBLICATION');
6944
6935
  if (node.publication && node.publication.length > 0) {
@@ -8221,7 +8212,7 @@ export class Deparser {
8221
8212
  }
8222
8213
  output.push('IS');
8223
8214
  if (node.label) {
8224
- output.push(`'${node.label}'`);
8215
+ output.push(QuoteUtils.escape(node.label));
8225
8216
  }
8226
8217
  else {
8227
8218
  output.push('NULL');
@@ -8278,10 +8269,10 @@ export class Deparser {
8278
8269
  output.push(conversionName);
8279
8270
  }
8280
8271
  if (node.for_encoding_name) {
8281
- output.push('FOR', `'${node.for_encoding_name}'`);
8272
+ output.push('FOR', QuoteUtils.escape(node.for_encoding_name));
8282
8273
  }
8283
8274
  if (node.to_encoding_name) {
8284
- output.push('TO', `'${node.to_encoding_name}'`);
8275
+ output.push('TO', QuoteUtils.escape(node.to_encoding_name));
8285
8276
  }
8286
8277
  if (node.func_name && node.func_name.length > 0) {
8287
8278
  output.push('FROM');
@@ -9416,7 +9407,7 @@ export class Deparser {
9416
9407
  const preservedDefName = quotedDefName !== defName ? quotedDefName : defName;
9417
9408
  // Handle String arguments with single quotes for string literals
9418
9409
  if (defValue.String) {
9419
- return `${preservedDefName} = '${defValue.String.sval}'`;
9410
+ return `${preservedDefName} = ${QuoteUtils.escape(defValue.String.sval)}`;
9420
9411
  }
9421
9412
  return `${preservedDefName} = ${this.visit(defValue, context)}`;
9422
9413
  }
@@ -9539,7 +9530,7 @@ export class Deparser {
9539
9530
  // For CREATE COLLATION, ensure String nodes are quoted as string literals
9540
9531
  let valueStr;
9541
9532
  if (defValue.String) {
9542
- valueStr = `'${defValue.String.sval}'`;
9533
+ valueStr = QuoteUtils.escape(defValue.String.sval);
9543
9534
  }
9544
9535
  else {
9545
9536
  valueStr = this.visit(defValue, context);
@@ -9806,6 +9797,21 @@ export class Deparser {
9806
9797
  }
9807
9798
  return output.join(' ');
9808
9799
  }
9800
+ quoteDefElemArg(arg, argValue) {
9801
+ if (arg && this.getNodeType(arg) === 'String') {
9802
+ return QuoteUtils.escape(this.getNodeData(arg).sval || '');
9803
+ }
9804
+ return typeof argValue === 'string' && !argValue.startsWith("'")
9805
+ ? QuoteUtils.escape(argValue)
9806
+ : argValue;
9807
+ }
9808
+ formatOptionValue(arg, argValue) {
9809
+ if (arg && this.getNodeType(arg) === 'String') {
9810
+ const sval = this.getNodeData(arg).sval || '';
9811
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(sval) ? sval : QuoteUtils.escape(sval);
9812
+ }
9813
+ return argValue;
9814
+ }
9809
9815
  Var(node, context) {
9810
9816
  if (node.varno && node.varattno) {
9811
9817
  return `$${node.varno}.${node.varattno}`;
@@ -9845,7 +9851,7 @@ export class Deparser {
9845
9851
  }
9846
9852
  if (node.rowexpr) {
9847
9853
  output.push(',');
9848
- output.push(`'${this.visit(node.rowexpr, context)}'`);
9854
+ output.push(this.visit(node.rowexpr, context));
9849
9855
  }
9850
9856
  if (node.colexprs && node.colexprs.length > 0) {
9851
9857
  output.push('COLUMNS');
@@ -9862,25 +9868,40 @@ export class Deparser {
9862
9868
  if (node.lateral) {
9863
9869
  output.push('LATERAL');
9864
9870
  }
9865
- if (node.docexpr) {
9866
- output.push(this.visit(node.docexpr, context));
9871
+ const args = [];
9872
+ if (node.namespaces && node.namespaces.length > 0) {
9873
+ const namespaces = ListUtils.unwrapList(node.namespaces).map(ns => {
9874
+ const target = this.getNodeData(ns);
9875
+ const uri = this.visit(target.val, context);
9876
+ return target.name ? `${uri} AS ${QuoteUtils.quoteIdentifier(target.name)}` : `DEFAULT ${uri}`;
9877
+ });
9878
+ args.push(`XMLNAMESPACES(${namespaces.join(', ')}),`);
9867
9879
  }
9868
9880
  if (node.rowexpr) {
9869
- output.push('PASSING');
9870
- output.push(this.visit(node.rowexpr, context));
9881
+ args.push(this.formatXmlTableExpr(node.rowexpr, context));
9882
+ }
9883
+ if (node.docexpr) {
9884
+ args.push('PASSING');
9885
+ args.push(this.formatXmlTableExpr(node.docexpr, context));
9871
9886
  }
9872
9887
  if (node.columns && node.columns.length > 0) {
9873
- output.push('COLUMNS');
9874
- const columns = ListUtils.unwrapList(node.columns)
9888
+ args.push('COLUMNS');
9889
+ args.push(ListUtils.unwrapList(node.columns)
9875
9890
  .map(col => this.visit(col, context))
9876
- .join(', ');
9877
- output.push(`(${columns})`);
9891
+ .join(', '));
9878
9892
  }
9893
+ output.push(`XMLTABLE(${args.join(' ')})`);
9879
9894
  if (node.alias) {
9880
9895
  output.push(this.Alias(node.alias, context));
9881
9896
  }
9882
9897
  return output.join(' ');
9883
9898
  }
9899
+ formatXmlTableExpr(expr, context) {
9900
+ const value = this.visit(expr, context);
9901
+ return ['A_Const', 'ColumnRef', 'ParamRef', 'FuncCall'].includes(this.getNodeType(expr))
9902
+ ? value
9903
+ : `(${value})`;
9904
+ }
9884
9905
  RangeTableFuncCol(node, context) {
9885
9906
  const output = [];
9886
9907
  if (node.colname) {
@@ -9894,12 +9915,15 @@ export class Deparser {
9894
9915
  }
9895
9916
  if (node.colexpr) {
9896
9917
  output.push('PATH');
9897
- output.push(`'${this.visit(node.colexpr, context)}'`);
9918
+ output.push(this.visit(node.colexpr, context));
9898
9919
  }
9899
9920
  if (node.coldefexpr) {
9900
9921
  output.push('DEFAULT');
9901
9922
  output.push(this.visit(node.coldefexpr, context));
9902
9923
  }
9924
+ if (node.is_not_null) {
9925
+ output.push('NOT NULL');
9926
+ }
9903
9927
  return output.join(' ');
9904
9928
  }
9905
9929
  formatJsonFormat(format) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgsql-deparser",
3
- "version": "18.3.8",
3
+ "version": "18.3.10",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PostgreSQL AST Deparser",
6
6
  "main": "index.js",
@@ -60,5 +60,5 @@
60
60
  "@pgsql/quotes": "18.2.4",
61
61
  "@pgsql/types": "^18.0.0"
62
62
  },
63
- "gitHead": "b93d0f73eab976c7db21f7bc7e2dad165d25173e"
63
+ "gitHead": "24882cc67810aaa1af8dd241594634cf54105126"
64
64
  }