pgsql-deparser 17.7.1 → 17.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,7 +24,7 @@ npm install pgsql-deparser
24
24
 
25
25
  ## Features
26
26
 
27
- * ⚡ **Pure TypeScript Performance** – Zero dependencies, no WASM, no compilation - just blazing fast SQL generation
27
+ * ⚡ **Pure TypeScript Performance** – Zero runtime dependencies, no WASM, no compilation - just blazing fast SQL generation
28
28
  * 🪶 **Ultra Lightweight** – Minimal footprint with laser-focused functionality for AST-to-SQL conversion only
29
29
  * 🧪 **Battle-Tested Reliability** – Validated against 23,000+ SQL statements ensuring production-grade stability
30
30
  * 🌍 **Universal Compatibility** – Runs anywhere JavaScript does - browsers, Node.js, edge functions, you name it
@@ -69,6 +69,48 @@ console.log(deparse(stmt));
69
69
  // Output: SELECT * FROM another_table
70
70
  ```
71
71
 
72
+ ## Options
73
+
74
+ The deparser accepts optional configuration for formatting and output control:
75
+
76
+ ```ts
77
+ import { deparseSync as deparse } from 'pgsql-deparser';
78
+
79
+ const options = {
80
+ pretty: true, // Enable pretty formatting (default: false)
81
+ newline: '\n', // Newline character (default: '\n')
82
+ tab: ' ', // Tab/indentation character (default: ' ')
83
+ semicolons: true // Add semicolons to statements (default: true)
84
+ };
85
+
86
+ const sql = deparse(ast, options);
87
+ ```
88
+
89
+ | Option | Type | Default | Description |
90
+ |--------|------|---------|-------------|
91
+ | `pretty` | `boolean` | `false` | Enable pretty formatting with indentation and line breaks |
92
+ | `newline` | `string` | `'\n'` | Character(s) used for line breaks |
93
+ | `tab` | `string` | `' '` | Character(s) used for indentation |
94
+ | `semicolons` | `boolean` | `true` | Add semicolons to SQL statements |
95
+
96
+ **Pretty formatting example:**
97
+ ```ts
98
+ // Without pretty formatting
99
+ const sql1 = deparse(selectAst, { pretty: false });
100
+ // "SELECT id, name FROM users WHERE active = true;"
101
+
102
+ // With pretty formatting
103
+ const sql2 = deparse(selectAst, { pretty: true });
104
+ // SELECT
105
+ // id,
106
+ // name
107
+ // FROM users
108
+ // WHERE
109
+ // active = true;
110
+ ```
111
+
112
+ For complete documentation and advanced options, see [DEPARSER_USAGE.md](../../DEPARSER_USAGE.md).
113
+
72
114
  ## Why Use `pgsql-deparser`?
73
115
 
74
116
  `pgsql-deparser` is particularly useful in development environments where native dependencies are problematic or in applications where only the deparser functionality is required. Its independence from the full `pgsql-parser` package allows for more focused and lightweight SQL generation tasks.
@@ -98,4 +140,4 @@ Built on the excellent work of several contributors:
98
140
 
99
141
  AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
100
142
 
101
- No developer or entity involved in creating Software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Software code or Software CLI, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
143
+ No developer or entity involved in creating Software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Software code or Software CLI, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
package/deparser.d.ts CHANGED
@@ -6,6 +6,7 @@ export interface DeparserOptions {
6
6
  tab?: string;
7
7
  functionDelimiter?: string;
8
8
  functionDelimiterFallback?: string;
9
+ pretty?: boolean;
9
10
  }
10
11
  /**
11
12
  * Deparser - Converts PostgreSQL AST nodes back to SQL strings
@@ -294,4 +295,5 @@ export declare class Deparser implements DeparserVisitor {
294
295
  AlterObjectSchemaStmt(node: t.AlterObjectSchemaStmt, context: DeparserContext): string;
295
296
  AlterRoleSetStmt(node: t.AlterRoleSetStmt, context: DeparserContext): string;
296
297
  CreateForeignTableStmt(node: t.CreateForeignTableStmt, context: DeparserContext): string;
298
+ private containsMultilineStringLiteral;
297
299
  }
package/deparser.js CHANGED
@@ -51,7 +51,7 @@ class Deparser {
51
51
  tree;
52
52
  options;
53
53
  constructor(tree, opts = {}) {
54
- this.formatter = new sql_formatter_1.SqlFormatter(opts.newline, opts.tab);
54
+ this.formatter = new sql_formatter_1.SqlFormatter(opts.newline, opts.tab, opts.pretty);
55
55
  // Set default options
56
56
  this.options = {
57
57
  functionDelimiter: '$$',
@@ -184,7 +184,9 @@ class Deparser {
184
184
  }
185
185
  if (!node.op || node.op === 'SETOP_NONE') {
186
186
  if (node.valuesLists == null) {
187
- output.push('SELECT');
187
+ if (!this.formatter.isPretty() || !node.targetList) {
188
+ output.push('SELECT');
189
+ }
188
190
  }
189
191
  }
190
192
  else {
@@ -230,41 +232,82 @@ class Deparser {
230
232
  output.push(rightStmt);
231
233
  }
232
234
  }
235
+ // Handle DISTINCT clause - in pretty mode, we'll include it in the SELECT clause
236
+ let distinctPart = '';
233
237
  if (node.distinctClause) {
234
238
  const distinctClause = list_utils_1.ListUtils.unwrapList(node.distinctClause);
235
239
  if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
236
- output.push('DISTINCT ON');
237
240
  const clause = distinctClause
238
241
  .map(e => this.visit(e, { ...context, select: true }))
239
242
  .join(', ');
240
- output.push(this.formatter.parens(clause));
243
+ distinctPart = ' DISTINCT ON ' + this.formatter.parens(clause);
241
244
  }
242
245
  else {
243
- output.push('DISTINCT');
246
+ distinctPart = ' DISTINCT';
247
+ }
248
+ if (!this.formatter.isPretty()) {
249
+ if (distinctClause.length > 0 && Object.keys(distinctClause[0]).length > 0) {
250
+ output.push('DISTINCT ON');
251
+ const clause = distinctClause
252
+ .map(e => this.visit(e, { ...context, select: true }))
253
+ .join(', ');
254
+ output.push(this.formatter.parens(clause));
255
+ }
256
+ else {
257
+ output.push('DISTINCT');
258
+ }
244
259
  }
245
260
  }
246
261
  if (node.targetList) {
247
262
  const targetList = list_utils_1.ListUtils.unwrapList(node.targetList);
248
- const targets = targetList
249
- .map(e => this.visit(e, { ...context, select: true }))
250
- .join(', ');
251
- output.push(targets);
263
+ 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;
269
+ }
270
+ return this.formatter.indent(targetStr);
271
+ });
272
+ const formattedTargets = targetStrings.join(',' + this.formatter.newline());
273
+ output.push('SELECT' + distinctPart);
274
+ output.push(formattedTargets);
275
+ }
276
+ else {
277
+ const targets = targetList
278
+ .map(e => this.visit(e, { ...context, select: true }))
279
+ .join(', ');
280
+ output.push(targets);
281
+ }
252
282
  }
253
283
  if (node.intoClause) {
254
284
  output.push('INTO');
255
285
  output.push(this.IntoClause(node.intoClause, context));
256
286
  }
257
287
  if (node.fromClause) {
258
- output.push('FROM');
259
288
  const fromList = list_utils_1.ListUtils.unwrapList(node.fromClause);
260
289
  const fromItems = fromList
261
290
  .map(e => this.deparse(e, { ...context, from: true }))
262
291
  .join(', ');
263
- output.push(fromItems);
292
+ output.push('FROM ' + fromItems.trim());
264
293
  }
265
294
  if (node.whereClause) {
266
- output.push('WHERE');
267
- output.push(this.visit(node.whereClause, context));
295
+ if (this.formatter.isPretty()) {
296
+ output.push('WHERE');
297
+ const whereExpr = this.visit(node.whereClause, context);
298
+ const lines = whereExpr.split(this.formatter.newline());
299
+ const indentedLines = lines.map((line, index) => {
300
+ if (index === 0) {
301
+ return this.formatter.indent(line);
302
+ }
303
+ return line;
304
+ });
305
+ output.push(indentedLines.join(this.formatter.newline()));
306
+ }
307
+ else {
308
+ output.push('WHERE');
309
+ output.push(this.visit(node.whereClause, context));
310
+ }
268
311
  }
269
312
  if (node.valuesLists) {
270
313
  output.push('VALUES');
@@ -275,16 +318,43 @@ class Deparser {
275
318
  output.push(lists.join(', '));
276
319
  }
277
320
  if (node.groupClause) {
278
- output.push('GROUP BY');
279
321
  const groupList = list_utils_1.ListUtils.unwrapList(node.groupClause);
280
- const groupItems = groupList
281
- .map(e => this.visit(e, { ...context, group: true }))
282
- .join(', ');
283
- output.push(groupItems);
322
+ if (this.formatter.isPretty()) {
323
+ const groupItems = groupList
324
+ .map(e => {
325
+ const groupStr = this.visit(e, { ...context, group: true });
326
+ if (this.containsMultilineStringLiteral(groupStr)) {
327
+ return groupStr;
328
+ }
329
+ return this.formatter.indent(groupStr);
330
+ })
331
+ .join(',' + this.formatter.newline());
332
+ output.push('GROUP BY');
333
+ output.push(groupItems);
334
+ }
335
+ else {
336
+ output.push('GROUP BY');
337
+ const groupItems = groupList
338
+ .map(e => this.visit(e, { ...context, group: true }))
339
+ .join(', ');
340
+ output.push(groupItems);
341
+ }
284
342
  }
285
343
  if (node.havingClause) {
286
- output.push('HAVING');
287
- output.push(this.visit(node.havingClause, context));
344
+ if (this.formatter.isPretty()) {
345
+ output.push('HAVING');
346
+ const havingStr = this.visit(node.havingClause, context);
347
+ if (this.containsMultilineStringLiteral(havingStr)) {
348
+ output.push(havingStr);
349
+ }
350
+ else {
351
+ output.push(this.formatter.indent(havingStr));
352
+ }
353
+ }
354
+ else {
355
+ output.push('HAVING');
356
+ output.push(this.visit(node.havingClause, context));
357
+ }
288
358
  }
289
359
  if (node.windowClause) {
290
360
  output.push('WINDOW');
@@ -295,20 +365,33 @@ class Deparser {
295
365
  output.push(windowClauses);
296
366
  }
297
367
  if (node.sortClause) {
298
- output.push('ORDER BY');
299
368
  const sortList = list_utils_1.ListUtils.unwrapList(node.sortClause);
300
- const sortItems = sortList
301
- .map(e => this.visit(e, { ...context, sort: true }))
302
- .join(', ');
303
- output.push(sortItems);
369
+ if (this.formatter.isPretty()) {
370
+ const sortItems = sortList
371
+ .map(e => {
372
+ const sortStr = this.visit(e, { ...context, sort: true });
373
+ if (this.containsMultilineStringLiteral(sortStr)) {
374
+ return sortStr;
375
+ }
376
+ return this.formatter.indent(sortStr);
377
+ })
378
+ .join(',' + this.formatter.newline());
379
+ output.push('ORDER BY');
380
+ output.push(sortItems);
381
+ }
382
+ else {
383
+ output.push('ORDER BY');
384
+ const sortItems = sortList
385
+ .map(e => this.visit(e, { ...context, sort: true }))
386
+ .join(', ');
387
+ output.push(sortItems);
388
+ }
304
389
  }
305
390
  if (node.limitCount) {
306
- output.push('LIMIT');
307
- output.push(this.visit(node.limitCount, context));
391
+ output.push('LIMIT ' + this.visit(node.limitCount, context));
308
392
  }
309
393
  if (node.limitOffset) {
310
- output.push('OFFSET');
311
- output.push(this.visit(node.limitOffset, context));
394
+ output.push('OFFSET ' + this.visit(node.limitOffset, context));
312
395
  }
313
396
  if (node.lockingClause) {
314
397
  const lockingList = list_utils_1.ListUtils.unwrapList(node.lockingClause);
@@ -317,6 +400,10 @@ class Deparser {
317
400
  .join(' ');
318
401
  output.push(lockingClauses);
319
402
  }
403
+ if (this.formatter.isPretty()) {
404
+ const filteredOutput = output.filter(item => item.trim() !== '');
405
+ return filteredOutput.join(this.formatter.newline());
406
+ }
320
407
  return output.join(' ');
321
408
  }
322
409
  A_Expr(node, context) {
@@ -806,9 +893,23 @@ class Deparser {
806
893
  if (node.recursive) {
807
894
  output.push('RECURSIVE');
808
895
  }
809
- const ctes = list_utils_1.ListUtils.unwrapList(node.ctes);
810
- const cteStrs = ctes.map(cte => this.visit(cte, context));
811
- output.push(cteStrs.join(', '));
896
+ if (node.ctes && node.ctes.length > 0) {
897
+ const ctes = list_utils_1.ListUtils.unwrapList(node.ctes);
898
+ if (this.formatter.isPretty()) {
899
+ const cteStrings = ctes.map(cte => {
900
+ const cteStr = this.visit(cte, context);
901
+ if (this.containsMultilineStringLiteral(cteStr)) {
902
+ return this.formatter.newline() + cteStr;
903
+ }
904
+ return this.formatter.newline() + this.formatter.indent(cteStr);
905
+ });
906
+ output.push(cteStrings.join(','));
907
+ }
908
+ else {
909
+ const cteStrings = ctes.map(cte => this.visit(cte, context));
910
+ output.push(cteStrings.join(', '));
911
+ }
912
+ }
812
913
  return output.join(' ');
813
914
  }
814
915
  ResTarget(node, context) {
@@ -892,11 +993,23 @@ class Deparser {
892
993
  // return formatStr.replace('%s', () => andArgs); // ✅ Function callback prevents interpretation
893
994
  switch (boolop) {
894
995
  case 'AND_EXPR':
895
- const andArgs = args.map(arg => this.visit(arg, boolContext)).join(' AND ');
896
- return formatStr.replace('%s', () => andArgs);
996
+ if (this.formatter.isPretty() && args.length > 1) {
997
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' AND ');
998
+ return formatStr.replace('%s', () => andArgs);
999
+ }
1000
+ else {
1001
+ const andArgs = args.map(arg => this.visit(arg, boolContext)).join(' AND ');
1002
+ return formatStr.replace('%s', () => andArgs);
1003
+ }
897
1004
  case 'OR_EXPR':
898
- const orArgs = args.map(arg => this.visit(arg, boolContext)).join(' OR ');
899
- return formatStr.replace('%s', () => orArgs);
1005
+ if (this.formatter.isPretty() && args.length > 1) {
1006
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(this.formatter.newline() + ' OR ');
1007
+ return formatStr.replace('%s', () => orArgs);
1008
+ }
1009
+ else {
1010
+ const orArgs = args.map(arg => this.visit(arg, boolContext)).join(' OR ');
1011
+ return formatStr.replace('%s', () => orArgs);
1012
+ }
900
1013
  case 'NOT_EXPR':
901
1014
  return `NOT (${this.visit(args[0], context)})`;
902
1015
  default:
@@ -1943,7 +2056,13 @@ class Deparser {
1943
2056
  const elementStrs = elements.map(el => {
1944
2057
  return this.deparse(el, context);
1945
2058
  });
1946
- output.push(this.formatter.parens(elementStrs.join(', ')));
2059
+ if (this.formatter.isPretty()) {
2060
+ const formattedElements = elementStrs.map(el => this.formatter.indent(el)).join(',' + this.formatter.newline());
2061
+ output.push('(' + this.formatter.newline() + formattedElements + this.formatter.newline() + ')');
2062
+ }
2063
+ else {
2064
+ output.push(this.formatter.parens(elementStrs.join(', ')));
2065
+ }
1947
2066
  }
1948
2067
  else if (!node.partbound) {
1949
2068
  output.push(this.formatter.parens(''));
@@ -2228,38 +2347,52 @@ class Deparser {
2228
2347
  }
2229
2348
  }
2230
2349
  if (node.fk_upd_action && node.fk_upd_action !== 'a') {
2231
- output.push('ON UPDATE');
2350
+ let updateClause = 'ON UPDATE ';
2232
2351
  switch (node.fk_upd_action) {
2233
2352
  case 'r':
2234
- output.push('RESTRICT');
2353
+ updateClause += 'RESTRICT';
2235
2354
  break;
2236
2355
  case 'c':
2237
- output.push('CASCADE');
2356
+ updateClause += 'CASCADE';
2238
2357
  break;
2239
2358
  case 'n':
2240
- output.push('SET NULL');
2359
+ updateClause += 'SET NULL';
2241
2360
  break;
2242
2361
  case 'd':
2243
- output.push('SET DEFAULT');
2362
+ updateClause += 'SET DEFAULT';
2244
2363
  break;
2245
2364
  }
2365
+ if (this.formatter.isPretty()) {
2366
+ output.push('\n' + this.formatter.indent(updateClause));
2367
+ }
2368
+ else {
2369
+ output.push('ON UPDATE');
2370
+ output.push(updateClause.replace('ON UPDATE ', ''));
2371
+ }
2246
2372
  }
2247
2373
  if (node.fk_del_action && node.fk_del_action !== 'a') {
2248
- output.push('ON DELETE');
2374
+ let deleteClause = 'ON DELETE ';
2249
2375
  switch (node.fk_del_action) {
2250
2376
  case 'r':
2251
- output.push('RESTRICT');
2377
+ deleteClause += 'RESTRICT';
2252
2378
  break;
2253
2379
  case 'c':
2254
- output.push('CASCADE');
2380
+ deleteClause += 'CASCADE';
2255
2381
  break;
2256
2382
  case 'n':
2257
- output.push('SET NULL');
2383
+ deleteClause += 'SET NULL';
2258
2384
  break;
2259
2385
  case 'd':
2260
- output.push('SET DEFAULT');
2386
+ deleteClause += 'SET DEFAULT';
2261
2387
  break;
2262
2388
  }
2389
+ if (this.formatter.isPretty()) {
2390
+ output.push('\n' + this.formatter.indent(deleteClause));
2391
+ }
2392
+ else {
2393
+ output.push('ON DELETE');
2394
+ output.push(deleteClause.replace('ON DELETE ', ''));
2395
+ }
2263
2396
  }
2264
2397
  // Handle NOT VALID for foreign key constraints - only for table constraints, not domain constraints
2265
2398
  if (node.skip_validation && !context.isDomainConstraint) {
@@ -2317,17 +2450,48 @@ class Deparser {
2317
2450
  // Handle deferrable constraints for all constraint types that support it
2318
2451
  if (node.contype === 'CONSTR_PRIMARY' || node.contype === 'CONSTR_UNIQUE' || node.contype === 'CONSTR_FOREIGN') {
2319
2452
  if (node.deferrable) {
2320
- output.push('DEFERRABLE');
2321
- if (node.initdeferred === true) {
2322
- output.push('INITIALLY DEFERRED');
2453
+ if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2454
+ output.push('\n' + this.formatter.indent('DEFERRABLE'));
2455
+ if (node.initdeferred === true) {
2456
+ output.push('\n' + this.formatter.indent('INITIALLY DEFERRED'));
2457
+ }
2458
+ else if (node.initdeferred === false) {
2459
+ output.push('\n' + this.formatter.indent('INITIALLY IMMEDIATE'));
2460
+ }
2323
2461
  }
2324
- else if (node.initdeferred === false) {
2325
- output.push('INITIALLY IMMEDIATE');
2462
+ else {
2463
+ output.push('DEFERRABLE');
2464
+ if (node.initdeferred === true) {
2465
+ output.push('INITIALLY DEFERRED');
2466
+ }
2467
+ else if (node.initdeferred === false) {
2468
+ output.push('INITIALLY IMMEDIATE');
2469
+ }
2326
2470
  }
2327
2471
  }
2328
2472
  else if (node.deferrable === false) {
2329
- output.push('NOT DEFERRABLE');
2473
+ if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2474
+ output.push('\n' + this.formatter.indent('NOT DEFERRABLE'));
2475
+ }
2476
+ else {
2477
+ output.push('NOT DEFERRABLE');
2478
+ }
2479
+ }
2480
+ }
2481
+ if (this.formatter.isPretty() && node.contype === 'CONSTR_FOREIGN') {
2482
+ let result = '';
2483
+ for (let i = 0; i < output.length; i++) {
2484
+ if (output[i].startsWith('\n')) {
2485
+ result += output[i];
2486
+ }
2487
+ else {
2488
+ if (i > 0 && !output[i - 1].startsWith('\n')) {
2489
+ result += ' ';
2490
+ }
2491
+ result += output[i];
2492
+ }
2330
2493
  }
2494
+ return result;
2331
2495
  }
2332
2496
  return output.join(' ');
2333
2497
  }
@@ -3051,11 +3215,9 @@ class Deparser {
3051
3215
  }
3052
3216
  switch (node.jointype) {
3053
3217
  case 'JOIN_INNER':
3054
- // Handle NATURAL JOIN first - it has isNatural=true (NATURAL already added above)
3055
3218
  if (node.isNatural) {
3056
3219
  joinStr += 'JOIN';
3057
3220
  }
3058
- // Handle CROSS JOIN case - when there's no quals, no usingClause, and not natural
3059
3221
  else if (!node.quals && (!node.usingClause || node.usingClause.length === 0)) {
3060
3222
  joinStr += 'CROSS JOIN';
3061
3223
  }
@@ -3075,26 +3237,51 @@ class Deparser {
3075
3237
  default:
3076
3238
  joinStr += 'JOIN';
3077
3239
  }
3078
- output.push(joinStr);
3079
3240
  if (node.rarg) {
3080
3241
  let rargStr = this.visit(node.rarg, context);
3081
3242
  if (node.rarg && 'JoinExpr' in node.rarg && !node.rarg.JoinExpr.alias) {
3082
3243
  rargStr = `(${rargStr})`;
3083
3244
  }
3084
- output.push(rargStr);
3245
+ if (this.formatter.isPretty()) {
3246
+ output.push(this.formatter.newline() + joinStr + ' ' + rargStr);
3247
+ }
3248
+ else {
3249
+ output.push(joinStr + ' ' + rargStr);
3250
+ }
3251
+ }
3252
+ else {
3253
+ if (this.formatter.isPretty()) {
3254
+ output.push(this.formatter.newline() + joinStr);
3255
+ }
3256
+ else {
3257
+ output.push(joinStr);
3258
+ }
3085
3259
  }
3086
3260
  if (node.usingClause && node.usingClause.length > 0) {
3087
- output.push('USING');
3088
3261
  const usingList = list_utils_1.ListUtils.unwrapList(node.usingClause);
3089
3262
  const columnNames = usingList.map(col => this.visit(col, context));
3090
- output.push(`(${columnNames.join(', ')})`);
3263
+ if (this.formatter.isPretty()) {
3264
+ output.push(` USING (${columnNames.join(', ')})`);
3265
+ }
3266
+ else {
3267
+ output.push(`USING (${columnNames.join(', ')})`);
3268
+ }
3091
3269
  }
3092
3270
  else if (node.quals) {
3093
- output.push('ON');
3094
- output.push(this.visit(node.quals, context));
3271
+ if (this.formatter.isPretty()) {
3272
+ output.push(` ON ${this.visit(node.quals, context)}`);
3273
+ }
3274
+ else {
3275
+ output.push(`ON ${this.visit(node.quals, context)}`);
3276
+ }
3277
+ }
3278
+ let result;
3279
+ if (this.formatter.isPretty()) {
3280
+ result = output.join('');
3281
+ }
3282
+ else {
3283
+ result = output.join(' ');
3095
3284
  }
3096
- let result = output.join(' ');
3097
- // Handle join_using_alias first (for USING clause aliases like "AS x")
3098
3285
  if (node.join_using_alias && node.join_using_alias.aliasname) {
3099
3286
  let aliasStr = node.join_using_alias.aliasname;
3100
3287
  if (node.join_using_alias.colnames && node.join_using_alias.colnames.length > 0) {
@@ -3104,7 +3291,6 @@ class Deparser {
3104
3291
  }
3105
3292
  result += ` AS ${aliasStr}`;
3106
3293
  }
3107
- // Handle regular alias (for outer table aliases like "y")
3108
3294
  if (node.alias && node.alias.aliasname) {
3109
3295
  let aliasStr = node.alias.aliasname;
3110
3296
  if (node.alias.colnames && node.alias.colnames.length > 0) {
@@ -5823,38 +6009,82 @@ class Deparser {
5823
6009
  return output.join(' ');
5824
6010
  }
5825
6011
  CreatePolicyStmt(node, context) {
5826
- const output = ['CREATE', 'POLICY'];
6012
+ const output = [];
6013
+ const initialParts = ['CREATE', 'POLICY'];
5827
6014
  if (node.policy_name) {
5828
- output.push(`"${node.policy_name}"`);
6015
+ initialParts.push(`"${node.policy_name}"`);
5829
6016
  }
5830
- output.push('ON');
6017
+ output.push(initialParts.join(' '));
6018
+ // Add ON clause on new line in pretty mode
5831
6019
  if (node.table) {
5832
- output.push(this.RangeVar(node.table, context));
6020
+ if (this.formatter.isPretty()) {
6021
+ output.push(this.formatter.newline() + this.formatter.indent(`ON ${this.RangeVar(node.table, context)}`));
6022
+ }
6023
+ else {
6024
+ output.push('ON');
6025
+ output.push(this.RangeVar(node.table, context));
6026
+ }
5833
6027
  }
5834
6028
  // Handle AS RESTRICTIVE/PERMISSIVE clause
5835
6029
  if (node.permissive === undefined) {
5836
- output.push('AS', 'RESTRICTIVE');
6030
+ if (this.formatter.isPretty()) {
6031
+ output.push(this.formatter.newline() + this.formatter.indent('AS RESTRICTIVE'));
6032
+ }
6033
+ else {
6034
+ output.push('AS', 'RESTRICTIVE');
6035
+ }
5837
6036
  }
5838
6037
  else if (node.permissive === true) {
5839
- output.push('AS', 'PERMISSIVE');
6038
+ if (this.formatter.isPretty()) {
6039
+ output.push(this.formatter.newline() + this.formatter.indent('AS PERMISSIVE'));
6040
+ }
6041
+ else {
6042
+ output.push('AS', 'PERMISSIVE');
6043
+ }
5840
6044
  }
5841
6045
  if (node.cmd_name) {
5842
- output.push('FOR', node.cmd_name.toUpperCase());
6046
+ if (this.formatter.isPretty()) {
6047
+ output.push(this.formatter.newline() + this.formatter.indent(`FOR ${node.cmd_name.toUpperCase()}`));
6048
+ }
6049
+ else {
6050
+ output.push('FOR', node.cmd_name.toUpperCase());
6051
+ }
5843
6052
  }
5844
6053
  if (node.roles && node.roles.length > 0) {
5845
- output.push('TO');
5846
6054
  const roles = list_utils_1.ListUtils.unwrapList(node.roles).map(role => this.visit(role, context));
5847
- output.push(roles.join(', '));
6055
+ if (this.formatter.isPretty()) {
6056
+ output.push(this.formatter.newline() + this.formatter.indent(`TO ${roles.join(', ')}`));
6057
+ }
6058
+ else {
6059
+ output.push('TO');
6060
+ output.push(roles.join(', '));
6061
+ }
5848
6062
  }
5849
6063
  if (node.qual) {
5850
- output.push('USING');
5851
- output.push(`(${this.visit(node.qual, context)})`);
6064
+ if (this.formatter.isPretty()) {
6065
+ const qualExpr = this.visit(node.qual, context);
6066
+ output.push(this.formatter.newline() + this.formatter.indent('USING ('));
6067
+ output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(qualExpr)));
6068
+ output.push(this.formatter.newline() + this.formatter.indent(')'));
6069
+ }
6070
+ else {
6071
+ output.push('USING');
6072
+ output.push(`(${this.visit(node.qual, context)})`);
6073
+ }
5852
6074
  }
5853
6075
  if (node.with_check) {
5854
- output.push('WITH CHECK');
5855
- output.push(`(${this.visit(node.with_check, context)})`);
6076
+ if (this.formatter.isPretty()) {
6077
+ const checkExpr = this.visit(node.with_check, context);
6078
+ output.push(this.formatter.newline() + this.formatter.indent('WITH CHECK ('));
6079
+ output.push(this.formatter.newline() + this.formatter.indent(this.formatter.indent(checkExpr)));
6080
+ output.push(this.formatter.newline() + this.formatter.indent(')'));
6081
+ }
6082
+ else {
6083
+ output.push('WITH CHECK');
6084
+ output.push(`(${this.visit(node.with_check, context)})`);
6085
+ }
5856
6086
  }
5857
- return output.join(' ');
6087
+ return this.formatter.isPretty() ? output.join('') : output.join(' ');
5858
6088
  }
5859
6089
  AlterPolicyStmt(node, context) {
5860
6090
  const output = ['ALTER', 'POLICY'];
@@ -9720,5 +9950,9 @@ class Deparser {
9720
9950
  }
9721
9951
  return output.join(' ');
9722
9952
  }
9953
+ containsMultilineStringLiteral(content) {
9954
+ const stringLiteralRegex = /'[^']*\n[^']*'/g;
9955
+ return stringLiteralRegex.test(content);
9956
+ }
9723
9957
  }
9724
9958
  exports.Deparser = Deparser;