prettier-plugin-tsql 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dotnet/PrettierSql.Core.dll +0 -0
- package/bin/dotnet/SqlScriptDom.dll +0 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +2 -24
- package/dist/parser/index.js.map +1 -1
- package/dist/printer/admin.d.ts +1 -0
- package/dist/printer/admin.d.ts.map +1 -1
- package/dist/printer/admin.js +26 -4
- package/dist/printer/admin.js.map +1 -1
- package/dist/printer/ddl.d.ts.map +1 -1
- package/dist/printer/ddl.js +417 -57
- package/dist/printer/ddl.js.map +1 -1
- package/dist/printer/expressions.d.ts.map +1 -1
- package/dist/printer/expressions.js +109 -16
- package/dist/printer/expressions.js.map +1 -1
- package/dist/printer/helpers.d.ts.map +1 -1
- package/dist/printer/helpers.js +12 -7
- package/dist/printer/helpers.js.map +1 -1
- package/dist/printer/index.js +1 -1
- package/dist/printer/index.js.map +1 -1
- package/dist/printer/procedural.d.ts +1 -1
- package/dist/printer/procedural.d.ts.map +1 -1
- package/dist/printer/procedural.js +94 -14
- package/dist/printer/procedural.js.map +1 -1
- package/dist/printer/security.d.ts.map +1 -1
- package/dist/printer/security.js +7 -3
- package/dist/printer/security.js.map +1 -1
- package/dist/printer/statements.d.ts.map +1 -1
- package/dist/printer/statements.js +146 -33
- package/dist/printer/statements.js.map +1 -1
- package/package.json +1 -1
package/dist/printer/ddl.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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 '@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)
|
|
@@ -6,6 +6,18 @@ import { printStatementWithComments, printNode, printBool, qexpr } from './state
|
|
|
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)
|
|
@@ -33,22 +45,77 @@ function withOptionsClause(options, opts) {
|
|
|
33
45
|
// ---------------------------------------------------------------------------
|
|
34
46
|
// CREATE TABLE
|
|
35
47
|
// ---------------------------------------------------------------------------
|
|
48
|
+
/** Inline INDEX definition within CREATE TABLE body. */
|
|
49
|
+
function printInlineIndex(node, opts) {
|
|
50
|
+
const indexName = propStr(node, 'indexName') ?? '';
|
|
51
|
+
const isUnique = node.props?.['unique'];
|
|
52
|
+
const kind = propStr(node, 'kind'); // 'clustered', 'nonclustered', etc.
|
|
53
|
+
const columns = propArr(node, 'columns');
|
|
54
|
+
const includeColumns = node.props?.['includeColumns'];
|
|
55
|
+
const filterPredicate = propStr(node, 'filterPredicate');
|
|
56
|
+
const indexOptions = node.props?.['indexOptions'];
|
|
57
|
+
const uniqueKw = isUnique ? [keyword('UNIQUE', opts), ' '] : '';
|
|
58
|
+
const kindKw = kind ? [keyword(kind.toUpperCase(), opts), ' '] : '';
|
|
59
|
+
const colDocs = columns.map((c) => {
|
|
60
|
+
const colName = propStr(c, 'name') ?? '';
|
|
61
|
+
const sort = propStr(c, 'sortOrder') ?? 'Ascending';
|
|
62
|
+
return sort === 'Descending' ? [colName, ' ', keyword('DESC', opts)] : [colName, ' ', keyword('ASC', opts)];
|
|
63
|
+
});
|
|
64
|
+
const includePart = includeColumns?.length
|
|
65
|
+
? [' ', keyword('INCLUDE', opts), ' ', parenList(includeColumns)]
|
|
66
|
+
: '';
|
|
67
|
+
const filterPart = filterPredicate ? [' ', keyword('WHERE', opts), ' ', filterPredicate] : '';
|
|
68
|
+
const withPart = indexOptions?.length ? [' ', keyword('WITH', opts), ' (', join(', ', indexOptions), ')'] : '';
|
|
69
|
+
return [
|
|
70
|
+
keyword('INDEX', opts),
|
|
71
|
+
' ',
|
|
72
|
+
indexName,
|
|
73
|
+
' ',
|
|
74
|
+
uniqueKw,
|
|
75
|
+
kindKw,
|
|
76
|
+
parenList(colDocs),
|
|
77
|
+
includePart,
|
|
78
|
+
filterPart,
|
|
79
|
+
withPart,
|
|
80
|
+
];
|
|
81
|
+
}
|
|
36
82
|
export function printCreateTable(node, opts) {
|
|
37
83
|
const columns = propArr(node, 'columns');
|
|
38
84
|
const constraints = propArr(node, 'constraints');
|
|
39
85
|
const options = node.props?.['options'];
|
|
86
|
+
const systemTimePeriod = node.props?.['systemTimePeriod'];
|
|
87
|
+
const indexes = propArr(node, 'indexes');
|
|
40
88
|
const allDefs = [
|
|
41
89
|
...columns.map((col) => printColumnDef(col, opts)),
|
|
42
90
|
...constraints.map((c) => printConstraintDef(c, opts)),
|
|
91
|
+
...indexes.map((idx) => printInlineIndex(idx, opts)),
|
|
43
92
|
];
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
keyword('
|
|
48
|
-
' ',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
93
|
+
// PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo) — always last in the table body
|
|
94
|
+
if (systemTimePeriod) {
|
|
95
|
+
allDefs.push([
|
|
96
|
+
keyword('PERIOD FOR SYSTEM_TIME', opts),
|
|
97
|
+
' (',
|
|
98
|
+
systemTimePeriod.startColumn,
|
|
99
|
+
', ',
|
|
100
|
+
systemTimePeriod.endColumn,
|
|
101
|
+
')',
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
const withPart = options && options.length > 0 ? [hardline, keyword('WITH', opts), ' ', parenList(options)] : '';
|
|
105
|
+
const onFileGroup = propStr(node, 'onFileGroup');
|
|
106
|
+
const textimageOn = propStr(node, 'textimageOn');
|
|
107
|
+
const fileStreamOn = propStr(node, 'fileStreamOn');
|
|
108
|
+
const onPart = onFileGroup ? [hardline, keyword('ON', opts), ' ', onFileGroup] : '';
|
|
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
|
+
: '';
|
|
52
119
|
return group([
|
|
53
120
|
keyword('CREATE TABLE', opts),
|
|
54
121
|
' ',
|
|
@@ -57,16 +124,27 @@ export function printCreateTable(node, opts) {
|
|
|
57
124
|
indent([hardline, join([',', hardline], allDefs)]),
|
|
58
125
|
hardline,
|
|
59
126
|
')',
|
|
127
|
+
graphPart,
|
|
128
|
+
onPart,
|
|
129
|
+
fileStreamPart,
|
|
130
|
+
textimagePart,
|
|
60
131
|
withPart,
|
|
61
132
|
';',
|
|
62
133
|
]);
|
|
63
134
|
}
|
|
64
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 */';
|
|
65
141
|
const name = propStr(node, 'name') ?? 'col';
|
|
66
|
-
// Computed column: Name AS expression [PERSISTED]
|
|
142
|
+
// Computed column: Name AS expression [PERSISTED] [NOT NULL|NULL]
|
|
67
143
|
const computedExpr = prop(node, 'computedExpression');
|
|
68
144
|
if (computedExpr) {
|
|
69
145
|
const isPersisted = node.props?.['isPersisted'];
|
|
146
|
+
// Computed PERSISTED columns may have an explicit nullability constraint
|
|
147
|
+
const computedNullPart = nullablePart(node.props?.['nullable'], opts);
|
|
70
148
|
return [
|
|
71
149
|
name,
|
|
72
150
|
' ',
|
|
@@ -74,28 +152,124 @@ export function printColumnDef(node, opts) {
|
|
|
74
152
|
' ',
|
|
75
153
|
printNode(computedExpr, opts),
|
|
76
154
|
isPersisted ? [' ', keyword('PERSISTED', opts)] : '',
|
|
155
|
+
computedNullPart,
|
|
77
156
|
];
|
|
78
157
|
}
|
|
79
158
|
const dataType = propStr(node, 'dataType') ?? 'INT';
|
|
80
159
|
const params = node.props?.['dataTypeParams'];
|
|
160
|
+
const xmlSchemaCollection = propStr(node, 'xmlSchemaCollection');
|
|
161
|
+
const xmlTypeOption = propStr(node, 'xmlTypeOption');
|
|
81
162
|
// Read nullable as a tristate (true/false/undefined) — propBool only returns true/false.
|
|
82
163
|
const isNullable = node.props?.['nullable'];
|
|
83
164
|
const isIdentity = propBool(node, 'identity');
|
|
84
165
|
const identitySeed = propStr(node, 'identitySeed');
|
|
85
166
|
const identityIncrement = propStr(node, 'identityIncrement');
|
|
86
167
|
const defaultValue = prop(node, 'defaultValue');
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
168
|
+
const checkConstraint = prop(node, 'checkConstraint');
|
|
169
|
+
const collation = propStr(node, 'collation');
|
|
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
|
+
})();
|
|
90
182
|
const parts = [name, ' ', typeStr];
|
|
183
|
+
// COLLATE clause comes right after the data type
|
|
184
|
+
if (collation)
|
|
185
|
+
parts.push(' ', keyword('COLLATE', opts), ' ', collation);
|
|
91
186
|
if (isIdentity) {
|
|
92
187
|
const seed = identitySeed ?? '1';
|
|
93
188
|
const inc = identityIncrement ?? '1';
|
|
94
189
|
parts.push(' ', keyword('IDENTITY', opts), `(${seed}, ${inc})`);
|
|
190
|
+
if (node.props?.['identityNotForReplication'])
|
|
191
|
+
parts.push(' ', keyword('NOT FOR REPLICATION', opts));
|
|
192
|
+
}
|
|
193
|
+
if (node.props?.['isRowGuidCol'])
|
|
194
|
+
parts.push(' ', keyword('ROWGUIDCOL', opts));
|
|
195
|
+
// SPARSE / FILESTREAM / COLUMN_SET
|
|
196
|
+
if (node.props?.['isSparse'])
|
|
197
|
+
parts.push(' ', keyword('SPARSE', opts));
|
|
198
|
+
if (node.props?.['isFileStream'])
|
|
199
|
+
parts.push(' ', keyword('FILESTREAM', opts));
|
|
200
|
+
if (node.props?.['isColumnSet'])
|
|
201
|
+
parts.push(' ', keyword('COLUMN_SET FOR ALL_SPARSE_COLUMNS', opts));
|
|
202
|
+
// Temporal table: GENERATED ALWAYS AS ROW START / ROW END [HIDDEN]
|
|
203
|
+
const generatedAlways = propStr(node, 'generatedAlways');
|
|
204
|
+
if (generatedAlways) {
|
|
205
|
+
const gaMap = {
|
|
206
|
+
RowStart: 'ROW START',
|
|
207
|
+
RowEnd: 'ROW END',
|
|
208
|
+
UserIdStart: 'USER ID START',
|
|
209
|
+
UserIdEnd: 'USER ID END',
|
|
210
|
+
UserNameStart: 'USER NAME START',
|
|
211
|
+
UserNameEnd: 'USER NAME END',
|
|
212
|
+
TransactionIdStart: 'TRANSACTION ID START',
|
|
213
|
+
TransactionIdEnd: 'TRANSACTION ID END',
|
|
214
|
+
SequenceNumberStart: 'SEQUENCE NUMBER START',
|
|
215
|
+
SequenceNumberEnd: 'SEQUENCE NUMBER END',
|
|
216
|
+
};
|
|
217
|
+
const gaKw = gaMap[generatedAlways] ?? generatedAlways.toUpperCase();
|
|
218
|
+
parts.push(' ', keyword('GENERATED ALWAYS AS', opts), ' ', keyword(gaKw, opts));
|
|
219
|
+
}
|
|
220
|
+
if (node.props?.['isHidden'])
|
|
221
|
+
parts.push(' ', keyword('HIDDEN', opts));
|
|
222
|
+
// Dynamic data masking
|
|
223
|
+
if (node.props?.['isMasked']) {
|
|
224
|
+
const maskFn = propStr(node, 'maskingFunction') ?? 'default()';
|
|
225
|
+
parts.push(' ', keyword('MASKED WITH', opts), ' (', keyword('FUNCTION', opts), ` = '${maskFn}')`);
|
|
226
|
+
}
|
|
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));
|
|
95
231
|
}
|
|
96
|
-
if (defaultValue)
|
|
97
|
-
parts.push(' ', keyword('DEFAULT', opts), ' ', printNode(defaultValue, opts));
|
|
98
232
|
parts.push(nullablePart(isNullable, opts));
|
|
233
|
+
if (checkConstraint) {
|
|
234
|
+
const checkName = propStr(node, 'checkConstraintName');
|
|
235
|
+
const checkPrefix = checkName ? [keyword('CONSTRAINT', opts), ' ', checkName, ' '] : '';
|
|
236
|
+
parts.push(' ', checkPrefix, keyword('CHECK', opts), ' (', printBool(checkConstraint, opts), ')');
|
|
237
|
+
}
|
|
238
|
+
// Inline PRIMARY KEY / UNIQUE constraint (e.g. in table variable declarations)
|
|
239
|
+
const uniqueConstraint = node.props?.['uniqueConstraint'];
|
|
240
|
+
if (uniqueConstraint) {
|
|
241
|
+
const constraintNamePrefix = uniqueConstraint.constraintName
|
|
242
|
+
? [keyword('CONSTRAINT', opts), ' ', uniqueConstraint.constraintName, ' ']
|
|
243
|
+
: '';
|
|
244
|
+
const uqKw = uniqueConstraint.isPrimaryKey ? keyword('PRIMARY KEY', opts) : keyword('UNIQUE', opts);
|
|
245
|
+
const clusteredKw = uniqueConstraint.clustered === true
|
|
246
|
+
? [' ', keyword('CLUSTERED', opts)]
|
|
247
|
+
: uniqueConstraint.clustered === false
|
|
248
|
+
? [' ', keyword('NONCLUSTERED', opts)]
|
|
249
|
+
: '';
|
|
250
|
+
parts.push(' ', constraintNamePrefix, uqKw, clusteredKw);
|
|
251
|
+
}
|
|
252
|
+
// Inline REFERENCES (column-level foreign key: col type [CONSTRAINT name] REFERENCES Table(col))
|
|
253
|
+
const foreignKey = node.props?.['foreignKey'];
|
|
254
|
+
if (foreignKey) {
|
|
255
|
+
if (foreignKey.constraintName) {
|
|
256
|
+
parts.push(' ', keyword('CONSTRAINT', opts), ' ', foreignKey.constraintName);
|
|
257
|
+
}
|
|
258
|
+
const refColsPart = foreignKey.refColumns?.length ? [' (', join(', ', foreignKey.refColumns), ')'] : '';
|
|
259
|
+
parts.push(' ', keyword('REFERENCES', opts), ' ', schemaObjectName(foreignKey.refTable), refColsPart);
|
|
260
|
+
if (foreignKey.deleteAction) {
|
|
261
|
+
parts.push(' ', keyword('ON DELETE', opts), ' ', keyword(foreignKey.deleteAction
|
|
262
|
+
.replace(/([A-Z])/g, ' $1')
|
|
263
|
+
.trim()
|
|
264
|
+
.toUpperCase(), opts));
|
|
265
|
+
}
|
|
266
|
+
if (foreignKey.updateAction) {
|
|
267
|
+
parts.push(' ', keyword('ON UPDATE', opts), ' ', keyword(foreignKey.updateAction
|
|
268
|
+
.replace(/([A-Z])/g, ' $1')
|
|
269
|
+
.trim()
|
|
270
|
+
.toUpperCase(), opts));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
99
273
|
return parts;
|
|
100
274
|
}
|
|
101
275
|
export function printConstraintDef(node, opts) {
|
|
@@ -104,14 +278,40 @@ export function printConstraintDef(node, opts) {
|
|
|
104
278
|
switch (node.type) {
|
|
105
279
|
case 'UniqueConstraint': {
|
|
106
280
|
const isPK = propBool(node, 'isPrimaryKey');
|
|
107
|
-
const
|
|
281
|
+
const clustered = node.props?.['clustered'];
|
|
282
|
+
// Only emit CLUSTERED/NONCLUSTERED when explicitly specified in DDL
|
|
283
|
+
const clusteredKw = clustered === true
|
|
284
|
+
? [keyword('CLUSTERED', opts), ' ']
|
|
285
|
+
: clustered === false
|
|
286
|
+
? [keyword('NONCLUSTERED', opts), ' ']
|
|
287
|
+
: '';
|
|
108
288
|
const kw = isPK ? keyword('PRIMARY KEY', opts) : keyword('UNIQUE', opts);
|
|
109
|
-
|
|
110
|
-
|
|
289
|
+
// Columns are now {name, order} objects; fall back to plain strings for compat
|
|
290
|
+
const rawCols = Array.isArray(node.props?.['columns']) ? node.props['columns'] : [];
|
|
291
|
+
const colDocs = rawCols.map((c) => {
|
|
292
|
+
if (typeof c === 'string')
|
|
293
|
+
return c;
|
|
294
|
+
const dir = c.order === 'Descending' ? [' ', keyword('DESC', opts)] : '';
|
|
295
|
+
return [c.name, dir];
|
|
296
|
+
});
|
|
297
|
+
const colsDoc = parenList(colDocs);
|
|
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]);
|
|
111
303
|
}
|
|
112
304
|
case 'CheckConstraint': {
|
|
113
305
|
const expr = prop(node, 'expression');
|
|
114
|
-
|
|
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
|
+
];
|
|
115
315
|
}
|
|
116
316
|
case 'ForeignKeyConstraint': {
|
|
117
317
|
const cols = Array.isArray(node.props?.['columns']) ? node.props?.['columns'] : [];
|
|
@@ -120,6 +320,7 @@ export function printConstraintDef(node, opts) {
|
|
|
120
320
|
const refName = refTable ? schemaObjectName(refTable) : '';
|
|
121
321
|
const deleteAction = propStr(node, 'deleteAction');
|
|
122
322
|
const updateAction = propStr(node, 'updateAction');
|
|
323
|
+
const nfr = propBool(node, 'notForReplication');
|
|
123
324
|
const refActionKw = (action) => keyword(action
|
|
124
325
|
.replace(/([A-Z])/g, ' $1')
|
|
125
326
|
.trim()
|
|
@@ -139,6 +340,7 @@ export function printConstraintDef(node, opts) {
|
|
|
139
340
|
]),
|
|
140
341
|
deleteAction ? [line, keyword('ON DELETE', opts), ' ', refActionKw(deleteAction)] : '',
|
|
141
342
|
updateAction ? [line, keyword('ON UPDATE', opts), ' ', refActionKw(updateAction)] : '',
|
|
343
|
+
nfr ? [line, keyword('NOT FOR REPLICATION', opts)] : '',
|
|
142
344
|
]),
|
|
143
345
|
]),
|
|
144
346
|
];
|
|
@@ -179,6 +381,11 @@ export function printAlterTable(node, opts) {
|
|
|
179
381
|
join([',', line], elements.map((e) => e.name)),
|
|
180
382
|
]),
|
|
181
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
|
+
: '';
|
|
182
389
|
return [
|
|
183
390
|
keyword('ALTER TABLE', opts),
|
|
184
391
|
' ',
|
|
@@ -188,6 +395,7 @@ export function printAlterTable(node, opts) {
|
|
|
188
395
|
ifExists ? [' ', keyword('IF EXISTS', opts)] : '',
|
|
189
396
|
' ',
|
|
190
397
|
nameList,
|
|
398
|
+
withPart,
|
|
191
399
|
';',
|
|
192
400
|
];
|
|
193
401
|
}
|
|
@@ -213,7 +421,49 @@ export function printAlterTable(node, opts) {
|
|
|
213
421
|
}
|
|
214
422
|
if (alterType === 'AlterTableAlterColumnStatement') {
|
|
215
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]
|
|
216
462
|
const dataType = propStr(node, 'dataType') ?? '';
|
|
463
|
+
const collationAC = propStr(node, 'collation');
|
|
464
|
+
const collatePart = collationAC
|
|
465
|
+
? [' ', keyword('COLLATE', opts), ' ', collationAC]
|
|
466
|
+
: '';
|
|
217
467
|
const nullPart = nullablePart(node.props?.['nullable'], opts);
|
|
218
468
|
return [
|
|
219
469
|
keyword('ALTER TABLE', opts),
|
|
@@ -225,18 +475,16 @@ export function printAlterTable(node, opts) {
|
|
|
225
475
|
column,
|
|
226
476
|
' ',
|
|
227
477
|
keyword(dataType, opts),
|
|
478
|
+
collatePart,
|
|
228
479
|
nullPart,
|
|
229
480
|
';',
|
|
230
481
|
];
|
|
231
482
|
}
|
|
232
483
|
if (alterType === 'AlterTableSetStatement') {
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
.replace(/^_/, '')
|
|
237
|
-
.toUpperCase();
|
|
484
|
+
// Options come pre-serialized from SerializeTableOption (e.g. "lock_escalation = table",
|
|
485
|
+
// "system_versioning = on (history_table = dbo.Tbl)"). Render them verbatim — applying
|
|
486
|
+
// keyword() casing would uppercase embedded schema/table names.
|
|
238
487
|
const options = (node.props?.['options'] ?? []);
|
|
239
|
-
const optDocs = options.map((o) => [keyword(toSqlKw(o.kind), opts), ' = ', keyword(toSqlKw(o.value), opts)]);
|
|
240
488
|
return [
|
|
241
489
|
keyword('ALTER TABLE', opts),
|
|
242
490
|
' ',
|
|
@@ -244,7 +492,7 @@ export function printAlterTable(node, opts) {
|
|
|
244
492
|
hardline,
|
|
245
493
|
keyword('SET', opts),
|
|
246
494
|
' (',
|
|
247
|
-
join(', ',
|
|
495
|
+
join(', ', options),
|
|
248
496
|
')',
|
|
249
497
|
';',
|
|
250
498
|
];
|
|
@@ -273,9 +521,13 @@ export function printAlterTable(node, opts) {
|
|
|
273
521
|
const sourcePartition = propStr(node, 'sourcePartition');
|
|
274
522
|
const targetTable = prop(node, 'targetTable');
|
|
275
523
|
const targetPartition = propStr(node, 'targetPartition');
|
|
524
|
+
const switchOptions = node.props?.['switchOptions'];
|
|
276
525
|
const sourceDoc = sourcePartition ? [' ', keyword('PARTITION', opts), ' ', sourcePartition] : '';
|
|
277
526
|
const targetDoc = targetTable ? schemaObjectName(targetTable) : '';
|
|
278
527
|
const targetPartDoc = targetPartition ? [' ', keyword('PARTITION', opts), ' ', targetPartition] : '';
|
|
528
|
+
const switchOptDoc = switchOptions?.length
|
|
529
|
+
? [' ', keyword('WITH', opts), ' (', join(', ', switchOptions), ')']
|
|
530
|
+
: '';
|
|
279
531
|
return [
|
|
280
532
|
keyword('ALTER TABLE', opts),
|
|
281
533
|
' ',
|
|
@@ -288,9 +540,18 @@ export function printAlterTable(node, opts) {
|
|
|
288
540
|
' ',
|
|
289
541
|
targetDoc,
|
|
290
542
|
targetPartDoc,
|
|
543
|
+
switchOptDoc,
|
|
291
544
|
';',
|
|
292
545
|
];
|
|
293
546
|
}
|
|
547
|
+
if (alterType === 'AlterTableTriggerModificationStatement') {
|
|
548
|
+
const enable = propBool(node, 'enable');
|
|
549
|
+
const triggerAll = node.props?.['triggerAll'];
|
|
550
|
+
const triggerNames = node.props?.['triggerNames'];
|
|
551
|
+
const verb = enable ? keyword('ENABLE TRIGGER', opts) : keyword('DISABLE TRIGGER', opts);
|
|
552
|
+
const targets = triggerAll ? keyword('ALL', opts) : join(', ', triggerNames ?? []);
|
|
553
|
+
return [keyword('ALTER TABLE', opts), ' ', name, hardline, verb, ' ', targets, ';'];
|
|
554
|
+
}
|
|
294
555
|
return [keyword('ALTER TABLE', opts), ' ', name, ' /* ', alterType, ' */;'];
|
|
295
556
|
}
|
|
296
557
|
// ---------------------------------------------------------------------------
|
|
@@ -299,10 +560,10 @@ export function printAlterTable(node, opts) {
|
|
|
299
560
|
export function printCreateIndex(node, opts) {
|
|
300
561
|
const indexName = propStr(node, 'indexName') ?? 'idx';
|
|
301
562
|
const isUnique = propBool(node, 'unique');
|
|
302
|
-
const isClustered = propBool(node, 'clustered');
|
|
303
563
|
const table = prop(node, 'table');
|
|
304
564
|
const columns = propArr(node, 'columns');
|
|
305
565
|
const includeColumns = node.props?.['includeColumns'];
|
|
566
|
+
const filterPredicate = propStr(node, 'filterPredicate');
|
|
306
567
|
const colDocs = columns.map((c) => {
|
|
307
568
|
const colName = propStr(c, 'name') ?? c.text ?? '';
|
|
308
569
|
const sort = propStr(c, 'sortOrder') ?? 'Ascending';
|
|
@@ -310,8 +571,14 @@ export function printCreateIndex(node, opts) {
|
|
|
310
571
|
? [colName, ' ', keyword('DESC', opts)]
|
|
311
572
|
: [colName, ' ', keyword('ASC', opts)];
|
|
312
573
|
});
|
|
313
|
-
const uniqueKw = isUnique ? keyword('UNIQUE
|
|
314
|
-
|
|
574
|
+
const uniqueKw = isUnique ? [keyword('UNIQUE', opts), ' '] : '';
|
|
575
|
+
// Preserve CLUSTERED / NONCLUSTERED exactly as written; omit when not specified.
|
|
576
|
+
const clusteredProp = node.props?.['clustered'];
|
|
577
|
+
const clusteredKw = clusteredProp === true
|
|
578
|
+
? [keyword('CLUSTERED', opts), ' ']
|
|
579
|
+
: clusteredProp === false
|
|
580
|
+
? [keyword('NONCLUSTERED', opts), ' ']
|
|
581
|
+
: '';
|
|
315
582
|
const onClause = [
|
|
316
583
|
keyword('ON', opts),
|
|
317
584
|
' ',
|
|
@@ -322,21 +589,26 @@ export function printCreateIndex(node, opts) {
|
|
|
322
589
|
')',
|
|
323
590
|
];
|
|
324
591
|
const includePart = Array.isArray(includeColumns) && includeColumns.length > 0
|
|
325
|
-
? [
|
|
326
|
-
hardline,
|
|
327
|
-
keyword('INCLUDE', opts),
|
|
328
|
-
' ',
|
|
329
|
-
parenList(includeColumns),
|
|
330
|
-
]
|
|
592
|
+
? [hardline, keyword('INCLUDE', opts), ' ', parenList(includeColumns)]
|
|
331
593
|
: '';
|
|
594
|
+
const filterPart = filterPredicate ? [hardline, keyword('WHERE', opts), ' ', filterPredicate] : '';
|
|
595
|
+
const indexOptions = node.props?.['indexOptions'];
|
|
596
|
+
const withPart = indexOptions && indexOptions.length > 0
|
|
597
|
+
? [hardline, keyword('WITH', opts), ' (', join(', ', indexOptions), ')']
|
|
598
|
+
: '';
|
|
599
|
+
const onFileGroup = propStr(node, 'onFileGroup');
|
|
600
|
+
const fileGroupPart = onFileGroup ? [hardline, keyword('ON', opts), ' ', onFileGroup] : '';
|
|
332
601
|
return group([
|
|
333
|
-
keyword('CREATE
|
|
602
|
+
keyword('CREATE', opts),
|
|
603
|
+
' ',
|
|
334
604
|
uniqueKw,
|
|
335
605
|
clusteredKw,
|
|
336
606
|
keyword('INDEX', opts),
|
|
337
607
|
' ',
|
|
338
608
|
indexName,
|
|
339
|
-
indent([hardline, onClause, includePart]),
|
|
609
|
+
indent([hardline, onClause, includePart, filterPart]),
|
|
610
|
+
withPart,
|
|
611
|
+
fileGroupPart,
|
|
340
612
|
';',
|
|
341
613
|
]);
|
|
342
614
|
}
|
|
@@ -354,6 +626,12 @@ export function printAlterIndex(node, opts) {
|
|
|
354
626
|
Set: 'SET',
|
|
355
627
|
};
|
|
356
628
|
const typeKw = keyword(typeKwMap[alterType] ?? alterType.toUpperCase(), opts);
|
|
629
|
+
const indexOptions = node.props?.['indexOptions'];
|
|
630
|
+
const partition = propStr(node, 'partition');
|
|
631
|
+
const withPart = indexOptions && indexOptions.length > 0
|
|
632
|
+
? [' ', keyword('WITH', opts), ' (', join(', ', indexOptions), ')']
|
|
633
|
+
: '';
|
|
634
|
+
const partitionPart = partition ? [' ', keyword('PARTITION', opts), ' = ', partition] : '';
|
|
357
635
|
return [
|
|
358
636
|
keyword('ALTER INDEX', opts),
|
|
359
637
|
' ',
|
|
@@ -364,6 +642,8 @@ export function printAlterIndex(node, opts) {
|
|
|
364
642
|
schemaObjectName(table),
|
|
365
643
|
' ',
|
|
366
644
|
typeKw,
|
|
645
|
+
partitionPart,
|
|
646
|
+
withPart,
|
|
367
647
|
';',
|
|
368
648
|
];
|
|
369
649
|
}
|
|
@@ -410,10 +690,13 @@ export function printCreateProcedure(node, opts) {
|
|
|
410
690
|
const paramDocs = parameters.map((p) => {
|
|
411
691
|
const pName = propStr(p, 'name') ?? '@p';
|
|
412
692
|
const dt = propStr(p, 'dataType') ?? 'INT';
|
|
693
|
+
const isUdt = propBool(p, 'isUdt');
|
|
413
694
|
const isOutput = propBool(p, 'output');
|
|
414
695
|
const isReadonly = propBool(p, 'readonly');
|
|
415
696
|
const defaultVal = prop(p, 'defaultValue');
|
|
416
|
-
|
|
697
|
+
// UDT names are identifiers, not SQL keywords — skip keyword-casing
|
|
698
|
+
const dtDoc = isUdt ? dt : keyword(dt, opts);
|
|
699
|
+
const parts = [pName, ' ', dtDoc];
|
|
417
700
|
if (defaultVal)
|
|
418
701
|
parts.push(' = ', printNode(defaultVal, opts));
|
|
419
702
|
if (isOutput)
|
|
@@ -422,7 +705,11 @@ export function printCreateProcedure(node, opts) {
|
|
|
422
705
|
parts.push(' ', keyword('READONLY', opts));
|
|
423
706
|
return parts;
|
|
424
707
|
});
|
|
425
|
-
|
|
708
|
+
// Natively compiled procs have a single BEGIN ATOMIC WITH (...) body statement.
|
|
709
|
+
const atomicBlock = body.length === 1 && body[0].type === 'BeginEndAtomicBlock' ? body[0] : null;
|
|
710
|
+
const atomicOptions = atomicBlock?.props?.['atomicOptions'];
|
|
711
|
+
const innerBody = atomicBlock ? propArr(atomicBlock, 'statements') : unwrapBodyBlock(body);
|
|
712
|
+
const bodyDocs = innerBody.map((s) => printStatementWithComments(s, opts));
|
|
426
713
|
const preBody = commentsBlock(node.preBodyComments);
|
|
427
714
|
const postParam = commentsBlock(node.postParamComments);
|
|
428
715
|
const procKw = node.type === 'CreateOrAlterProcedureStatement'
|
|
@@ -430,6 +717,26 @@ export function printCreateProcedure(node, opts) {
|
|
|
430
717
|
: node.type === 'AlterProcedureStatement'
|
|
431
718
|
? keyword('ALTER PROCEDURE', opts)
|
|
432
719
|
: keyword('CREATE PROCEDURE', opts);
|
|
720
|
+
// CLR stored procedure: AS EXTERNAL NAME assembly.[class].method
|
|
721
|
+
const externalName = propStr(node, 'externalName');
|
|
722
|
+
if (externalName) {
|
|
723
|
+
return group([
|
|
724
|
+
procKw,
|
|
725
|
+
' ',
|
|
726
|
+
schemaObjectName(prop(node, 'name')),
|
|
727
|
+
preBody,
|
|
728
|
+
parameters.length > 0 ? indent([hardline, join([',', hardline], paramDocs)]) : '',
|
|
729
|
+
postParam,
|
|
730
|
+
printModuleOptions(node, opts),
|
|
731
|
+
hardline,
|
|
732
|
+
keyword('AS', opts),
|
|
733
|
+
' ',
|
|
734
|
+
keyword('EXTERNAL NAME', opts),
|
|
735
|
+
' ',
|
|
736
|
+
externalName,
|
|
737
|
+
';',
|
|
738
|
+
]);
|
|
739
|
+
}
|
|
433
740
|
return group([
|
|
434
741
|
procKw,
|
|
435
742
|
' ',
|
|
@@ -441,7 +748,19 @@ export function printCreateProcedure(node, opts) {
|
|
|
441
748
|
hardline,
|
|
442
749
|
keyword('AS', opts),
|
|
443
750
|
hardline,
|
|
444
|
-
|
|
751
|
+
...(atomicOptions?.length
|
|
752
|
+
? [
|
|
753
|
+
keyword('BEGIN', opts),
|
|
754
|
+
' ',
|
|
755
|
+
keyword('ATOMIC', opts),
|
|
756
|
+
' ',
|
|
757
|
+
keyword('WITH', opts),
|
|
758
|
+
' (',
|
|
759
|
+
indent([hardline, join([',', hardline], atomicOptions)]),
|
|
760
|
+
hardline,
|
|
761
|
+
')',
|
|
762
|
+
]
|
|
763
|
+
: [keyword('BEGIN', opts)]),
|
|
445
764
|
indent([hardline, join([hardline, hardline], bodyDocs)]),
|
|
446
765
|
hardline,
|
|
447
766
|
keyword('END', opts),
|
|
@@ -459,7 +778,9 @@ export function printCreateFunction(node, opts) {
|
|
|
459
778
|
const paramDocs = parameters.map((p) => {
|
|
460
779
|
const pName = propStr(p, 'name') ?? '@p';
|
|
461
780
|
const dt = propStr(p, 'dataType') ?? 'INT';
|
|
462
|
-
|
|
781
|
+
const isUdt = propBool(p, 'isUdt');
|
|
782
|
+
// UDT names are identifiers — skip keyword-casing
|
|
783
|
+
return [pName, ' ', isUdt ? dt : keyword(dt, opts)];
|
|
463
784
|
});
|
|
464
785
|
const preBody = commentsBlock(node.preBodyComments);
|
|
465
786
|
const postParam = commentsBlock(node.postParamComments);
|
|
@@ -468,25 +789,43 @@ export function printCreateFunction(node, opts) {
|
|
|
468
789
|
: node.type === 'AlterFunctionStatement'
|
|
469
790
|
? keyword('ALTER FUNCTION', opts)
|
|
470
791
|
: keyword('CREATE FUNCTION', opts);
|
|
471
|
-
const
|
|
792
|
+
const nameAndParamsNoOpts = [
|
|
472
793
|
fnKw,
|
|
473
794
|
' ',
|
|
474
795
|
schemaObjectName(prop(node, 'name')),
|
|
475
796
|
preBody,
|
|
476
797
|
group(['(', parameters.length > 0 ? [indent([softline, join([',', line], paramDocs)]), softline] : '', ')']),
|
|
477
798
|
postParam,
|
|
478
|
-
printModuleOptions(node, opts),
|
|
479
799
|
];
|
|
800
|
+
// CLR function: EXTERNAL NAME assembly.[class].method (no body)
|
|
801
|
+
const externalName = propStr(node, 'externalName');
|
|
802
|
+
if (externalName) {
|
|
803
|
+
return [
|
|
804
|
+
nameAndParamsNoOpts,
|
|
805
|
+
hardline,
|
|
806
|
+
keyword('RETURNS', opts),
|
|
807
|
+
' ',
|
|
808
|
+
keyword(returnType, opts),
|
|
809
|
+
printModuleOptions(node, opts),
|
|
810
|
+
hardline,
|
|
811
|
+
keyword('AS', opts),
|
|
812
|
+
' ',
|
|
813
|
+
keyword('EXTERNAL NAME', opts),
|
|
814
|
+
' ',
|
|
815
|
+
externalName,
|
|
816
|
+
';',
|
|
817
|
+
];
|
|
818
|
+
}
|
|
480
819
|
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
|
|
820
|
+
// Inline TVF: RETURNS TABLE [WITH options] AS RETURN (query) — no BEGIN/END
|
|
483
821
|
const queryDoc = body && !Array.isArray(body) ? qexpr(body, opts) : '/* query */';
|
|
484
822
|
return [
|
|
485
|
-
|
|
486
|
-
|
|
823
|
+
nameAndParamsNoOpts,
|
|
824
|
+
hardline,
|
|
487
825
|
keyword('RETURNS', opts),
|
|
488
826
|
' ',
|
|
489
827
|
keyword('TABLE', opts),
|
|
828
|
+
printModuleOptions(node, opts),
|
|
490
829
|
hardline,
|
|
491
830
|
keyword('AS', opts),
|
|
492
831
|
hardline,
|
|
@@ -499,7 +838,7 @@ export function printCreateFunction(node, opts) {
|
|
|
499
838
|
];
|
|
500
839
|
}
|
|
501
840
|
// Scalar or multi-statement TVF — both use BEGIN...END
|
|
502
|
-
const stmts = Array.isArray(body) ? body.map((s) => printStatementWithComments(s, opts)) : [];
|
|
841
|
+
const stmts = Array.isArray(body) ? unwrapBodyBlock(body).map((s) => printStatementWithComments(s, opts)) : [];
|
|
503
842
|
const bodyDoc = join([hardline, hardline], stmts);
|
|
504
843
|
let retTypePart;
|
|
505
844
|
if (bodyType === 'inline-table') {
|
|
@@ -519,12 +858,15 @@ export function printCreateFunction(node, opts) {
|
|
|
519
858
|
else {
|
|
520
859
|
retTypePart = keyword(returnType, opts);
|
|
521
860
|
}
|
|
861
|
+
// WITH options come AFTER RETURNS (per T-SQL syntax):
|
|
862
|
+
// CREATE FUNCTION ... (params) RETURNS type WITH options AS BEGIN ... END
|
|
522
863
|
return [
|
|
523
|
-
|
|
524
|
-
|
|
864
|
+
nameAndParamsNoOpts,
|
|
865
|
+
hardline,
|
|
525
866
|
keyword('RETURNS', opts),
|
|
526
867
|
' ',
|
|
527
868
|
retTypePart,
|
|
869
|
+
printModuleOptions(node, opts),
|
|
528
870
|
hardline,
|
|
529
871
|
keyword('AS', opts),
|
|
530
872
|
hardline,
|
|
@@ -547,9 +889,7 @@ export function printCreateView(node, opts) {
|
|
|
547
889
|
: node.type === 'AlterViewStatement'
|
|
548
890
|
? keyword('ALTER VIEW', opts)
|
|
549
891
|
: keyword('CREATE VIEW', opts);
|
|
550
|
-
const colsPart = columns?.length
|
|
551
|
-
? [' ', parenList(columns)]
|
|
552
|
-
: '';
|
|
892
|
+
const colsPart = columns?.length ? [' ', parenList(columns)] : '';
|
|
553
893
|
const withPart = withOptions?.length
|
|
554
894
|
? [
|
|
555
895
|
hardline,
|
|
@@ -559,6 +899,8 @@ export function printCreateView(node, opts) {
|
|
|
559
899
|
]
|
|
560
900
|
: '';
|
|
561
901
|
const preBodyPart = commentsBlock(node.preBodyComments);
|
|
902
|
+
const withCheckOption = node.props?.['withCheckOption'];
|
|
903
|
+
const checkOptionPart = withCheckOption ? [hardline, keyword('WITH CHECK OPTION', opts)] : '';
|
|
562
904
|
return group([
|
|
563
905
|
kw,
|
|
564
906
|
' ',
|
|
@@ -570,6 +912,7 @@ export function printCreateView(node, opts) {
|
|
|
570
912
|
keyword('AS', opts),
|
|
571
913
|
hardline,
|
|
572
914
|
body ? qexpr(body, opts) : '',
|
|
915
|
+
checkOptionPart,
|
|
573
916
|
';',
|
|
574
917
|
]);
|
|
575
918
|
}
|
|
@@ -589,7 +932,15 @@ export function printCreateTrigger(node, opts) {
|
|
|
589
932
|
const actionList = Array.isArray(actions)
|
|
590
933
|
? join(', ', actions.map((a) => keyword(a.toUpperCase(), opts)))
|
|
591
934
|
: '';
|
|
592
|
-
const
|
|
935
|
+
const notForReplication = propBool(node, 'notForReplication');
|
|
936
|
+
const notForReplicationDoc = notForReplication ? [hardline, keyword('NOT FOR REPLICATION', opts)] : '';
|
|
937
|
+
const bodyDocs = unwrapBodyBlock(propArr(node, 'body')).map((s) => printStatementWithComments(s, opts));
|
|
938
|
+
const triggerScope = propStr(node, 'triggerScope'); // 'Database' or 'Server' for DDL triggers
|
|
939
|
+
const onTarget = triggerScope === 'Database'
|
|
940
|
+
? keyword('DATABASE', opts)
|
|
941
|
+
: triggerScope === 'Server'
|
|
942
|
+
? keyword('ALL SERVER', opts)
|
|
943
|
+
: schemaObjectName(prop(node, 'onName'));
|
|
593
944
|
return [
|
|
594
945
|
kw,
|
|
595
946
|
' ',
|
|
@@ -597,11 +948,12 @@ export function printCreateTrigger(node, opts) {
|
|
|
597
948
|
hardline,
|
|
598
949
|
keyword('ON', opts),
|
|
599
950
|
' ',
|
|
600
|
-
|
|
951
|
+
onTarget,
|
|
601
952
|
hardline,
|
|
602
953
|
typeKw,
|
|
603
954
|
' ',
|
|
604
955
|
actionList,
|
|
956
|
+
notForReplicationDoc,
|
|
605
957
|
hardline,
|
|
606
958
|
keyword('AS', opts),
|
|
607
959
|
hardline,
|
|
@@ -749,9 +1101,14 @@ export function printDropObjects(objType, node, opts) {
|
|
|
749
1101
|
];
|
|
750
1102
|
}
|
|
751
1103
|
export function printDropIndex(node, opts) {
|
|
1104
|
+
const ifExists = propBool(node, 'ifExists');
|
|
752
1105
|
const indices = propArr(node, 'indices');
|
|
753
1106
|
const indexDocs = indices.map((idx) => [propStr(idx, 'name') ?? '', ' ', keyword('ON', opts), ' ', schemaObjectName(prop(idx, 'table'))]);
|
|
754
|
-
|
|
1107
|
+
const ifExistsPart = ifExists ? [' ', keyword('IF EXISTS', opts)] : '';
|
|
1108
|
+
if (indexDocs.length === 1) {
|
|
1109
|
+
return [keyword('DROP INDEX', opts), ifExistsPart, ' ', indexDocs[0], ';'];
|
|
1110
|
+
}
|
|
1111
|
+
return [keyword('DROP INDEX', opts), ifExistsPart, indent([hardline, join([',', hardline], indexDocs)]), ';'];
|
|
755
1112
|
}
|
|
756
1113
|
// ---------------------------------------------------------------------------
|
|
757
1114
|
// CREATE / DROP SYNONYM
|
|
@@ -964,7 +1321,10 @@ export function printUpdateStatistics(node, opts) {
|
|
|
964
1321
|
const subElements = node.props?.['subElements'];
|
|
965
1322
|
const options = node.props?.['options'];
|
|
966
1323
|
const parts = [keyword('UPDATE STATISTICS', opts), ' ', table ? schemaObjectName(table) : ''];
|
|
967
|
-
|
|
1324
|
+
// Single stat name: no parens needed. Multiple: wrap in parens.
|
|
1325
|
+
if (subElements?.length === 1)
|
|
1326
|
+
parts.push([' ', subElements[0]]);
|
|
1327
|
+
else if (subElements?.length)
|
|
968
1328
|
parts.push([' ', parenList(subElements)]);
|
|
969
1329
|
if (options?.length)
|
|
970
1330
|
parts.push([hardline, withOptionsClause(options, opts)]);
|