prettier-plugin-tsql 0.5.0 → 0.6.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 (55) hide show
  1. package/README.md +2 -2
  2. package/bin/dotnet/PrettierSql.Core.dll +0 -0
  3. package/bin/dotnet/SqlScriptDom.deps.json +15 -1
  4. package/bin/dotnet/SqlScriptDom.dll +0 -0
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/parser/index.d.ts +1 -1
  10. package/dist/parser/index.d.ts.map +1 -1
  11. package/dist/parser/index.js +2 -24
  12. package/dist/parser/index.js.map +1 -1
  13. package/dist/printer/admin.d.ts +2 -2
  14. package/dist/printer/admin.d.ts.map +1 -1
  15. package/dist/printer/admin.js +5 -13
  16. package/dist/printer/admin.js.map +1 -1
  17. package/dist/printer/ddl.d.ts +2 -2
  18. package/dist/printer/ddl.d.ts.map +1 -1
  19. package/dist/printer/ddl.js +222 -45
  20. package/dist/printer/ddl.js.map +1 -1
  21. package/dist/printer/expressions.d.ts +2 -2
  22. package/dist/printer/expressions.d.ts.map +1 -1
  23. package/dist/printer/expressions.js +90 -13
  24. package/dist/printer/expressions.js.map +1 -1
  25. package/dist/printer/helpers.d.ts +3 -22
  26. package/dist/printer/helpers.d.ts.map +1 -1
  27. package/dist/printer/helpers.js +2 -33
  28. package/dist/printer/helpers.js.map +1 -1
  29. package/dist/printer/index.d.ts +1 -1
  30. package/dist/printer/index.d.ts.map +1 -1
  31. package/dist/printer/procedural.d.ts +3 -3
  32. package/dist/printer/procedural.d.ts.map +1 -1
  33. package/dist/printer/procedural.js +49 -12
  34. package/dist/printer/procedural.js.map +1 -1
  35. package/dist/printer/security.d.ts +2 -2
  36. package/dist/printer/security.d.ts.map +1 -1
  37. package/dist/printer/security.js +18 -16
  38. package/dist/printer/security.js.map +1 -1
  39. package/dist/printer/statements.d.ts +2 -2
  40. package/dist/printer/statements.d.ts.map +1 -1
  41. package/dist/printer/statements.js +20 -12
  42. package/dist/printer/statements.js.map +1 -1
  43. package/package.json +10 -15
  44. package/dist/options.d.ts +0 -3
  45. package/dist/options.d.ts.map +0 -1
  46. package/dist/options.js +0 -35
  47. package/dist/options.js.map +0 -1
  48. package/dist/parser/types.d.ts +0 -21
  49. package/dist/parser/types.d.ts.map +0 -1
  50. package/dist/parser/types.js +0 -2
  51. package/dist/parser/types.js.map +0 -1
  52. package/dist/printer/utils.d.ts +0 -48
  53. package/dist/printer/utils.d.ts.map +0 -1
  54. package/dist/printer/utils.js +0 -83
  55. package/dist/printer/utils.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { keyword, hardline, join, indent, group, softline, line, ifExistsDoc, commentsBlock, parenList } from './utils.js';
1
+ import { keyword, hardline, join, indent, group, softline, line, ifExistsDoc, commentsBlock, parenList, } from '@prettier-sql/core/printer/utils';
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)
@@ -33,22 +33,67 @@ function withOptionsClause(options, opts) {
33
33
  // ---------------------------------------------------------------------------
34
34
  // CREATE TABLE
35
35
  // ---------------------------------------------------------------------------
36
+ /** Inline INDEX definition within CREATE TABLE body. */
37
+ function printInlineIndex(node, opts) {
38
+ const indexName = propStr(node, 'indexName') ?? '';
39
+ const isUnique = node.props?.['unique'];
40
+ const kind = propStr(node, 'kind'); // 'clustered', 'nonclustered', etc.
41
+ const columns = propArr(node, 'columns');
42
+ const includeColumns = node.props?.['includeColumns'];
43
+ const filterPredicate = propStr(node, 'filterPredicate');
44
+ const indexOptions = node.props?.['indexOptions'];
45
+ const uniqueKw = isUnique ? [keyword('UNIQUE', opts), ' '] : '';
46
+ const kindKw = kind ? [keyword(kind.toUpperCase(), opts), ' '] : '';
47
+ const colDocs = columns.map((c) => {
48
+ const colName = propStr(c, 'name') ?? '';
49
+ const sort = propStr(c, 'sortOrder') ?? 'Ascending';
50
+ return sort === 'Descending' ? [colName, ' ', keyword('DESC', opts)] : [colName, ' ', keyword('ASC', opts)];
51
+ });
52
+ const includePart = includeColumns?.length
53
+ ? [' ', keyword('INCLUDE', opts), ' ', parenList(includeColumns)]
54
+ : '';
55
+ const filterPart = filterPredicate ? [' ', keyword('WHERE', opts), ' ', filterPredicate] : '';
56
+ const withPart = indexOptions?.length ? [' ', keyword('WITH', opts), ' (', join(', ', indexOptions), ')'] : '';
57
+ return [
58
+ keyword('INDEX', opts),
59
+ ' ',
60
+ indexName,
61
+ ' ',
62
+ uniqueKw,
63
+ kindKw,
64
+ parenList(colDocs),
65
+ includePart,
66
+ filterPart,
67
+ withPart,
68
+ ];
69
+ }
36
70
  export function printCreateTable(node, opts) {
37
71
  const columns = propArr(node, 'columns');
38
72
  const constraints = propArr(node, 'constraints');
39
73
  const options = node.props?.['options'];
74
+ const systemTimePeriod = node.props?.['systemTimePeriod'];
75
+ const indexes = propArr(node, 'indexes');
40
76
  const allDefs = [
41
77
  ...columns.map((col) => printColumnDef(col, opts)),
42
78
  ...constraints.map((c) => printConstraintDef(c, opts)),
79
+ ...indexes.map((idx) => printInlineIndex(idx, opts)),
43
80
  ];
44
- const withPart = options && options.length > 0
45
- ? [
46
- hardline,
47
- keyword('WITH', opts),
48
- ' ',
49
- parenList(options),
50
- ]
51
- : '';
81
+ // PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo) always last in the table body
82
+ if (systemTimePeriod) {
83
+ allDefs.push([
84
+ keyword('PERIOD FOR SYSTEM_TIME', opts),
85
+ ' (',
86
+ systemTimePeriod.startColumn,
87
+ ', ',
88
+ systemTimePeriod.endColumn,
89
+ ')',
90
+ ]);
91
+ }
92
+ const withPart = options && options.length > 0 ? [hardline, keyword('WITH', opts), ' ', parenList(options)] : '';
93
+ const onFileGroup = propStr(node, 'onFileGroup');
94
+ const textimageOn = propStr(node, 'textimageOn');
95
+ const onPart = onFileGroup ? [hardline, keyword('ON', opts), ' ', onFileGroup] : '';
96
+ const textimagePart = textimageOn ? [hardline, keyword('TEXTIMAGE_ON', opts), ' ', textimageOn] : '';
52
97
  return group([
53
98
  keyword('CREATE TABLE', opts),
54
99
  ' ',
@@ -57,6 +102,8 @@ export function printCreateTable(node, opts) {
57
102
  indent([hardline, join([',', hardline], allDefs)]),
58
103
  hardline,
59
104
  ')',
105
+ onPart,
106
+ textimagePart,
60
107
  withPart,
61
108
  ';',
62
109
  ]);
@@ -84,18 +131,94 @@ export function printColumnDef(node, opts) {
84
131
  const identitySeed = propStr(node, 'identitySeed');
85
132
  const identityIncrement = propStr(node, 'identityIncrement');
86
133
  const defaultValue = prop(node, 'defaultValue');
134
+ const checkConstraint = prop(node, 'checkConstraint');
135
+ const collation = propStr(node, 'collation');
87
136
  const typeStr = Array.isArray(params) && params.length > 0
88
137
  ? [keyword(dataType, opts), `(${params.join(', ')})`]
89
138
  : keyword(dataType, opts);
90
139
  const parts = [name, ' ', typeStr];
140
+ // COLLATE clause comes right after the data type
141
+ if (collation)
142
+ parts.push(' ', keyword('COLLATE', opts), ' ', collation);
91
143
  if (isIdentity) {
92
144
  const seed = identitySeed ?? '1';
93
145
  const inc = identityIncrement ?? '1';
94
146
  parts.push(' ', keyword('IDENTITY', opts), `(${seed}, ${inc})`);
95
147
  }
148
+ if (node.props?.['isRowGuidCol'])
149
+ parts.push(' ', keyword('ROWGUIDCOL', opts));
150
+ // SPARSE / FILESTREAM / COLUMN_SET
151
+ if (node.props?.['isSparse'])
152
+ parts.push(' ', keyword('SPARSE', opts));
153
+ if (node.props?.['isFileStream'])
154
+ parts.push(' ', keyword('FILESTREAM', opts));
155
+ if (node.props?.['isColumnSet'])
156
+ parts.push(' ', keyword('COLUMN_SET FOR ALL_SPARSE_COLUMNS', opts));
157
+ // Temporal table: GENERATED ALWAYS AS ROW START / ROW END [HIDDEN]
158
+ const generatedAlways = propStr(node, 'generatedAlways');
159
+ if (generatedAlways) {
160
+ const gaMap = {
161
+ RowStart: 'ROW START',
162
+ RowEnd: 'ROW END',
163
+ UserIdStart: 'USER ID START',
164
+ UserIdEnd: 'USER ID END',
165
+ UserNameStart: 'USER NAME START',
166
+ UserNameEnd: 'USER NAME END',
167
+ TransactionIdStart: 'TRANSACTION ID START',
168
+ TransactionIdEnd: 'TRANSACTION ID END',
169
+ SequenceNumberStart: 'SEQUENCE NUMBER START',
170
+ SequenceNumberEnd: 'SEQUENCE NUMBER END',
171
+ };
172
+ const gaKw = gaMap[generatedAlways] ?? generatedAlways.toUpperCase();
173
+ parts.push(' ', keyword('GENERATED ALWAYS AS', opts), ' ', keyword(gaKw, opts));
174
+ }
175
+ if (node.props?.['isHidden'])
176
+ parts.push(' ', keyword('HIDDEN', opts));
177
+ // Dynamic data masking
178
+ if (node.props?.['isMasked']) {
179
+ const maskFn = propStr(node, 'maskingFunction') ?? 'default()';
180
+ parts.push(' ', keyword('MASKED WITH', opts), ' (', keyword('FUNCTION', opts), ` = '${maskFn}')`);
181
+ }
96
182
  if (defaultValue)
97
183
  parts.push(' ', keyword('DEFAULT', opts), ' ', printNode(defaultValue, opts));
98
184
  parts.push(nullablePart(isNullable, opts));
185
+ if (checkConstraint) {
186
+ const checkName = propStr(node, 'checkConstraintName');
187
+ const checkPrefix = checkName ? [keyword('CONSTRAINT', opts), ' ', checkName, ' '] : '';
188
+ parts.push(' ', checkPrefix, keyword('CHECK', opts), ' (', printBool(checkConstraint, opts), ')');
189
+ }
190
+ // Inline PRIMARY KEY / UNIQUE constraint (e.g. in table variable declarations)
191
+ const uniqueConstraint = node.props?.['uniqueConstraint'];
192
+ if (uniqueConstraint) {
193
+ const constraintNamePrefix = uniqueConstraint.constraintName
194
+ ? [keyword('CONSTRAINT', opts), ' ', uniqueConstraint.constraintName, ' ']
195
+ : '';
196
+ const uqKw = uniqueConstraint.isPrimaryKey ? keyword('PRIMARY KEY', opts) : keyword('UNIQUE', opts);
197
+ const clusteredKw = uniqueConstraint.clustered === true
198
+ ? [' ', keyword('CLUSTERED', opts)]
199
+ : uniqueConstraint.clustered === false
200
+ ? [' ', keyword('NONCLUSTERED', opts)]
201
+ : '';
202
+ parts.push(' ', constraintNamePrefix, uqKw, clusteredKw);
203
+ }
204
+ // Inline REFERENCES (column-level foreign key: col type REFERENCES Table(col))
205
+ const foreignKey = node.props?.['foreignKey'];
206
+ if (foreignKey) {
207
+ const refColsPart = foreignKey.refColumns?.length ? [' (', join(', ', foreignKey.refColumns), ')'] : '';
208
+ parts.push(' ', keyword('REFERENCES', opts), ' ', schemaObjectName(foreignKey.refTable), refColsPart);
209
+ if (foreignKey.deleteAction) {
210
+ parts.push(' ', keyword('ON DELETE', opts), ' ', keyword(foreignKey.deleteAction
211
+ .replace(/([A-Z])/g, ' $1')
212
+ .trim()
213
+ .toUpperCase(), opts));
214
+ }
215
+ if (foreignKey.updateAction) {
216
+ parts.push(' ', keyword('ON UPDATE', opts), ' ', keyword(foreignKey.updateAction
217
+ .replace(/([A-Z])/g, ' $1')
218
+ .trim()
219
+ .toUpperCase(), opts));
220
+ }
221
+ }
99
222
  return parts;
100
223
  }
101
224
  export function printConstraintDef(node, opts) {
@@ -104,10 +227,24 @@ export function printConstraintDef(node, opts) {
104
227
  switch (node.type) {
105
228
  case 'UniqueConstraint': {
106
229
  const isPK = propBool(node, 'isPrimaryKey');
107
- const cols = Array.isArray(node.props?.['columns']) ? node.props?.['columns'] : [];
230
+ const clustered = node.props?.['clustered'];
231
+ // Only emit CLUSTERED/NONCLUSTERED when explicitly specified in DDL
232
+ const clusteredKw = clustered === true
233
+ ? [keyword('CLUSTERED', opts), ' ']
234
+ : clustered === false
235
+ ? [keyword('NONCLUSTERED', opts), ' ']
236
+ : '';
108
237
  const kw = isPK ? keyword('PRIMARY KEY', opts) : keyword('UNIQUE', opts);
109
- const colsDoc = parenList(cols);
110
- return group([namePrefix, indent([softline, kw, ' ', colsDoc])]);
238
+ // Columns are now {name, order} objects; fall back to plain strings for compat
239
+ const rawCols = Array.isArray(node.props?.['columns']) ? node.props['columns'] : [];
240
+ const colDocs = rawCols.map((c) => {
241
+ if (typeof c === 'string')
242
+ return c;
243
+ const dir = c.order === 'Descending' ? [' ', keyword('DESC', opts)] : '';
244
+ return [c.name, dir];
245
+ });
246
+ const colsDoc = parenList(colDocs);
247
+ return group([namePrefix, indent([softline, kw, ' ', clusteredKw, colsDoc])]);
111
248
  }
112
249
  case 'CheckConstraint': {
113
250
  const expr = prop(node, 'expression');
@@ -230,13 +367,10 @@ export function printAlterTable(node, opts) {
230
367
  ];
231
368
  }
232
369
  if (alterType === 'AlterTableSetStatement') {
233
- // Convert camelCase enum names to SQL_KEYWORD style: LockEscalation → LOCK_ESCALATION
234
- const toSqlKw = (s) => s
235
- .replace(/([A-Z])/g, '_$1')
236
- .replace(/^_/, '')
237
- .toUpperCase();
370
+ // Options come pre-serialized from SerializeTableOption (e.g. "lock_escalation = table",
371
+ // "system_versioning = on (history_table = dbo.Tbl)"). Render them verbatim — applying
372
+ // keyword() casing would uppercase embedded schema/table names.
238
373
  const options = (node.props?.['options'] ?? []);
239
- const optDocs = options.map((o) => [keyword(toSqlKw(o.kind), opts), ' = ', keyword(toSqlKw(o.value), opts)]);
240
374
  return [
241
375
  keyword('ALTER TABLE', opts),
242
376
  ' ',
@@ -244,7 +378,7 @@ export function printAlterTable(node, opts) {
244
378
  hardline,
245
379
  keyword('SET', opts),
246
380
  ' (',
247
- join(', ', optDocs),
381
+ join(', ', options),
248
382
  ')',
249
383
  ';',
250
384
  ];
@@ -291,6 +425,14 @@ export function printAlterTable(node, opts) {
291
425
  ';',
292
426
  ];
293
427
  }
428
+ if (alterType === 'AlterTableTriggerModificationStatement') {
429
+ const enable = propBool(node, 'enable');
430
+ const triggerAll = node.props?.['triggerAll'];
431
+ const triggerNames = node.props?.['triggerNames'];
432
+ const verb = enable ? keyword('ENABLE TRIGGER', opts) : keyword('DISABLE TRIGGER', opts);
433
+ const targets = triggerAll ? keyword('ALL', opts) : join(', ', triggerNames ?? []);
434
+ return [keyword('ALTER TABLE', opts), ' ', name, hardline, verb, ' ', targets, ';'];
435
+ }
294
436
  return [keyword('ALTER TABLE', opts), ' ', name, ' /* ', alterType, ' */;'];
295
437
  }
296
438
  // ---------------------------------------------------------------------------
@@ -299,10 +441,10 @@ export function printAlterTable(node, opts) {
299
441
  export function printCreateIndex(node, opts) {
300
442
  const indexName = propStr(node, 'indexName') ?? 'idx';
301
443
  const isUnique = propBool(node, 'unique');
302
- const isClustered = propBool(node, 'clustered');
303
444
  const table = prop(node, 'table');
304
445
  const columns = propArr(node, 'columns');
305
446
  const includeColumns = node.props?.['includeColumns'];
447
+ const filterPredicate = propStr(node, 'filterPredicate');
306
448
  const colDocs = columns.map((c) => {
307
449
  const colName = propStr(c, 'name') ?? c.text ?? '';
308
450
  const sort = propStr(c, 'sortOrder') ?? 'Ascending';
@@ -310,8 +452,14 @@ export function printCreateIndex(node, opts) {
310
452
  ? [colName, ' ', keyword('DESC', opts)]
311
453
  : [colName, ' ', keyword('ASC', opts)];
312
454
  });
313
- const uniqueKw = isUnique ? keyword('UNIQUE ', opts) : '';
314
- const clusteredKw = isClustered ? keyword('CLUSTERED ', opts) : keyword('NONCLUSTERED ', opts);
455
+ const uniqueKw = isUnique ? [keyword('UNIQUE', opts), ' '] : '';
456
+ // Preserve CLUSTERED / NONCLUSTERED exactly as written; omit when not specified.
457
+ const clusteredProp = node.props?.['clustered'];
458
+ const clusteredKw = clusteredProp === true
459
+ ? [keyword('CLUSTERED', opts), ' ']
460
+ : clusteredProp === false
461
+ ? [keyword('NONCLUSTERED', opts), ' ']
462
+ : '';
315
463
  const onClause = [
316
464
  keyword('ON', opts),
317
465
  ' ',
@@ -322,21 +470,23 @@ export function printCreateIndex(node, opts) {
322
470
  ')',
323
471
  ];
324
472
  const includePart = Array.isArray(includeColumns) && includeColumns.length > 0
325
- ? [
326
- hardline,
327
- keyword('INCLUDE', opts),
328
- ' ',
329
- parenList(includeColumns),
330
- ]
473
+ ? [hardline, keyword('INCLUDE', opts), ' ', parenList(includeColumns)]
474
+ : '';
475
+ const filterPart = filterPredicate ? [hardline, keyword('WHERE', opts), ' ', filterPredicate] : '';
476
+ const indexOptions = node.props?.['indexOptions'];
477
+ const withPart = indexOptions && indexOptions.length > 0
478
+ ? [hardline, keyword('WITH', opts), ' (', join(', ', indexOptions), ')']
331
479
  : '';
332
480
  return group([
333
- keyword('CREATE ', opts),
481
+ keyword('CREATE', opts),
482
+ ' ',
334
483
  uniqueKw,
335
484
  clusteredKw,
336
485
  keyword('INDEX', opts),
337
486
  ' ',
338
487
  indexName,
339
- indent([hardline, onClause, includePart]),
488
+ indent([hardline, onClause, includePart, filterPart]),
489
+ withPart,
340
490
  ';',
341
491
  ]);
342
492
  }
@@ -354,6 +504,12 @@ export function printAlterIndex(node, opts) {
354
504
  Set: 'SET',
355
505
  };
356
506
  const typeKw = keyword(typeKwMap[alterType] ?? alterType.toUpperCase(), opts);
507
+ const indexOptions = node.props?.['indexOptions'];
508
+ const partition = propStr(node, 'partition');
509
+ const withPart = indexOptions && indexOptions.length > 0
510
+ ? [' ', keyword('WITH', opts), ' (', join(', ', indexOptions), ')']
511
+ : '';
512
+ const partitionPart = partition ? [' ', keyword('PARTITION', opts), ' = ', partition] : '';
357
513
  return [
358
514
  keyword('ALTER INDEX', opts),
359
515
  ' ',
@@ -364,6 +520,8 @@ export function printAlterIndex(node, opts) {
364
520
  schemaObjectName(table),
365
521
  ' ',
366
522
  typeKw,
523
+ withPart,
524
+ partitionPart,
367
525
  ';',
368
526
  ];
369
527
  }
@@ -410,10 +568,13 @@ export function printCreateProcedure(node, opts) {
410
568
  const paramDocs = parameters.map((p) => {
411
569
  const pName = propStr(p, 'name') ?? '@p';
412
570
  const dt = propStr(p, 'dataType') ?? 'INT';
571
+ const isUdt = propBool(p, 'isUdt');
413
572
  const isOutput = propBool(p, 'output');
414
573
  const isReadonly = propBool(p, 'readonly');
415
574
  const defaultVal = prop(p, 'defaultValue');
416
- const parts = [pName, ' ', keyword(dt, opts)];
575
+ // UDT names are identifiers, not SQL keywords — skip keyword-casing
576
+ const dtDoc = isUdt ? dt : keyword(dt, opts);
577
+ const parts = [pName, ' ', dtDoc];
417
578
  if (defaultVal)
418
579
  parts.push(' = ', printNode(defaultVal, opts));
419
580
  if (isOutput)
@@ -459,7 +620,9 @@ export function printCreateFunction(node, opts) {
459
620
  const paramDocs = parameters.map((p) => {
460
621
  const pName = propStr(p, 'name') ?? '@p';
461
622
  const dt = propStr(p, 'dataType') ?? 'INT';
462
- return [pName, ' ', keyword(dt, opts)];
623
+ const isUdt = propBool(p, 'isUdt');
624
+ // UDT names are identifiers — skip keyword-casing
625
+ return [pName, ' ', isUdt ? dt : keyword(dt, opts)];
463
626
  });
464
627
  const preBody = commentsBlock(node.preBodyComments);
465
628
  const postParam = commentsBlock(node.postParamComments);
@@ -468,25 +631,24 @@ export function printCreateFunction(node, opts) {
468
631
  : node.type === 'AlterFunctionStatement'
469
632
  ? keyword('ALTER FUNCTION', opts)
470
633
  : keyword('CREATE FUNCTION', opts);
471
- const nameAndParams = [
634
+ const nameAndParamsNoOpts = [
472
635
  fnKw,
473
636
  ' ',
474
637
  schemaObjectName(prop(node, 'name')),
475
638
  preBody,
476
639
  group(['(', parameters.length > 0 ? [indent([softline, join([',', line], paramDocs)]), softline] : '', ')']),
477
640
  postParam,
478
- printModuleOptions(node, opts),
479
641
  ];
480
642
  if (bodyType === 'table') {
481
- // Inline TVF: RETURNS TABLE AS RETURN (query) — no BEGIN/END
482
- // returnType raw text contains the SELECT, not the word TABLE — hardcode TABLE
643
+ // Inline TVF: RETURNS TABLE [WITH options] AS RETURN (query) — no BEGIN/END
483
644
  const queryDoc = body && !Array.isArray(body) ? qexpr(body, opts) : '/* query */';
484
645
  return [
485
- nameAndParams,
486
- ' ',
646
+ nameAndParamsNoOpts,
647
+ hardline,
487
648
  keyword('RETURNS', opts),
488
649
  ' ',
489
650
  keyword('TABLE', opts),
651
+ printModuleOptions(node, opts),
490
652
  hardline,
491
653
  keyword('AS', opts),
492
654
  hardline,
@@ -519,12 +681,15 @@ export function printCreateFunction(node, opts) {
519
681
  else {
520
682
  retTypePart = keyword(returnType, opts);
521
683
  }
684
+ // WITH options come AFTER RETURNS (per T-SQL syntax):
685
+ // CREATE FUNCTION ... (params) RETURNS type WITH options AS BEGIN ... END
522
686
  return [
523
- nameAndParams,
524
- ' ',
687
+ nameAndParamsNoOpts,
688
+ hardline,
525
689
  keyword('RETURNS', opts),
526
690
  ' ',
527
691
  retTypePart,
692
+ printModuleOptions(node, opts),
528
693
  hardline,
529
694
  keyword('AS', opts),
530
695
  hardline,
@@ -547,9 +712,7 @@ export function printCreateView(node, opts) {
547
712
  : node.type === 'AlterViewStatement'
548
713
  ? keyword('ALTER VIEW', opts)
549
714
  : keyword('CREATE VIEW', opts);
550
- const colsPart = columns?.length
551
- ? [' ', parenList(columns)]
552
- : '';
715
+ const colsPart = columns?.length ? [' ', parenList(columns)] : '';
553
716
  const withPart = withOptions?.length
554
717
  ? [
555
718
  hardline,
@@ -559,6 +722,8 @@ export function printCreateView(node, opts) {
559
722
  ]
560
723
  : '';
561
724
  const preBodyPart = commentsBlock(node.preBodyComments);
725
+ const withCheckOption = node.props?.['withCheckOption'];
726
+ const checkOptionPart = withCheckOption ? [hardline, keyword('WITH CHECK OPTION', opts)] : '';
562
727
  return group([
563
728
  kw,
564
729
  ' ',
@@ -570,6 +735,7 @@ export function printCreateView(node, opts) {
570
735
  keyword('AS', opts),
571
736
  hardline,
572
737
  body ? qexpr(body, opts) : '',
738
+ checkOptionPart,
573
739
  ';',
574
740
  ]);
575
741
  }
@@ -589,6 +755,8 @@ export function printCreateTrigger(node, opts) {
589
755
  const actionList = Array.isArray(actions)
590
756
  ? join(', ', actions.map((a) => keyword(a.toUpperCase(), opts)))
591
757
  : '';
758
+ const notForReplication = propBool(node, 'notForReplication');
759
+ const notForReplicationDoc = notForReplication ? [hardline, keyword('NOT FOR REPLICATION', opts)] : '';
592
760
  const bodyDocs = propArr(node, 'body').map((s) => printStatementWithComments(s, opts));
593
761
  return [
594
762
  kw,
@@ -602,6 +770,7 @@ export function printCreateTrigger(node, opts) {
602
770
  typeKw,
603
771
  ' ',
604
772
  actionList,
773
+ notForReplicationDoc,
605
774
  hardline,
606
775
  keyword('AS', opts),
607
776
  hardline,
@@ -749,9 +918,14 @@ export function printDropObjects(objType, node, opts) {
749
918
  ];
750
919
  }
751
920
  export function printDropIndex(node, opts) {
921
+ const ifExists = propBool(node, 'ifExists');
752
922
  const indices = propArr(node, 'indices');
753
923
  const indexDocs = indices.map((idx) => [propStr(idx, 'name') ?? '', ' ', keyword('ON', opts), ' ', schemaObjectName(prop(idx, 'table'))]);
754
- return group([keyword('DROP INDEX', opts), ' ', join([',', hardline], indexDocs), ';']);
924
+ const ifExistsPart = ifExists ? [' ', keyword('IF EXISTS', opts)] : '';
925
+ if (indexDocs.length === 1) {
926
+ return [keyword('DROP INDEX', opts), ifExistsPart, ' ', indexDocs[0], ';'];
927
+ }
928
+ return [keyword('DROP INDEX', opts), ifExistsPart, indent([hardline, join([',', hardline], indexDocs)]), ';'];
755
929
  }
756
930
  // ---------------------------------------------------------------------------
757
931
  // CREATE / DROP SYNONYM
@@ -964,7 +1138,10 @@ export function printUpdateStatistics(node, opts) {
964
1138
  const subElements = node.props?.['subElements'];
965
1139
  const options = node.props?.['options'];
966
1140
  const parts = [keyword('UPDATE STATISTICS', opts), ' ', table ? schemaObjectName(table) : ''];
967
- if (subElements?.length)
1141
+ // Single stat name: no parens needed. Multiple: wrap in parens.
1142
+ if (subElements?.length === 1)
1143
+ parts.push([' ', subElements[0]]);
1144
+ else if (subElements?.length)
968
1145
  parts.push([' ', parenList(subElements)]);
969
1146
  if (options?.length)
970
1147
  parts.push([hardline, withOptionsClause(options, opts)]);