@mikro-orm/mssql 7.1.0-dev.2 → 7.1.0-dev.20
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/MsSqlPlatform.d.ts +1 -0
- package/MsSqlPlatform.js +4 -0
- package/MsSqlSchemaHelper.d.ts +9 -1
- package/MsSqlSchemaHelper.js +119 -9
- package/package.json +4 -4
package/MsSqlPlatform.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
8
8
|
#private;
|
|
9
9
|
protected readonly schemaHelper: MsSqlSchemaHelper;
|
|
10
10
|
protected readonly exceptionConverter: MsSqlExceptionConverter;
|
|
11
|
+
formatIndexHint(indexNames: string[]): string;
|
|
11
12
|
/** @inheritDoc */
|
|
12
13
|
lookupExtensions(orm: MikroORM<MsSqlDriver>): void;
|
|
13
14
|
/** @inheritDoc */
|
package/MsSqlPlatform.js
CHANGED
|
@@ -16,6 +16,9 @@ export class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
16
16
|
bigint: 'bigint',
|
|
17
17
|
boolean: 'bit',
|
|
18
18
|
};
|
|
19
|
+
formatIndexHint(indexNames) {
|
|
20
|
+
return `with (index(${indexNames.join(', ')}))`;
|
|
21
|
+
}
|
|
19
22
|
/** @inheritDoc */
|
|
20
23
|
lookupExtensions(orm) {
|
|
21
24
|
MsSqlSchemaGenerator.register(orm);
|
|
@@ -136,6 +139,7 @@ export class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
136
139
|
return 'uniqueidentifier';
|
|
137
140
|
}
|
|
138
141
|
validateMetadata(meta) {
|
|
142
|
+
super.validateMetadata(meta);
|
|
139
143
|
for (const prop of meta.props) {
|
|
140
144
|
if ((prop.runtimeType === 'string' || ['string', 'nvarchar'].includes(prop.type)) &&
|
|
141
145
|
!['uuid'].includes(prop.type) &&
|
package/MsSqlSchemaHelper.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AbstractSqlConnection, type CheckDef, type Column, type DatabaseSchema, type DatabaseTable, type Dictionary, type ForeignKey, type IndexDef, SchemaHelper, type Table, type TableDifference, type Transaction, type Type } from '@mikro-orm/sql';
|
|
1
|
+
import { type AbstractSqlConnection, type CheckDef, type Column, type DatabaseSchema, type DatabaseTable, type Dictionary, type ForeignKey, type IndexDef, SchemaHelper, type Table, type TableDifference, type SqlTriggerDef, type Transaction, type Type } from '@mikro-orm/sql';
|
|
2
2
|
/** Schema introspection helper for Microsoft SQL Server. */
|
|
3
3
|
export declare class MsSqlSchemaHelper extends SchemaHelper {
|
|
4
4
|
static readonly DEFAULT_VALUES: {
|
|
@@ -6,6 +6,8 @@ export declare class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
6
6
|
false: string[];
|
|
7
7
|
'getdate()': string[];
|
|
8
8
|
};
|
|
9
|
+
private static readonly AUTO_NOT_NULL_RE;
|
|
10
|
+
protected get bracketQuotedIdentifiers(): boolean;
|
|
9
11
|
getManagementDbName(): string;
|
|
10
12
|
getDropDatabaseSQL(name: string): string;
|
|
11
13
|
disableForeignKeysSQL(): string;
|
|
@@ -23,6 +25,12 @@ export declare class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
23
25
|
private getEnumDefinitions;
|
|
24
26
|
private getChecksSQL;
|
|
25
27
|
getAllChecks(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>, ctx?: Transaction): Promise<Dictionary<CheckDef[]>>;
|
|
28
|
+
/** Generates SQL to create an MSSQL trigger. MSSQL supports AFTER and INSTEAD OF only. */
|
|
29
|
+
createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
30
|
+
/** Generates SQL to drop an MSSQL trigger. */
|
|
31
|
+
dropTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
32
|
+
private getSchemaQualifiedName;
|
|
33
|
+
getAllTriggers(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>): Promise<Dictionary<SqlTriggerDef[]>>;
|
|
26
34
|
loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[], ctx?: Transaction): Promise<void>;
|
|
27
35
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
28
36
|
getPostAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
package/MsSqlSchemaHelper.js
CHANGED
|
@@ -7,6 +7,16 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
7
7
|
false: ['0'],
|
|
8
8
|
'getdate()': ['current_timestamp'],
|
|
9
9
|
};
|
|
10
|
+
// `stripAutoNotNullFilter` unwraps balanced per-clause parens before calling `.exec`, so we
|
|
11
|
+
// only need to match the bare form here — previously the pattern allowed independently
|
|
12
|
+
// optional leading/trailing parens, which accepted unbalanced strings like `([col] IS NOT NULL`.
|
|
13
|
+
static AUTO_NOT_NULL_RE = /^\[([^\]]+)\]\s+IS\s+NOT\s+NULL$/i;
|
|
14
|
+
// MSSQL `filter_definition` and `where` predicates use `[…]` bracket-quoting for identifiers,
|
|
15
|
+
// so `splitTopLevelAnd` must treat `[` as opening a quoted span (otherwise `[some and col]`
|
|
16
|
+
// would split mid-identifier).
|
|
17
|
+
get bracketQuotedIdentifiers() {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
10
20
|
getManagementDbName() {
|
|
11
21
|
return 'master';
|
|
12
22
|
}
|
|
@@ -148,6 +158,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
148
158
|
col.name as column_name,
|
|
149
159
|
schema_name(t.schema_id) as schema_name,
|
|
150
160
|
(case when filter_definition is not null then concat('where ', filter_definition) else null end) as expression,
|
|
161
|
+
filter_definition as filter_definition,
|
|
151
162
|
ind.is_disabled as is_disabled,
|
|
152
163
|
ind.type as index_type,
|
|
153
164
|
ind.fill_factor as fill_factor,
|
|
@@ -192,15 +203,31 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
192
203
|
if (index.fill_factor > 0) {
|
|
193
204
|
indexDef.fillFactor = index.fill_factor;
|
|
194
205
|
}
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
/* v8 ignore next: function-based / computed-column introspection path, same as pre-PR */
|
|
207
|
+
if (index.column_name?.match(/[(): ,"'`]/)) {
|
|
208
|
+
indexDef.expression = index.expression;
|
|
197
209
|
indexDef.expression = this.getCreateIndexSQL(index.table_name, indexDef, !!index.expression);
|
|
198
210
|
}
|
|
211
|
+
else if (index.filter_definition) {
|
|
212
|
+
// Auto-NOT-NULL stripping runs post-mapIndexes (needs the consolidated column list).
|
|
213
|
+
indexDef.where = index.filter_definition;
|
|
214
|
+
}
|
|
199
215
|
ret[key] ??= [];
|
|
200
216
|
ret[key].push(indexDef);
|
|
201
217
|
}
|
|
202
218
|
for (const key of Object.keys(ret)) {
|
|
203
219
|
ret[key] = await this.mapIndexes(ret[key]);
|
|
220
|
+
for (const idx of ret[key]) {
|
|
221
|
+
if (idx.where) {
|
|
222
|
+
const stripped = this.stripAutoNotNullFilter(idx.where, idx.columnNames, MsSqlSchemaHelper.AUTO_NOT_NULL_RE);
|
|
223
|
+
if (stripped === '') {
|
|
224
|
+
delete idx.where;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
idx.where = stripped;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
204
231
|
}
|
|
205
232
|
return ret;
|
|
206
233
|
}
|
|
@@ -285,6 +312,82 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
285
312
|
}
|
|
286
313
|
return ret;
|
|
287
314
|
}
|
|
315
|
+
/** Generates SQL to create an MSSQL trigger. MSSQL supports AFTER and INSTEAD OF only. */
|
|
316
|
+
createTrigger(table, trigger) {
|
|
317
|
+
if (trigger.expression) {
|
|
318
|
+
return trigger.expression;
|
|
319
|
+
}
|
|
320
|
+
/* v8 ignore next 3 */
|
|
321
|
+
if (trigger.timing === 'before') {
|
|
322
|
+
throw new Error(`MSSQL does not support BEFORE triggers. Use AFTER or INSTEAD OF for trigger "${trigger.name}".`);
|
|
323
|
+
}
|
|
324
|
+
const timing = trigger.timing.toUpperCase();
|
|
325
|
+
const events = trigger.events.map(e => e.toUpperCase()).join(', ');
|
|
326
|
+
const qualifiedName = this.getSchemaQualifiedName(table, trigger.name);
|
|
327
|
+
return `create trigger ${qualifiedName} on ${table.getQuotedName()} ${timing} ${events} as begin ${trigger.body}; end`;
|
|
328
|
+
}
|
|
329
|
+
/** Generates SQL to drop an MSSQL trigger. */
|
|
330
|
+
dropTrigger(table, trigger) {
|
|
331
|
+
return `drop trigger if exists ${this.getSchemaQualifiedName(table, trigger.name)}`;
|
|
332
|
+
}
|
|
333
|
+
getSchemaQualifiedName(table, name) {
|
|
334
|
+
const defaultSchema = this.platform.getDefaultSchemaName();
|
|
335
|
+
if (table.schema && table.schema !== defaultSchema) {
|
|
336
|
+
return `${this.quote(table.schema)}.${this.quote(name)}`;
|
|
337
|
+
}
|
|
338
|
+
return this.quote(name);
|
|
339
|
+
}
|
|
340
|
+
async getAllTriggers(connection, tablesBySchemas) {
|
|
341
|
+
const conditions = [];
|
|
342
|
+
for (const [schema, tables] of tablesBySchemas) {
|
|
343
|
+
const names = tables.map(t => this.platform.quoteValue(t.table_name)).join(', ');
|
|
344
|
+
const schemaName = this.platform.quoteValue(schema ?? this.platform.getDefaultSchemaName());
|
|
345
|
+
conditions.push(`(schema_name(p.schema_id) = ${schemaName} and p.name in (${names}))`);
|
|
346
|
+
}
|
|
347
|
+
const sql = `select t.name as trigger_name, schema_name(p.schema_id) as schema_name,
|
|
348
|
+
p.name as table_name, te.type_desc as event,
|
|
349
|
+
case when t.is_instead_of_trigger = 1 then 'INSTEAD OF' else 'AFTER' end as timing,
|
|
350
|
+
object_definition(t.object_id) as definition
|
|
351
|
+
from sys.triggers t
|
|
352
|
+
join sys.trigger_events te on t.object_id = te.object_id
|
|
353
|
+
join sys.objects p on t.parent_id = p.object_id
|
|
354
|
+
where (${conditions.join(' or ')})
|
|
355
|
+
order by t.name, te.type_desc`;
|
|
356
|
+
const allTriggers = await connection.execute(sql);
|
|
357
|
+
const ret = {};
|
|
358
|
+
const triggerMap = new Map();
|
|
359
|
+
for (const row of allTriggers) {
|
|
360
|
+
const key = this.getTableKey(row);
|
|
361
|
+
const dedupeKey = `${key}:${row.trigger_name}`;
|
|
362
|
+
const event = row.event.toLowerCase();
|
|
363
|
+
if (triggerMap.has(dedupeKey)) {
|
|
364
|
+
const existing = triggerMap.get(dedupeKey);
|
|
365
|
+
if (!existing.events.includes(event)) {
|
|
366
|
+
existing.events.push(event);
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
// Parse body from full trigger definition
|
|
371
|
+
let body = '';
|
|
372
|
+
if (row.definition) {
|
|
373
|
+
const bodyMatch = /\bas\s+begin\s+([\s\S]*)\s*end\s*;?\s*$/i.exec(row.definition);
|
|
374
|
+
if (bodyMatch) {
|
|
375
|
+
body = bodyMatch[1].trim().replace(/;\s*$/, '');
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
ret[key] ??= [];
|
|
379
|
+
const trigger = {
|
|
380
|
+
name: row.trigger_name,
|
|
381
|
+
timing: row.timing.toLowerCase(),
|
|
382
|
+
events: [event],
|
|
383
|
+
forEach: 'row', // MSSQL has no FOR EACH ROW/STATEMENT syntax; match the metadata default to avoid false diffs
|
|
384
|
+
body,
|
|
385
|
+
};
|
|
386
|
+
ret[key].push(trigger);
|
|
387
|
+
triggerMap.set(dedupeKey, trigger);
|
|
388
|
+
}
|
|
389
|
+
return ret;
|
|
390
|
+
}
|
|
288
391
|
async loadInformationSchema(schema, connection, tables, schemas, ctx) {
|
|
289
392
|
if (tables.length === 0) {
|
|
290
393
|
return;
|
|
@@ -294,12 +397,16 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
294
397
|
const indexes = await this.getAllIndexes(connection, tablesBySchema, ctx);
|
|
295
398
|
const checks = await this.getAllChecks(connection, tablesBySchema, ctx);
|
|
296
399
|
const fks = await this.getAllForeignKeys(connection, tablesBySchema, ctx);
|
|
400
|
+
const triggers = await this.getAllTriggers(connection, tablesBySchema);
|
|
297
401
|
for (const t of tables) {
|
|
298
402
|
const key = this.getTableKey(t);
|
|
299
403
|
const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
|
|
300
404
|
const pks = await this.getPrimaryKeys(connection, indexes[key], table.name, table.schema);
|
|
301
405
|
const enums = this.getEnumDefinitions(checks[key] ?? []);
|
|
302
406
|
table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums);
|
|
407
|
+
if (triggers[key]) {
|
|
408
|
+
table.setTriggers(triggers[key]);
|
|
409
|
+
}
|
|
303
410
|
}
|
|
304
411
|
}
|
|
305
412
|
getPreAlterTable(tableDiff, safe) {
|
|
@@ -460,7 +567,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
460
567
|
const clustered = index.clustered ? 'clustered ' : '';
|
|
461
568
|
let sql = `create ${index.unique ? 'unique ' : ''}${clustered}index ${keyName} on ${this.quote(tableName)} `;
|
|
462
569
|
if (index.expression && partialExpression) {
|
|
463
|
-
return sql + `(${index.expression})` + this.getMsSqlIndexSuffix(index);
|
|
570
|
+
return sql + `(${index.expression})` + this.getMsSqlIndexSuffix(index) + this.getIndexWhereClause(index);
|
|
464
571
|
}
|
|
465
572
|
// Build column list with advanced options
|
|
466
573
|
const columns = this.getIndexColumns(index);
|
|
@@ -469,7 +576,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
469
576
|
if (index.include?.length) {
|
|
470
577
|
sql += ` include (${index.include.map(c => this.quote(c)).join(', ')})`;
|
|
471
578
|
}
|
|
472
|
-
sql += this.getMsSqlIndexSuffix(index);
|
|
579
|
+
sql += this.getMsSqlIndexSuffix(index) + this.getIndexWhereClause(index);
|
|
473
580
|
// Disabled indexes need to be created first, then disabled
|
|
474
581
|
if (index.disabled) {
|
|
475
582
|
sql += `;\nalter index ${keyName} on ${this.quote(tableName)} disable`;
|
|
@@ -514,13 +621,16 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
514
621
|
if (index.expression) {
|
|
515
622
|
return index.expression;
|
|
516
623
|
}
|
|
517
|
-
const
|
|
518
|
-
if (!
|
|
624
|
+
const needsAutoNotNull = index.unique && index.columnNames.some(column => table.getColumn(column)?.nullable);
|
|
625
|
+
if (!needsAutoNotNull) {
|
|
519
626
|
return this.getCreateIndexSQL(table.getShortestName(), index);
|
|
520
627
|
}
|
|
521
|
-
//
|
|
522
|
-
|
|
523
|
-
|
|
628
|
+
// Strip `index.where` from the base SQL so we can combine it with the auto NOT-NULL guard
|
|
629
|
+
// ourselves, wrapping the user predicate in parens to defuse operator precedence
|
|
630
|
+
// (a bare `a = 1 or b = 2 and [col] is not null` would bind as `a = 1 or (b = 2 and …)`).
|
|
631
|
+
let sql = this.getCreateIndexSQL(table.getShortestName(), { ...index, where: undefined, disabled: false });
|
|
632
|
+
const autoNotNull = index.columnNames.map(c => `${this.quote(c)} is not null`).join(' and ');
|
|
633
|
+
sql += index.where ? ` where (${index.where}) and ${autoNotNull}` : ` where ${autoNotNull}`;
|
|
524
634
|
if (index.disabled) {
|
|
525
635
|
sql += `;\nalter index ${this.quote(index.keyName)} on ${table.getQuotedName()} disable`;
|
|
526
636
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/mssql",
|
|
3
|
-
"version": "7.1.0-dev.
|
|
3
|
+
"version": "7.1.0-dev.20",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -47,17 +47,17 @@
|
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@mikro-orm/sql": "7.1.0-dev.
|
|
50
|
+
"@mikro-orm/sql": "7.1.0-dev.20",
|
|
51
51
|
"kysely": "0.28.16",
|
|
52
52
|
"tarn": "3.0.2",
|
|
53
53
|
"tedious": "19.2.1",
|
|
54
54
|
"tsqlstring": "1.0.1"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@mikro-orm/core": "^7.0.
|
|
57
|
+
"@mikro-orm/core": "^7.0.12"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@mikro-orm/core": "7.1.0-dev.
|
|
60
|
+
"@mikro-orm/core": "7.1.0-dev.20"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|
|
63
63
|
"node": ">= 22.17.0"
|