@mikro-orm/mssql 7.1.0-dev.43 → 7.1.0-dev.44

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.
@@ -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
  }
@@ -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;
@@ -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: {
@@ -29,6 +29,14 @@ export declare class MsSqlSchemaHelper extends SchemaHelper {
29
29
  createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
30
30
  /** Generates SQL to drop an MSSQL trigger. */
31
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;
32
40
  private getSchemaQualifiedName;
33
41
  getDatabaseCollation(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string | undefined>;
34
42
  getAllTriggers(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>): Promise<Dictionary<SqlTriggerDef[]>>;
@@ -353,6 +353,113 @@ export class MsSqlSchemaHelper extends SchemaHelper {
353
353
  dropTrigger(table, trigger) {
354
354
  return `drop trigger if exists ${this.getSchemaQualifiedName(table, trigger.name)}`;
355
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
+ }
356
463
  getSchemaQualifiedName(table, name) {
357
464
  const defaultSchema = this.platform.getDefaultSchemaName();
358
465
  if (table.schema && table.schema !== defaultSchema) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/mssql",
3
- "version": "7.1.0-dev.43",
3
+ "version": "7.1.0-dev.44",
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.1.0-dev.43",
50
+ "@mikro-orm/sql": "7.1.0-dev.44",
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.1.0-dev.43"
60
+ "@mikro-orm/core": "7.1.0-dev.44"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">= 22.17.0"