@mikro-orm/mssql 7.1.0-dev.8 → 7.1.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 +3 -3
- package/MsSqlMikroORM.js +3 -2
- package/MsSqlPlatform.d.ts +2 -0
- package/MsSqlPlatform.js +10 -4
- package/MsSqlSchemaHelper.d.ts +12 -1
- package/MsSqlSchemaHelper.js +194 -26
- package/README.md +2 -1
- package/index.d.ts +4 -0
- package/index.js +2 -0
- package/package.json +5 -5
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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type AnyEntity, type EntityClass, type EntitySchema, MikroORM, type Options, type IDatabaseDriver, type EntityManager, type EntityManagerType } from '@mikro-orm/core';
|
|
2
|
-
import type
|
|
1
|
+
import { type AnyEntity, type EntityClass, type EntitySchema, type MikroORM, type Options, type IDatabaseDriver, type EntityManager, type EntityManagerType } from '@mikro-orm/core';
|
|
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
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>>;
|
|
@@ -8,7 +8,7 @@ export declare function defineMsSqlConfig<EM extends SqlEntityManager<MsSqlDrive
|
|
|
8
8
|
/**
|
|
9
9
|
* @inheritDoc
|
|
10
10
|
*/
|
|
11
|
-
export declare class MsSqlMikroORM<EM extends SqlEntityManager<MsSqlDriver> = SqlEntityManager<MsSqlDriver>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends
|
|
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
|
*/
|
package/MsSqlMikroORM.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { defineConfig,
|
|
1
|
+
import { defineConfig, } from '@mikro-orm/core';
|
|
2
|
+
import { SqlMikroORM } from '@mikro-orm/sql';
|
|
2
3
|
import { MsSqlDriver } from './MsSqlDriver.js';
|
|
3
4
|
/** Creates a type-safe configuration object for the MSSQL driver. */
|
|
4
5
|
export function defineMsSqlConfig(options) {
|
|
@@ -7,7 +8,7 @@ export function defineMsSqlConfig(options) {
|
|
|
7
8
|
/**
|
|
8
9
|
* @inheritDoc
|
|
9
10
|
*/
|
|
10
|
-
export class MsSqlMikroORM extends
|
|
11
|
+
export class MsSqlMikroORM extends SqlMikroORM {
|
|
11
12
|
/**
|
|
12
13
|
* @inheritDoc
|
|
13
14
|
*/
|
package/MsSqlPlatform.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export declare class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
21
21
|
convertDateToJSValue(value: string | Date): string;
|
|
22
22
|
convertsJsonAutomatically(): boolean;
|
|
23
23
|
indexForeignKeys(): boolean;
|
|
24
|
+
/** SQL Server identifier limit (sysname). */
|
|
25
|
+
getMaxIdentifierLength(): number;
|
|
24
26
|
supportsSchemas(): boolean;
|
|
25
27
|
getCurrentTimestampSQL(length: number): string;
|
|
26
28
|
getDateTimeTypeDeclarationSQL(column: {
|
package/MsSqlPlatform.js
CHANGED
|
@@ -55,6 +55,10 @@ export class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
55
55
|
indexForeignKeys() {
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
|
+
/** SQL Server identifier limit (sysname). */
|
|
59
|
+
getMaxIdentifierLength() {
|
|
60
|
+
return 128;
|
|
61
|
+
}
|
|
58
62
|
supportsSchemas() {
|
|
59
63
|
return true;
|
|
60
64
|
}
|
|
@@ -139,6 +143,7 @@ export class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
139
143
|
return 'uniqueidentifier';
|
|
140
144
|
}
|
|
141
145
|
validateMetadata(meta) {
|
|
146
|
+
super.validateMetadata(meta);
|
|
142
147
|
for (const prop of meta.props) {
|
|
143
148
|
if ((prop.runtimeType === 'string' || ['string', 'nvarchar'].includes(prop.type)) &&
|
|
144
149
|
!['uuid'].includes(prop.type) &&
|
|
@@ -153,16 +158,17 @@ export class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
153
158
|
getSearchJsonPropertyKey(path, type, aliased, value) {
|
|
154
159
|
const [a, ...b] = path;
|
|
155
160
|
/* v8 ignore next */
|
|
156
|
-
const root =
|
|
161
|
+
const root = aliased ? `[${ALIAS_REPLACEMENT}].${this.quoteIdentifier(a)}` : this.quoteIdentifier(a);
|
|
157
162
|
const types = {
|
|
158
163
|
boolean: 'bit',
|
|
159
164
|
};
|
|
160
165
|
const cast = (key) => raw(type in types ? `cast(${key} as ${types[type]})` : key);
|
|
166
|
+
const jsonPath = this.quoteValue(`$.${b.map(this.quoteJsonKey).join('.')}`);
|
|
161
167
|
/* v8 ignore next */
|
|
162
168
|
if (path.length === 0) {
|
|
163
|
-
return cast(`json_value(${root},
|
|
169
|
+
return cast(`json_value(${root}, ${jsonPath})`);
|
|
164
170
|
}
|
|
165
|
-
return cast(`json_value(${root},
|
|
171
|
+
return cast(`json_value(${root}, ${jsonPath})`);
|
|
166
172
|
}
|
|
167
173
|
getJsonArrayFromSQL(column, alias, properties) {
|
|
168
174
|
const columns = properties
|
|
@@ -190,7 +196,7 @@ export class MsSqlPlatform extends AbstractSqlPlatform {
|
|
|
190
196
|
if (RawQueryFragment.isKnownFragment(id)) {
|
|
191
197
|
return super.quoteIdentifier(id);
|
|
192
198
|
}
|
|
193
|
-
return `[${id.toString().replace('.', `].[`)}]`;
|
|
199
|
+
return `[${id.toString().replaceAll(']', ']]').replace('.', `].[`)}]`;
|
|
194
200
|
}
|
|
195
201
|
escape(value) {
|
|
196
202
|
if (value instanceof UnicodeString) {
|
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 SqlTriggerDef, 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;
|
|
@@ -27,7 +29,16 @@ export declare class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
27
29
|
createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
28
30
|
/** Generates SQL to drop an MSSQL trigger. */
|
|
29
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;
|
|
30
40
|
private getSchemaQualifiedName;
|
|
41
|
+
getDatabaseCollation(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string | undefined>;
|
|
31
42
|
getAllTriggers(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>): Promise<Dictionary<SqlTriggerDef[]>>;
|
|
32
43
|
loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[], ctx?: Transaction): Promise<void>;
|
|
33
44
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
package/MsSqlSchemaHelper.js
CHANGED
|
@@ -7,12 +7,43 @@ 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
|
}
|
|
13
23
|
getDropDatabaseSQL(name) {
|
|
24
|
+
// `set offline` rejects all connections including the issuing session, so there is no
|
|
25
|
+
// single-user race window where a torn-down pool connection can reconnect between the
|
|
26
|
+
// mode switch and the drop (SQL Server error 3702 "currently in use"). Dropping an
|
|
27
|
+
// offline database leaves the underlying `.mdf`/`.ldf` files behind though, which
|
|
28
|
+
// makes a subsequent `create database` with the same name fail with error 5170
|
|
29
|
+
// ("file already exists"). Capture the physical paths up front and call
|
|
30
|
+
// `master.sys.xp_delete_files` after the drop to clean them up.
|
|
14
31
|
const quoted = this.quote(name);
|
|
15
|
-
|
|
32
|
+
const literal = this.platform.quoteValue(name);
|
|
33
|
+
return (`if db_id(${literal}) is not null begin ` +
|
|
34
|
+
`declare @drop_files table (path nvarchar(260)); ` +
|
|
35
|
+
`insert into @drop_files (path) select physical_name from sys.master_files where database_id = db_id(${literal}); ` +
|
|
36
|
+
`alter database ${quoted} set offline with rollback immediate; ` +
|
|
37
|
+
`drop database ${quoted}; ` +
|
|
38
|
+
`declare @drop_path nvarchar(260); ` +
|
|
39
|
+
`declare drop_files_cursor cursor local fast_forward for select path from @drop_files; ` +
|
|
40
|
+
`open drop_files_cursor; fetch next from drop_files_cursor into @drop_path; ` +
|
|
41
|
+
`while @@fetch_status = 0 begin ` +
|
|
42
|
+
`begin try exec master.sys.xp_delete_files @drop_path; end try begin catch end catch; ` +
|
|
43
|
+
`fetch next from drop_files_cursor into @drop_path; ` +
|
|
44
|
+
`end ` +
|
|
45
|
+
`close drop_files_cursor; deallocate drop_files_cursor; ` +
|
|
46
|
+
`end`);
|
|
16
47
|
}
|
|
17
48
|
disableForeignKeysSQL() {
|
|
18
49
|
return `exec sp_MSforeachtable 'alter table ? nocheck constraint all';`;
|
|
@@ -83,7 +114,8 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
83
114
|
numeric_scale as numeric_scale,
|
|
84
115
|
datetime_precision as datetime_precision,
|
|
85
116
|
character_maximum_length as character_maximum_length,
|
|
86
|
-
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
|
|
87
119
|
from information_schema.columns ic
|
|
88
120
|
inner join sys.columns sc on sc.name = ic.column_name and sc.object_id = object_id(ic.table_schema + '.' + ic.table_name)
|
|
89
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)
|
|
@@ -135,6 +167,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
135
167
|
precision: col.numeric_precision,
|
|
136
168
|
scale: col.numeric_scale,
|
|
137
169
|
comment: col.column_comment,
|
|
170
|
+
collation: col.collation_name ?? undefined,
|
|
138
171
|
generated,
|
|
139
172
|
});
|
|
140
173
|
}
|
|
@@ -148,6 +181,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
148
181
|
col.name as column_name,
|
|
149
182
|
schema_name(t.schema_id) as schema_name,
|
|
150
183
|
(case when filter_definition is not null then concat('where ', filter_definition) else null end) as expression,
|
|
184
|
+
filter_definition as filter_definition,
|
|
151
185
|
ind.is_disabled as is_disabled,
|
|
152
186
|
ind.type as index_type,
|
|
153
187
|
ind.fill_factor as fill_factor,
|
|
@@ -192,15 +226,31 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
192
226
|
if (index.fill_factor > 0) {
|
|
193
227
|
indexDef.fillFactor = index.fill_factor;
|
|
194
228
|
}
|
|
195
|
-
|
|
196
|
-
|
|
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;
|
|
197
232
|
indexDef.expression = this.getCreateIndexSQL(index.table_name, indexDef, !!index.expression);
|
|
198
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
|
+
}
|
|
199
238
|
ret[key] ??= [];
|
|
200
239
|
ret[key].push(indexDef);
|
|
201
240
|
}
|
|
202
241
|
for (const key of Object.keys(ret)) {
|
|
203
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
|
+
}
|
|
204
254
|
}
|
|
205
255
|
return ret;
|
|
206
256
|
}
|
|
@@ -303,6 +353,113 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
303
353
|
dropTrigger(table, trigger) {
|
|
304
354
|
return `drop trigger if exists ${this.getSchemaQualifiedName(table, trigger.name)}`;
|
|
305
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
|
+
}
|
|
306
463
|
getSchemaQualifiedName(table, name) {
|
|
307
464
|
const defaultSchema = this.platform.getDefaultSchemaName();
|
|
308
465
|
if (table.schema && table.schema !== defaultSchema) {
|
|
@@ -310,6 +467,10 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
310
467
|
}
|
|
311
468
|
return this.quote(name);
|
|
312
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
|
+
}
|
|
313
474
|
async getAllTriggers(connection, tablesBySchemas) {
|
|
314
475
|
const conditions = [];
|
|
315
476
|
for (const [schema, tables] of tablesBySchemas) {
|
|
@@ -371,9 +532,11 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
371
532
|
const checks = await this.getAllChecks(connection, tablesBySchema, ctx);
|
|
372
533
|
const fks = await this.getAllForeignKeys(connection, tablesBySchema, ctx);
|
|
373
534
|
const triggers = await this.getAllTriggers(connection, tablesBySchema);
|
|
535
|
+
const dbCollation = await this.getDatabaseCollation(connection, ctx);
|
|
374
536
|
for (const t of tables) {
|
|
375
537
|
const key = this.getTableKey(t);
|
|
376
538
|
const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
|
|
539
|
+
table.collation = dbCollation;
|
|
377
540
|
const pks = await this.getPrimaryKeys(connection, indexes[key], table.name, table.schema);
|
|
378
541
|
const enums = this.getEnumDefinitions(checks[key] ?? []);
|
|
379
542
|
table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums);
|
|
@@ -495,7 +658,11 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
495
658
|
else {
|
|
496
659
|
col.push(columnType);
|
|
497
660
|
}
|
|
498
|
-
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')));
|
|
499
666
|
Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
|
|
500
667
|
Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
|
|
501
668
|
if (column.autoincrement &&
|
|
@@ -517,7 +684,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
517
684
|
const [constraint] = this.getDropDefaultsSQL(table.name, [column], table.schema);
|
|
518
685
|
parts.push(constraint);
|
|
519
686
|
}
|
|
520
|
-
if (changedProperties.has('type') || changedProperties.has('nullable')) {
|
|
687
|
+
if (changedProperties.has('type') || changedProperties.has('nullable') || changedProperties.has('collation')) {
|
|
521
688
|
const col = this.createTableColumn(column, table, changedProperties);
|
|
522
689
|
parts.push(`alter table ${table.getQuotedName()} alter column ${col}`);
|
|
523
690
|
}
|
|
@@ -540,7 +707,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
540
707
|
const clustered = index.clustered ? 'clustered ' : '';
|
|
541
708
|
let sql = `create ${index.unique ? 'unique ' : ''}${clustered}index ${keyName} on ${this.quote(tableName)} `;
|
|
542
709
|
if (index.expression && partialExpression) {
|
|
543
|
-
return sql + `(${index.expression})` + this.getMsSqlIndexSuffix(index);
|
|
710
|
+
return sql + `(${index.expression})` + this.getMsSqlIndexSuffix(index) + this.getIndexWhereClause(index);
|
|
544
711
|
}
|
|
545
712
|
// Build column list with advanced options
|
|
546
713
|
const columns = this.getIndexColumns(index);
|
|
@@ -549,7 +716,7 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
549
716
|
if (index.include?.length) {
|
|
550
717
|
sql += ` include (${index.include.map(c => this.quote(c)).join(', ')})`;
|
|
551
718
|
}
|
|
552
|
-
sql += this.getMsSqlIndexSuffix(index);
|
|
719
|
+
sql += this.getMsSqlIndexSuffix(index) + this.getIndexWhereClause(index);
|
|
553
720
|
// Disabled indexes need to be created first, then disabled
|
|
554
721
|
if (index.disabled) {
|
|
555
722
|
sql += `;\nalter index ${keyName} on ${this.quote(tableName)} disable`;
|
|
@@ -560,19 +727,17 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
560
727
|
* Build the column list for a MSSQL index.
|
|
561
728
|
*/
|
|
562
729
|
getIndexColumns(index) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
return index.columnNames.map(c => this.quote(c)).join(', ');
|
|
730
|
+
return index.columnNames
|
|
731
|
+
.map(name => {
|
|
732
|
+
const col = index.columns?.find(c => c.name === name);
|
|
733
|
+
let colDef = this.quote(name);
|
|
734
|
+
// MSSQL supports sort order
|
|
735
|
+
if (col?.sort) {
|
|
736
|
+
colDef += ` ${col.sort}`;
|
|
737
|
+
}
|
|
738
|
+
return colDef;
|
|
739
|
+
})
|
|
740
|
+
.join(', ');
|
|
576
741
|
}
|
|
577
742
|
/**
|
|
578
743
|
* Get MSSQL-specific index WITH options like fill factor.
|
|
@@ -594,13 +759,16 @@ export class MsSqlSchemaHelper extends SchemaHelper {
|
|
|
594
759
|
if (index.expression) {
|
|
595
760
|
return index.expression;
|
|
596
761
|
}
|
|
597
|
-
const
|
|
598
|
-
if (!
|
|
762
|
+
const needsAutoNotNull = index.unique && index.columnNames.some(column => table.getColumn(column)?.nullable);
|
|
763
|
+
if (!needsAutoNotNull) {
|
|
599
764
|
return this.getCreateIndexSQL(table.getShortestName(), index);
|
|
600
765
|
}
|
|
601
|
-
//
|
|
602
|
-
|
|
603
|
-
|
|
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}`;
|
|
604
772
|
if (index.disabled) {
|
|
605
773
|
sql += `;\nalter index ${this.quote(index.keyName)} on ${table.getQuotedName()} disable`;
|
|
606
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/index.d.ts
CHANGED
|
@@ -5,3 +5,7 @@ export * from './MsSqlPlatform.js';
|
|
|
5
5
|
export * from './MsSqlSchemaHelper.js';
|
|
6
6
|
export * from './UnicodeStringType.js';
|
|
7
7
|
export { MsSqlMikroORM as MikroORM, type MsSqlOptions as Options, defineMsSqlConfig as defineConfig, } from './MsSqlMikroORM.js';
|
|
8
|
+
import { type AbstractSqlDriver, SqlEntityManager } from '@mikro-orm/sql';
|
|
9
|
+
import type { MsSqlDriver } from './MsSqlDriver.js';
|
|
10
|
+
export type EntityManager<Driver extends AbstractSqlDriver = MsSqlDriver> = SqlEntityManager<Driver>;
|
|
11
|
+
export declare const EntityManager: typeof SqlEntityManager;
|
package/index.js
CHANGED
|
@@ -5,3 +5,5 @@ export * from './MsSqlPlatform.js';
|
|
|
5
5
|
export * from './MsSqlSchemaHelper.js';
|
|
6
6
|
export * from './UnicodeStringType.js';
|
|
7
7
|
export { MsSqlMikroORM as MikroORM, defineMsSqlConfig as defineConfig, } from './MsSqlMikroORM.js';
|
|
8
|
+
import { SqlEntityManager } from '@mikro-orm/sql';
|
|
9
|
+
export const EntityManager = SqlEntityManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/mssql",
|
|
3
|
-
"version": "7.1.0
|
|
3
|
+
"version": "7.1.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,17 +47,17 @@
|
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@mikro-orm/sql": "7.1.0
|
|
51
|
-
"kysely": "0.
|
|
50
|
+
"@mikro-orm/sql": "7.1.0",
|
|
51
|
+
"kysely": "0.29.2",
|
|
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.1.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@mikro-orm/core": "7.1.0
|
|
60
|
+
"@mikro-orm/core": "7.1.0"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|
|
63
63
|
"node": ">= 22.17.0"
|