@mikro-orm/mssql 7.0.17 → 7.0.18-dev.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.
- package/MsSqlConnection.d.ts +2 -0
- package/MsSqlConnection.js +48 -1
- package/MsSqlMikroORM.d.ts +4 -4
- package/MsSqlPlatform.d.ts +1 -0
- package/MsSqlPlatform.js +4 -0
- package/MsSqlSchemaHelper.d.ts +18 -1
- package/MsSqlSchemaHelper.js +241 -12
- package/README.md +2 -1
- package/package.json +3 -3
package/MsSqlConnection.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { AbstractSqlConnection, type TransactionEventBroadcaster } from '@mikro-orm/sql';
|
|
2
2
|
import { type ControlledTransaction, MssqlDialect } from 'kysely';
|
|
3
|
+
import { type Routine, type Transaction } from '@mikro-orm/core';
|
|
3
4
|
import type { ConnectionConfiguration } from 'tedious';
|
|
4
5
|
/** Microsoft SQL Server database connection using the `tedious` driver. */
|
|
5
6
|
export declare class MsSqlConnection extends AbstractSqlConnection {
|
|
6
7
|
createKyselyDialect(overrides: ConnectionConfiguration): MssqlDialect;
|
|
7
8
|
private mapOptions;
|
|
9
|
+
callRoutine<T>(routine: Routine, args?: Record<string, unknown>, ctx?: Transaction): Promise<T>;
|
|
8
10
|
commit(ctx: ControlledTransaction<any, any>, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
|
|
9
11
|
protected transformRawResult<T>(res: any, method: 'all' | 'get' | 'run'): T;
|
|
10
12
|
}
|
package/MsSqlConnection.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AbstractSqlConnection, Utils } from '@mikro-orm/sql';
|
|
1
|
+
import { AbstractSqlConnection, DatabaseSchema, Utils, } from '@mikro-orm/sql';
|
|
2
2
|
import { MssqlDialect } from 'kysely';
|
|
3
3
|
import * as Tedious from 'tedious';
|
|
4
4
|
import * as Tarn from 'tarn';
|
|
@@ -55,6 +55,53 @@ export class MsSqlConnection extends AbstractSqlConnection {
|
|
|
55
55
|
}
|
|
56
56
|
return Utils.mergeConfig(ret, overrides);
|
|
57
57
|
}
|
|
58
|
+
async callRoutine(routine, args = {}, ctx) {
|
|
59
|
+
if (routine.type === 'function') {
|
|
60
|
+
return this.callRoutineFunction(routine, args, ctx);
|
|
61
|
+
}
|
|
62
|
+
// MSSQL scalar UDF calls must be schema-qualified — `select sql_hash(...)` fails to parse.
|
|
63
|
+
const schema = routine.schema ?? this.platform.getDefaultSchemaName() ?? 'dbo';
|
|
64
|
+
const qualified = `${this.platform.quoteIdentifier(schema)}.${this.platform.quoteIdentifier(routine.name)}`;
|
|
65
|
+
// T-SQL session variables don't persist across execute() calls (different pool connections),
|
|
66
|
+
// so DECLARE/SET/EXEC/SELECT must go through as a single batch.
|
|
67
|
+
const declareLines = [];
|
|
68
|
+
const setLines = [];
|
|
69
|
+
const setValues = [];
|
|
70
|
+
const callArgs = [];
|
|
71
|
+
const inValues = [];
|
|
72
|
+
const outVars = [];
|
|
73
|
+
routine.params.forEach((p, i) => {
|
|
74
|
+
if (p.direction === 'in') {
|
|
75
|
+
callArgs.push('?');
|
|
76
|
+
inValues.push(this.convertRoutineInbound(args[p.name], p));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const varName = `@_mikro_orm_routine_${i}`;
|
|
80
|
+
// Logical aliases like `'string'`/`'number'` aren't valid T-SQL types — translate them
|
|
81
|
+
// through the platform's type system, matching the DDL side in `DatabaseSchema`.
|
|
82
|
+
const declType = DatabaseSchema.resolveRoutineColumnType(p.type, this.platform);
|
|
83
|
+
declareLines.push(`declare ${varName} ${declType}`);
|
|
84
|
+
outVars.push({ name: p.name, varName, param: p });
|
|
85
|
+
if (p.direction === 'inout') {
|
|
86
|
+
setLines.push(`set ${varName} = ?`);
|
|
87
|
+
setValues.push(this.convertRoutineInbound(args[p.name], p));
|
|
88
|
+
}
|
|
89
|
+
callArgs.push(`${varName} output`);
|
|
90
|
+
});
|
|
91
|
+
const allValues = [...setValues, ...inValues];
|
|
92
|
+
const batch = [...declareLines, ...setLines, `exec ${qualified} ${callArgs.join(', ')}`];
|
|
93
|
+
if (outVars.length > 0) {
|
|
94
|
+
const selectClause = outVars.map(o => `${o.varName} as ${this.platform.quoteIdentifier(o.name)}`).join(', ');
|
|
95
|
+
batch.push(`select ${selectClause}`);
|
|
96
|
+
}
|
|
97
|
+
const result = await this.execute(batch.join('; '), allValues, 'all', ctx);
|
|
98
|
+
if (outVars.length === 0) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const rows = result;
|
|
102
|
+
this.applyRoutineOutParams(rows[0] ?? {}, outVars.map(o => o.param), args);
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
58
105
|
async commit(ctx, eventBroadcaster) {
|
|
59
106
|
if ('savepointName' in ctx) {
|
|
60
107
|
return;
|
package/MsSqlMikroORM.d.ts
CHANGED
|
@@ -2,17 +2,17 @@ import { type AnyEntity, type EntityClass, type EntitySchema, type MikroORM, typ
|
|
|
2
2
|
import { SqlMikroORM, type SqlEntityManager } from '@mikro-orm/sql';
|
|
3
3
|
import { MsSqlDriver } from './MsSqlDriver.js';
|
|
4
4
|
/** Configuration options for the MSSQL driver. */
|
|
5
|
-
export type MsSqlOptions<EM extends SqlEntityManager<MsSqlDriver> = SqlEntityManager<MsSqlDriver>, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> = Partial<Options<MsSqlDriver, EM, Entities>>;
|
|
5
|
+
export type MsSqlOptions<EM extends SqlEntityManager<MsSqlDriver> = SqlEntityManager<MsSqlDriver>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> = Partial<Options<MsSqlDriver, EM, Entities>>;
|
|
6
6
|
/** Creates a type-safe configuration object for the MSSQL driver. */
|
|
7
|
-
export declare function defineMsSqlConfig<EM extends SqlEntityManager<MsSqlDriver> = SqlEntityManager<MsSqlDriver>, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<MsSqlDriver, EM, Entities>>): Partial<Options<MsSqlDriver, EM, Entities>>;
|
|
7
|
+
export declare function defineMsSqlConfig<EM extends SqlEntityManager<MsSqlDriver> = SqlEntityManager<MsSqlDriver>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<MsSqlDriver, EM, Entities>>): Partial<Options<MsSqlDriver, EM, Entities>>;
|
|
8
8
|
/**
|
|
9
9
|
* @inheritDoc
|
|
10
10
|
*/
|
|
11
|
-
export declare class MsSqlMikroORM<EM extends SqlEntityManager<MsSqlDriver> = SqlEntityManager<MsSqlDriver>, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends SqlMikroORM<MsSqlDriver, EM, Entities> {
|
|
11
|
+
export declare class MsSqlMikroORM<EM extends SqlEntityManager<MsSqlDriver> = SqlEntityManager<MsSqlDriver>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends SqlMikroORM<MsSqlDriver, EM, Entities> {
|
|
12
12
|
/**
|
|
13
13
|
* @inheritDoc
|
|
14
14
|
*/
|
|
15
|
-
static init<D extends IDatabaseDriver = MsSqlDriver, EM extends EntityManager<D> = D[typeof EntityManagerType] & EntityManager<D>, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Promise<MikroORM<D, EM, Entities>>;
|
|
15
|
+
static init<D extends IDatabaseDriver = MsSqlDriver, EM extends EntityManager<D> = D[typeof EntityManagerType] & EntityManager<D>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Promise<MikroORM<D, EM, Entities>>;
|
|
16
16
|
/**
|
|
17
17
|
* @inheritDoc
|
|
18
18
|
*/
|
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);
|
|
@@ -140,6 +143,7 @@ export class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
140
143
|
return 'uniqueidentifier';
|
|
141
144
|
}
|
|
142
145
|
validateMetadata(meta) {
|
|
146
|
+
super.validateMetadata(meta);
|
|
143
147
|
for (const prop of meta.props) {
|
|
144
148
|
if ((prop.runtimeType === 'string' || ['string', 'nvarchar'].includes(prop.type)) &&
|
|
145
149
|
!['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 SqlRoutineDef, 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,21 @@ 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
|
+
routineParamReference(name: string): string;
|
|
33
|
+
/** T-SQL's `OUTPUT` covers both OUT and INOUT; `sys.parameters.is_output` is true for both. */
|
|
34
|
+
normaliseRoutineParamDirection(direction: 'in' | 'out' | 'inout'): 'in' | 'out' | 'inout';
|
|
35
|
+
createRoutine(routine: SqlRoutineDef): string;
|
|
36
|
+
dropRoutine(routine: SqlRoutineDef): string;
|
|
37
|
+
getAllRoutines(connection: AbstractSqlConnection): Promise<SqlRoutineDef[]>;
|
|
38
|
+
private getAllRoutineParams;
|
|
39
|
+
private unwrapMsSqlBody;
|
|
40
|
+
private getSchemaQualifiedName;
|
|
41
|
+
getDatabaseCollation(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string | undefined>;
|
|
42
|
+
getAllTriggers(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>): Promise<Dictionary<SqlTriggerDef[]>>;
|
|
26
43
|
loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[], ctx?: Transaction): Promise<void>;
|
|
27
44
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
28
45
|
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
|
}
|
|
@@ -104,7 +114,8 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
104
114
|
numeric_scale as numeric_scale,
|
|
105
115
|
datetime_precision as datetime_precision,
|
|
106
116
|
character_maximum_length as character_maximum_length,
|
|
107
|
-
columnproperty(sc.object_id, column_name, 'IsIdentity') is_identity
|
|
117
|
+
columnproperty(sc.object_id, column_name, 'IsIdentity') is_identity,
|
|
118
|
+
nullif(ic.collation_name, convert(nvarchar(128), databasepropertyex(db_name(), 'Collation'))) as collation_name
|
|
108
119
|
from information_schema.columns ic
|
|
109
120
|
inner join sys.columns sc on sc.name = ic.column_name and sc.object_id = object_id(ic.table_schema + '.' + ic.table_name)
|
|
110
121
|
left join sys.computed_columns cmp on cmp.name = ic.column_name and cmp.object_id = object_id(ic.table_schema + '.' + ic.table_name)
|
|
@@ -156,6 +167,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
156
167
|
precision: col.numeric_precision,
|
|
157
168
|
scale: col.numeric_scale,
|
|
158
169
|
comment: col.column_comment,
|
|
170
|
+
collation: col.collation_name ?? undefined,
|
|
159
171
|
generated,
|
|
160
172
|
});
|
|
161
173
|
}
|
|
@@ -169,6 +181,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
169
181
|
col.name as column_name,
|
|
170
182
|
schema_name(t.schema_id) as schema_name,
|
|
171
183
|
(case when filter_definition is not null then concat('where ', filter_definition) else null end) as expression,
|
|
184
|
+
filter_definition as filter_definition,
|
|
172
185
|
ind.is_disabled as is_disabled,
|
|
173
186
|
ind.type as index_type,
|
|
174
187
|
ind.fill_factor as fill_factor,
|
|
@@ -213,15 +226,31 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
213
226
|
if (index.fill_factor > 0) {
|
|
214
227
|
indexDef.fillFactor = index.fill_factor;
|
|
215
228
|
}
|
|
216
|
-
|
|
217
|
-
|
|
229
|
+
/* v8 ignore next: function-based / computed-column introspection path, same as pre-PR */
|
|
230
|
+
if (index.column_name?.match(/[(): ,"'`]/)) {
|
|
231
|
+
indexDef.expression = index.expression;
|
|
218
232
|
indexDef.expression = this.getCreateIndexSQL(index.table_name, indexDef, !!index.expression);
|
|
219
233
|
}
|
|
234
|
+
else if (index.filter_definition) {
|
|
235
|
+
// Auto-NOT-NULL stripping runs post-mapIndexes (needs the consolidated column list).
|
|
236
|
+
indexDef.where = index.filter_definition;
|
|
237
|
+
}
|
|
220
238
|
ret[key] ??= [];
|
|
221
239
|
ret[key].push(indexDef);
|
|
222
240
|
}
|
|
223
241
|
for (const key of Object.keys(ret)) {
|
|
224
242
|
ret[key] = await this.mapIndexes(ret[key]);
|
|
243
|
+
for (const idx of ret[key]) {
|
|
244
|
+
if (idx.where) {
|
|
245
|
+
const stripped = this.stripAutoNotNullFilter(idx.where, idx.columnNames, MsSqlSchemaHelper.AUTO_NOT_NULL_RE);
|
|
246
|
+
if (stripped === '') {
|
|
247
|
+
delete idx.where;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
idx.where = stripped;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
225
254
|
}
|
|
226
255
|
return ret;
|
|
227
256
|
}
|
|
@@ -306,6 +335,193 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
306
335
|
}
|
|
307
336
|
return ret;
|
|
308
337
|
}
|
|
338
|
+
/** Generates SQL to create an MSSQL trigger. MSSQL supports AFTER and INSTEAD OF only. */
|
|
339
|
+
createTrigger(table, trigger) {
|
|
340
|
+
if (trigger.expression) {
|
|
341
|
+
return trigger.expression;
|
|
342
|
+
}
|
|
343
|
+
/* v8 ignore next 3 */
|
|
344
|
+
if (trigger.timing === 'before') {
|
|
345
|
+
throw new Error(`MSSQL does not support BEFORE triggers. Use AFTER or INSTEAD OF for trigger "${trigger.name}".`);
|
|
346
|
+
}
|
|
347
|
+
const timing = trigger.timing.toUpperCase();
|
|
348
|
+
const events = trigger.events.map(e => e.toUpperCase()).join(', ');
|
|
349
|
+
const qualifiedName = this.getSchemaQualifiedName(table, trigger.name);
|
|
350
|
+
return `create trigger ${qualifiedName} on ${table.getQuotedName()} ${timing} ${events} as begin ${trigger.body}; end`;
|
|
351
|
+
}
|
|
352
|
+
/** Generates SQL to drop an MSSQL trigger. */
|
|
353
|
+
dropTrigger(table, trigger) {
|
|
354
|
+
return `drop trigger if exists ${this.getSchemaQualifiedName(table, trigger.name)}`;
|
|
355
|
+
}
|
|
356
|
+
routineParamReference(name) {
|
|
357
|
+
return `@${name}`;
|
|
358
|
+
}
|
|
359
|
+
/** T-SQL's `OUTPUT` covers both OUT and INOUT; `sys.parameters.is_output` is true for both. */
|
|
360
|
+
normaliseRoutineParamDirection(direction) {
|
|
361
|
+
return direction === 'out' ? 'inout' : direction;
|
|
362
|
+
}
|
|
363
|
+
createRoutine(routine) {
|
|
364
|
+
if (routine.expression) {
|
|
365
|
+
return routine.expression;
|
|
366
|
+
}
|
|
367
|
+
const qualifiedName = this.qualifiedRoutineName(routine);
|
|
368
|
+
const params = routine.params
|
|
369
|
+
.map(p => {
|
|
370
|
+
const dir = p.direction === 'out' || p.direction === 'inout' ? ' OUTPUT' : '';
|
|
371
|
+
return `@${p.name} ${p.type}${dir}`;
|
|
372
|
+
})
|
|
373
|
+
.join(', ');
|
|
374
|
+
const body = this.wrapRoutineBody(routine.body ?? '');
|
|
375
|
+
if (routine.type === 'procedure') {
|
|
376
|
+
return `create or alter procedure ${qualifiedName} ${params} as ${body}`;
|
|
377
|
+
}
|
|
378
|
+
const returnType = routine.returns?.type ?? 'nvarchar(max)';
|
|
379
|
+
return `create or alter function ${qualifiedName}(${params}) returns ${returnType} as ${body}`;
|
|
380
|
+
}
|
|
381
|
+
dropRoutine(routine) {
|
|
382
|
+
const kind = routine.type === 'procedure' ? 'procedure' : 'function';
|
|
383
|
+
return `drop ${kind} if exists ${this.qualifiedRoutineName(routine)}`;
|
|
384
|
+
}
|
|
385
|
+
async getAllRoutines(connection) {
|
|
386
|
+
const sql = `
|
|
387
|
+
select
|
|
388
|
+
s.name as schema_name,
|
|
389
|
+
o.name as name,
|
|
390
|
+
case
|
|
391
|
+
when o.type = 'P' then 'procedure'
|
|
392
|
+
when o.type in ('FN', 'IF', 'TF') then 'function'
|
|
393
|
+
end as kind,
|
|
394
|
+
m.definition as definition,
|
|
395
|
+
ep.value as comment
|
|
396
|
+
from sys.objects o
|
|
397
|
+
join sys.schemas s on s.schema_id = o.schema_id
|
|
398
|
+
join sys.sql_modules m on m.object_id = o.object_id
|
|
399
|
+
left join sys.extended_properties ep
|
|
400
|
+
on ep.major_id = o.object_id and ep.minor_id = 0 and ep.name = 'MS_Description'
|
|
401
|
+
where o.type in ('P', 'FN', 'IF', 'TF')
|
|
402
|
+
and o.is_ms_shipped = 0
|
|
403
|
+
`;
|
|
404
|
+
const [rows, paramsAndReturns] = await Promise.all([
|
|
405
|
+
connection.execute(sql),
|
|
406
|
+
this.getAllRoutineParams(connection),
|
|
407
|
+
]);
|
|
408
|
+
const { params, returns } = paramsAndReturns;
|
|
409
|
+
return rows.map(row => ({
|
|
410
|
+
name: row.name,
|
|
411
|
+
schema: row.schema_name,
|
|
412
|
+
type: row.kind,
|
|
413
|
+
body: this.unwrapMsSqlBody(row.definition),
|
|
414
|
+
comment: row.comment ?? undefined,
|
|
415
|
+
params: params.get(`${row.schema_name}.${row.name}`) ?? [],
|
|
416
|
+
returns: row.kind === 'function'
|
|
417
|
+
? (returns.get(`${row.schema_name}.${row.name}`) ?? { type: 'nvarchar(max)', nullable: true })
|
|
418
|
+
: undefined,
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
421
|
+
async getAllRoutineParams(connection) {
|
|
422
|
+
// `parameter_id = 0` is the function's return type; positive IDs are formal parameters.
|
|
423
|
+
const sql = `
|
|
424
|
+
select
|
|
425
|
+
s.name as schema_name,
|
|
426
|
+
o.name as routine_name,
|
|
427
|
+
p.name as param_name,
|
|
428
|
+
type_name(p.user_type_id) as type,
|
|
429
|
+
p.is_output as is_output,
|
|
430
|
+
p.parameter_id as position
|
|
431
|
+
from sys.parameters p
|
|
432
|
+
join sys.objects o on o.object_id = p.object_id
|
|
433
|
+
join sys.schemas s on s.schema_id = o.schema_id
|
|
434
|
+
where o.type in ('P', 'FN', 'IF', 'TF')
|
|
435
|
+
and o.is_ms_shipped = 0
|
|
436
|
+
order by o.object_id, p.parameter_id
|
|
437
|
+
`;
|
|
438
|
+
const rows = await connection.execute(sql);
|
|
439
|
+
const params = new Map();
|
|
440
|
+
const returns = new Map();
|
|
441
|
+
for (const row of rows) {
|
|
442
|
+
const key = `${row.schema_name}.${row.routine_name}`;
|
|
443
|
+
if (row.position === 0) {
|
|
444
|
+
returns.set(key, { type: row.type, nullable: true });
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (!params.has(key)) {
|
|
448
|
+
params.set(key, []);
|
|
449
|
+
}
|
|
450
|
+
// is_output is true for both OUT and INOUT; we always report `inout`. See normaliseRoutineParamDirection.
|
|
451
|
+
params.get(key).push({
|
|
452
|
+
name: row.param_name.replace(/^@/, ''),
|
|
453
|
+
type: row.type,
|
|
454
|
+
direction: row.is_output ? 'inout' : 'in',
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return { params, returns };
|
|
458
|
+
}
|
|
459
|
+
unwrapMsSqlBody(definition) {
|
|
460
|
+
const asMatch = /\bas\s+([\s\S]*)$/i.exec(definition);
|
|
461
|
+
return this.stripRoutineBody(asMatch ? asMatch[1] : definition);
|
|
462
|
+
}
|
|
463
|
+
getSchemaQualifiedName(table, name) {
|
|
464
|
+
const defaultSchema = this.platform.getDefaultSchemaName();
|
|
465
|
+
if (table.schema && table.schema !== defaultSchema) {
|
|
466
|
+
return `${this.quote(table.schema)}.${this.quote(name)}`;
|
|
467
|
+
}
|
|
468
|
+
return this.quote(name);
|
|
469
|
+
}
|
|
470
|
+
async getDatabaseCollation(connection, ctx) {
|
|
471
|
+
const [row] = await connection.execute(`select convert(nvarchar(128), databasepropertyex(db_name(), 'Collation')) as collation`, [], 'all', ctx);
|
|
472
|
+
return row?.collation;
|
|
473
|
+
}
|
|
474
|
+
async getAllTriggers(connection, tablesBySchemas) {
|
|
475
|
+
const conditions = [];
|
|
476
|
+
for (const [schema, tables] of tablesBySchemas) {
|
|
477
|
+
const names = tables.map(t => this.platform.quoteValue(t.table_name)).join(', ');
|
|
478
|
+
const schemaName = this.platform.quoteValue(schema ?? this.platform.getDefaultSchemaName());
|
|
479
|
+
conditions.push(`(schema_name(p.schema_id) = ${schemaName} and p.name in (${names}))`);
|
|
480
|
+
}
|
|
481
|
+
const sql = `select t.name as trigger_name, schema_name(p.schema_id) as schema_name,
|
|
482
|
+
p.name as table_name, te.type_desc as event,
|
|
483
|
+
case when t.is_instead_of_trigger = 1 then 'INSTEAD OF' else 'AFTER' end as timing,
|
|
484
|
+
object_definition(t.object_id) as definition
|
|
485
|
+
from sys.triggers t
|
|
486
|
+
join sys.trigger_events te on t.object_id = te.object_id
|
|
487
|
+
join sys.objects p on t.parent_id = p.object_id
|
|
488
|
+
where (${conditions.join(' or ')})
|
|
489
|
+
order by t.name, te.type_desc`;
|
|
490
|
+
const allTriggers = await connection.execute(sql);
|
|
491
|
+
const ret = {};
|
|
492
|
+
const triggerMap = new Map();
|
|
493
|
+
for (const row of allTriggers) {
|
|
494
|
+
const key = this.getTableKey(row);
|
|
495
|
+
const dedupeKey = `${key}:${row.trigger_name}`;
|
|
496
|
+
const event = row.event.toLowerCase();
|
|
497
|
+
if (triggerMap.has(dedupeKey)) {
|
|
498
|
+
const existing = triggerMap.get(dedupeKey);
|
|
499
|
+
if (!existing.events.includes(event)) {
|
|
500
|
+
existing.events.push(event);
|
|
501
|
+
}
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
// Parse body from full trigger definition
|
|
505
|
+
let body = '';
|
|
506
|
+
if (row.definition) {
|
|
507
|
+
const bodyMatch = /\bas\s+begin\s+([\s\S]*)\s*end\s*;?\s*$/i.exec(row.definition);
|
|
508
|
+
if (bodyMatch) {
|
|
509
|
+
body = bodyMatch[1].trim().replace(/;\s*$/, '');
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
ret[key] ??= [];
|
|
513
|
+
const trigger = {
|
|
514
|
+
name: row.trigger_name,
|
|
515
|
+
timing: row.timing.toLowerCase(),
|
|
516
|
+
events: [event],
|
|
517
|
+
forEach: 'row', // MSSQL has no FOR EACH ROW/STATEMENT syntax; match the metadata default to avoid false diffs
|
|
518
|
+
body,
|
|
519
|
+
};
|
|
520
|
+
ret[key].push(trigger);
|
|
521
|
+
triggerMap.set(dedupeKey, trigger);
|
|
522
|
+
}
|
|
523
|
+
return ret;
|
|
524
|
+
}
|
|
309
525
|
async loadInformationSchema(schema, connection, tables, schemas, ctx) {
|
|
310
526
|
if (tables.length === 0) {
|
|
311
527
|
return;
|
|
@@ -315,12 +531,18 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
315
531
|
const indexes = await this.getAllIndexes(connection, tablesBySchema, ctx);
|
|
316
532
|
const checks = await this.getAllChecks(connection, tablesBySchema, ctx);
|
|
317
533
|
const fks = await this.getAllForeignKeys(connection, tablesBySchema, ctx);
|
|
534
|
+
const triggers = await this.getAllTriggers(connection, tablesBySchema);
|
|
535
|
+
const dbCollation = await this.getDatabaseCollation(connection, ctx);
|
|
318
536
|
for (const t of tables) {
|
|
319
537
|
const key = this.getTableKey(t);
|
|
320
538
|
const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
|
|
539
|
+
table.collation = dbCollation;
|
|
321
540
|
const pks = await this.getPrimaryKeys(connection, indexes[key], table.name, table.schema);
|
|
322
541
|
const enums = this.getEnumDefinitions(checks[key] ?? []);
|
|
323
542
|
table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums);
|
|
543
|
+
if (triggers[key]) {
|
|
544
|
+
table.setTriggers(triggers[key]);
|
|
545
|
+
}
|
|
324
546
|
}
|
|
325
547
|
}
|
|
326
548
|
getPreAlterTable(tableDiff, safe) {
|
|
@@ -436,7 +658,11 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
436
658
|
else {
|
|
437
659
|
col.push(columnType);
|
|
438
660
|
}
|
|
439
|
-
Utils.runIfNotEmpty(() => col.push(
|
|
661
|
+
Utils.runIfNotEmpty(() => col.push(this.getCollateSQL(column.collation)), column.collation);
|
|
662
|
+
// `IDENTITY(1,1)` is rejected inside `ALTER COLUMN`, so it must only be emitted when the
|
|
663
|
+
// change actually involves the identity attribute or is a fresh column (no `changedProperties`).
|
|
664
|
+
Utils.runIfNotEmpty(() => col.push('identity(1,1)'), column.autoincrement &&
|
|
665
|
+
(!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type')));
|
|
440
666
|
Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
|
|
441
667
|
Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
|
|
442
668
|
if (column.autoincrement &&
|
|
@@ -458,7 +684,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
458
684
|
const [constraint] = this.getDropDefaultsSQL(table.name, [column], table.schema);
|
|
459
685
|
parts.push(constraint);
|
|
460
686
|
}
|
|
461
|
-
if (changedProperties.has('type') || changedProperties.has('nullable')) {
|
|
687
|
+
if (changedProperties.has('type') || changedProperties.has('nullable') || changedProperties.has('collation')) {
|
|
462
688
|
const col = this.createTableColumn(column, table, changedProperties);
|
|
463
689
|
parts.push(`alter table ${table.getQuotedName()} alter column ${col}`);
|
|
464
690
|
}
|
|
@@ -481,7 +707,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
481
707
|
const clustered = index.clustered ? 'clustered ' : '';
|
|
482
708
|
let sql = `create ${index.unique ? 'unique ' : ''}${clustered}index ${keyName} on ${this.quote(tableName)} `;
|
|
483
709
|
if (index.expression && partialExpression) {
|
|
484
|
-
return sql + `(${index.expression})` + this.getMsSqlIndexSuffix(index);
|
|
710
|
+
return sql + `(${index.expression})` + this.getMsSqlIndexSuffix(index) + this.getIndexWhereClause(index);
|
|
485
711
|
}
|
|
486
712
|
// Build column list with advanced options
|
|
487
713
|
const columns = this.getIndexColumns(index);
|
|
@@ -490,7 +716,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
490
716
|
if (index.include?.length) {
|
|
491
717
|
sql += ` include (${index.include.map(c => this.quote(c)).join(', ')})`;
|
|
492
718
|
}
|
|
493
|
-
sql += this.getMsSqlIndexSuffix(index);
|
|
719
|
+
sql += this.getMsSqlIndexSuffix(index) + this.getIndexWhereClause(index);
|
|
494
720
|
// Disabled indexes need to be created first, then disabled
|
|
495
721
|
if (index.disabled) {
|
|
496
722
|
sql += `;\nalter index ${keyName} on ${this.quote(tableName)} disable`;
|
|
@@ -533,13 +759,16 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
533
759
|
if (index.expression) {
|
|
534
760
|
return index.expression;
|
|
535
761
|
}
|
|
536
|
-
const
|
|
537
|
-
if (!
|
|
762
|
+
const needsAutoNotNull = index.unique && index.columnNames.some(column => table.getColumn(column)?.nullable);
|
|
763
|
+
if (!needsAutoNotNull) {
|
|
538
764
|
return this.getCreateIndexSQL(table.getShortestName(), index);
|
|
539
765
|
}
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
766
|
+
// Strip `index.where` from the base SQL so we can combine it with the auto NOT-NULL guard
|
|
767
|
+
// ourselves, wrapping the user predicate in parens to defuse operator precedence
|
|
768
|
+
// (a bare `a = 1 or b = 2 and [col] is not null` would bind as `a = 1 or (b = 2 and …)`).
|
|
769
|
+
let sql = this.getCreateIndexSQL(table.getShortestName(), { ...index, where: undefined, disabled: false });
|
|
770
|
+
const autoNotNull = index.columnNames.map(c => `${this.quote(c)} is not null`).join(' and ');
|
|
771
|
+
sql += index.where ? ` where (${index.where}) and ${autoNotNull}` : ` where ${autoNotNull}`;
|
|
543
772
|
if (index.disabled) {
|
|
544
773
|
sql += `;\nalter index ${this.quote(index.keyName)} on ${table.getQuotedName()} disable`;
|
|
545
774
|
}
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<a href="https://mikro-orm.io"><img src="https://raw.githubusercontent.com/mikro-orm/mikro-orm/master/docs/static/img/logo-readme.svg?sanitize=true" alt="MikroORM" /></a>
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
|
-
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite (including libSQL), MSSQL and Oracle databases.
|
|
5
|
+
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL (including CockroachDB and PGlite), SQLite (including libSQL), MSSQL and Oracle databases.
|
|
6
6
|
|
|
7
7
|
> Heavily inspired by [Doctrine](https://www.doctrine-project.org/) and [Hibernate](https://hibernate.org/).
|
|
8
8
|
|
|
@@ -19,6 +19,7 @@ Install a driver package for your database:
|
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
21
|
npm install @mikro-orm/postgresql # PostgreSQL
|
|
22
|
+
npm install @mikro-orm/pglite # PGlite (embedded PostgreSQL in WASM)
|
|
22
23
|
npm install @mikro-orm/mysql # MySQL
|
|
23
24
|
npm install @mikro-orm/mariadb # MariaDB
|
|
24
25
|
npm install @mikro-orm/sqlite # SQLite
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/mssql",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.18-dev.0",
|
|
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,7 +47,7 @@
|
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@mikro-orm/sql": "7.0.
|
|
50
|
+
"@mikro-orm/sql": "7.0.18-dev.0",
|
|
51
51
|
"kysely": "0.29.2",
|
|
52
52
|
"tarn": "3.0.2",
|
|
53
53
|
"tedious": "19.2.1",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@mikro-orm/core": "^7.0.17"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@mikro-orm/core": "7.0.
|
|
60
|
+
"@mikro-orm/core": "7.0.18-dev.0"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|
|
63
63
|
"node": ">= 22.17.0"
|