prettier-plugin-tsql 0.1.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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/bin/dotnet/Microsoft.SqlServer.TransactSql.ScriptDom.dll +0 -0
  4. package/bin/dotnet/SqlScriptDom.deps.json +41 -0
  5. package/bin/dotnet/SqlScriptDom.dll +0 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +21 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/language.d.ts +3 -0
  11. package/dist/language.d.ts.map +1 -0
  12. package/dist/language.js +9 -0
  13. package/dist/language.js.map +1 -0
  14. package/dist/options.d.ts +3 -0
  15. package/dist/options.d.ts.map +1 -0
  16. package/dist/options.js +35 -0
  17. package/dist/options.js.map +1 -0
  18. package/dist/parser/index.d.ts +5 -0
  19. package/dist/parser/index.d.ts.map +1 -0
  20. package/dist/parser/index.js +263 -0
  21. package/dist/parser/index.js.map +1 -0
  22. package/dist/parser/types.d.ts +21 -0
  23. package/dist/parser/types.d.ts.map +1 -0
  24. package/dist/parser/types.js +2 -0
  25. package/dist/parser/types.js.map +1 -0
  26. package/dist/printer/admin.d.ts +22 -0
  27. package/dist/printer/admin.d.ts.map +1 -0
  28. package/dist/printer/admin.js +250 -0
  29. package/dist/printer/admin.js.map +1 -0
  30. package/dist/printer/ddl.d.ts +36 -0
  31. package/dist/printer/ddl.d.ts.map +1 -0
  32. package/dist/printer/ddl.js +836 -0
  33. package/dist/printer/ddl.js.map +1 -0
  34. package/dist/printer/expressions.d.ts +11 -0
  35. package/dist/printer/expressions.d.ts.map +1 -0
  36. package/dist/printer/expressions.js +1475 -0
  37. package/dist/printer/expressions.js.map +1 -0
  38. package/dist/printer/helpers.d.ts +25 -0
  39. package/dist/printer/helpers.d.ts.map +1 -0
  40. package/dist/printer/helpers.js +61 -0
  41. package/dist/printer/helpers.js.map +1 -0
  42. package/dist/printer/index.d.ts +4 -0
  43. package/dist/printer/index.d.ts.map +1 -0
  44. package/dist/printer/index.js +30 -0
  45. package/dist/printer/index.js.map +1 -0
  46. package/dist/printer/procedural.d.ts +41 -0
  47. package/dist/printer/procedural.d.ts.map +1 -0
  48. package/dist/printer/procedural.js +364 -0
  49. package/dist/printer/procedural.js.map +1 -0
  50. package/dist/printer/security.d.ts +15 -0
  51. package/dist/printer/security.d.ts.map +1 -0
  52. package/dist/printer/security.js +309 -0
  53. package/dist/printer/security.js.map +1 -0
  54. package/dist/printer/statements.d.ts +18 -0
  55. package/dist/printer/statements.d.ts.map +1 -0
  56. package/dist/printer/statements.js +689 -0
  57. package/dist/printer/statements.js.map +1 -0
  58. package/dist/printer/utils.d.ts +34 -0
  59. package/dist/printer/utils.d.ts.map +1 -0
  60. package/dist/printer/utils.js +61 -0
  61. package/dist/printer/utils.js.map +1 -0
  62. package/package.json +63 -0
@@ -0,0 +1,1475 @@
1
+ import { keyword, getDensity, hardSep, softSep, hardline, join, group, indent, line, softline, fill, appendTrailingLines, } from './utils.js';
2
+ import { prop, propArr, propStr, propBool, schemaObjectName, assignmentOp } from './helpers.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Scalar expressions
5
+ // ---------------------------------------------------------------------------
6
+ export function printExpression(node, opts, printFn) {
7
+ switch (node.type) {
8
+ case 'WildcardColumn':
9
+ return '*';
10
+ case 'ColumnReference':
11
+ return printColumnRef(node);
12
+ case 'IntegerLiteral':
13
+ return node.text ?? '0';
14
+ case 'NumericLiteral':
15
+ return node.text ?? '0';
16
+ case 'RealLiteral':
17
+ return node.text ?? '0';
18
+ case 'MoneyLiteral':
19
+ return node.text ?? '0';
20
+ case 'StringLiteral':
21
+ return `'${node.text ?? ''}'`;
22
+ case 'BinaryLiteral':
23
+ return `0x${node.text ?? ''}`;
24
+ case 'NullLiteral':
25
+ return keyword('NULL', opts);
26
+ case 'BooleanLiteral':
27
+ return node.text?.toUpperCase() ?? 'TRUE';
28
+ case 'VariableReference':
29
+ return node.text ?? '@var';
30
+ case 'GlobalVariable':
31
+ return node.text ?? '@@var';
32
+ case 'SelectStar':
33
+ return node.text ?? '*';
34
+ case 'SelectScalar':
35
+ return printSelectScalar(node, opts, printFn);
36
+ case 'SelectSetVariable':
37
+ return printSelectSetVariable(node, opts, printFn);
38
+ case 'FunctionCall':
39
+ return printFunctionCall(node, opts, printFn);
40
+ case 'BinaryExpression':
41
+ return printBinaryExpr(node, opts, printFn);
42
+ case 'UnaryExpression':
43
+ return printUnaryExpr(node, opts, printFn);
44
+ case 'ParenthesisExpression':
45
+ return printParenExpr(node, opts, printFn);
46
+ case 'CaseExpression':
47
+ return printCaseExpr(node, opts, printFn);
48
+ case 'CastCall':
49
+ return printCastCall(node, opts, printFn);
50
+ case 'ConvertCall':
51
+ return printConvertCall(node, opts, printFn);
52
+ case 'IIfCall':
53
+ return printIIfCall(node, opts, printFn);
54
+ case 'CoalesceExpression':
55
+ return printCoalesceExpr(node, opts, printFn);
56
+ case 'NullIfExpression':
57
+ return printNullIfExpr(node, opts, printFn);
58
+ case 'TryCastCall':
59
+ return printTryCastCall(node, opts, printFn);
60
+ case 'TryConvertCall':
61
+ return printTryConvertCall(node, opts, printFn);
62
+ case 'AtTimeZoneCall':
63
+ return printAtTimeZone(node, opts, printFn);
64
+ case 'ScalarSubquery':
65
+ return printScalarSubquery(node, opts, printFn);
66
+ case 'NextValueFor':
67
+ return printNextValueFor(node, opts, printFn);
68
+ case 'ParseCall':
69
+ case 'TryParseCall':
70
+ return printParseCall(node, opts, printFn);
71
+ case 'ParameterlessCall':
72
+ return keyword(node.text ?? 'CURRENT_TIMESTAMP', opts);
73
+ case 'DefaultLiteral':
74
+ return keyword('DEFAULT', opts);
75
+ case 'PartitionFunctionCall':
76
+ return printPartitionFunctionCall(node, opts, printFn);
77
+ case 'IdentityFunctionCall':
78
+ return printIdentityFunctionCall(node, opts, printFn);
79
+ case 'ExtractFromExpression':
80
+ return printExtractFrom(node, opts, printFn);
81
+ case 'OverClause':
82
+ return printOverClause(node, opts, printFn);
83
+ case 'RollupSpec':
84
+ return printGroupingSet('ROLLUP', node, opts, printFn);
85
+ case 'CubeSpec':
86
+ return printGroupingSet('CUBE', node, opts, printFn);
87
+ case 'GroupingSetsSpec':
88
+ return printGroupingSets(node, opts, printFn);
89
+ case 'CompositeGroupingSpec':
90
+ return printCompositeGroup(node, opts, printFn);
91
+ case 'GrandTotalSpec':
92
+ return '()';
93
+ // Query nodes — appear as subqueries inside expressions
94
+ case 'QuerySpecification':
95
+ case 'BinaryQueryExpression':
96
+ case 'QueryParenthesis':
97
+ return printQueryExpression(node, opts, printFn);
98
+ default:
99
+ return node.text ?? `/* ${node.type} */`;
100
+ }
101
+ }
102
+ function printColumnRef(node) {
103
+ return node.text ?? propArr(node, 'parts').join('.');
104
+ }
105
+ function printSelectScalar(node, opts, printFn) {
106
+ const expr = prop(node, 'expression');
107
+ const alias = propStr(node, 'alias');
108
+ const exprDoc = expr ? printExpression(expr, opts, printFn) : '';
109
+ if (alias) {
110
+ return [exprDoc, ' ', keyword('AS', opts), ' ', alias];
111
+ }
112
+ return exprDoc;
113
+ }
114
+ function printSelectSetVariable(node, opts, printFn) {
115
+ const varName = propStr(node, 'variable') ?? '@var';
116
+ const op = assignmentOp(propStr(node, 'operator') ?? 'Equals');
117
+ const val = prop(node, 'value');
118
+ return [varName, ' ', op, ' ', val ? printExpression(val, opts, printFn) : ''];
119
+ }
120
+ function printNullOnNullClause(node, opts) {
121
+ const raw = propStr(node, 'nullOnNull');
122
+ if (!raw)
123
+ return '';
124
+ // ScriptDom gives only the first keyword: 'absent' or 'NULL'
125
+ return raw.toLowerCase() === 'absent' ? keyword('ABSENT ON NULL', opts) : keyword('NULL ON NULL', opts);
126
+ }
127
+ function printJsonKeyValue(kv, opts, printFn) {
128
+ const keyNode = prop(kv, 'key');
129
+ const valNode = prop(kv, 'value');
130
+ const keyDoc = keyNode ? printExpression(keyNode, opts, printFn) : '';
131
+ const valDoc = valNode ? printExpression(valNode, opts, printFn) : '';
132
+ return [keyDoc, ': ', valDoc];
133
+ }
134
+ function printFunctionCall(node, opts, printFn) {
135
+ const name = propStr(node, 'name') ?? 'FUNC';
136
+ const args = propArr(node, 'args').map((a) => printExpression(a, opts, printFn));
137
+ const over = prop(node, 'over');
138
+ const uniqueRowFilter = propStr(node, 'uniqueRowFilter');
139
+ const distinctDoc = uniqueRowFilter === 'Distinct' ? [keyword('DISTINCT', opts), ' '] : [];
140
+ const nullOnNullDoc = printNullOnNullClause(node, opts);
141
+ // TRIM(LEADING|TRAILING|BOTH [chars] FROM str) — SQL Server 2022+
142
+ const trimOptions = propStr(node, 'trimOptions');
143
+ if (trimOptions && name.toUpperCase() === 'TRIM') {
144
+ const dirDoc = keyword(trimOptions.toUpperCase(), opts);
145
+ if (args.length === 2) {
146
+ return group([
147
+ keyword('TRIM', opts),
148
+ '(',
149
+ indent([softline, dirDoc, ' ', args[0], ' ', keyword('FROM', opts), line, args[1]]),
150
+ softline,
151
+ ')',
152
+ ]);
153
+ }
154
+ else if (args.length === 1) {
155
+ return group([
156
+ keyword('TRIM', opts),
157
+ '(',
158
+ indent([softline, dirDoc, ' ', keyword('FROM', opts), line, args[0]]),
159
+ softline,
160
+ ')',
161
+ ]);
162
+ }
163
+ }
164
+ // JSON_OBJECT('key': value [, ...] [ABSENT ON NULL | NULL ON NULL]) — SQL Server 2022+
165
+ const jsonParams = propArr(node, 'jsonParams');
166
+ if (jsonParams.length > 0) {
167
+ const pairs = jsonParams.map((kv) => printJsonKeyValue(kv, opts, printFn));
168
+ const nullClause = nullOnNullDoc ? [' ', nullOnNullDoc] : '';
169
+ return group([
170
+ keyword(name, opts),
171
+ '(',
172
+ indent([softline, join([',', line], pairs), nullClause]),
173
+ softline,
174
+ ')',
175
+ ]);
176
+ }
177
+ // JSON_ARRAYAGG(expr [ORDER BY ...] [ABSENT ON NULL | NULL ON NULL]) — SQL Server 2022+
178
+ const jsonOrderBy = prop(node, 'jsonOrderBy');
179
+ if (jsonOrderBy && name.toUpperCase() === 'JSON_ARRAYAGG') {
180
+ const orderByDoc = printOrderByClause(jsonOrderBy, opts, printFn);
181
+ const nullClause = nullOnNullDoc ? [' ', nullOnNullDoc] : '';
182
+ return group([
183
+ keyword(name, opts),
184
+ '(',
185
+ indent([softline, join([',', line], args), ' ', orderByDoc, nullClause]),
186
+ softline,
187
+ ')',
188
+ ]);
189
+ }
190
+ // Standard function call (JSON_ARRAY and others with optional ABSENT/NULL ON NULL)
191
+ const nullClause = nullOnNullDoc ? [' ', nullOnNullDoc] : '';
192
+ const argsDoc = group([
193
+ keyword(name, opts),
194
+ '(',
195
+ indent([softline, ...distinctDoc, join([',', line], args), nullClause]),
196
+ softline,
197
+ ')',
198
+ ]);
199
+ // IGNORE NULLS / RESPECT NULLS modifier — SQL Server 2022+
200
+ const nullsModifier = propStr(node, 'nulls');
201
+ const nullsDoc = nullsModifier ? [' ', keyword(nullsModifier.toUpperCase(), opts)] : '';
202
+ // WITHIN GROUP (ORDER BY ...) for STRING_AGG, PERCENTILE_CONT/DISC etc.
203
+ const withinGroup = prop(node, 'withinGroup');
204
+ if (withinGroup) {
205
+ const withinDoc = [
206
+ ' ',
207
+ keyword('WITHIN GROUP', opts),
208
+ ' (',
209
+ printOrderByClause(withinGroup, opts, printFn),
210
+ ')',
211
+ ];
212
+ if (over)
213
+ return [
214
+ argsDoc,
215
+ nullsDoc,
216
+ withinDoc,
217
+ ' ',
218
+ keyword('OVER', opts),
219
+ ' ',
220
+ printOverClause(over, opts, printFn),
221
+ ];
222
+ return [argsDoc, nullsDoc, withinDoc];
223
+ }
224
+ if (over) {
225
+ return [argsDoc, nullsDoc, ' ', keyword('OVER', opts), ' ', printOverClause(over, opts, printFn)];
226
+ }
227
+ return argsDoc;
228
+ }
229
+ // Flatten a left-recursive + (Add/Concatenate) chain into its leaf terms.
230
+ // Stops at any other operator so e.g. the `a * b` in `a * b + c` stays grouped.
231
+ function collectConcatChain(node) {
232
+ const op = propStr(node, 'operator');
233
+ if (node.type !== 'BinaryExpression' || (op !== 'Add' && op !== 'Concatenate')) {
234
+ return [node];
235
+ }
236
+ const left = prop(node, 'left');
237
+ const right = prop(node, 'right');
238
+ return [...(left ? collectConcatChain(left) : []), ...(right ? [right] : [])];
239
+ }
240
+ function printBinaryExpr(node, opts, printFn) {
241
+ const op = propStr(node, 'operator') ?? '+';
242
+ // For + chains: flatten the whole tree and use fill so that terms pack
243
+ // onto each line up to printWidth, breaking before + only when the next
244
+ // term would overflow. Flat: "a + b + c". Filling: "a + b\n+ c + d".
245
+ // This prevents Prettier from descending into function args to break there.
246
+ if (op === 'Add' || op === 'Concatenate') {
247
+ const terms = collectConcatChain(node);
248
+ const termDocs = terms.map((t) => printExpression(t, opts, printFn));
249
+ const parts = [termDocs[0]];
250
+ for (let i = 1; i < termDocs.length; i++) {
251
+ parts.push([line, '+ ']);
252
+ parts.push(termDocs[i]);
253
+ }
254
+ return fill(parts);
255
+ }
256
+ const left = prop(node, 'left');
257
+ const right = prop(node, 'right');
258
+ const opStr = mapBinaryOp(op);
259
+ return group([
260
+ left ? printExpression(left, opts, printFn) : '',
261
+ ' ',
262
+ opStr,
263
+ ' ',
264
+ right ? printExpression(right, opts, printFn) : '',
265
+ ]);
266
+ }
267
+ function mapBinaryOp(op) {
268
+ const map = {
269
+ Add: '+',
270
+ Subtract: '-',
271
+ Multiply: '*',
272
+ Divide: '/',
273
+ Modulo: '%',
274
+ BitwiseAnd: '&',
275
+ BitwiseOr: '|',
276
+ BitwiseXor: '^',
277
+ Concatenate: '+',
278
+ };
279
+ return map[op] ?? op;
280
+ }
281
+ function printUnaryExpr(node, opts, printFn) {
282
+ const expr = prop(node, 'expr');
283
+ const op = propStr(node, 'operator') ?? '-';
284
+ const opStr = op === 'Positive' ? '+' : op === 'Negative' ? '-' : op === 'BitwiseNot' ? '~' : op;
285
+ return [opStr, expr ? printExpression(expr, opts, printFn) : ''];
286
+ }
287
+ function printParenExpr(node, opts, printFn) {
288
+ const expr = prop(node, 'expr');
289
+ return ['(', expr ? printExpression(expr, opts, printFn) : '', ')'];
290
+ }
291
+ function printCaseExpr(node, opts, printFn) {
292
+ const caseType = propStr(node, 'caseType');
293
+ const whens = propArr(node, 'whens');
294
+ const elseExpr = prop(node, 'else');
295
+ const input = prop(node, 'input');
296
+ const density = getDensity(opts);
297
+ const whenDocs = whens.map((w) => {
298
+ const whenExpr = prop(w, 'when');
299
+ const thenExpr = prop(w, 'then');
300
+ const isSearched = caseType === 'searched';
301
+ const whenPart = isSearched && whenExpr
302
+ ? printBoolExpr(whenExpr, opts, printFn)
303
+ : whenExpr
304
+ ? printExpression(whenExpr, opts, printFn)
305
+ : keyword('NULL', opts);
306
+ const thenPart = thenExpr ? printExpression(thenExpr, opts, printFn) : keyword('NULL', opts);
307
+ // Nested CASE: break after THEN and indent the inner case block.
308
+ const thenDoc = thenExpr?.type === 'CaseExpression'
309
+ ? [keyword('THEN', opts), indent([hardline, thenPart])]
310
+ : [keyword('THEN', opts), ' ', thenPart];
311
+ const inline = isSearched && density !== 'spacious' && whenExpr?.type !== 'BooleanBinary';
312
+ if (!isSearched || inline) {
313
+ return [keyword('WHEN', opts), ' ', whenPart, ' ', thenDoc];
314
+ }
315
+ return [keyword('WHEN', opts), indent([hardline, whenPart]), hardline, thenDoc];
316
+ });
317
+ const elseDoc = elseExpr ? printExpression(elseExpr, opts, printFn) : null;
318
+ const elsePart = elseDoc
319
+ ? elseExpr?.type === 'CaseExpression'
320
+ ? [hardline, keyword('ELSE', opts), indent([hardline, elseDoc])]
321
+ : [hardline, keyword('ELSE', opts), ' ', elseDoc]
322
+ : [];
323
+ const inputPart = input ? [' ', printExpression(input, opts, printFn)] : [];
324
+ return group([
325
+ keyword('CASE', opts),
326
+ ...inputPart,
327
+ indent([...whenDocs.map((w) => [hardline, ...w]), ...elsePart]),
328
+ hardline,
329
+ keyword('END', opts),
330
+ ]);
331
+ }
332
+ function printCastCall(node, opts, printFn) {
333
+ const expr = prop(node, 'expr');
334
+ const dataType = propStr(node, 'dataType') ?? 'INT';
335
+ return [
336
+ keyword('CAST', opts),
337
+ '(',
338
+ expr ? printExpression(expr, opts, printFn) : '',
339
+ ' ',
340
+ keyword('AS', opts),
341
+ ' ',
342
+ keyword(dataType, opts),
343
+ ')',
344
+ ];
345
+ }
346
+ function printConvertCall(node, opts, printFn) {
347
+ const expr = prop(node, 'expr');
348
+ const dataType = propStr(node, 'dataType') ?? 'INT';
349
+ const style = prop(node, 'style');
350
+ const parts = [
351
+ keyword('CONVERT', opts),
352
+ '(',
353
+ keyword(dataType, opts),
354
+ ', ',
355
+ expr ? printExpression(expr, opts, printFn) : '',
356
+ ];
357
+ if (style) {
358
+ parts.push(', ', printExpression(style, opts, printFn));
359
+ }
360
+ parts.push(')');
361
+ return parts;
362
+ }
363
+ function printIIfCall(node, opts, printFn) {
364
+ const condition = prop(node, 'condition');
365
+ const trueVal = prop(node, 'trueVal');
366
+ const falseVal = prop(node, 'falseVal');
367
+ return [
368
+ keyword('IIF', opts),
369
+ '(',
370
+ condition ? printBoolExpr(condition, opts, printFn) : '',
371
+ ', ',
372
+ trueVal ? printExpression(trueVal, opts, printFn) : '',
373
+ ', ',
374
+ falseVal ? printExpression(falseVal, opts, printFn) : '',
375
+ ')',
376
+ ];
377
+ }
378
+ function printCoalesceExpr(node, opts, printFn) {
379
+ const args = propArr(node, 'args');
380
+ return [
381
+ keyword('COALESCE', opts),
382
+ '(',
383
+ join(', ', args.map((a) => printExpression(a, opts, printFn))),
384
+ ')',
385
+ ];
386
+ }
387
+ function printNullIfExpr(node, opts, printFn) {
388
+ const first = prop(node, 'first');
389
+ const second = prop(node, 'second');
390
+ return [
391
+ keyword('NULLIF', opts),
392
+ '(',
393
+ first ? printExpression(first, opts, printFn) : '',
394
+ ', ',
395
+ second ? printExpression(second, opts, printFn) : '',
396
+ ')',
397
+ ];
398
+ }
399
+ function printTryCastCall(node, opts, printFn) {
400
+ const expr = prop(node, 'expr');
401
+ const dataType = propStr(node, 'dataType') ?? 'INT';
402
+ return [
403
+ keyword('TRY_CAST', opts),
404
+ '(',
405
+ expr ? printExpression(expr, opts, printFn) : '',
406
+ ' ',
407
+ keyword('AS', opts),
408
+ ' ',
409
+ keyword(dataType, opts),
410
+ ')',
411
+ ];
412
+ }
413
+ function printTryConvertCall(node, opts, printFn) {
414
+ const expr = prop(node, 'expr');
415
+ const dataType = propStr(node, 'dataType') ?? 'INT';
416
+ const style = prop(node, 'style');
417
+ const parts = [
418
+ keyword('TRY_CONVERT', opts),
419
+ '(',
420
+ keyword(dataType, opts),
421
+ ', ',
422
+ expr ? printExpression(expr, opts, printFn) : '',
423
+ ];
424
+ if (style)
425
+ parts.push(', ', printExpression(style, opts, printFn));
426
+ parts.push(')');
427
+ return parts;
428
+ }
429
+ function printAtTimeZone(node, opts, printFn) {
430
+ const source = prop(node, 'source');
431
+ const timeZone = prop(node, 'timeZone');
432
+ return [
433
+ source ? printExpression(source, opts, printFn) : '',
434
+ ' ',
435
+ keyword('AT TIME ZONE', opts),
436
+ ' ',
437
+ timeZone ? printExpression(timeZone, opts, printFn) : '',
438
+ ];
439
+ }
440
+ function printScalarSubquery(node, opts, printFn) {
441
+ const query = prop(node, 'query');
442
+ if (!query)
443
+ return '(/* subquery */)';
444
+ const sep = getDensity(opts) === 'compact' ? softline : hardline;
445
+ return group(['(', indent([sep, printQueryExpression(query, opts, printFn)]), sep, ')']);
446
+ }
447
+ // ---------------------------------------------------------------------------
448
+ // Query expressions (SELECT, UNION, etc.) — kept here to avoid circular imports
449
+ // with statements.ts which handles top-level statement formatting.
450
+ // ---------------------------------------------------------------------------
451
+ export function printQueryExpression(node, opts, printFn) {
452
+ switch (node.type) {
453
+ case 'QuerySpecification':
454
+ return printQuerySpec(node, opts, printFn);
455
+ case 'BinaryQueryExpression':
456
+ return printBinaryQuery(node, opts, printFn);
457
+ case 'QueryParenthesis': {
458
+ const q = prop(node, 'query');
459
+ if (!q)
460
+ return '()';
461
+ const sep = getDensity(opts) === 'compact' ? softline : hardline;
462
+ return group(['(', indent([sep, printQueryExpression(q, opts, printFn)]), sep, ')']);
463
+ }
464
+ case 'QueryDerivedTable': {
465
+ const q = prop(node, 'query');
466
+ const alias = propStr(node, 'alias');
467
+ const inner = q ? printQueryExpression(q, opts, printFn) : '/* query */';
468
+ const sep = getDensity(opts) === 'compact' ? softline : hardline;
469
+ return alias
470
+ ? group(['(', indent([sep, inner]), sep, ') ', keyword('AS', opts), ' ', alias])
471
+ : group(['(', indent([sep, inner]), sep, ')']);
472
+ }
473
+ default:
474
+ return node.text ?? `/* ${node.type} */`;
475
+ }
476
+ }
477
+ function printQuerySpec(node, opts, printFn) {
478
+ const density = getDensity(opts);
479
+ const uniqueRowFilter = propStr(node, 'uniqueRowFilter');
480
+ const top = prop(node, 'top');
481
+ const selectElements = propArr(node, 'selectElements');
482
+ const forClause = prop(node, 'forClause');
483
+ const from = prop(node, 'from');
484
+ const where = prop(node, 'where');
485
+ const groupBy = prop(node, 'groupBy');
486
+ const having = prop(node, 'having');
487
+ const orderBy = prop(node, 'orderBy');
488
+ const windowDefs = propArr(node, 'windowDefs');
489
+ const selectKw = uniqueRowFilter === 'Distinct' ? keyword('SELECT DISTINCT', opts) : keyword('SELECT', opts);
490
+ const topDoc = top ? printTop(top, opts, printFn) : null;
491
+ const colDocs = selectElements.map((se) => printExpression(se, opts, printFn));
492
+ if (density === 'compact') {
493
+ // Compact: try to keep everything inline; wrap at printWidth
494
+ const colList = group(join(softSep(opts), colDocs));
495
+ const parts = [selectKw, ...(topDoc ? [' ', topDoc] : []), ' ', colList];
496
+ if (from) {
497
+ const tableRefs = propArr(from, 'tableReferences');
498
+ const fromDocs = tableRefs.map((tr) => printTableRef(tr, opts, printFn));
499
+ // Try to keep FROM on one line; if too long, each join on its own line
500
+ parts.push(line, keyword('FROM', opts), ' ', group(join(softSep(opts), fromDocs)));
501
+ }
502
+ if (where) {
503
+ parts.push(line, keyword('WHERE', opts), ' ', boolWithTrailing(where, printBoolExpr(where, opts, printFn)));
504
+ }
505
+ if (groupBy) {
506
+ const elems = propArr(groupBy, 'elements');
507
+ const elemDocs = elems.map((e) => printExpression(e, opts, printFn));
508
+ parts.push(line, keyword('GROUP BY', opts), ' ', join(softSep(opts), elemDocs));
509
+ }
510
+ if (having) {
511
+ parts.push(line, keyword('HAVING', opts), ' ', boolWithTrailing(having, printBoolExpr(having, opts, printFn)));
512
+ }
513
+ if (orderBy) {
514
+ parts.push(line, printOrderByClause(orderBy, opts, printFn));
515
+ }
516
+ if (windowDefs.length > 0) {
517
+ parts.push(line, printWindowClause(windowDefs, opts, printFn));
518
+ }
519
+ if (forClause) {
520
+ parts.push(line, printForClause(forClause, opts));
521
+ }
522
+ return group(parts);
523
+ }
524
+ // Standard / Spacious: single column stays inline; multiple each on own line.
525
+ // A CASE expression always expands to multiple lines, so force it onto its own indented line.
526
+ const singleExprType = (prop(selectElements[0], 'expression') ?? selectElements[0])?.type;
527
+ const colList = density === 'standard' && colDocs.length === 1 && singleExprType !== 'CaseExpression'
528
+ ? [' ', colDocs[0]]
529
+ : indent([hardline, join(hardSep(opts), colDocs)]);
530
+ const parts = [selectKw, ...(topDoc ? [' ', topDoc] : []), colList];
531
+ if (from) {
532
+ const tableRefs = propArr(from, 'tableReferences');
533
+ const fromDocs = tableRefs.map((tr) => printTableRef(tr, opts, printFn));
534
+ // standard: single table (no joins) stays inline; multiple/joins each on own line
535
+ const singleTable = density === 'standard' &&
536
+ tableRefs.length === 1 &&
537
+ tableRefs[0].type !== 'QualifiedJoin' &&
538
+ tableRefs[0].type !== 'UnqualifiedJoin';
539
+ if (singleTable) {
540
+ parts.push(hardline, keyword('FROM', opts), ' ', fromDocs[0]);
541
+ }
542
+ else {
543
+ parts.push(hardline, keyword('FROM', opts), indent([hardline, join(hardSep(opts), fromDocs)]));
544
+ }
545
+ }
546
+ if (where) {
547
+ // standard: single predicate inline; multiple each on own line
548
+ // spacious: always indented
549
+ const inline = density === 'standard' && where.type !== 'BooleanBinary';
550
+ if (inline) {
551
+ parts.push(hardline, keyword('WHERE', opts), ' ', boolWithTrailing(where, printBoolExpr(where, opts, printFn)));
552
+ }
553
+ else {
554
+ parts.push(hardline, keyword('WHERE', opts), indent([hardline, boolWithTrailing(where, printBoolExpr(where, opts, printFn))]));
555
+ }
556
+ }
557
+ if (groupBy) {
558
+ const elems = propArr(groupBy, 'elements');
559
+ const elemDocs = elems.map((e) => printExpression(e, opts, printFn));
560
+ const inline = density === 'standard' && elems.length === 1;
561
+ if (inline) {
562
+ parts.push(hardline, keyword('GROUP BY', opts), ' ', elemDocs[0]);
563
+ }
564
+ else {
565
+ parts.push(hardline, keyword('GROUP BY', opts), indent([hardline, join(hardSep(opts), elemDocs)]));
566
+ }
567
+ }
568
+ if (having) {
569
+ const inline = density === 'standard' && having.type !== 'BooleanBinary';
570
+ if (inline) {
571
+ parts.push(hardline, keyword('HAVING', opts), ' ', boolWithTrailing(having, printBoolExpr(having, opts, printFn)));
572
+ }
573
+ else {
574
+ parts.push(hardline, keyword('HAVING', opts), indent([hardline, boolWithTrailing(having, printBoolExpr(having, opts, printFn))]));
575
+ }
576
+ }
577
+ if (orderBy) {
578
+ parts.push(hardline, printOrderByClause(orderBy, opts, printFn));
579
+ }
580
+ if (windowDefs.length > 0) {
581
+ parts.push(hardline, printWindowClause(windowDefs, opts, printFn));
582
+ }
583
+ if (forClause) {
584
+ parts.push(hardline, printForClause(forClause, opts));
585
+ }
586
+ return group(parts);
587
+ }
588
+ function printTop(node, opts, printFn) {
589
+ const expr = prop(node, 'expression');
590
+ const isPercent = propBool(node, 'percent');
591
+ const withTies = propBool(node, 'withTies');
592
+ const parts = [keyword('TOP', opts), ' (', expr ? printExpression(expr, opts, printFn) : '', ')'];
593
+ if (isPercent)
594
+ parts.push(' ', keyword('PERCENT', opts));
595
+ if (withTies)
596
+ parts.push(' ', keyword('WITH TIES', opts));
597
+ return parts;
598
+ }
599
+ function printGroupingSet(kw, node, opts, printFn) {
600
+ const exprs = propArr(node, 'expressions').map((e) => printExpression(e, opts, printFn));
601
+ return group([keyword(kw, opts), '(', indent([softline, join([',', line], exprs)]), softline, ')']);
602
+ }
603
+ function printGroupingSets(node, opts, printFn) {
604
+ const sets = propArr(node, 'sets').map((s) => printExpression(s, opts, printFn));
605
+ return group([keyword('GROUPING SETS', opts), '(', indent([softline, join([',', line], sets)]), softline, ')']);
606
+ }
607
+ function printCompositeGroup(node, opts, printFn) {
608
+ const items = propArr(node, 'items').map((e) => printExpression(e, opts, printFn));
609
+ return group(['(', indent([softline, join([',', line], items)]), softline, ')']);
610
+ }
611
+ function printBinaryQuery(node, opts, printFn) {
612
+ const left = prop(node, 'left');
613
+ const right = prop(node, 'right');
614
+ const op = propStr(node, 'operator') ?? 'Union';
615
+ const isAll = propBool(node, 'all');
616
+ const opKw = op === 'Union'
617
+ ? keyword('UNION', opts)
618
+ : op === 'Intersect'
619
+ ? keyword('INTERSECT', opts)
620
+ : keyword('EXCEPT', opts);
621
+ return [
622
+ left ? printQueryExpression(left, opts, printFn) : '',
623
+ hardline,
624
+ hardline,
625
+ opKw,
626
+ isAll ? [' ', keyword('ALL', opts)] : '',
627
+ hardline,
628
+ hardline,
629
+ right ? printQueryExpression(right, opts, printFn) : '',
630
+ ];
631
+ }
632
+ export function printOverClause(node, opts, printFn) {
633
+ // Named window reference: OVER (w) — SQL Server 2022+
634
+ const windowName = propStr(node, 'windowName');
635
+ if (windowName)
636
+ return ['(', windowName, ')'];
637
+ const partitions = propArr(node, 'partitionBy');
638
+ const orderBy = prop(node, 'orderBy');
639
+ const frame = prop(node, 'frame');
640
+ const parts = [];
641
+ if (partitions.length > 0) {
642
+ parts.push(keyword('PARTITION BY', opts), ' ', join([',', line], partitions.map((p) => printExpression(p, opts, printFn))));
643
+ }
644
+ if (orderBy) {
645
+ if (parts.length > 0)
646
+ parts.push(hardline);
647
+ parts.push(printOrderByClause(orderBy, opts, printFn));
648
+ }
649
+ if (frame) {
650
+ if (parts.length > 0)
651
+ parts.push(hardline);
652
+ parts.push(printWindowFrame(frame, opts, printFn));
653
+ }
654
+ return group(['(', indent([softline, ...parts]), softline, ')']);
655
+ }
656
+ function printWindowFrame(node, opts, printFn) {
657
+ const frameType = keyword(propStr(node, 'frameType') ?? 'ROWS', opts); // 'Rows' | 'Range'
658
+ const top = prop(node, 'top');
659
+ const bottom = prop(node, 'bottom');
660
+ if (bottom) {
661
+ return [
662
+ frameType,
663
+ ' ',
664
+ keyword('BETWEEN', opts),
665
+ ' ',
666
+ printWindowDelimiter(top, opts, printFn),
667
+ ' ',
668
+ keyword('AND', opts),
669
+ ' ',
670
+ printWindowDelimiter(bottom, opts, printFn),
671
+ ];
672
+ }
673
+ return [frameType, ' ', printWindowDelimiter(top, opts, printFn)];
674
+ }
675
+ function printWindowDelimiter(node, opts, printFn) {
676
+ const delimType = propStr(node, 'delimType') ?? '';
677
+ const offset = prop(node, 'offset');
678
+ switch (delimType) {
679
+ case 'UnboundedPreceding':
680
+ return [keyword('UNBOUNDED', opts), ' ', keyword('PRECEDING', opts)];
681
+ case 'ValuePreceding':
682
+ return [offset ? printExpression(offset, opts, printFn) : '', ' ', keyword('PRECEDING', opts)];
683
+ case 'CurrentRow':
684
+ return [keyword('CURRENT', opts), ' ', keyword('ROW', opts)];
685
+ case 'ValueFollowing':
686
+ return [offset ? printExpression(offset, opts, printFn) : '', ' ', keyword('FOLLOWING', opts)];
687
+ case 'UnboundedFollowing':
688
+ return [keyword('UNBOUNDED', opts), ' ', keyword('FOLLOWING', opts)];
689
+ default:
690
+ return delimType;
691
+ }
692
+ }
693
+ function printWindowDefinition(node, opts, printFn) {
694
+ const name = propStr(node, 'name') ?? '';
695
+ const refWindowName = propStr(node, 'refWindowName');
696
+ const partitions = propArr(node, 'partitionBy');
697
+ const orderBy = prop(node, 'orderBy');
698
+ const frame = prop(node, 'frame');
699
+ const inner = [];
700
+ if (refWindowName)
701
+ inner.push(refWindowName);
702
+ if (partitions.length > 0) {
703
+ if (inner.length > 0)
704
+ inner.push(hardline);
705
+ inner.push(keyword('PARTITION BY', opts), ' ', join([',', line], partitions.map((p) => printExpression(p, opts, printFn))));
706
+ }
707
+ if (orderBy) {
708
+ if (inner.length > 0)
709
+ inner.push(hardline);
710
+ inner.push(printOrderByClause(orderBy, opts, printFn));
711
+ }
712
+ if (frame) {
713
+ if (inner.length > 0)
714
+ inner.push(hardline);
715
+ inner.push(printWindowFrame(frame, opts, printFn));
716
+ }
717
+ const body = inner.length > 0 ? group(['(', indent([softline, ...inner]), softline, ')']) : '()';
718
+ return [name, ' ', keyword('AS', opts), ' ', body];
719
+ }
720
+ export function printWindowClause(defs, opts, printFn) {
721
+ if (defs.length === 0)
722
+ return '';
723
+ const defDocs = defs.map((d) => printWindowDefinition(d, opts, printFn));
724
+ if (defs.length === 1) {
725
+ return [keyword('WINDOW', opts), ' ', defDocs[0]];
726
+ }
727
+ return [keyword('WINDOW', opts), indent([hardline, join([',', hardline], defDocs)])];
728
+ }
729
+ // ---------------------------------------------------------------------------
730
+ // Boolean expressions
731
+ // ---------------------------------------------------------------------------
732
+ export function printBoolExpr(node, opts, printFn) {
733
+ switch (node.type) {
734
+ case 'BooleanComparison':
735
+ return printBoolComparison(node, opts, printFn);
736
+ case 'BooleanBinary':
737
+ return printBoolBinary(node, opts, printFn);
738
+ case 'BooleanNot':
739
+ return printBoolNot(node, opts, printFn);
740
+ case 'BooleanParenthesis':
741
+ return printBoolParen(node, opts, printFn);
742
+ case 'IsNullExpression':
743
+ return printIsNull(node, opts, printFn);
744
+ case 'InPredicate':
745
+ return printInPredicate(node, opts, printFn);
746
+ case 'LikePredicate':
747
+ return printLikePredicate(node, opts, printFn);
748
+ case 'ExistsPredicate':
749
+ return printExistsPredicate(node, opts, printFn);
750
+ case 'BetweenExpression':
751
+ return printBetween(node, opts, printFn);
752
+ case 'FullTextPredicate':
753
+ return printFullTextPredicate(node, opts, printFn);
754
+ case 'DistinctPredicate':
755
+ return printDistinctPredicate(node, opts, printFn);
756
+ default:
757
+ return node.text ?? `/* ${node.type} */`;
758
+ }
759
+ }
760
+ function cmpOp(op) {
761
+ const map = {
762
+ Equals: '=',
763
+ NotEqualToBrackets: '<>',
764
+ NotEqualToExclamation: '!=',
765
+ GreaterThan: '>',
766
+ LessThan: '<',
767
+ GreaterThanOrEqualTo: '>=',
768
+ LessThanOrEqualTo: '<=',
769
+ LeftOuterJoin: '*=',
770
+ RightOuterJoin: '=*',
771
+ NotLessThan: '!<',
772
+ NotGreaterThan: '!>',
773
+ };
774
+ return map[op] ?? op;
775
+ }
776
+ function printBoolComparison(node, opts, printFn) {
777
+ const left = prop(node, 'left');
778
+ const right = prop(node, 'right');
779
+ const op = cmpOp(propStr(node, 'operator') ?? '=');
780
+ return group([
781
+ left ? printExpression(left, opts, printFn) : '',
782
+ ' ',
783
+ op,
784
+ ' ',
785
+ right ? printExpression(right, opts, printFn) : '',
786
+ ]);
787
+ }
788
+ function printDistinctPredicate(node, opts, printFn) {
789
+ const left = prop(node, 'left');
790
+ const right = prop(node, 'right');
791
+ const isNot = propBool(node, 'isNot');
792
+ const opKw = isNot ? keyword('IS NOT DISTINCT FROM', opts) : keyword('IS DISTINCT FROM', opts);
793
+ return group([
794
+ left ? printExpression(left, opts, printFn) : '',
795
+ ' ',
796
+ opKw,
797
+ ' ',
798
+ right ? printExpression(right, opts, printFn) : '',
799
+ ]);
800
+ }
801
+ // Walk to the rightmost non-BooleanBinary leaf of a boolean subtree.
802
+ function rightmostPred(node) {
803
+ if (!node)
804
+ return null;
805
+ if (node.type === 'BooleanBinary')
806
+ return rightmostPred(prop(node, 'right'));
807
+ return node;
808
+ }
809
+ // Append any trailing comment on the rightmost predicate leaf to the doc.
810
+ function boolWithTrailing(node, doc) {
811
+ const rp = rightmostPred(node);
812
+ const trailing = rp ? rightmostTrailingComment(rp, rp.endOffset) : undefined;
813
+ return appendTrailingLines(doc, trailing);
814
+ }
815
+ function printBoolBinary(node, opts, printFn) {
816
+ const left = prop(node, 'left');
817
+ const right = prop(node, 'right');
818
+ const op = propStr(node, 'operator') === 'Or' ? keyword('OR', opts) : keyword('AND', opts);
819
+ // compact: stay inline, wrap at printWidth; standard/spacious: each predicate on own line
820
+ const sep = getDensity(opts) === 'compact' ? line : hardline;
821
+ const leftDoc = left ? printBoolExpr(left, opts, printFn) : '';
822
+ const rightDoc = right ? printBoolExpr(right, opts, printFn) : '';
823
+ // A comment attached to the rightmost leaf of the left subtree means a
824
+ // commented-out predicate sits between left and right in the source.
825
+ // The parent BooleanBinary always handles it — never the level that holds
826
+ // the predicate as its own right child — so no double-printing occurs.
827
+ // Use rightmostTrailingComment to also find comments attached to scalar
828
+ // children of the predicate (e.g. the literal in "col = 1") since those
829
+ // share the same endOffset as the predicate itself.
830
+ const rp = rightmostPred(left);
831
+ const betweenComment = rp ? rightmostTrailingComment(rp, rp.endOffset) : undefined;
832
+ if (betweenComment) {
833
+ const commentLines = betweenComment.split('\n').flatMap((c) => [hardline, c]);
834
+ return group([leftDoc, ...commentLines, hardline, op, ' ', rightDoc]);
835
+ }
836
+ return group([leftDoc, sep, op, ' ', rightDoc]);
837
+ }
838
+ function printBoolNot(node, opts, printFn) {
839
+ const expr = prop(node, 'expr');
840
+ return [keyword('NOT', opts), ' ', expr ? printBoolExpr(expr, opts, printFn) : ''];
841
+ }
842
+ function printBoolParen(node, opts, printFn) {
843
+ const expr = prop(node, 'expr');
844
+ if (!expr)
845
+ return '()';
846
+ return group(['(', indent([softline, printBoolExpr(expr, opts, printFn)]), softline, ')']);
847
+ }
848
+ function printIsNull(node, opts, printFn) {
849
+ const expr = prop(node, 'expr');
850
+ const isNot = propBool(node, 'isNot');
851
+ return [
852
+ expr ? printExpression(expr, opts, printFn) : '',
853
+ ' ',
854
+ keyword('IS', opts),
855
+ isNot ? [' ', keyword('NOT', opts)] : '',
856
+ ' ',
857
+ keyword('NULL', opts),
858
+ ];
859
+ }
860
+ function printInPredicate(node, opts, printFn) {
861
+ const expr = prop(node, 'expr');
862
+ const isNot = propBool(node, 'negated');
863
+ const values = propArr(node, 'values');
864
+ const subquery = prop(node, 'subquery');
865
+ const lhs = [
866
+ expr ? printExpression(expr, opts, printFn) : '',
867
+ ' ',
868
+ ...(isNot ? [keyword('NOT', opts), ' '] : []),
869
+ keyword('IN', opts),
870
+ ];
871
+ if (subquery) {
872
+ // Subquery: keep existing softline/indent behaviour
873
+ return [...lhs, ' (', indent([softline, printQueryExpression(subquery, opts, printFn)]), softline, ')'];
874
+ }
875
+ // Value list: all inline when it fits; when it doesn't, each value on its
876
+ // own indented line with ) dropping back to the indentation of the IN line.
877
+ const valueDocs = values.map((v) => printExpression(v, opts, printFn));
878
+ return [...lhs, group([' (', indent([softline, join([',', line], valueDocs)]), softline, ')'])];
879
+ }
880
+ function printLikePredicate(node, opts, printFn) {
881
+ const expr = prop(node, 'expr');
882
+ const pattern = prop(node, 'pattern');
883
+ const isNot = propBool(node, 'negated');
884
+ const escape = prop(node, 'escape');
885
+ const parts = [
886
+ expr ? printExpression(expr, opts, printFn) : '',
887
+ ' ',
888
+ isNot ? [keyword('NOT', opts), ' '] : '',
889
+ keyword('LIKE', opts),
890
+ ' ',
891
+ pattern ? printExpression(pattern, opts, printFn) : '',
892
+ ];
893
+ if (escape) {
894
+ parts.push(' ', keyword('ESCAPE', opts), ' ', printExpression(escape, opts, printFn));
895
+ }
896
+ return parts;
897
+ }
898
+ function printExistsPredicate(node, opts, printFn) {
899
+ const subquery = prop(node, 'subquery');
900
+ if (!subquery)
901
+ return keyword('EXISTS', opts) + '()';
902
+ const sep = getDensity(opts) === 'compact' ? softline : hardline;
903
+ return group([
904
+ keyword('EXISTS', opts),
905
+ ' (',
906
+ indent([sep, printQueryExpression(subquery, opts, printFn)]),
907
+ sep,
908
+ ')',
909
+ ]);
910
+ }
911
+ function printBetween(node, opts, printFn) {
912
+ const expr = prop(node, 'expr');
913
+ const from = prop(node, 'from');
914
+ const to = prop(node, 'to');
915
+ const isNot = propBool(node, 'negated');
916
+ return [
917
+ expr ? printExpression(expr, opts, printFn) : '',
918
+ ' ',
919
+ isNot ? [keyword('NOT', opts), ' '] : '',
920
+ keyword('BETWEEN', opts),
921
+ ' ',
922
+ from ? printExpression(from, opts, printFn) : '',
923
+ ' ',
924
+ keyword('AND', opts),
925
+ ' ',
926
+ to ? printExpression(to, opts, printFn) : '',
927
+ ];
928
+ }
929
+ // ---------------------------------------------------------------------------
930
+ // FROM / JOIN
931
+ // ---------------------------------------------------------------------------
932
+ export function printTableRef(node, opts, printFn) {
933
+ switch (node.type) {
934
+ case 'NamedTableReference':
935
+ return printNamedTableRef(node, opts, printFn);
936
+ case 'VariableTableReference':
937
+ return node.text ?? '/* unknown table var */';
938
+ case 'QualifiedJoin':
939
+ return printQualifiedJoin(node, opts, printFn);
940
+ case 'UnqualifiedJoin':
941
+ return printUnqualifiedJoin(node, opts, printFn);
942
+ case 'JoinParenthesisTableReference':
943
+ return printJoinParenthesis(node, opts, printFn);
944
+ case 'QueryDerivedTable':
945
+ return printQueryDerivedTable(node, opts, printFn);
946
+ case 'SchemaObjectFunctionTableReference':
947
+ return printSchemaObjectFunctionTableRef(node, opts, printFn);
948
+ case 'FullTextTableReference':
949
+ return printFullTextTableRef(node, opts, printFn);
950
+ case 'OpenXmlTableReference':
951
+ return printOpenXmlTableRef(node, opts);
952
+ case 'OpenJsonTableReference':
953
+ return printOpenJsonTableRef(node, opts);
954
+ case 'OpenRowsetTableReference':
955
+ return printOpenRowsetTableRef(node, opts);
956
+ case 'BulkOpenRowset':
957
+ return printBulkOpenRowset(node, opts);
958
+ case 'PivotedTableReference':
959
+ return printPivotedTableRef(node, opts, printFn);
960
+ case 'UnpivotedTableReference':
961
+ return printUnpivotedTableRef(node, opts, printFn);
962
+ default:
963
+ return node.text ?? `/* ${node.type} */`;
964
+ }
965
+ }
966
+ function printNamedTableRef(node, opts, printFn) {
967
+ const alias = propStr(node, 'alias');
968
+ const hints = node.props?.['hints'];
969
+ const nameDoc = schemaObjectName(prop(node, 'name'));
970
+ const aliasDoc = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
971
+ const hintsDoc = hints?.length
972
+ ? [
973
+ ' ',
974
+ keyword('WITH', opts),
975
+ ' (',
976
+ join(', ', hints.map((h) => keyword(h, opts))),
977
+ ')',
978
+ ]
979
+ : '';
980
+ const tableSample = prop(node, 'tableSample');
981
+ const temporal = prop(node, 'temporal');
982
+ const sampleDoc = tableSample ? printTableSample(tableSample, opts, printFn) : '';
983
+ const temporalDoc = temporal ? printTemporalClause(temporal, opts, printFn) : '';
984
+ // Order: name, temporal, alias, tablesample, hints
985
+ return [nameDoc, temporalDoc, aliasDoc, sampleDoc, hintsDoc];
986
+ }
987
+ function joinTypeKeyword(jt, opts) {
988
+ const map = {
989
+ Inner: 'INNER JOIN',
990
+ LeftOuter: 'LEFT JOIN',
991
+ RightOuter: 'RIGHT JOIN',
992
+ FullOuter: 'FULL JOIN',
993
+ };
994
+ const kw = map[jt] ?? `${jt} JOIN`;
995
+ return keyword(kw, opts);
996
+ }
997
+ /**
998
+ * Walk the rightmost path of an AST subtree (props in reverse insertion order)
999
+ * to find a trailingComment, but only on nodes whose endOffset equals
1000
+ * targetEndOffset. This constraint is essential: Pass 3 routes a between-join
1001
+ * comment to the rightmost descendant of the *direct* left-child join (i.e.
1002
+ * a node whose endOffset equals left.endOffset). Without this restriction,
1003
+ * subsequent joins would walk back through the entire ancestor chain and
1004
+ * re-discover the same comment on every subsequent gap.
1005
+ */
1006
+ function rightmostTrailingComment(node, targetEndOffset) {
1007
+ if (!node || node.endOffset !== targetEndOffset)
1008
+ return undefined;
1009
+ if (node.trailingComment)
1010
+ return node.trailingComment;
1011
+ const props = node.props;
1012
+ if (!props)
1013
+ return undefined;
1014
+ const vals = Object.values(props);
1015
+ for (let i = vals.length - 1; i >= 0; i--) {
1016
+ const v = vals[i];
1017
+ if (v && typeof v === 'object' && 'type' in v) {
1018
+ const found = rightmostTrailingComment(v, targetEndOffset);
1019
+ if (found)
1020
+ return found;
1021
+ }
1022
+ }
1023
+ return undefined;
1024
+ }
1025
+ function printQualifiedJoin(node, opts, printFn) {
1026
+ const density = getDensity(opts);
1027
+ const left = prop(node, 'left');
1028
+ const right = prop(node, 'right');
1029
+ const condition = prop(node, 'condition');
1030
+ const jt = propStr(node, 'joinType') ?? 'Inner';
1031
+ // compact: try to keep joins on one line (line = space when flat)
1032
+ // standard/spacious: always new line before each JOIN keyword
1033
+ const joinBreak = density === 'compact' ? line : hardline;
1034
+ let onDoc = '';
1035
+ if (condition) {
1036
+ const isMultiple = condition.type === 'BooleanBinary';
1037
+ if (density === 'compact') {
1038
+ // inline with JOIN, wraps at printWidth via BoolBinary using `line`
1039
+ onDoc = [' ', keyword('ON', opts), ' ', printBoolExpr(condition, opts, printFn)];
1040
+ }
1041
+ else if (density === 'standard' && !isMultiple) {
1042
+ // single predicate: ON stays on join line, predicate wraps below if too long
1043
+ onDoc = [' ', keyword('ON', opts), group([indent([line, printBoolExpr(condition, opts, printFn)])])];
1044
+ }
1045
+ else {
1046
+ // standard (multiple) or spacious (always): ON on join line, predicates indented below
1047
+ onDoc = [' ', keyword('ON', opts), indent([hardline, printBoolExpr(condition, opts, printFn)])];
1048
+ }
1049
+ }
1050
+ const leftDoc = left ? printTableRef(left, opts, printFn) : '';
1051
+ // A comment between two JOIN clauses lands on the rightmost descendant of
1052
+ // the left-child join (via Pass 3 `>=` tie-breaking). Restrict search to
1053
+ // nodes whose endOffset == left.endOffset so subsequent joins don't
1054
+ // re-discover the same comment from an ancestor.
1055
+ const betweenComment = left ? rightmostTrailingComment(left, left.endOffset) : undefined;
1056
+ const commentLines = betweenComment ? betweenComment.split('\n').flatMap((c) => [hardline, c]) : [];
1057
+ const separator = commentLines.length > 0 ? [...commentLines, hardline] : joinBreak;
1058
+ return [
1059
+ leftDoc,
1060
+ separator,
1061
+ joinTypeKeyword(jt, opts),
1062
+ ' ',
1063
+ right ? printTableRef(right, opts, printFn) : '',
1064
+ onDoc,
1065
+ ];
1066
+ }
1067
+ function printUnqualifiedJoin(node, opts, printFn) {
1068
+ const left = prop(node, 'left');
1069
+ const right = prop(node, 'right');
1070
+ const jt = propStr(node, 'joinType') ?? 'Cross';
1071
+ const kw = jt === 'CrossJoin'
1072
+ ? keyword('CROSS JOIN', opts)
1073
+ : jt === 'OuterApply'
1074
+ ? keyword('OUTER APPLY', opts)
1075
+ : keyword('CROSS APPLY', opts);
1076
+ const leftDoc = left ? printTableRef(left, opts, printFn) : '';
1077
+ // Unqualified joins have no condition; comment lands on left node itself.
1078
+ const betweenComment = left ? rightmostTrailingComment(left, left.endOffset) : undefined;
1079
+ const commentLines = betweenComment ? betweenComment.split('\n').flatMap((c) => [hardline, c]) : [];
1080
+ const separator = commentLines.length > 0 ? [...commentLines, hardline] : hardline;
1081
+ return [leftDoc, separator, kw, ' ', right ? printTableRef(right, opts, printFn) : ''];
1082
+ }
1083
+ function printJoinParenthesis(node, opts, printFn) {
1084
+ const join = prop(node, 'join');
1085
+ const joinDoc = join ? printTableRef(join, opts, printFn) : '';
1086
+ return ['(', indent([hardline, joinDoc]), hardline, ')'];
1087
+ }
1088
+ function printQueryDerivedTable(node, opts, printFn) {
1089
+ const query = prop(node, 'query');
1090
+ const alias = propStr(node, 'alias');
1091
+ const queryDoc = query ? printQueryExpression(query, opts, printFn) : '/* query */';
1092
+ if (alias) {
1093
+ return ['(', indent([hardline, queryDoc]), hardline, ') ', keyword('AS', opts), ' ', alias];
1094
+ }
1095
+ return ['(', indent([hardline, queryDoc]), hardline, ')'];
1096
+ }
1097
+ function printSchemaObjectFunctionTableRef(node, opts, printFn) {
1098
+ const args = propArr(node, 'args');
1099
+ const alias = propStr(node, 'alias');
1100
+ const argsDoc = join(', ', args.map((a) => printExpression(a, opts, printFn)));
1101
+ const aliasDoc = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
1102
+ return [schemaObjectName(prop(node, 'name')), '(', argsDoc, ')', aliasDoc];
1103
+ }
1104
+ // ---------------------------------------------------------------------------
1105
+ // Full-text: CONTAINS / FREETEXT predicates and CONTAINSTABLE / FREETEXTTABLE
1106
+ // ---------------------------------------------------------------------------
1107
+ /** Render the column-list argument: single column → bare name, multiple → (a, b), wildcard → * */
1108
+ function fullTextColumnsPart(columns, printFn) {
1109
+ if (columns.length === 0)
1110
+ return '*';
1111
+ if (columns.length === 1 && columns[0].type === 'WildcardColumn')
1112
+ return '*';
1113
+ if (columns.length === 1)
1114
+ return printFn(columns[0]);
1115
+ return ['(', join(', ', columns.map(printFn)), ')'];
1116
+ }
1117
+ function printFullTextPredicate(node, opts, printFn) {
1118
+ const fnType = propStr(node, 'functionType') ?? 'Contains';
1119
+ const fnKw = fnType === 'FreeText' ? keyword('FREETEXT', opts) : keyword('CONTAINS', opts);
1120
+ const columns = propArr(node, 'columns');
1121
+ const value = prop(node, 'value');
1122
+ const language = propStr(node, 'language');
1123
+ const args = [
1124
+ fullTextColumnsPart(columns, printFn),
1125
+ ', ',
1126
+ value ? printExpression(value, opts, printFn) : '',
1127
+ ];
1128
+ if (language)
1129
+ args.push(', ', keyword('LANGUAGE', opts), ' ', language);
1130
+ return [fnKw, '(', ...args, ')'];
1131
+ }
1132
+ function printFullTextTableRef(node, opts, printFn) {
1133
+ const fnType = propStr(node, 'functionType') ?? 'Contains';
1134
+ const fnKw = fnType === 'FreeText' ? keyword('FREETEXTTABLE', opts) : keyword('CONTAINSTABLE', opts);
1135
+ const tableName = prop(node, 'tableName');
1136
+ const columns = propArr(node, 'columns');
1137
+ const searchCondition = prop(node, 'searchCondition');
1138
+ const topN = prop(node, 'topN');
1139
+ const language = propStr(node, 'language');
1140
+ const alias = propStr(node, 'alias');
1141
+ const args = [
1142
+ schemaObjectName(tableName),
1143
+ ', ',
1144
+ fullTextColumnsPart(columns, printFn),
1145
+ ', ',
1146
+ searchCondition ? printExpression(searchCondition, opts, printFn) : '',
1147
+ ];
1148
+ if (language)
1149
+ args.push(', ', keyword('LANGUAGE', opts), ' ', language);
1150
+ if (topN)
1151
+ args.push(', ', printExpression(topN, opts, printFn));
1152
+ const aliasDoc = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
1153
+ return [fnKw, '(', ...args, ')', aliasDoc];
1154
+ }
1155
+ function rowsetWithClause(items, opts) {
1156
+ return [
1157
+ ' ',
1158
+ keyword('WITH', opts),
1159
+ ' (',
1160
+ indent([
1161
+ hardline,
1162
+ join([',', hardline], items.map((i) => i.text ?? '')),
1163
+ ]),
1164
+ hardline,
1165
+ ')',
1166
+ ];
1167
+ }
1168
+ function printOpenXmlTableRef(node, opts) {
1169
+ const variable = propStr(node, 'variable') ?? '';
1170
+ const rowPattern = propStr(node, 'rowPattern');
1171
+ const flags = propStr(node, 'flags');
1172
+ const withItems = propArr(node, 'withItems');
1173
+ const tableName = prop(node, 'tableName');
1174
+ const alias = propStr(node, 'alias');
1175
+ const args = [variable];
1176
+ if (rowPattern)
1177
+ args.push(', ', rowPattern);
1178
+ if (flags)
1179
+ args.push(', ', flags);
1180
+ const withPart = withItems.length
1181
+ ? rowsetWithClause(withItems, opts)
1182
+ : tableName
1183
+ ? [' ', keyword('WITH', opts), ' ', schemaObjectName(tableName)]
1184
+ : '';
1185
+ const aliasPart = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
1186
+ return [keyword('OPENXML', opts), '(', ...args, ')', withPart, aliasPart];
1187
+ }
1188
+ function printOpenJsonTableRef(node, opts) {
1189
+ const variable = propStr(node, 'variable') ?? '';
1190
+ const rowPattern = propStr(node, 'rowPattern');
1191
+ const withItems = propArr(node, 'withItems');
1192
+ const alias = propStr(node, 'alias');
1193
+ const args = [variable];
1194
+ if (rowPattern)
1195
+ args.push(', ', rowPattern);
1196
+ const withPart = withItems.length ? rowsetWithClause(withItems, opts) : '';
1197
+ const aliasPart = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
1198
+ return [keyword('OPENJSON', opts), '(', ...args, ')', withPart, aliasPart];
1199
+ }
1200
+ // ---------------------------------------------------------------------------
1201
+ // OPENROWSET — provider form and BULK form
1202
+ // ---------------------------------------------------------------------------
1203
+ function printOpenRowsetTableRef(node, opts) {
1204
+ const providerName = propStr(node, 'providerName') ?? '';
1205
+ const providerString = propStr(node, 'providerString');
1206
+ const dataSource = propStr(node, 'dataSource');
1207
+ const userId = propStr(node, 'userId');
1208
+ const password = propStr(node, 'password');
1209
+ const query = propStr(node, 'query');
1210
+ const obj = prop(node, 'object');
1211
+ const alias = propStr(node, 'alias');
1212
+ // Connection: either a single provider string or three-part datasource;userid;password
1213
+ const connection = providerString
1214
+ ? providerString
1215
+ : [dataSource ?? '', ';', userId ?? '', ';', password ?? ''];
1216
+ // Third argument: either an ad-hoc query string or a remote schema object name
1217
+ const third = query ? query : schemaObjectName(obj);
1218
+ const aliasPart = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
1219
+ return [
1220
+ group([
1221
+ keyword('OPENROWSET', opts),
1222
+ '(',
1223
+ indent([softline, providerName, ',', line, connection, ',', line, third]),
1224
+ softline,
1225
+ ')',
1226
+ ]),
1227
+ aliasPart,
1228
+ ];
1229
+ }
1230
+ function printBulkOpenRowset(node, opts) {
1231
+ const dataFiles = node.props?.['dataFiles'];
1232
+ const options = node.props?.['options'];
1233
+ const alias = propStr(node, 'alias');
1234
+ const dataFile = dataFiles?.[0] ?? '';
1235
+ const allArgs = [keyword('BULK', opts), ' ', dataFile, ...(options ?? []).map((o) => [',', line, o])];
1236
+ const aliasPart = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
1237
+ return [group([keyword('OPENROWSET', opts), '(', indent([softline, ...allArgs]), softline, ')']), aliasPart];
1238
+ }
1239
+ // ---------------------------------------------------------------------------
1240
+ // ORDER BY
1241
+ // ---------------------------------------------------------------------------
1242
+ export function printOrderByClause(node, opts, printFn) {
1243
+ const density = getDensity(opts);
1244
+ const elements = propArr(node, 'elements');
1245
+ const elDocs = elements.map((e) => {
1246
+ const expr = prop(e, 'expression');
1247
+ const sort = propStr(e, 'sortOrder');
1248
+ const sortDoc = sort === 'Descending' ? [' ', keyword('DESC', opts)] : [' ', keyword('ASC', opts)];
1249
+ return [expr ? printExpression(expr, opts, printFn) : '', ...sortDoc];
1250
+ });
1251
+ // compact: inline with ORDER BY, wraps; standard + single: inline; else: each on own line
1252
+ if (density === 'compact') {
1253
+ return [keyword('ORDER BY', opts), ' ', join(softSep(opts), elDocs)];
1254
+ }
1255
+ if (density === 'standard' && elements.length === 1) {
1256
+ return [keyword('ORDER BY', opts), ' ', elDocs[0]];
1257
+ }
1258
+ return [keyword('ORDER BY', opts), indent([hardline, join(hardSep(opts), elDocs)])];
1259
+ }
1260
+ // ---------------------------------------------------------------------------
1261
+ // NEXT VALUE FOR, PARSE / TRY_PARSE
1262
+ // ---------------------------------------------------------------------------
1263
+ function printNextValueFor(node, opts, printFn) {
1264
+ const nameDoc = schemaObjectName(prop(node, 'name'));
1265
+ const over = prop(node, 'over');
1266
+ const parts = [keyword('NEXT VALUE FOR', opts), ' ', nameDoc];
1267
+ if (over)
1268
+ parts.push(' ', keyword('OVER', opts), ' ', printOverClause(over, opts, printFn));
1269
+ return parts;
1270
+ }
1271
+ function printPartitionFunctionCall(node, opts, printFn) {
1272
+ const db = propStr(node, 'database');
1273
+ const name = propStr(node, 'name') ?? '';
1274
+ const args = propArr(node, 'args').map((a) => printExpression(a, opts, printFn));
1275
+ const prefix = db ? `${db}.$PARTITION.` : '$PARTITION.';
1276
+ return group([prefix, name, '(', indent([softline, join([',', line], args)]), softline, ')']);
1277
+ }
1278
+ function printIdentityFunctionCall(node, opts, printFn) {
1279
+ const dataType = propStr(node, 'dataType') ?? '';
1280
+ const seed = prop(node, 'seed');
1281
+ const inc = prop(node, 'increment');
1282
+ const parts = [keyword(dataType, opts)];
1283
+ if (seed)
1284
+ parts.push(', ', printExpression(seed, opts, printFn));
1285
+ if (inc)
1286
+ parts.push(', ', printExpression(inc, opts, printFn));
1287
+ return [keyword('IDENTITY', opts), '(', ...parts, ')'];
1288
+ }
1289
+ function printExtractFrom(node, opts, printFn) {
1290
+ const element = propStr(node, 'element') ?? '';
1291
+ const expr = prop(node, 'expression');
1292
+ return [
1293
+ keyword('EXTRACT', opts),
1294
+ '(',
1295
+ keyword(element, opts),
1296
+ ' ',
1297
+ keyword('FROM', opts),
1298
+ ' ',
1299
+ expr ? printExpression(expr, opts, printFn) : '',
1300
+ ')',
1301
+ ];
1302
+ }
1303
+ function printParseCall(node, opts, printFn) {
1304
+ const isTry = node.type === 'TryParseCall';
1305
+ const fnKw = isTry ? keyword('TRY_PARSE', opts) : keyword('PARSE', opts);
1306
+ const valueDoc = printExpression(prop(node, 'value'), opts, printFn);
1307
+ const dataType = propStr(node, 'dataType') ?? '';
1308
+ const culture = prop(node, 'culture');
1309
+ const parts = [valueDoc, ' ', keyword('AS', opts), ' ', keyword(dataType, opts)];
1310
+ if (culture)
1311
+ parts.push(' ', keyword('USING', opts), ' ', printExpression(culture, opts, printFn));
1312
+ return group([fnKw, '(', indent([softline, ...parts]), softline, ')']);
1313
+ }
1314
+ // ---------------------------------------------------------------------------
1315
+ // TABLESAMPLE
1316
+ // ---------------------------------------------------------------------------
1317
+ function printTableSample(node, opts, printFn) {
1318
+ const isSystem = node.props?.['system'];
1319
+ const sampleNumber = prop(node, 'sampleNumber');
1320
+ const option = propStr(node, 'option');
1321
+ const repeatSeed = prop(node, 'repeatSeed');
1322
+ const systemKw = isSystem ? [' ', keyword('SYSTEM', opts)] : '';
1323
+ const numDoc = sampleNumber ? printExpression(sampleNumber, opts, printFn) : '';
1324
+ const optDoc = option ? [' ', keyword(option.toUpperCase(), opts)] : '';
1325
+ const repeatDoc = repeatSeed
1326
+ ? [' ', keyword('REPEATABLE', opts), ' (', printExpression(repeatSeed, opts, printFn), ')']
1327
+ : '';
1328
+ return [' ', keyword('TABLESAMPLE', opts), systemKw, ' (', numDoc, optDoc, ')', repeatDoc];
1329
+ }
1330
+ // ---------------------------------------------------------------------------
1331
+ // FOR SYSTEM_TIME (temporal tables)
1332
+ // ---------------------------------------------------------------------------
1333
+ function printTemporalClause(node, opts, printFn) {
1334
+ const clauseType = propStr(node, 'clauseType');
1335
+ const startTime = prop(node, 'startTime');
1336
+ const endTime = prop(node, 'endTime');
1337
+ const startDoc = startTime ? printExpression(startTime, opts, printFn) : '';
1338
+ const endDoc = endTime ? printExpression(endTime, opts, printFn) : '';
1339
+ const prefix = [' ', keyword('FOR SYSTEM_TIME', opts)];
1340
+ switch (clauseType) {
1341
+ case 'AsOf':
1342
+ return [...prefix, ' ', keyword('AS OF', opts), ' ', startDoc];
1343
+ case 'FromTo':
1344
+ return [...prefix, ' ', keyword('FROM', opts), ' ', startDoc, ' ', keyword('TO', opts), ' ', endDoc];
1345
+ case 'Between':
1346
+ return [...prefix, ' ', keyword('BETWEEN', opts), ' ', startDoc, ' ', keyword('AND', opts), ' ', endDoc];
1347
+ case 'ContainedIn':
1348
+ return [...prefix, ' ', keyword('CONTAINED IN', opts), ' (', startDoc, ', ', endDoc, ')'];
1349
+ case 'TemporalAll':
1350
+ return [...prefix, ' ', keyword('ALL', opts)];
1351
+ default:
1352
+ return '';
1353
+ }
1354
+ }
1355
+ // ---------------------------------------------------------------------------
1356
+ // FOR XML / FOR JSON
1357
+ // ---------------------------------------------------------------------------
1358
+ // Map ScriptDom enum names to their SQL keyword equivalents
1359
+ const XML_JSON_OPTION_KW = {
1360
+ IncludeNullValues: 'INCLUDE_NULL_VALUES',
1361
+ WithoutArrayWrapper: 'WITHOUT_ARRAY_WRAPPER',
1362
+ BinaryBase64: 'BINARY BASE64',
1363
+ XmlSchema: 'XMLSCHEMA',
1364
+ XmlData: 'XMLDATA',
1365
+ ElementsXsiNil: 'ELEMENTS XSINIL',
1366
+ ElementsAbsent: 'ELEMENTS ABSENT',
1367
+ ElementsAll: 'ELEMENTS',
1368
+ };
1369
+ function xmlJsonOptionKw(kind, opts) {
1370
+ return keyword(XML_JSON_OPTION_KW[kind] ?? kind.toUpperCase(), opts);
1371
+ }
1372
+ function printForXmlJsonOptions(forKw, node, opts) {
1373
+ const options = node.props?.['options'];
1374
+ const kwDoc = keyword(forKw, opts);
1375
+ if (!options?.length)
1376
+ return kwDoc;
1377
+ const optDocs = options.map((o) => {
1378
+ const kw = xmlJsonOptionKw(o.kind, opts);
1379
+ return o.value != null && o.value !== '' ? [kw, "('", o.value, "')"] : kw;
1380
+ });
1381
+ return [kwDoc, ' ', join(', ', optDocs)];
1382
+ }
1383
+ function printForClause(node, opts) {
1384
+ switch (node.type) {
1385
+ case 'ForXmlClause':
1386
+ return printForXmlJsonOptions('FOR XML', node, opts);
1387
+ case 'ForJsonClause':
1388
+ return printForXmlJsonOptions('FOR JSON', node, opts);
1389
+ case 'ForBrowseClause':
1390
+ return keyword('FOR BROWSE', opts);
1391
+ case 'ForReadOnlyClause':
1392
+ return keyword('FOR READ ONLY', opts);
1393
+ case 'ForUpdateClause': {
1394
+ const cols = node.props?.['columns'];
1395
+ if (cols?.length)
1396
+ return [keyword('FOR UPDATE OF', opts), ' ', join(', ', cols)];
1397
+ return keyword('FOR UPDATE', opts);
1398
+ }
1399
+ default:
1400
+ return node.text ?? keyword('FOR', opts);
1401
+ }
1402
+ }
1403
+ // ---------------------------------------------------------------------------
1404
+ // PIVOT / UNPIVOT
1405
+ // ---------------------------------------------------------------------------
1406
+ function printPivotedTableRef(node, opts, printFn) {
1407
+ const tableRef = prop(node, 'tableRef');
1408
+ const aggregateFn = propStr(node, 'aggregateFn') ?? 'agg';
1409
+ const valueColumns = node.props?.['valueColumns'];
1410
+ const pivotColumn = propStr(node, 'pivotColumn') ?? '';
1411
+ const inColumns = node.props?.['inColumns'];
1412
+ const alias = propStr(node, 'alias');
1413
+ const tableDoc = tableRef ? printTableRef(tableRef, opts, printFn) : '';
1414
+ const aggArgs = (valueColumns ?? []).join(', ');
1415
+ const inCols = (inColumns ?? []).map((c) => `[${c}]`).join(', ');
1416
+ const aliasDoc = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
1417
+ return group([
1418
+ tableDoc,
1419
+ hardline,
1420
+ keyword('PIVOT', opts),
1421
+ ' (',
1422
+ indent([
1423
+ softline,
1424
+ keyword(aggregateFn, opts),
1425
+ '(',
1426
+ aggArgs,
1427
+ ')',
1428
+ hardline,
1429
+ keyword('FOR', opts),
1430
+ ' ',
1431
+ pivotColumn,
1432
+ ' ',
1433
+ keyword('IN', opts),
1434
+ ' (',
1435
+ inCols,
1436
+ ')',
1437
+ ]),
1438
+ softline,
1439
+ ')',
1440
+ aliasDoc,
1441
+ ]);
1442
+ }
1443
+ function printUnpivotedTableRef(node, opts, printFn) {
1444
+ const tableRef = prop(node, 'tableRef');
1445
+ const valueColumn = propStr(node, 'valueColumn') ?? '';
1446
+ const pivotColumn = propStr(node, 'pivotColumn') ?? '';
1447
+ const inColumns = node.props?.['inColumns'];
1448
+ const alias = propStr(node, 'alias');
1449
+ const tableDoc = tableRef ? printTableRef(tableRef, opts, printFn) : '';
1450
+ const inCols = (inColumns ?? []).join(', ');
1451
+ const aliasDoc = alias ? [' ', keyword('AS', opts), ' ', alias] : '';
1452
+ return group([
1453
+ tableDoc,
1454
+ hardline,
1455
+ keyword('UNPIVOT', opts),
1456
+ ' (',
1457
+ indent([
1458
+ softline,
1459
+ valueColumn,
1460
+ ' ',
1461
+ keyword('FOR', opts),
1462
+ ' ',
1463
+ pivotColumn,
1464
+ ' ',
1465
+ keyword('IN', opts),
1466
+ ' (',
1467
+ inCols,
1468
+ ')',
1469
+ ]),
1470
+ softline,
1471
+ ')',
1472
+ aliasDoc,
1473
+ ]);
1474
+ }
1475
+ //# sourceMappingURL=expressions.js.map