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