prettier-plugin-tsql 0.6.0 → 0.6.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.
@@ -1,11 +1,23 @@
1
- import { keyword, hardline, join, indent, group, softline, line, ifExistsDoc, commentsBlock, parenList, } from '@prettier-sql/core/printer/utils';
1
+ import { keyword, hardline, join, indent, group, softline, line, ifExistsDoc, commentsBlock, parenList, } from '../_core/printer/utils.js';
2
2
  import { prop, propArr, propStr, propBool, schemaObjectName } from './helpers.js';
3
3
  // printNode / printBool / qexpr / printStatementWithComments are imported from statements.ts
4
4
  // — circular but safe in ESM (all imports are function references, never accessed during init)
5
- import { printStatementWithComments, printNode, printBool, qexpr } from './statements.js';
5
+ import { printStatementWithComments, printNode, printBool, printBoolClause, qexpr } from './statements.js';
6
6
  // ---------------------------------------------------------------------------
7
7
  // Shared helpers
8
8
  // ---------------------------------------------------------------------------
9
+ /**
10
+ * When ScriptDOM sees `AS BEGIN...END` it wraps the body in a single
11
+ * BeginEndBlockStatement. Unwrap that one outer block so that proc/function/
12
+ * trigger printers can emit their own BEGIN/END delimiters cleanly, while
13
+ * a *standalone* BEGIN...END statement still prints with its own delimiters.
14
+ */
15
+ function unwrapBodyBlock(stmts) {
16
+ if (stmts.length === 1 && stmts[0]?.type === 'BeginEndBlock') {
17
+ return propArr(stmts[0], 'statements');
18
+ }
19
+ return stmts;
20
+ }
9
21
  /** Render ` NULL` / ` NOT NULL` from a tristate `nullable` prop value. */
10
22
  function nullablePart(nullable, opts) {
11
23
  if (nullable === true)
@@ -40,7 +52,7 @@ function printInlineIndex(node, opts) {
40
52
  const kind = propStr(node, 'kind'); // 'clustered', 'nonclustered', etc.
41
53
  const columns = propArr(node, 'columns');
42
54
  const includeColumns = node.props?.['includeColumns'];
43
- const filterPredicate = propStr(node, 'filterPredicate');
55
+ const filterPredicateNode = prop(node, 'filterPredicate');
44
56
  const indexOptions = node.props?.['indexOptions'];
45
57
  const uniqueKw = isUnique ? [keyword('UNIQUE', opts), ' '] : '';
46
58
  const kindKw = kind ? [keyword(kind.toUpperCase(), opts), ' '] : '';
@@ -52,7 +64,7 @@ function printInlineIndex(node, opts) {
52
64
  const includePart = includeColumns?.length
53
65
  ? [' ', keyword('INCLUDE', opts), ' ', parenList(includeColumns)]
54
66
  : '';
55
- const filterPart = filterPredicate ? [' ', keyword('WHERE', opts), ' ', filterPredicate] : '';
67
+ const filterPart = filterPredicateNode ? [' ', printBoolClause('WHERE', filterPredicateNode, opts)] : '';
56
68
  const withPart = indexOptions?.length ? [' ', keyword('WITH', opts), ' (', join(', ', indexOptions), ')'] : '';
57
69
  return [
58
70
  keyword('INDEX', opts),
@@ -92,8 +104,18 @@ export function printCreateTable(node, opts) {
92
104
  const withPart = options && options.length > 0 ? [hardline, keyword('WITH', opts), ' ', parenList(options)] : '';
93
105
  const onFileGroup = propStr(node, 'onFileGroup');
94
106
  const textimageOn = propStr(node, 'textimageOn');
107
+ const fileStreamOn = propStr(node, 'fileStreamOn');
95
108
  const onPart = onFileGroup ? [hardline, keyword('ON', opts), ' ', onFileGroup] : '';
96
109
  const textimagePart = textimageOn ? [hardline, keyword('TEXTIMAGE_ON', opts), ' ', textimageOn] : '';
110
+ const fileStreamPart = fileStreamOn ? [hardline, keyword('FILESTREAM_ON', opts), ' ', fileStreamOn] : '';
111
+ // Graph table types (AS NODE / AS EDGE)
112
+ const asNode = node.props?.['asNode'];
113
+ const asEdge = node.props?.['asEdge'];
114
+ const graphPart = asNode
115
+ ? [' ', keyword('AS NODE', opts)]
116
+ : asEdge
117
+ ? [' ', keyword('AS EDGE', opts)]
118
+ : '';
97
119
  return group([
98
120
  keyword('CREATE TABLE', opts),
99
121
  ' ',
@@ -102,18 +124,27 @@ export function printCreateTable(node, opts) {
102
124
  indent([hardline, join([',', hardline], allDefs)]),
103
125
  hardline,
104
126
  ')',
127
+ graphPart,
105
128
  onPart,
129
+ fileStreamPart,
106
130
  textimagePart,
107
131
  withPart,
108
132
  ';',
109
133
  ]);
110
134
  }
111
135
  export function printColumnDef(node, opts) {
136
+ // Raw leaf (e.g. ENCRYPTED WITH — property names vary across ScriptDOM versions).
137
+ // The C# AstBuilder emits `Leaf("ColumnDefinition", col, rawText)` which sets
138
+ // `node.text` to the original column fragment and leaves `node.props` undefined.
139
+ if (!node.props)
140
+ return node.text ?? '/* column */';
112
141
  const name = propStr(node, 'name') ?? 'col';
113
- // Computed column: Name AS expression [PERSISTED]
142
+ // Computed column: Name AS expression [PERSISTED] [NOT NULL|NULL]
114
143
  const computedExpr = prop(node, 'computedExpression');
115
144
  if (computedExpr) {
116
145
  const isPersisted = node.props?.['isPersisted'];
146
+ // Computed PERSISTED columns may have an explicit nullability constraint
147
+ const computedNullPart = nullablePart(node.props?.['nullable'], opts);
117
148
  return [
118
149
  name,
119
150
  ' ',
@@ -121,10 +152,13 @@ export function printColumnDef(node, opts) {
121
152
  ' ',
122
153
  printNode(computedExpr, opts),
123
154
  isPersisted ? [' ', keyword('PERSISTED', opts)] : '',
155
+ computedNullPart,
124
156
  ];
125
157
  }
126
158
  const dataType = propStr(node, 'dataType') ?? 'INT';
127
159
  const params = node.props?.['dataTypeParams'];
160
+ const xmlSchemaCollection = propStr(node, 'xmlSchemaCollection');
161
+ const xmlTypeOption = propStr(node, 'xmlTypeOption');
128
162
  // Read nullable as a tristate (true/false/undefined) — propBool only returns true/false.
129
163
  const isNullable = node.props?.['nullable'];
130
164
  const isIdentity = propBool(node, 'identity');
@@ -133,9 +167,18 @@ export function printColumnDef(node, opts) {
133
167
  const defaultValue = prop(node, 'defaultValue');
134
168
  const checkConstraint = prop(node, 'checkConstraint');
135
169
  const collation = propStr(node, 'collation');
136
- const typeStr = Array.isArray(params) && params.length > 0
137
- ? [keyword(dataType, opts), `(${params.join(', ')})`]
138
- : keyword(dataType, opts);
170
+ const typeStr = (() => {
171
+ const baseType = keyword(dataType, opts);
172
+ if (Array.isArray(params) && params.length > 0) {
173
+ return [baseType, `(${params.join(', ')})`];
174
+ }
175
+ if (xmlSchemaCollection) {
176
+ // xml(CONTENT|DOCUMENT schema_collection) — CONTENT/DOCUMENT are optional keywords
177
+ const prefix = xmlTypeOption ? `${keyword(xmlTypeOption, opts)} ` : '';
178
+ return [baseType, '(', prefix, xmlSchemaCollection, ')'];
179
+ }
180
+ return baseType;
181
+ })();
139
182
  const parts = [name, ' ', typeStr];
140
183
  // COLLATE clause comes right after the data type
141
184
  if (collation)
@@ -144,6 +187,8 @@ export function printColumnDef(node, opts) {
144
187
  const seed = identitySeed ?? '1';
145
188
  const inc = identityIncrement ?? '1';
146
189
  parts.push(' ', keyword('IDENTITY', opts), `(${seed}, ${inc})`);
190
+ if (node.props?.['identityNotForReplication'])
191
+ parts.push(' ', keyword('NOT FOR REPLICATION', opts));
147
192
  }
148
193
  if (node.props?.['isRowGuidCol'])
149
194
  parts.push(' ', keyword('ROWGUIDCOL', opts));
@@ -179,8 +224,11 @@ export function printColumnDef(node, opts) {
179
224
  const maskFn = propStr(node, 'maskingFunction') ?? 'default()';
180
225
  parts.push(' ', keyword('MASKED WITH', opts), ' (', keyword('FUNCTION', opts), ` = '${maskFn}')`);
181
226
  }
182
- if (defaultValue)
183
- parts.push(' ', keyword('DEFAULT', opts), ' ', printNode(defaultValue, opts));
227
+ if (defaultValue) {
228
+ const defaultName = propStr(node, 'defaultConstraintName');
229
+ const defaultNamePrefix = defaultName ? [keyword('CONSTRAINT', opts), ' ', defaultName, ' '] : '';
230
+ parts.push(' ', defaultNamePrefix, keyword('DEFAULT', opts), ' ', printNode(defaultValue, opts));
231
+ }
184
232
  parts.push(nullablePart(isNullable, opts));
185
233
  if (checkConstraint) {
186
234
  const checkName = propStr(node, 'checkConstraintName');
@@ -201,9 +249,12 @@ export function printColumnDef(node, opts) {
201
249
  : '';
202
250
  parts.push(' ', constraintNamePrefix, uqKw, clusteredKw);
203
251
  }
204
- // Inline REFERENCES (column-level foreign key: col type REFERENCES Table(col))
252
+ // Inline REFERENCES (column-level foreign key: col type [CONSTRAINT name] REFERENCES Table(col))
205
253
  const foreignKey = node.props?.['foreignKey'];
206
254
  if (foreignKey) {
255
+ if (foreignKey.constraintName) {
256
+ parts.push(' ', keyword('CONSTRAINT', opts), ' ', foreignKey.constraintName);
257
+ }
207
258
  const refColsPart = foreignKey.refColumns?.length ? [' (', join(', ', foreignKey.refColumns), ')'] : '';
208
259
  parts.push(' ', keyword('REFERENCES', opts), ' ', schemaObjectName(foreignKey.refTable), refColsPart);
209
260
  if (foreignKey.deleteAction) {
@@ -244,11 +295,23 @@ export function printConstraintDef(node, opts) {
244
295
  return [c.name, dir];
245
296
  });
246
297
  const colsDoc = parenList(colDocs);
247
- return group([namePrefix, indent([softline, kw, ' ', clusteredKw, colsDoc])]);
298
+ const indexOptions = node.props?.['indexOptions'];
299
+ const withPart = indexOptions?.length
300
+ ? [' ', keyword('WITH', opts), ' (', join(', ', indexOptions), ')']
301
+ : '';
302
+ return group([namePrefix, indent([softline, kw, ' ', clusteredKw, colsDoc]), withPart]);
248
303
  }
249
304
  case 'CheckConstraint': {
250
305
  const expr = prop(node, 'expression');
251
- return [namePrefix, keyword('CHECK', opts), ' (', expr ? printBool(expr, opts) : '', ')'];
306
+ const nfr = propBool(node, 'notForReplication');
307
+ return [
308
+ namePrefix,
309
+ keyword('CHECK', opts),
310
+ nfr ? [' ', keyword('NOT FOR REPLICATION', opts)] : '',
311
+ ' (',
312
+ expr ? printBool(expr, opts) : '',
313
+ ')',
314
+ ];
252
315
  }
253
316
  case 'ForeignKeyConstraint': {
254
317
  const cols = Array.isArray(node.props?.['columns']) ? node.props?.['columns'] : [];
@@ -257,6 +320,7 @@ export function printConstraintDef(node, opts) {
257
320
  const refName = refTable ? schemaObjectName(refTable) : '';
258
321
  const deleteAction = propStr(node, 'deleteAction');
259
322
  const updateAction = propStr(node, 'updateAction');
323
+ const nfr = propBool(node, 'notForReplication');
260
324
  const refActionKw = (action) => keyword(action
261
325
  .replace(/([A-Z])/g, ' $1')
262
326
  .trim()
@@ -276,6 +340,7 @@ export function printConstraintDef(node, opts) {
276
340
  ]),
277
341
  deleteAction ? [line, keyword('ON DELETE', opts), ' ', refActionKw(deleteAction)] : '',
278
342
  updateAction ? [line, keyword('ON UPDATE', opts), ' ', refActionKw(updateAction)] : '',
343
+ nfr ? [line, keyword('NOT FOR REPLICATION', opts)] : '',
279
344
  ]),
280
345
  ]),
281
346
  ];
@@ -316,6 +381,11 @@ export function printAlterTable(node, opts) {
316
381
  join([',', line], elements.map((e) => e.name)),
317
382
  ]),
318
383
  ]);
384
+ // WITH (ONLINE = ON, WAIT_AT_LOW_PRIORITY ...) on DROP CLUSTERED CONSTRAINT
385
+ const allDropOptions = elements.flatMap((e) => e.dropOptions ?? []);
386
+ const withPart = allDropOptions.length
387
+ ? [' ', keyword('WITH', opts), ' (', join(', ', allDropOptions), ')']
388
+ : '';
319
389
  return [
320
390
  keyword('ALTER TABLE', opts),
321
391
  ' ',
@@ -325,6 +395,7 @@ export function printAlterTable(node, opts) {
325
395
  ifExists ? [' ', keyword('IF EXISTS', opts)] : '',
326
396
  ' ',
327
397
  nameList,
398
+ withPart,
328
399
  ';',
329
400
  ];
330
401
  }
@@ -350,7 +421,49 @@ export function printAlterTable(node, opts) {
350
421
  }
351
422
  if (alterType === 'AlterTableAlterColumnStatement') {
352
423
  const column = propStr(node, 'column') ?? '';
424
+ const alterColumnOption = propStr(node, 'alterColumnOption');
425
+ const maskingFunction = propStr(node, 'maskingFunction');
426
+ // ADD/DROP modifier variants (no data type change, just add/remove a column property)
427
+ if (alterColumnOption) {
428
+ let optDoc;
429
+ if (alterColumnOption === 'AddMaskingFunction') {
430
+ // ALTER COLUMN col ADD MASKED WITH (FUNCTION = 'fn()')
431
+ const fn = maskingFunction ?? 'default()';
432
+ optDoc = [keyword('ADD MASKED WITH', opts), ' (', keyword('FUNCTION', opts), ` = '${fn}')`];
433
+ }
434
+ else {
435
+ const optMap = {
436
+ DropMaskingFunction: 'DROP MASKED',
437
+ AddSparse: 'ADD SPARSE',
438
+ DropSparse: 'DROP SPARSE',
439
+ AddRowGuidCol: 'ADD ROWGUIDCOL',
440
+ DropRowGuidCol: 'DROP ROWGUIDCOL',
441
+ AddHidden: 'ADD HIDDEN',
442
+ DropHidden: 'DROP HIDDEN',
443
+ AddPersisted: 'ADD PERSISTED',
444
+ DropPersisted: 'DROP PERSISTED',
445
+ };
446
+ optDoc = keyword(optMap[alterColumnOption] ?? alterColumnOption.toUpperCase(), opts);
447
+ }
448
+ return [
449
+ keyword('ALTER TABLE', opts),
450
+ ' ',
451
+ name,
452
+ hardline,
453
+ keyword('ALTER COLUMN', opts),
454
+ ' ',
455
+ column,
456
+ ' ',
457
+ optDoc,
458
+ ';',
459
+ ];
460
+ }
461
+ // Normal type-change: ALTER COLUMN col newtype [COLLATE ...] [NULL|NOT NULL]
353
462
  const dataType = propStr(node, 'dataType') ?? '';
463
+ const collationAC = propStr(node, 'collation');
464
+ const collatePart = collationAC
465
+ ? [' ', keyword('COLLATE', opts), ' ', collationAC]
466
+ : '';
354
467
  const nullPart = nullablePart(node.props?.['nullable'], opts);
355
468
  return [
356
469
  keyword('ALTER TABLE', opts),
@@ -362,6 +475,7 @@ export function printAlterTable(node, opts) {
362
475
  column,
363
476
  ' ',
364
477
  keyword(dataType, opts),
478
+ collatePart,
365
479
  nullPart,
366
480
  ';',
367
481
  ];
@@ -407,9 +521,13 @@ export function printAlterTable(node, opts) {
407
521
  const sourcePartition = propStr(node, 'sourcePartition');
408
522
  const targetTable = prop(node, 'targetTable');
409
523
  const targetPartition = propStr(node, 'targetPartition');
524
+ const switchOptions = node.props?.['switchOptions'];
410
525
  const sourceDoc = sourcePartition ? [' ', keyword('PARTITION', opts), ' ', sourcePartition] : '';
411
526
  const targetDoc = targetTable ? schemaObjectName(targetTable) : '';
412
527
  const targetPartDoc = targetPartition ? [' ', keyword('PARTITION', opts), ' ', targetPartition] : '';
528
+ const switchOptDoc = switchOptions?.length
529
+ ? [' ', keyword('WITH', opts), ' (', join(', ', switchOptions), ')']
530
+ : '';
413
531
  return [
414
532
  keyword('ALTER TABLE', opts),
415
533
  ' ',
@@ -422,6 +540,7 @@ export function printAlterTable(node, opts) {
422
540
  ' ',
423
541
  targetDoc,
424
542
  targetPartDoc,
543
+ switchOptDoc,
425
544
  ';',
426
545
  ];
427
546
  }
@@ -444,7 +563,7 @@ export function printCreateIndex(node, opts) {
444
563
  const table = prop(node, 'table');
445
564
  const columns = propArr(node, 'columns');
446
565
  const includeColumns = node.props?.['includeColumns'];
447
- const filterPredicate = propStr(node, 'filterPredicate');
566
+ const filterPredicateNode = prop(node, 'filterPredicate');
448
567
  const colDocs = columns.map((c) => {
449
568
  const colName = propStr(c, 'name') ?? c.text ?? '';
450
569
  const sort = propStr(c, 'sortOrder') ?? 'Ascending';
@@ -472,11 +591,15 @@ export function printCreateIndex(node, opts) {
472
591
  const includePart = Array.isArray(includeColumns) && includeColumns.length > 0
473
592
  ? [hardline, keyword('INCLUDE', opts), ' ', parenList(includeColumns)]
474
593
  : '';
475
- const filterPart = filterPredicate ? [hardline, keyword('WHERE', opts), ' ', filterPredicate] : '';
594
+ const filterPart = filterPredicateNode
595
+ ? [hardline, printBoolClause('WHERE', filterPredicateNode, opts)]
596
+ : '';
476
597
  const indexOptions = node.props?.['indexOptions'];
477
598
  const withPart = indexOptions && indexOptions.length > 0
478
599
  ? [hardline, keyword('WITH', opts), ' (', join(', ', indexOptions), ')']
479
600
  : '';
601
+ const onFileGroup = propStr(node, 'onFileGroup');
602
+ const fileGroupPart = onFileGroup ? [hardline, keyword('ON', opts), ' ', onFileGroup] : '';
480
603
  return group([
481
604
  keyword('CREATE', opts),
482
605
  ' ',
@@ -487,6 +610,7 @@ export function printCreateIndex(node, opts) {
487
610
  indexName,
488
611
  indent([hardline, onClause, includePart, filterPart]),
489
612
  withPart,
613
+ fileGroupPart,
490
614
  ';',
491
615
  ]);
492
616
  }
@@ -520,8 +644,8 @@ export function printAlterIndex(node, opts) {
520
644
  schemaObjectName(table),
521
645
  ' ',
522
646
  typeKw,
523
- withPart,
524
647
  partitionPart,
648
+ withPart,
525
649
  ';',
526
650
  ];
527
651
  }
@@ -583,7 +707,11 @@ export function printCreateProcedure(node, opts) {
583
707
  parts.push(' ', keyword('READONLY', opts));
584
708
  return parts;
585
709
  });
586
- const bodyDocs = body.map((s) => printStatementWithComments(s, opts));
710
+ // Natively compiled procs have a single BEGIN ATOMIC WITH (...) body statement.
711
+ const atomicBlock = body.length === 1 && body[0].type === 'BeginEndAtomicBlock' ? body[0] : null;
712
+ const atomicOptions = atomicBlock?.props?.['atomicOptions'];
713
+ const innerBody = atomicBlock ? propArr(atomicBlock, 'statements') : unwrapBodyBlock(body);
714
+ const bodyDocs = innerBody.map((s) => printStatementWithComments(s, opts));
587
715
  const preBody = commentsBlock(node.preBodyComments);
588
716
  const postParam = commentsBlock(node.postParamComments);
589
717
  const procKw = node.type === 'CreateOrAlterProcedureStatement'
@@ -591,6 +719,26 @@ export function printCreateProcedure(node, opts) {
591
719
  : node.type === 'AlterProcedureStatement'
592
720
  ? keyword('ALTER PROCEDURE', opts)
593
721
  : keyword('CREATE PROCEDURE', opts);
722
+ // CLR stored procedure: AS EXTERNAL NAME assembly.[class].method
723
+ const externalName = propStr(node, 'externalName');
724
+ if (externalName) {
725
+ return group([
726
+ procKw,
727
+ ' ',
728
+ schemaObjectName(prop(node, 'name')),
729
+ preBody,
730
+ parameters.length > 0 ? indent([hardline, join([',', hardline], paramDocs)]) : '',
731
+ postParam,
732
+ printModuleOptions(node, opts),
733
+ hardline,
734
+ keyword('AS', opts),
735
+ ' ',
736
+ keyword('EXTERNAL NAME', opts),
737
+ ' ',
738
+ externalName,
739
+ ';',
740
+ ]);
741
+ }
594
742
  return group([
595
743
  procKw,
596
744
  ' ',
@@ -602,7 +750,19 @@ export function printCreateProcedure(node, opts) {
602
750
  hardline,
603
751
  keyword('AS', opts),
604
752
  hardline,
605
- keyword('BEGIN', opts),
753
+ ...(atomicOptions?.length
754
+ ? [
755
+ keyword('BEGIN', opts),
756
+ ' ',
757
+ keyword('ATOMIC', opts),
758
+ ' ',
759
+ keyword('WITH', opts),
760
+ ' (',
761
+ indent([hardline, join([',', hardline], atomicOptions)]),
762
+ hardline,
763
+ ')',
764
+ ]
765
+ : [keyword('BEGIN', opts)]),
606
766
  indent([hardline, join([hardline, hardline], bodyDocs)]),
607
767
  hardline,
608
768
  keyword('END', opts),
@@ -639,6 +799,25 @@ export function printCreateFunction(node, opts) {
639
799
  group(['(', parameters.length > 0 ? [indent([softline, join([',', line], paramDocs)]), softline] : '', ')']),
640
800
  postParam,
641
801
  ];
802
+ // CLR function: EXTERNAL NAME assembly.[class].method (no body)
803
+ const externalName = propStr(node, 'externalName');
804
+ if (externalName) {
805
+ return [
806
+ nameAndParamsNoOpts,
807
+ hardline,
808
+ keyword('RETURNS', opts),
809
+ ' ',
810
+ keyword(returnType, opts),
811
+ printModuleOptions(node, opts),
812
+ hardline,
813
+ keyword('AS', opts),
814
+ ' ',
815
+ keyword('EXTERNAL NAME', opts),
816
+ ' ',
817
+ externalName,
818
+ ';',
819
+ ];
820
+ }
642
821
  if (bodyType === 'table') {
643
822
  // Inline TVF: RETURNS TABLE [WITH options] AS RETURN (query) — no BEGIN/END
644
823
  const queryDoc = body && !Array.isArray(body) ? qexpr(body, opts) : '/* query */';
@@ -661,7 +840,7 @@ export function printCreateFunction(node, opts) {
661
840
  ];
662
841
  }
663
842
  // Scalar or multi-statement TVF — both use BEGIN...END
664
- const stmts = Array.isArray(body) ? body.map((s) => printStatementWithComments(s, opts)) : [];
843
+ const stmts = Array.isArray(body) ? unwrapBodyBlock(body).map((s) => printStatementWithComments(s, opts)) : [];
665
844
  const bodyDoc = join([hardline, hardline], stmts);
666
845
  let retTypePart;
667
846
  if (bodyType === 'inline-table') {
@@ -757,7 +936,13 @@ export function printCreateTrigger(node, opts) {
757
936
  : '';
758
937
  const notForReplication = propBool(node, 'notForReplication');
759
938
  const notForReplicationDoc = notForReplication ? [hardline, keyword('NOT FOR REPLICATION', opts)] : '';
760
- const bodyDocs = propArr(node, 'body').map((s) => printStatementWithComments(s, opts));
939
+ const bodyDocs = unwrapBodyBlock(propArr(node, 'body')).map((s) => printStatementWithComments(s, opts));
940
+ const triggerScope = propStr(node, 'triggerScope'); // 'Database' or 'Server' for DDL triggers
941
+ const onTarget = triggerScope === 'Database'
942
+ ? keyword('DATABASE', opts)
943
+ : triggerScope === 'Server'
944
+ ? keyword('ALL SERVER', opts)
945
+ : schemaObjectName(prop(node, 'onName'));
761
946
  return [
762
947
  kw,
763
948
  ' ',
@@ -765,7 +950,7 @@ export function printCreateTrigger(node, opts) {
765
950
  hardline,
766
951
  keyword('ON', opts),
767
952
  ' ',
768
- schemaObjectName(prop(node, 'onName')),
953
+ onTarget,
769
954
  hardline,
770
955
  typeKw,
771
956
  ' ',
@@ -1058,7 +1243,7 @@ export function printCreateColumnStoreIndex(node, opts) {
1058
1243
  const clustered = node.props?.['clustered'];
1059
1244
  const onName = prop(node, 'onName');
1060
1245
  const columns = node.props?.['columns'];
1061
- const filterPredicate = propStr(node, 'filterPredicate');
1246
+ const filterPredicateNode = prop(node, 'filterPredicate');
1062
1247
  const options = node.props?.['options'];
1063
1248
  const clusterKw = clustered === true
1064
1249
  ? [keyword('CLUSTERED', opts), ' ']
@@ -1070,8 +1255,8 @@ export function printCreateColumnStoreIndex(node, opts) {
1070
1255
  if (columns?.length) {
1071
1256
  parts.push([' ', parenList(columns)]);
1072
1257
  }
1073
- if (filterPredicate)
1074
- parts.push([hardline, keyword('WHERE', opts), ' ', filterPredicate]);
1258
+ if (filterPredicateNode)
1259
+ parts.push([hardline, printBoolClause('WHERE', filterPredicateNode, opts)]);
1075
1260
  if (options?.length) {
1076
1261
  parts.push([
1077
1262
  hardline,
@@ -1119,15 +1304,15 @@ export function printCreateStatistics(node, opts) {
1119
1304
  const name = propStr(node, 'name') ?? '';
1120
1305
  const onName = prop(node, 'onName');
1121
1306
  const columns = node.props?.['columns'];
1122
- const filterPredicate = propStr(node, 'filterPredicate');
1307
+ const filterPredicateNode = prop(node, 'filterPredicate');
1123
1308
  const options = node.props?.['options'];
1124
1309
  const parts = [keyword('CREATE STATISTICS', opts), ' ', name];
1125
1310
  parts.push([hardline, keyword('ON', opts), ' ', onName ? schemaObjectName(onName) : '']);
1126
1311
  if (columns?.length) {
1127
1312
  parts.push([' ', parenList(columns)]);
1128
1313
  }
1129
- if (filterPredicate)
1130
- parts.push([hardline, keyword('WHERE', opts), ' ', filterPredicate]);
1314
+ if (filterPredicateNode)
1315
+ parts.push([hardline, printBoolClause('WHERE', filterPredicateNode, opts)]);
1131
1316
  if (options?.length)
1132
1317
  parts.push([hardline, withOptionsClause(options, opts)]);
1133
1318
  parts.push(';');