pgsql-deparser 17.8.0 → 17.8.2

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.js CHANGED
@@ -4,6 +4,62 @@ exports.Deparser = void 0;
4
4
  const sql_formatter_1 = require("./utils/sql-formatter");
5
5
  const quote_utils_1 = require("./utils/quote-utils");
6
6
  const list_utils_1 = require("./utils/list-utils");
7
+ /**
8
+ * List of real PostgreSQL built-in types as they appear in pg_catalog.pg_type.typname.
9
+ * These are stored in lowercase in PostgreSQL system catalogs.
10
+ * Use these for lookups, validations, or introspection logic.
11
+ */
12
+ const pgCatalogTypes = [
13
+ // Integers
14
+ 'int2', // smallint
15
+ 'int4', // integer
16
+ 'int8', // bigint
17
+ // Floating-point & numeric
18
+ 'float4', // real
19
+ 'float8', // double precision
20
+ 'numeric', // arbitrary precision (aka "decimal")
21
+ // Text & string
22
+ 'varchar', // variable-length string
23
+ 'char', // internal one-byte type (used in special cases)
24
+ 'bpchar', // blank-padded char(n)
25
+ 'text', // unlimited string
26
+ 'bool', // boolean
27
+ // Dates & times
28
+ 'date', // calendar date
29
+ 'time', // time without time zone
30
+ 'timetz', // time with time zone
31
+ 'timestamp', // timestamp without time zone
32
+ 'timestamptz', // timestamp with time zone
33
+ 'interval', // duration
34
+ // Binary & structured
35
+ 'bytea', // binary data
36
+ 'uuid', // universally unique identifier
37
+ // JSON & XML
38
+ 'json', // textual JSON
39
+ 'jsonb', // binary JSON
40
+ 'xml', // XML format
41
+ // Money & bitstrings
42
+ 'money', // currency value
43
+ 'bit', // fixed-length bit string
44
+ 'varbit', // variable-length bit string
45
+ // Network types
46
+ 'inet', // IPv4 or IPv6 address
47
+ 'cidr', // network address
48
+ 'macaddr', // MAC address (6 bytes)
49
+ 'macaddr8' // MAC address (8 bytes)
50
+ ];
51
+ /**
52
+ * Parser-level type aliases accepted by PostgreSQL SQL syntax,
53
+ * but not present in pg_catalog.pg_type. These are resolved to
54
+ * real types during parsing and never appear in introspection.
55
+ */
56
+ const pgCatalogTypeAliases = [
57
+ ['numeric', ['decimal', 'dec']],
58
+ ['int4', ['int', 'integer']],
59
+ ['float8', ['float']],
60
+ ['bpchar', ['character']],
61
+ ['varchar', ['character varying']]
62
+ ];
7
63
  // Type guards for better type safety
8
64
  function isParseResult(obj) {
9
65
  // A ParseResult is an object that could have stmts (but not required)
@@ -261,17 +317,36 @@ class Deparser {
261
317
  if (node.targetList) {
262
318
  const targetList = list_utils_1.ListUtils.unwrapList(node.targetList);
263
319
  if (this.formatter.isPretty()) {
264
- const targetStrings = targetList
265
- .map(e => {
266
- const targetStr = this.visit(e, { ...context, select: true });
267
- if (this.containsMultilineStringLiteral(targetStr)) {
268
- return targetStr;
320
+ if (targetList.length === 1) {
321
+ const targetNode = targetList[0];
322
+ const target = this.visit(targetNode, { ...context, select: true });
323
+ // Check if single target is complex - if so, use multiline format
324
+ if (this.isComplexSelectTarget(targetNode)) {
325
+ output.push('SELECT' + distinctPart);
326
+ if (this.containsMultilineStringLiteral(target)) {
327
+ output.push(target);
328
+ }
329
+ else {
330
+ output.push(this.formatter.indent(target));
331
+ }
269
332
  }
270
- return this.formatter.indent(targetStr);
271
- });
272
- const formattedTargets = targetStrings.join(',' + this.formatter.newline());
273
- output.push('SELECT' + distinctPart);
274
- output.push(formattedTargets);
333
+ else {
334
+ output.push('SELECT' + distinctPart + ' ' + target);
335
+ }
336
+ }
337
+ else {
338
+ const targetStrings = targetList
339
+ .map(e => {
340
+ const targetStr = this.visit(e, { ...context, select: true });
341
+ if (this.containsMultilineStringLiteral(targetStr)) {
342
+ return targetStr;
343
+ }
344
+ return this.formatter.indent(targetStr);
345
+ });
346
+ const formattedTargets = targetStrings.join(',' + this.formatter.newline());
347
+ output.push('SELECT' + distinctPart);
348
+ output.push(formattedTargets);
349
+ }
275
350
  }
276
351
  else {
277
352
  const targets = targetList
@@ -310,12 +385,28 @@ class Deparser {
310
385
  }
311
386
  }
312
387
  if (node.valuesLists) {
313
- output.push('VALUES');
314
- const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
315
- const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
316
- return this.formatter.parens(values.join(', '));
317
- });
318
- output.push(lists.join(', '));
388
+ if (this.formatter.isPretty()) {
389
+ output.push('VALUES');
390
+ const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
391
+ const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
392
+ return this.formatter.parens(values.join(', '));
393
+ });
394
+ const indentedTuples = lists.map(tuple => {
395
+ if (this.containsMultilineStringLiteral(tuple)) {
396
+ return tuple;
397
+ }
398
+ return this.formatter.indent(tuple);
399
+ });
400
+ output.push(indentedTuples.join(',\n'));
401
+ }
402
+ else {
403
+ output.push('VALUES');
404
+ const lists = list_utils_1.ListUtils.unwrapList(node.valuesLists).map(list => {
405
+ const values = list_utils_1.ListUtils.unwrapList(list).map(val => this.visit(val, context));
406
+ return this.formatter.parens(values.join(', '));
407
+ });
408
+ output.push(lists.join(', '));
409
+ }
319
410
  }
320
411
  if (node.groupClause) {
321
412
  const groupList = list_utils_1.ListUtils.unwrapList(node.groupClause);
@@ -684,6 +775,64 @@ class Deparser {
684
775
  node.SubLink ||
685
776
  node.A_Expr);
686
777
  }
778
+ isComplexSelectTarget(node) {
779
+ if (!node)
780
+ return false;
781
+ if (node.ResTarget?.val) {
782
+ return this.isComplexExpression(node.ResTarget.val);
783
+ }
784
+ // Always complex: CASE expressions
785
+ if (node.CaseExpr)
786
+ return true;
787
+ // Always complex: Subqueries and subselects
788
+ if (node.SubLink)
789
+ return true;
790
+ // Always complex: Boolean tests and expressions
791
+ if (node.NullTest || node.BooleanTest || node.BoolExpr)
792
+ return true;
793
+ // COALESCE and similar functions - complex if multiple arguments
794
+ if (node.CoalesceExpr) {
795
+ const args = node.CoalesceExpr.args;
796
+ if (args && Array.isArray(args) && args.length > 1)
797
+ return true;
798
+ }
799
+ // Function calls - complex if multiple args or has clauses
800
+ if (node.FuncCall) {
801
+ const funcCall = node.FuncCall;
802
+ const args = funcCall.args ? (Array.isArray(funcCall.args) ? funcCall.args : [funcCall.args]) : [];
803
+ // Complex if has window clause, filter, order by, etc.
804
+ if (funcCall.over || funcCall.agg_filter || funcCall.agg_order || funcCall.agg_distinct) {
805
+ return true;
806
+ }
807
+ // Complex if multiple arguments
808
+ if (args.length > 1)
809
+ return true;
810
+ if (args.length === 1) {
811
+ return this.isComplexSelectTarget(args[0]);
812
+ }
813
+ }
814
+ if (node.A_Expr) {
815
+ const expr = node.A_Expr;
816
+ // Check if operands are complex
817
+ if (expr.lexpr && this.isComplexSelectTarget(expr.lexpr))
818
+ return true;
819
+ if (expr.rexpr && this.isComplexSelectTarget(expr.rexpr))
820
+ return true;
821
+ return false;
822
+ }
823
+ if (node.TypeCast) {
824
+ return this.isComplexSelectTarget(node.TypeCast.arg);
825
+ }
826
+ if (node.A_ArrayExpr)
827
+ return true;
828
+ if (node.A_Indirection) {
829
+ return this.isComplexSelectTarget(node.A_Indirection.arg);
830
+ }
831
+ if (node.A_Const || node.ColumnRef || node.ParamRef || node.A_Star) {
832
+ return false;
833
+ }
834
+ return false;
835
+ }
687
836
  visitBetweenRange(rexpr, context) {
688
837
  if (rexpr && 'List' in rexpr && rexpr.List?.items) {
689
838
  const items = rexpr.List.items.map((item) => this.visit(item, context));
@@ -702,7 +851,14 @@ class Deparser {
702
851
  const cols = list_utils_1.ListUtils.unwrapList(node.cols);
703
852
  const insertContext = { ...context, insertColumns: true };
704
853
  const columnNames = cols.map(col => this.visit(col, insertContext));
705
- output.push(this.formatter.parens(columnNames.join(', ')));
854
+ if (this.formatter.isPretty()) {
855
+ // Always format columns in multiline parentheses for pretty printing
856
+ const indentedColumns = columnNames.map(col => this.formatter.indent(col));
857
+ output.push('(\n' + indentedColumns.join(',\n') + '\n)');
858
+ }
859
+ else {
860
+ output.push(this.formatter.parens(columnNames.join(', ')));
861
+ }
706
862
  }
707
863
  if (node.selectStmt) {
708
864
  output.push(this.visit(node.selectStmt, context));
@@ -896,14 +1052,15 @@ class Deparser {
896
1052
  if (node.ctes && node.ctes.length > 0) {
897
1053
  const ctes = list_utils_1.ListUtils.unwrapList(node.ctes);
898
1054
  if (this.formatter.isPretty()) {
899
- const cteStrings = ctes.map(cte => {
1055
+ const cteStrings = ctes.map((cte, index) => {
900
1056
  const cteStr = this.visit(cte, context);
1057
+ const prefix = index === 0 ? this.formatter.newline() : ',' + this.formatter.newline();
901
1058
  if (this.containsMultilineStringLiteral(cteStr)) {
902
- return this.formatter.newline() + cteStr;
1059
+ return prefix + cteStr;
903
1060
  }
904
- return this.formatter.newline() + this.formatter.indent(cteStr);
1061
+ return prefix + this.formatter.indent(cteStr);
905
1062
  });
906
- output.push(cteStrings.join(','));
1063
+ output.push(cteStrings.join(''));
907
1064
  }
908
1065
  else {
909
1066
  const cteStrings = ctes.map(cte => this.visit(cte, context));
@@ -1211,7 +1368,13 @@ class Deparser {
1211
1368
  windowParts.push(frameClause);
1212
1369
  }
1213
1370
  if (windowParts.length > 0) {
1214
- result += ` OVER (${windowParts.join(' ')})`;
1371
+ if (this.formatter.isPretty() && windowParts.length > 1) {
1372
+ const formattedParts = windowParts.map(part => this.formatter.indent(part));
1373
+ result += ` OVER (${this.formatter.newline()}${formattedParts.join(this.formatter.newline())}${this.formatter.newline()})`;
1374
+ }
1375
+ else {
1376
+ result += ` OVER (${windowParts.join(' ')})`;
1377
+ }
1215
1378
  }
1216
1379
  else {
1217
1380
  result += ` OVER ()`;
@@ -1487,9 +1650,6 @@ class Deparser {
1487
1650
  return output.join(' ');
1488
1651
  }
1489
1652
  if (catalog === 'pg_catalog') {
1490
- const builtinTypes = ['int2', 'int4', 'int8', 'float4', 'float8', 'numeric', 'decimal',
1491
- 'varchar', 'char', 'bpchar', 'text', 'bool', 'date', 'time', 'timestamp',
1492
- 'timestamptz', 'interval', 'bytea', 'uuid', 'json', 'jsonb'];
1493
1653
  let typeName = `${catalog}.${type}`;
1494
1654
  if (type === 'bpchar' && args) {
1495
1655
  typeName = 'char';
@@ -1784,6 +1944,18 @@ class Deparser {
1784
1944
  return `pg_catalog.${typeName}`;
1785
1945
  }
1786
1946
  }
1947
+ isPgCatalogType(typeName) {
1948
+ const cleanTypeName = typeName.replace(/^pg_catalog\./, '');
1949
+ if (pgCatalogTypes.includes(cleanTypeName)) {
1950
+ return true;
1951
+ }
1952
+ for (const [realType, aliases] of pgCatalogTypeAliases) {
1953
+ if (aliases.includes(cleanTypeName)) {
1954
+ return true;
1955
+ }
1956
+ }
1957
+ return false;
1958
+ }
1787
1959
  A_ArrayExpr(node, context) {
1788
1960
  const elements = list_utils_1.ListUtils.unwrapList(node.elements);
1789
1961
  const elementStrs = elements.map(el => this.visit(el, context));
@@ -1835,15 +2007,39 @@ class Deparser {
1835
2007
  output.push(this.visit(node.arg, context));
1836
2008
  }
1837
2009
  const args = list_utils_1.ListUtils.unwrapList(node.args);
1838
- for (const arg of args) {
1839
- output.push(this.visit(arg, context));
2010
+ if (this.formatter.isPretty() && args.length > 0) {
2011
+ for (const arg of args) {
2012
+ const whenClause = this.visit(arg, context);
2013
+ if (this.containsMultilineStringLiteral(whenClause)) {
2014
+ output.push(this.formatter.newline() + whenClause);
2015
+ }
2016
+ else {
2017
+ output.push(this.formatter.newline() + this.formatter.indent(whenClause));
2018
+ }
2019
+ }
2020
+ if (node.defresult) {
2021
+ const elseResult = this.visit(node.defresult, context);
2022
+ if (this.containsMultilineStringLiteral(elseResult)) {
2023
+ output.push(this.formatter.newline() + 'ELSE ' + elseResult);
2024
+ }
2025
+ else {
2026
+ output.push(this.formatter.newline() + this.formatter.indent('ELSE ' + elseResult));
2027
+ }
2028
+ }
2029
+ output.push(this.formatter.newline() + 'END');
2030
+ return output.join(' ');
1840
2031
  }
1841
- if (node.defresult) {
1842
- output.push('ELSE');
1843
- output.push(this.visit(node.defresult, context));
2032
+ else {
2033
+ for (const arg of args) {
2034
+ output.push(this.visit(arg, context));
2035
+ }
2036
+ if (node.defresult) {
2037
+ output.push('ELSE');
2038
+ output.push(this.visit(node.defresult, context));
2039
+ }
2040
+ output.push('END');
2041
+ return output.join(' ');
1844
2042
  }
1845
- output.push('END');
1846
- return output.join(' ');
1847
2043
  }
1848
2044
  CoalesceExpr(node, context) {
1849
2045
  const args = list_utils_1.ListUtils.unwrapList(node.args);
@@ -1853,28 +2049,29 @@ class Deparser {
1853
2049
  TypeCast(node, context) {
1854
2050
  const arg = this.visit(node.arg, context);
1855
2051
  const typeName = this.TypeName(node.typeName, context);
1856
- // Check if this is a bpchar typecast that should use traditional char syntax
1857
- if (typeName === 'bpchar' && node.typeName && node.typeName.names) {
1858
- const names = list_utils_1.ListUtils.unwrapList(node.typeName.names);
1859
- if (names.length === 2 &&
1860
- names[0].String?.sval === 'pg_catalog' &&
1861
- names[1].String?.sval === 'bpchar') {
1862
- return `char ${arg}`;
2052
+ // Check if this is a bpchar typecast that should preserve original syntax for AST consistency
2053
+ if (typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
2054
+ const names = node.typeName?.names;
2055
+ const isQualifiedBpchar = names && names.length === 2 &&
2056
+ names[0]?.String?.sval === 'pg_catalog' &&
2057
+ names[1]?.String?.sval === 'bpchar';
2058
+ if (isQualifiedBpchar) {
2059
+ return `CAST(${arg} AS ${typeName})`;
1863
2060
  }
1864
2061
  }
1865
- // Check if the argument is a complex expression that should preserve CAST syntax
1866
- const argType = this.getNodeType(node.arg);
1867
- const isComplexExpression = argType === 'A_Expr' || argType === 'FuncCall' || argType === 'OpExpr';
1868
- if (!isComplexExpression && (typeName.startsWith('interval') ||
1869
- typeName.startsWith('char') ||
1870
- typeName === '"char"' ||
1871
- typeName.startsWith('bpchar') ||
1872
- typeName === 'bytea' ||
1873
- typeName === 'orderedarray' ||
1874
- typeName === 'date')) {
1875
- // Remove pg_catalog prefix for :: syntax
1876
- const cleanTypeName = typeName.replace('pg_catalog.', '');
1877
- return `${arg}::${cleanTypeName}`;
2062
+ if (this.isPgCatalogType(typeName)) {
2063
+ const argType = this.getNodeType(node.arg);
2064
+ const isSimpleArgument = argType === 'A_Const' || argType === 'ColumnRef';
2065
+ const isFunctionCall = argType === 'FuncCall';
2066
+ if (isSimpleArgument || isFunctionCall) {
2067
+ // For simple arguments, avoid :: syntax if they have complex structure
2068
+ if (isSimpleArgument && (arg.includes('(') || arg.startsWith('-'))) {
2069
+ }
2070
+ else {
2071
+ const cleanTypeName = typeName.replace('pg_catalog.', '');
2072
+ return `${arg}::${cleanTypeName}`;
2073
+ }
2074
+ }
1878
2075
  }
1879
2076
  return `CAST(${arg} AS ${typeName})`;
1880
2077
  }
@@ -2057,7 +2254,14 @@ class Deparser {
2057
2254
  return this.deparse(el, context);
2058
2255
  });
2059
2256
  if (this.formatter.isPretty()) {
2060
- const formattedElements = elementStrs.map(el => this.formatter.indent(el)).join(',' + this.formatter.newline());
2257
+ const formattedElements = elementStrs.map(el => {
2258
+ const trimmedEl = el.trim();
2259
+ // Remove leading newlines from constraint elements to avoid extra blank lines
2260
+ if (trimmedEl.startsWith('\n')) {
2261
+ return this.formatter.indent(trimmedEl.substring(1));
2262
+ }
2263
+ return this.formatter.indent(trimmedEl);
2264
+ }).join(',' + this.formatter.newline());
2061
2265
  output.push('(' + this.formatter.newline() + formattedElements + this.formatter.newline() + ')');
2062
2266
  }
2063
2267
  else {
@@ -2219,9 +2423,25 @@ class Deparser {
2219
2423
  }
2220
2424
  break;
2221
2425
  case 'CONSTR_CHECK':
2222
- output.push('CHECK');
2426
+ if (this.formatter.isPretty() && !context.isColumnConstraint) {
2427
+ output.push('\n' + this.formatter.indent('CHECK'));
2428
+ }
2429
+ else {
2430
+ output.push('CHECK');
2431
+ }
2223
2432
  if (node.raw_expr) {
2224
- output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2433
+ if (this.formatter.isPretty()) {
2434
+ const checkExpr = this.visit(node.raw_expr, context);
2435
+ if (checkExpr.includes('\n')) {
2436
+ output.push('(\n' + this.formatter.indent(checkExpr) + '\n)');
2437
+ }
2438
+ else {
2439
+ output.push(`(${checkExpr})`);
2440
+ }
2441
+ }
2442
+ else {
2443
+ output.push(this.formatter.parens(this.visit(node.raw_expr, context)));
2444
+ }
2225
2445
  }
2226
2446
  // Handle NOT VALID for check constraints
2227
2447
  if (node.skip_validation) {
@@ -2300,7 +2520,12 @@ class Deparser {
2300
2520
  }
2301
2521
  break;
2302
2522
  case 'CONSTR_UNIQUE':
2303
- output.push('UNIQUE');
2523
+ if (this.formatter.isPretty() && !context.isColumnConstraint) {
2524
+ output.push('\n' + this.formatter.indent('UNIQUE'));
2525
+ }
2526
+ else {
2527
+ output.push('UNIQUE');
2528
+ }
2304
2529
  if (node.nulls_not_distinct) {
2305
2530
  output.push('NULLS NOT DISTINCT');
2306
2531
  }
@@ -2318,33 +2543,77 @@ class Deparser {
2318
2543
  case 'CONSTR_FOREIGN':
2319
2544
  // Only add "FOREIGN KEY" for table-level constraints, not column-level constraints
2320
2545
  if (!context.isColumnConstraint) {
2321
- output.push('FOREIGN KEY');
2322
- if (node.fk_attrs && node.fk_attrs.length > 0) {
2323
- const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2324
- .map(attr => this.visit(attr, context))
2325
- .join(', ');
2326
- output.push(`(${fkAttrs})`);
2546
+ if (this.formatter.isPretty()) {
2547
+ output.push('\n' + this.formatter.indent('FOREIGN KEY'));
2548
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2549
+ const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2550
+ .map(attr => this.visit(attr, context))
2551
+ .join(', ');
2552
+ output.push(`(${fkAttrs})`);
2553
+ }
2554
+ output.push('\n' + this.formatter.indent('REFERENCES'));
2555
+ }
2556
+ else {
2557
+ output.push('FOREIGN KEY');
2558
+ if (node.fk_attrs && node.fk_attrs.length > 0) {
2559
+ const fkAttrs = list_utils_1.ListUtils.unwrapList(node.fk_attrs)
2560
+ .map(attr => this.visit(attr, context))
2561
+ .join(', ');
2562
+ output.push(`(${fkAttrs})`);
2563
+ }
2564
+ output.push('REFERENCES');
2327
2565
  }
2328
2566
  }
2329
- output.push('REFERENCES');
2567
+ else {
2568
+ output.push('REFERENCES');
2569
+ }
2330
2570
  if (node.pktable) {
2331
- output.push(this.RangeVar(node.pktable, context));
2571
+ if (this.formatter.isPretty() && !context.isColumnConstraint) {
2572
+ const lastIndex = output.length - 1;
2573
+ if (lastIndex >= 0 && output[lastIndex].includes('REFERENCES')) {
2574
+ output[lastIndex] += ' ' + this.RangeVar(node.pktable, context);
2575
+ }
2576
+ else {
2577
+ output.push(this.RangeVar(node.pktable, context));
2578
+ }
2579
+ }
2580
+ else {
2581
+ output.push(this.RangeVar(node.pktable, context));
2582
+ }
2332
2583
  }
2333
2584
  if (node.pk_attrs && node.pk_attrs.length > 0) {
2334
2585
  const pkAttrs = list_utils_1.ListUtils.unwrapList(node.pk_attrs)
2335
2586
  .map(attr => this.visit(attr, context))
2336
2587
  .join(', ');
2337
- output.push(`(${pkAttrs})`);
2588
+ if (this.formatter.isPretty() && !context.isColumnConstraint) {
2589
+ const lastIndex = output.length - 1;
2590
+ if (lastIndex >= 0) {
2591
+ output[lastIndex] += ` (${pkAttrs})`;
2592
+ }
2593
+ else {
2594
+ output.push(`(${pkAttrs})`);
2595
+ }
2596
+ }
2597
+ else {
2598
+ output.push(`(${pkAttrs})`);
2599
+ }
2338
2600
  }
2339
2601
  if (node.fk_matchtype && node.fk_matchtype !== 's') {
2602
+ let matchClause = '';
2340
2603
  switch (node.fk_matchtype) {
2341
2604
  case 'f':
2342
- output.push('MATCH FULL');
2605
+ matchClause = 'MATCH FULL';
2343
2606
  break;
2344
2607
  case 'p':
2345
- output.push('MATCH PARTIAL');
2608
+ matchClause = 'MATCH PARTIAL';
2346
2609
  break;
2347
2610
  }
2611
+ if (this.formatter.isPretty() && !context.isColumnConstraint) {
2612
+ output.push('\n' + this.formatter.indent(matchClause));
2613
+ }
2614
+ else {
2615
+ output.push(matchClause);
2616
+ }
2348
2617
  }
2349
2618
  if (node.fk_upd_action && node.fk_upd_action !== 'a') {
2350
2619
  let updateClause = 'ON UPDATE ';
@@ -2396,7 +2665,12 @@ class Deparser {
2396
2665
  }
2397
2666
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2398
2667
  if (node.skip_validation && !context.isDomainConstraint) {
2399
- output.push('NOT VALID');
2668
+ if (this.formatter.isPretty() && !context.isColumnConstraint) {
2669
+ output.push('\n' + this.formatter.indent('NOT VALID'));
2670
+ }
2671
+ else {
2672
+ output.push('NOT VALID');
2673
+ }
2400
2674
  }
2401
2675
  break;
2402
2676
  case 'CONSTR_ATTR_DEFERRABLE':
@@ -3268,11 +3542,23 @@ class Deparser {
3268
3542
  }
3269
3543
  }
3270
3544
  else if (node.quals) {
3545
+ const qualsStr = this.visit(node.quals, context);
3271
3546
  if (this.formatter.isPretty()) {
3272
- output.push(` ON ${this.visit(node.quals, context)}`);
3547
+ // For complex JOIN conditions, format with proper indentation
3548
+ if (qualsStr.includes('AND') || qualsStr.includes('OR') || qualsStr.length > 50) {
3549
+ if (this.containsMultilineStringLiteral(qualsStr)) {
3550
+ output.push(` ON ${qualsStr}`);
3551
+ }
3552
+ else {
3553
+ output.push(` ON${this.formatter.newline()}${this.formatter.indent(qualsStr)}`);
3554
+ }
3555
+ }
3556
+ else {
3557
+ output.push(` ON ${qualsStr}`);
3558
+ }
3273
3559
  }
3274
3560
  else {
3275
- output.push(`ON ${this.visit(node.quals, context)}`);
3561
+ output.push(`ON ${qualsStr}`);
3276
3562
  }
3277
3563
  }
3278
3564
  let result;
@@ -3383,8 +3669,8 @@ class Deparser {
3383
3669
  else if (nodeData.sval !== undefined) {
3384
3670
  // Handle nested sval structure: { sval: { sval: "value" } }
3385
3671
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3386
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3387
- boolValue = stringValue === 'on' || stringValue === 'true';
3672
+ const stringValue = svalValue.replace(/'/g, '');
3673
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3388
3674
  }
3389
3675
  }
3390
3676
  return boolValue ? 'READ ONLY' : 'READ WRITE';
@@ -3407,8 +3693,8 @@ class Deparser {
3407
3693
  else if (nodeData.sval !== undefined) {
3408
3694
  // Handle nested sval structure: { sval: { sval: "value" } }
3409
3695
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3410
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3411
- boolValue = stringValue === 'on' || stringValue === 'true';
3696
+ const stringValue = svalValue.replace(/'/g, '');
3697
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3412
3698
  }
3413
3699
  }
3414
3700
  return boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE';
@@ -3475,8 +3761,8 @@ class Deparser {
3475
3761
  else if (nodeData.sval !== undefined) {
3476
3762
  // Handle nested sval structure: { sval: { sval: "value" } }
3477
3763
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3478
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3479
- boolValue = stringValue === 'on' || stringValue === 'true';
3764
+ const stringValue = svalValue.replace(/'/g, '');
3765
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3480
3766
  }
3481
3767
  }
3482
3768
  transactionOptions.push(boolValue ? 'READ ONLY' : 'READ WRITE');
@@ -3494,8 +3780,8 @@ class Deparser {
3494
3780
  else if (nodeData.sval !== undefined) {
3495
3781
  // Handle nested sval structure: { sval: { sval: "value" } }
3496
3782
  const svalValue = typeof nodeData.sval === 'object' ? nodeData.sval.sval : nodeData.sval;
3497
- const stringValue = svalValue.replace(/'/g, '').toLowerCase();
3498
- boolValue = stringValue === 'on' || stringValue === 'true';
3783
+ const stringValue = svalValue.replace(/'/g, '');
3784
+ boolValue = stringValue.toLowerCase() === 'on' || stringValue.toLowerCase() === 'true';
3499
3785
  }
3500
3786
  }
3501
3787
  transactionOptions.push(boolValue ? 'DEFERRABLE' : 'NOT DEFERRABLE');
@@ -3561,7 +3847,7 @@ class Deparser {
3561
3847
  }
3562
3848
  switch (node.roletype) {
3563
3849
  case 'ROLESPEC_PUBLIC':
3564
- return 'public';
3850
+ return 'PUBLIC';
3565
3851
  case 'ROLESPEC_CURRENT_USER':
3566
3852
  return 'CURRENT_USER';
3567
3853
  case 'ROLESPEC_SESSION_USER':
@@ -3569,7 +3855,7 @@ class Deparser {
3569
3855
  case 'ROLESPEC_CURRENT_ROLE':
3570
3856
  return 'CURRENT_ROLE';
3571
3857
  default:
3572
- return 'public';
3858
+ return 'PUBLIC';
3573
3859
  }
3574
3860
  }
3575
3861
  roletype(node, context) {
@@ -4033,10 +4319,25 @@ class Deparser {
4033
4319
  output.push(relationStr);
4034
4320
  }
4035
4321
  if (node.cmds && node.cmds.length > 0) {
4036
- const commandsStr = list_utils_1.ListUtils.unwrapList(node.cmds)
4037
- .map(cmd => this.visit(cmd, alterContext))
4038
- .join(', ');
4039
- output.push(commandsStr);
4322
+ const commands = list_utils_1.ListUtils.unwrapList(node.cmds);
4323
+ if (this.formatter.isPretty()) {
4324
+ const commandsStr = commands
4325
+ .map(cmd => {
4326
+ const cmdStr = this.visit(cmd, alterContext);
4327
+ if (cmdStr.startsWith('ADD CONSTRAINT') || cmdStr.startsWith('ADD ')) {
4328
+ return this.formatter.newline() + this.formatter.indent(cmdStr);
4329
+ }
4330
+ return cmdStr;
4331
+ })
4332
+ .join(',');
4333
+ output.push(commandsStr);
4334
+ }
4335
+ else {
4336
+ const commandsStr = commands
4337
+ .map(cmd => this.visit(cmd, alterContext))
4338
+ .join(', ');
4339
+ output.push(commandsStr);
4340
+ }
4040
4341
  }
4041
4342
  return output.join(' ');
4042
4343
  }
@@ -6012,7 +6313,7 @@ class Deparser {
6012
6313
  const output = [];
6013
6314
  const initialParts = ['CREATE', 'POLICY'];
6014
6315
  if (node.policy_name) {
6015
- initialParts.push(`"${node.policy_name}"`);
6316
+ initialParts.push(quote_utils_1.QuoteUtils.quote(node.policy_name));
6016
6317
  }
6017
6318
  output.push(initialParts.join(' '));
6018
6319
  // Add ON clause on new line in pretty mode
@@ -6089,7 +6390,7 @@ class Deparser {
6089
6390
  AlterPolicyStmt(node, context) {
6090
6391
  const output = ['ALTER', 'POLICY'];
6091
6392
  if (node.policy_name) {
6092
- output.push(`"${node.policy_name}"`);
6393
+ output.push(quote_utils_1.QuoteUtils.quote(node.policy_name));
6093
6394
  }
6094
6395
  if (node.table) {
6095
6396
  output.push('ON');
@@ -7587,7 +7888,12 @@ class Deparser {
7587
7888
  }
7588
7889
  if (node.action) {
7589
7890
  const actionStr = this.GrantStmt(node.action, context);
7590
- output.push(actionStr);
7891
+ if (this.formatter.isPretty()) {
7892
+ return output.join(' ') + this.formatter.newline() + this.formatter.indent(actionStr);
7893
+ }
7894
+ else {
7895
+ output.push(actionStr);
7896
+ }
7591
7897
  }
7592
7898
  return output.join(' ');
7593
7899
  }
@@ -7734,84 +8040,158 @@ class Deparser {
7734
8040
  if (node.trigname) {
7735
8041
  output.push(quote_utils_1.QuoteUtils.quote(node.trigname));
7736
8042
  }
7737
- const timing = [];
7738
- if (node.timing & 2)
7739
- timing.push('BEFORE');
7740
- else if (node.timing & 64)
7741
- timing.push('INSTEAD OF');
7742
- else
7743
- timing.push('AFTER'); // Default timing when no specific timing is set
7744
- output.push(timing.join(' '));
7745
- const events = [];
7746
- if (node.events & 4)
7747
- events.push('INSERT');
7748
- if (node.events & 8)
7749
- events.push('DELETE');
7750
- if (node.events & 16)
7751
- events.push('UPDATE');
7752
- if (node.events & 32)
7753
- events.push('TRUNCATE');
7754
- output.push(events.join(' OR '));
7755
- if (node.columns && node.columns.length > 0) {
7756
- output.push('OF');
7757
- const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
7758
- .map(col => this.visit(col, context))
7759
- .join(', ');
7760
- output.push(columnNames);
7761
- }
7762
- output.push('ON');
7763
- if (node.relation) {
7764
- output.push(this.RangeVar(node.relation, context));
7765
- }
7766
- if (node.constrrel) {
7767
- output.push('FROM');
7768
- output.push(this.RangeVar(node.constrrel, context));
7769
- }
7770
- if (node.deferrable) {
7771
- output.push('DEFERRABLE');
7772
- }
7773
- if (node.initdeferred) {
7774
- output.push('INITIALLY DEFERRED');
7775
- }
7776
- // Handle REFERENCING clauses
7777
- if (node.transitionRels && node.transitionRels.length > 0) {
7778
- output.push('REFERENCING');
7779
- const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
7780
- .map(rel => this.visit(rel, context))
7781
- .join(' ');
7782
- output.push(transitionClauses);
7783
- }
7784
- if (node.row) {
7785
- output.push('FOR EACH ROW');
7786
- }
7787
- else {
7788
- output.push('FOR EACH STATEMENT');
7789
- }
7790
- if (node.whenClause) {
7791
- output.push('WHEN');
7792
- output.push('(');
7793
- output.push(this.visit(node.whenClause, context));
7794
- output.push(')');
7795
- }
7796
- output.push('EXECUTE');
7797
- if (node.funcname && node.funcname.length > 0) {
7798
- const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
7799
- .map(name => this.visit(name, context))
7800
- .join('.');
7801
- output.push('FUNCTION', funcName);
7802
- }
7803
- if (node.args && node.args.length > 0) {
7804
- output.push('(');
7805
- const args = list_utils_1.ListUtils.unwrapList(node.args)
7806
- .map(arg => this.visit(arg, context))
7807
- .join(', ');
7808
- output.push(args);
7809
- output.push(')');
8043
+ if (this.formatter.isPretty()) {
8044
+ const components = [];
8045
+ const timing = [];
8046
+ if (node.timing & 2)
8047
+ timing.push('BEFORE');
8048
+ else if (node.timing & 64)
8049
+ timing.push('INSTEAD OF');
8050
+ else
8051
+ timing.push('AFTER');
8052
+ const events = [];
8053
+ if (node.events & 4)
8054
+ events.push('INSERT');
8055
+ if (node.events & 8)
8056
+ events.push('DELETE');
8057
+ if (node.events & 16) {
8058
+ let updateStr = 'UPDATE';
8059
+ if (node.columns && node.columns.length > 0) {
8060
+ const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
8061
+ .map(col => this.visit(col, context))
8062
+ .join(', ');
8063
+ updateStr += ' OF ' + columnNames;
8064
+ }
8065
+ events.push(updateStr);
8066
+ }
8067
+ if (node.events & 32)
8068
+ events.push('TRUNCATE');
8069
+ components.push(this.formatter.indent(timing.join(' ') + ' ' + events.join(' OR ')));
8070
+ if (node.relation) {
8071
+ components.push(this.formatter.indent('ON ' + this.RangeVar(node.relation, context)));
8072
+ }
8073
+ if (node.transitionRels && node.transitionRels.length > 0) {
8074
+ const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
8075
+ .map(rel => this.visit(rel, context))
8076
+ .join(' ');
8077
+ components.push(this.formatter.indent('REFERENCING ' + transitionClauses));
8078
+ }
8079
+ if (node.deferrable) {
8080
+ components.push(this.formatter.indent('DEFERRABLE'));
8081
+ }
8082
+ if (node.initdeferred) {
8083
+ components.push(this.formatter.indent('INITIALLY DEFERRED'));
8084
+ }
8085
+ if (node.row) {
8086
+ components.push(this.formatter.indent('FOR EACH ROW'));
8087
+ }
8088
+ else {
8089
+ components.push(this.formatter.indent('FOR EACH STATEMENT'));
8090
+ }
8091
+ if (node.whenClause) {
8092
+ const whenStr = 'WHEN (' + this.visit(node.whenClause, context) + ')';
8093
+ components.push(this.formatter.indent(whenStr));
8094
+ }
8095
+ let executeStr = 'EXECUTE';
8096
+ if (node.funcname && node.funcname.length > 0) {
8097
+ const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
8098
+ .map(name => this.visit(name, context))
8099
+ .join('.');
8100
+ executeStr += ' PROCEDURE ' + funcName;
8101
+ }
8102
+ if (node.args && node.args.length > 0) {
8103
+ const argContext = { ...context, isStringLiteral: true };
8104
+ const args = list_utils_1.ListUtils.unwrapList(node.args)
8105
+ .map(arg => this.visit(arg, argContext))
8106
+ .join(', ');
8107
+ executeStr += '(' + args + ')';
8108
+ }
8109
+ else {
8110
+ executeStr += '()';
8111
+ }
8112
+ components.push(this.formatter.indent(executeStr));
8113
+ return output.join(' ') + this.formatter.newline() + components.join(this.formatter.newline());
7810
8114
  }
7811
8115
  else {
7812
- output.push('()');
8116
+ const timing = [];
8117
+ if (node.timing & 2)
8118
+ timing.push('BEFORE');
8119
+ else if (node.timing & 64)
8120
+ timing.push('INSTEAD OF');
8121
+ else
8122
+ timing.push('AFTER');
8123
+ output.push(timing.join(' '));
8124
+ const events = [];
8125
+ if (node.events & 4)
8126
+ events.push('INSERT');
8127
+ if (node.events & 8)
8128
+ events.push('DELETE');
8129
+ if (node.events & 16)
8130
+ events.push('UPDATE');
8131
+ if (node.events & 32)
8132
+ events.push('TRUNCATE');
8133
+ output.push(events.join(' OR '));
8134
+ if (node.columns && node.columns.length > 0) {
8135
+ output.push('OF');
8136
+ const columnNames = list_utils_1.ListUtils.unwrapList(node.columns)
8137
+ .map(col => this.visit(col, context))
8138
+ .join(', ');
8139
+ output.push(columnNames);
8140
+ }
8141
+ output.push('ON');
8142
+ if (node.relation) {
8143
+ output.push(this.RangeVar(node.relation, context));
8144
+ }
8145
+ if (node.constrrel) {
8146
+ output.push('FROM');
8147
+ output.push(this.RangeVar(node.constrrel, context));
8148
+ }
8149
+ if (node.deferrable) {
8150
+ output.push('DEFERRABLE');
8151
+ }
8152
+ if (node.initdeferred) {
8153
+ output.push('INITIALLY DEFERRED');
8154
+ }
8155
+ if (node.transitionRels && node.transitionRels.length > 0) {
8156
+ output.push('REFERENCING');
8157
+ const transitionClauses = list_utils_1.ListUtils.unwrapList(node.transitionRels)
8158
+ .map(rel => this.visit(rel, context))
8159
+ .join(' ');
8160
+ output.push(transitionClauses);
8161
+ }
8162
+ if (node.row) {
8163
+ output.push('FOR EACH ROW');
8164
+ }
8165
+ else {
8166
+ output.push('FOR EACH STATEMENT');
8167
+ }
8168
+ if (node.whenClause) {
8169
+ output.push('WHEN');
8170
+ output.push('(');
8171
+ output.push(this.visit(node.whenClause, context));
8172
+ output.push(')');
8173
+ }
8174
+ output.push('EXECUTE');
8175
+ if (node.funcname && node.funcname.length > 0) {
8176
+ const funcName = list_utils_1.ListUtils.unwrapList(node.funcname)
8177
+ .map(name => this.visit(name, context))
8178
+ .join('.');
8179
+ output.push('FUNCTION', funcName);
8180
+ }
8181
+ if (node.args && node.args.length > 0) {
8182
+ output.push('(');
8183
+ const argContext = { ...context, isStringLiteral: true };
8184
+ const args = list_utils_1.ListUtils.unwrapList(node.args)
8185
+ .map(arg => this.visit(arg, argContext))
8186
+ .join(', ');
8187
+ output.push(args);
8188
+ output.push(')');
8189
+ }
8190
+ else {
8191
+ output.push('()');
8192
+ }
8193
+ return output.join(' ');
7813
8194
  }
7814
- return output.join(' ');
7815
8195
  }
7816
8196
  TriggerTransition(node, context) {
7817
8197
  const output = [];
@@ -8389,7 +8769,7 @@ class Deparser {
8389
8769
  AccessPriv(node, context) {
8390
8770
  const output = [];
8391
8771
  if (node.priv_name) {
8392
- output.push(node.priv_name);
8772
+ output.push(node.priv_name.toUpperCase());
8393
8773
  }
8394
8774
  else {
8395
8775
  output.push('ALL');