• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 14985617931

13 May 2025 12:45AM UTC coverage: 76.341% (-0.04%) from 76.378%
14985617931

push

github

web-flow
Fix/11466 mssql find operator (#11468)

* chore: enable driver `mssql` for test of issue #3113

* chore: add test case for #11298 to issue #11285 to prevent functionality of #11285

* fix: unhandled find operator with array value for mssql (#11466)

* fixup! fix: unhandled find operator with array value for mssql (#11466)

* Revert "chore: enable driver `mssql` for test of issue #3113"

This reverts commit a302d63ee.

* fixup! chore: add test case for #11298 to issue #11285 to prevent functionality of #11285

---------

Co-authored-by: Christian Forgács <christian@wunderbit.de>

9188 of 12738 branches covered (72.13%)

Branch coverage included in aggregate %.

11 of 12 new or added lines in 2 files covered. (91.67%)

8 existing lines in 3 files now uncovered.

18859 of 24001 relevant lines covered (78.58%)

197452.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.63
/src/driver/sqlserver/SqlServerDriver.ts
1
import { Driver } from "../Driver"
2
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
40✔
3
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
40✔
4
import { DriverUtils } from "../DriverUtils"
40✔
5
import { CteCapabilities } from "../types/CteCapabilities"
6
import { SqlServerQueryRunner } from "./SqlServerQueryRunner"
40✔
7
import { ObjectLiteral } from "../../common/ObjectLiteral"
8
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
9
import { DateUtils } from "../../util/DateUtils"
40✔
10
import { PlatformTools } from "../../platform/PlatformTools"
40✔
11
import { DataSource } from "../../data-source/DataSource"
12
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
40✔
13
import { SqlServerConnectionOptions } from "./SqlServerConnectionOptions"
14
import { MappedColumnTypes } from "../types/MappedColumnTypes"
15
import { ColumnType } from "../types/ColumnTypes"
16
import { DataTypeDefaults } from "../types/DataTypeDefaults"
17
import { MssqlParameter } from "./MssqlParameter"
40✔
18
import { TableColumn } from "../../schema-builder/table/TableColumn"
40✔
19
import { SqlServerConnectionCredentialsOptions } from "./SqlServerConnectionCredentialsOptions"
20
import { EntityMetadata } from "../../metadata/EntityMetadata"
21
import { OrmUtils } from "../../util/OrmUtils"
40✔
22
import { ApplyValueTransformers } from "../../util/ApplyValueTransformers"
40✔
23
import { ReplicationMode } from "../types/ReplicationMode"
24
import { Table } from "../../schema-builder/table/Table"
25
import { View } from "../../schema-builder/view/View"
26
import { TableForeignKey } from "../../schema-builder/table/TableForeignKey"
27
import { TypeORMError } from "../../error"
40✔
28
import { InstanceChecker } from "../../util/InstanceChecker"
40✔
29
import { UpsertType } from "../types/UpsertType"
30
import { FindOperator } from "../../find-options/FindOperator"
40✔
31

32
/**
33
 * Organizes communication with SQL Server DBMS.
34
 */
35
export class SqlServerDriver implements Driver {
40✔
36
    // -------------------------------------------------------------------------
37
    // Public Properties
38
    // -------------------------------------------------------------------------
39

40
    /**
41
     * Connection used by driver.
42
     */
43
    connection: DataSource
44

45
    /**
46
     * SQL Server library.
47
     */
48
    mssql: any
49

50
    /**
51
     * Pool for master database.
52
     */
53
    master: any
54

55
    /**
56
     * Pool for slave databases.
57
     * Used in replication.
58
     */
59
    slaves: any[] = []
4,293✔
60

61
    // -------------------------------------------------------------------------
62
    // Public Implemented Properties
63
    // -------------------------------------------------------------------------
64

65
    /**
66
     * Connection options.
67
     */
68
    options: SqlServerConnectionOptions
69

70
    /**
71
     * Database name used to perform all write queries.
72
     */
73
    database?: string
74

75
    /**
76
     * Schema name used to perform all write queries.
77
     */
78
    schema?: string
79

80
    /**
81
     * Schema that's used internally by SQL Server for object resolution.
82
     *
83
     * Because we never set this we have to track it in separately from the `schema` so
84
     * we know when we have to specify the full schema or not.
85
     *
86
     * In most cases this will be `dbo`.
87
     */
88
    searchSchema?: string
89

90
    /**
91
     * Indicates if replication is enabled.
92
     */
93
    isReplicated: boolean = false
4,293✔
94

95
    /**
96
     * Indicates if tree tables are supported by this driver.
97
     */
98
    treeSupport = true
4,293✔
99

100
    /**
101
     * Represent transaction support by this driver
102
     */
103
    transactionSupport = "simple" as const
4,293✔
104

105
    /**
106
     * Gets list of supported column data types by a driver.
107
     *
108
     * @see https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql
109
     */
110
    supportedDataTypes: ColumnType[] = [
4,293✔
111
        "int",
112
        "bigint",
113
        "bit",
114
        "decimal",
115
        "money",
116
        "numeric",
117
        "smallint",
118
        "smallmoney",
119
        "tinyint",
120
        "float",
121
        "real",
122
        "date",
123
        "datetime2",
124
        "datetime",
125
        "datetimeoffset",
126
        "smalldatetime",
127
        "time",
128
        "char",
129
        "varchar",
130
        "text",
131
        "nchar",
132
        "nvarchar",
133
        "ntext",
134
        "binary",
135
        "image",
136
        "varbinary",
137
        "hierarchyid",
138
        "sql_variant",
139
        "timestamp",
140
        "uniqueidentifier",
141
        "xml",
142
        "geometry",
143
        "geography",
144
        "rowversion",
145
    ]
146

147
    /**
148
     * Returns type of upsert supported by driver if any
149
     */
150
    supportedUpsertTypes: UpsertType[] = []
4,293✔
151

152
    /**
153
     * Gets list of spatial column data types.
154
     */
155
    spatialTypes: ColumnType[] = ["geometry", "geography"]
4,293✔
156

157
    /**
158
     * Gets list of column data types that support length by a driver.
159
     */
160
    withLengthColumnTypes: ColumnType[] = [
4,293✔
161
        "char",
162
        "varchar",
163
        "nchar",
164
        "nvarchar",
165
        "binary",
166
        "varbinary",
167
    ]
168

169
    /**
170
     * Gets list of column data types that support precision by a driver.
171
     */
172
    withPrecisionColumnTypes: ColumnType[] = [
4,293✔
173
        "decimal",
174
        "numeric",
175
        "time",
176
        "datetime2",
177
        "datetimeoffset",
178
    ]
179

180
    /**
181
     * Gets list of column data types that support scale by a driver.
182
     */
183
    withScaleColumnTypes: ColumnType[] = ["decimal", "numeric"]
4,293✔
184

185
    /**
186
     * Orm has special columns and we need to know what database column types should be for those types.
187
     * Column types are driver dependant.
188
     */
189
    mappedDataTypes: MappedColumnTypes = {
4,293✔
190
        createDate: "datetime2",
191
        createDateDefault: "getdate()",
192
        updateDate: "datetime2",
193
        updateDateDefault: "getdate()",
194
        deleteDate: "datetime2",
195
        deleteDateNullable: true,
196
        version: "int",
197
        treeLevel: "int",
198
        migrationId: "int",
199
        migrationName: "varchar",
200
        migrationTimestamp: "bigint",
201
        cacheId: "int",
202
        cacheIdentifier: "nvarchar",
203
        cacheTime: "bigint",
204
        cacheDuration: "int",
205
        cacheQuery: "nvarchar(MAX)" as any,
206
        cacheResult: "nvarchar(MAX)" as any,
207
        metadataType: "varchar",
208
        metadataDatabase: "varchar",
209
        metadataSchema: "varchar",
210
        metadataTable: "varchar",
211
        metadataName: "varchar",
212
        metadataValue: "nvarchar(MAX)" as any,
213
    }
214

215
    /**
216
     * The prefix used for the parameters
217
     */
218
    parametersPrefix: string = "@"
4,293✔
219

220
    /**
221
     * Default values of length, precision and scale depends on column data type.
222
     * Used in the cases when length/precision/scale is not specified by user.
223
     */
224
    dataTypeDefaults: DataTypeDefaults = {
4,293✔
225
        char: { length: 1 },
226
        nchar: { length: 1 },
227
        varchar: { length: 255 },
228
        nvarchar: { length: 255 },
229
        binary: { length: 1 },
230
        varbinary: { length: 1 },
231
        decimal: { precision: 18, scale: 0 },
232
        numeric: { precision: 18, scale: 0 },
233
        time: { precision: 7 },
234
        datetime2: { precision: 7 },
235
        datetimeoffset: { precision: 7 },
236
    }
237

238
    cteCapabilities: CteCapabilities = {
4,293✔
239
        enabled: true,
240
        // todo: enable it for SQL Server - it's partially supported, but there are issues with generation of non-standard OUTPUT clause
241
        writable: false,
242
    }
243

244
    /**
245
     * Max length allowed by MSSQL Server for aliases (identifiers).
246
     * @see https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server
247
     */
248
    maxAliasLength = 128
4,293✔
249

250
    // -------------------------------------------------------------------------
251
    // Constructor
252
    // -------------------------------------------------------------------------
253

254
    constructor(connection: DataSource) {
255
        this.connection = connection
4,293✔
256
        this.options = connection.options as SqlServerConnectionOptions
4,293✔
257
        this.isReplicated = this.options.replication ? true : false
4,293!
258

259
        // load mssql package
260
        this.loadDependencies()
4,293✔
261

262
        this.database = DriverUtils.buildDriverOptions(
4,293✔
263
            this.options.replication
4,293!
264
                ? this.options.replication.master
265
                : this.options,
266
        ).database
267
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
4,293✔
268

269
        // Object.assign(connection.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
270
        // validate options to make sure everything is set
271
        // if (!this.options.host)
272
        // throw new DriverOptionNotSetError("host");
273
        // if (!this.options.username)
274
        //     throw new DriverOptionNotSetError("username");
275
        // if (!this.options.database)
276
        //     throw new DriverOptionNotSetError("database");
277
    }
278

279
    // -------------------------------------------------------------------------
280
    // Public Implemented Methods
281
    // -------------------------------------------------------------------------
282

283
    /**
284
     * Performs connection to the database.
285
     * Based on pooling options, it can either create connection immediately,
286
     * either create a pool and create connection when needed.
287
     */
288
    async connect(): Promise<void> {
289
        if (this.options.replication) {
4,275!
290
            this.slaves = await Promise.all(
×
291
                this.options.replication.slaves.map((slave) => {
292
                    return this.createPool(this.options, slave)
×
293
                }),
294
            )
295
            this.master = await this.createPool(
×
296
                this.options,
297
                this.options.replication.master,
298
            )
299
        } else {
300
            this.master = await this.createPool(this.options, this.options)
4,275✔
301
        }
302

303
        if (!this.database || !this.searchSchema) {
4,275✔
304
            const queryRunner = this.createQueryRunner("master")
4,275✔
305

306
            if (!this.database) {
4,275!
307
                this.database = await queryRunner.getCurrentDatabase()
×
308
            }
309

310
            if (!this.searchSchema) {
4,275✔
311
                this.searchSchema = await queryRunner.getCurrentSchema()
4,275✔
312
            }
313

314
            await queryRunner.release()
4,275✔
315
        }
316

317
        if (!this.schema) {
4,275✔
318
            this.schema = this.searchSchema
4,257✔
319
        }
320
    }
321

322
    /**
323
     * Makes any action after connection (e.g. create extensions in Postgres driver).
324
     */
325
    afterConnect(): Promise<void> {
326
        return Promise.resolve()
4,257✔
327
    }
328

329
    /**
330
     * Closes connection with the database.
331
     */
332
    async disconnect(): Promise<void> {
333
        if (!this.master)
4,275!
334
            return Promise.reject(new ConnectionIsNotSetError("mssql"))
×
335

336
        await this.closePool(this.master)
4,275✔
337
        await Promise.all(this.slaves.map((slave) => this.closePool(slave)))
4,275✔
338
        this.master = undefined
4,275✔
339
        this.slaves = []
4,275✔
340
    }
341

342
    /**
343
     * Closes connection pool.
344
     */
345
    protected async closePool(pool: any): Promise<void> {
346
        return new Promise<void>((ok, fail) => {
4,275✔
347
            pool.close((err: any) => (err ? fail(err) : ok()))
4,275!
348
        })
349
    }
350

351
    /**
352
     * Creates a schema builder used to build and sync a schema.
353
     */
354
    createSchemaBuilder() {
355
        return new RdbmsSchemaBuilder(this.connection)
12,906✔
356
    }
357

358
    /**
359
     * Creates a query runner used to execute database queries.
360
     */
361
    createQueryRunner(mode: ReplicationMode) {
362
        return new SqlServerQueryRunner(this, mode)
107,424✔
363
    }
364

365
    /**
366
     * Replaces parameters in the given sql with special escaping character
367
     * and an array of parameter names to be passed to a query.
368
     */
369
    escapeQueryWithParameters(
370
        sql: string,
371
        parameters: ObjectLiteral,
372
        nativeParameters: ObjectLiteral,
373
    ): [string, any[]] {
374
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
138,231✔
375
            (key) => nativeParameters[key],
×
376
        )
377
        if (!parameters || !Object.keys(parameters).length)
138,231✔
378
            return [sql, escapedParameters]
6,246✔
379

380
        const parameterIndexMap = new Map<string, number>()
131,985✔
381
        sql = sql.replace(
131,985✔
382
            /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
383
            (full, isArray: string, key: string): string => {
384
                if (!parameters.hasOwnProperty(key)) {
310,941✔
385
                    return full
81✔
386
                }
387

388
                if (parameterIndexMap.has(key)) {
310,860✔
389
                    return this.parametersPrefix + parameterIndexMap.get(key)
117✔
390
                }
391

392
                const value: any = parameters[key]
310,743✔
393

394
                if (isArray) {
310,743✔
395
                    return value
972✔
396
                        .map((v: any) => {
397
                            escapedParameters.push(v)
1,395✔
398
                            return this.createParameter(
1,395✔
399
                                key,
400
                                escapedParameters.length - 1,
401
                            )
402
                        })
403
                        .join(", ")
404
                }
405

406
                if (typeof value === "function") {
309,771!
407
                    return value()
×
408
                }
409

410
                escapedParameters.push(value)
309,771✔
411
                parameterIndexMap.set(key, escapedParameters.length - 1)
309,771✔
412
                return this.createParameter(key, escapedParameters.length - 1)
309,771✔
413
            },
414
        ) // todo: make replace only in value statements, otherwise problems
415
        return [sql, escapedParameters]
131,985✔
416
    }
417

418
    /**
419
     * Escapes a column name.
420
     */
421
    escape(columnName: string): string {
422
        return `"${columnName}"`
12,877,830✔
423
    }
424

425
    /**
426
     * Build full table name with database name, schema name and table name.
427
     * E.g. myDB.mySchema.myTable
428
     */
429
    buildTableName(
430
        tableName: string,
431
        schema?: string,
432
        database?: string,
433
    ): string {
434
        const tablePath = [tableName]
2,926,908✔
435

436
        if (schema) {
2,926,908✔
437
            tablePath.unshift(schema)
2,853,783✔
438
        }
439

440
        if (database) {
2,926,908✔
441
            if (!schema) {
2,867,868✔
442
                tablePath.unshift("")
14,409✔
443
            }
444

445
            tablePath.unshift(database)
2,867,868✔
446
        }
447

448
        return tablePath.join(".")
2,926,908✔
449
    }
450

451
    /**
452
     * Parse a target table name or other types and return a normalized table definition.
453
     */
454
    parseTableName(
455
        target: EntityMetadata | Table | View | TableForeignKey | string,
456
    ): { database?: string; schema?: string; tableName: string } {
457
        const driverDatabase = this.database
4,714,209✔
458
        const driverSchema = this.schema
4,714,209✔
459

460
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
4,714,209✔
461
            const parsed = this.parseTableName(target.name)
1,555,326✔
462

463
            return {
1,555,326✔
464
                database: target.database || parsed.database || driverDatabase,
2,997,729!
465
                schema: target.schema || parsed.schema || driverSchema,
2,994,066!
466
                tableName: parsed.tableName,
467
            }
468
        }
469

470
        if (InstanceChecker.isTableForeignKey(target)) {
3,158,883✔
471
            const parsed = this.parseTableName(target.referencedTableName)
30,861✔
472

473
            return {
30,861✔
474
                database:
475
                    target.referencedDatabase ||
59,778!
476
                    parsed.database ||
477
                    driverDatabase,
478
                schema:
479
                    target.referencedSchema || parsed.schema || driverSchema,
59,706!
480
                tableName: parsed.tableName,
481
            }
482
        }
483

484
        if (InstanceChecker.isEntityMetadata(target)) {
3,128,022✔
485
            // EntityMetadata tableName is never a path
486

487
            return {
1,452,609✔
488
                database: target.database || driverDatabase,
2,902,887✔
489
                schema: target.schema || driverSchema,
2,898,918✔
490
                tableName: target.tableName,
491
            }
492
        }
493

494
        const parts = target.split(".")
1,675,413✔
495

496
        if (parts.length === 3) {
1,675,413✔
497
            return {
90,603✔
498
                database: parts[0] || driverDatabase,
90,603!
499
                schema: parts[1] || driverSchema,
104,355✔
500
                tableName: parts[2],
501
            }
502
        } else if (parts.length === 2) {
1,584,810✔
503
            return {
2,628✔
504
                database: driverDatabase,
505
                schema: parts[0],
506
                tableName: parts[1],
507
            }
508
        } else {
509
            return {
1,582,182✔
510
                database: driverDatabase,
511
                schema: driverSchema,
512
                tableName: target,
513
            }
514
        }
515
    }
516

517
    /**
518
     * Prepares given value to a value to be persisted, based on its column type and metadata.
519
     */
520
    preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
521
        if (columnMetadata.transformer)
242,847✔
522
            value = ApplyValueTransformers.transformTo(
288✔
523
                columnMetadata.transformer,
524
                value,
525
            )
526

527
        if (value === null || value === undefined) return value
242,847✔
528

529
        if (columnMetadata.type === Boolean) {
221,274✔
530
            return value === true ? 1 : 0
18,027✔
531
        } else if (columnMetadata.type === "date") {
203,247✔
532
            return DateUtils.mixedDateToDate(value)
27✔
533
        } else if (columnMetadata.type === "time") {
203,220✔
534
            return DateUtils.mixedTimeToDate(value)
54✔
535
        } else if (
203,166✔
536
            columnMetadata.type === "datetime" ||
609,435✔
537
            columnMetadata.type === "smalldatetime" ||
538
            columnMetadata.type === Date
539
        ) {
540
            return DateUtils.mixedDateToDate(value, false, false)
180✔
541
        } else if (
202,986✔
542
            columnMetadata.type === "datetime2" ||
405,864✔
543
            columnMetadata.type === "datetimeoffset"
544
        ) {
545
            return DateUtils.mixedDateToDate(value, false, true)
144✔
546
        } else if (columnMetadata.type === "simple-array") {
202,842✔
547
            return DateUtils.simpleArrayToString(value)
9✔
548
        } else if (columnMetadata.type === "simple-json") {
202,833✔
549
            return DateUtils.simpleJsonToString(value)
45✔
550
        } else if (columnMetadata.type === "simple-enum") {
202,788✔
551
            return DateUtils.simpleEnumToString(value)
90✔
552
        }
553

554
        return value
202,698✔
555
    }
556

557
    /**
558
     * Prepares given value to a value to be persisted, based on its column type or metadata.
559
     */
560
    prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
561
        if (value === null || value === undefined)
605,250✔
562
            return columnMetadata.transformer
35,370✔
563
                ? ApplyValueTransformers.transformFrom(
564
                      columnMetadata.transformer,
565
                      value,
566
                  )
567
                : value
568

569
        if (columnMetadata.type === Boolean) {
569,880✔
570
            value = value ? true : false
6,381✔
571
        } else if (
563,499✔
572
            columnMetadata.type === "datetime" ||
2,808,324✔
573
            columnMetadata.type === Date ||
574
            columnMetadata.type === "datetime2" ||
575
            columnMetadata.type === "smalldatetime" ||
576
            columnMetadata.type === "datetimeoffset"
577
        ) {
578
            value = DateUtils.normalizeHydratedDate(value)
4,365✔
579
        } else if (columnMetadata.type === "date") {
559,134✔
580
            value = DateUtils.mixedDateToDateString(value)
27✔
581
        } else if (columnMetadata.type === "time") {
559,107✔
582
            value = DateUtils.mixedTimeToString(value)
54✔
583
        } else if (columnMetadata.type === "simple-array") {
559,053✔
584
            value = DateUtils.stringToSimpleArray(value)
9✔
585
        } else if (columnMetadata.type === "simple-json") {
559,044✔
586
            value = DateUtils.stringToSimpleJson(value)
45✔
587
        } else if (columnMetadata.type === "simple-enum") {
558,999✔
588
            value = DateUtils.stringToSimpleEnum(value, columnMetadata)
360✔
589
        } else if (columnMetadata.type === Number) {
558,639✔
590
            // convert to number if number
591
            value = !isNaN(+value) ? parseInt(value) : value
148,365!
592
        }
593

594
        if (columnMetadata.transformer)
569,880✔
595
            value = ApplyValueTransformers.transformFrom(
603✔
596
                columnMetadata.transformer,
597
                value,
598
            )
599

600
        return value
569,880✔
601
    }
602

603
    /**
604
     * Creates a database type from a given column metadata.
605
     */
606
    normalizeType(column: {
607
        type?: ColumnType
608
        length?: number | string
609
        precision?: number | null
610
        scale?: number
611
    }): string {
612
        if (column.type === Number || column.type === "integer") {
813,420✔
613
            return "int"
292,203✔
614
        } else if (column.type === String) {
521,217✔
615
            return "nvarchar"
227,619✔
616
        } else if (column.type === Date) {
293,598✔
617
            return "datetime"
2,457✔
618
        } else if (column.type === Boolean) {
291,141✔
619
            return "bit"
26,217✔
620
        } else if ((column.type as any) === Buffer) {
264,924✔
621
            return "binary"
72✔
622
        } else if (column.type === "uuid") {
264,852✔
623
            return "uniqueidentifier"
7,992✔
624
        } else if (
256,860✔
625
            column.type === "simple-array" ||
513,648✔
626
            column.type === "simple-json"
627
        ) {
628
            return "ntext"
342✔
629
        } else if (column.type === "simple-enum") {
256,518✔
630
            return "nvarchar"
801✔
631
        } else if (column.type === "dec") {
255,717✔
632
            return "decimal"
144✔
633
        } else if (column.type === "double precision") {
255,573✔
634
            return "float"
252✔
635
        } else if (column.type === "rowversion") {
255,321✔
636
            return "timestamp" // the rowversion type's name in SQL server metadata is timestamp
63✔
637
        } else {
638
            return (column.type as string) || ""
255,258✔
639
        }
640
    }
641

642
    /**
643
     * Normalizes "default" value of the column.
644
     */
645
    normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
646
        const defaultValue = columnMetadata.default
253,017✔
647

648
        if (typeof defaultValue === "number") {
253,017✔
649
            return `${defaultValue}`
6,318✔
650
        }
651

652
        if (typeof defaultValue === "boolean") {
246,699✔
653
            return defaultValue ? "1" : "0"
162✔
654
        }
655

656
        if (typeof defaultValue === "function") {
246,537✔
657
            const value = defaultValue()
5,922✔
658
            if (value.toUpperCase() === "CURRENT_TIMESTAMP") {
5,922✔
659
                return "getdate()"
378✔
660
            }
661
            return value
5,544✔
662
        }
663

664
        if (typeof defaultValue === "string") {
240,615✔
665
            return `'${defaultValue}'`
5,400✔
666
        }
667

668
        if (defaultValue === undefined || defaultValue === null) {
235,215✔
669
            return undefined
235,215✔
670
        }
671

672
        return `${defaultValue}`
×
673
    }
674

675
    /**
676
     * Normalizes "isUnique" value of the column.
677
     */
678
    normalizeIsUnique(column: ColumnMetadata): boolean {
679
        return column.entityMetadata.uniques.some(
286,209✔
680
            (uq) => uq.columns.length === 1 && uq.columns[0] === column,
37,296✔
681
        )
682
    }
683

684
    /**
685
     * Returns default column lengths, which is required on column creation.
686
     */
687
    getColumnLength(column: ColumnMetadata | TableColumn): string {
688
        if (column.length) return column.length.toString()
513,243✔
689

690
        if (
399,177✔
691
            column.type === "varchar" ||
1,186,290✔
692
            column.type === "nvarchar" ||
693
            column.type === String
694
        )
695
            return "255"
114,129✔
696

697
        return ""
285,048✔
698
    }
699

700
    /**
701
     * Creates column type definition including length, precision and scale
702
     */
703
    createFullType(column: TableColumn): string {
704
        // The Database Engine determines the data type of the computed column by applying the rules
705
        // of data type precedence to the expressions specified in the formula.
706
        if (column.asExpression) return ""
170,271✔
707

708
        let type = column.type
169,920✔
709

710
        // used 'getColumnLength()' method, because SqlServer sets `varchar` and `nvarchar` length to 1 by default.
711
        if (this.getColumnLength(column)) {
169,920✔
712
            type += `(${this.getColumnLength(column)})`
56,295✔
713
        } else if (
113,625✔
714
            column.precision !== null &&
227,628✔
715
            column.precision !== undefined &&
716
            column.scale !== null &&
717
            column.scale !== undefined
718
        ) {
719
            type += `(${column.precision},${column.scale})`
99✔
720
        } else if (
113,526✔
721
            column.precision !== null &&
227,052✔
722
            column.precision !== undefined
723
        ) {
724
            type += `(${column.precision})`
90✔
725
        }
726

727
        if (column.isArray) type += " array"
169,920!
728

729
        return type
169,920✔
730
    }
731

732
    /**
733
     * Obtains a new database connection to a master server.
734
     * Used for replication.
735
     * If replication is not setup then returns default connection's database connection.
736
     */
737
    obtainMasterConnection(): Promise<any> {
738
        if (!this.master) {
544,878!
739
            return Promise.reject(new TypeORMError("Driver not Connected"))
×
740
        }
741

742
        return Promise.resolve(this.master)
544,878✔
743
    }
744

745
    /**
746
     * Obtains a new database connection to a slave server.
747
     * Used for replication.
748
     * If replication is not setup then returns master (default) connection's database connection.
749
     */
750
    obtainSlaveConnection(): Promise<any> {
751
        if (!this.slaves.length) return this.obtainMasterConnection()
20,160✔
752

753
        const random = Math.floor(Math.random() * this.slaves.length)
×
754
        return Promise.resolve(this.slaves[random])
×
755
    }
756

757
    /**
758
     * Creates generated map of values generated or returned by database after INSERT query.
759
     */
760
    createGeneratedMap(metadata: EntityMetadata, insertResult: ObjectLiteral) {
761
        if (!insertResult) return undefined
85,419✔
762

763
        return Object.keys(insertResult).reduce((map, key) => {
32,454✔
764
            const column = metadata.findColumnWithDatabaseName(key)
39,294✔
765
            if (column) {
39,294✔
766
                OrmUtils.mergeDeep(
39,294✔
767
                    map,
768
                    column.createValueMap(
769
                        this.prepareHydratedValue(insertResult[key], column),
770
                    ),
771
                )
772
            }
773
            return map
39,294✔
774
        }, {} as ObjectLiteral)
775
    }
776

777
    /**
778
     * Differentiate columns of this table and columns from the given column metadatas columns
779
     * and returns only changed.
780
     */
781
    findChangedColumns(
782
        tableColumns: TableColumn[],
783
        columnMetadatas: ColumnMetadata[],
784
    ): ColumnMetadata[] {
785
        return columnMetadatas.filter((columnMetadata) => {
44,460✔
786
            const tableColumn = tableColumns.find(
143,100✔
787
                (c) => c.name === columnMetadata.databaseName,
385,623✔
788
            )
789
            if (!tableColumn) return false // we don't need new columns, we only need exist and changed
143,100✔
790

791
            const isColumnChanged =
792
                tableColumn.name !== columnMetadata.databaseName ||
143,073✔
793
                this.compareColumnType(tableColumn, columnMetadata) ||
794
                this.compareColumnLength(tableColumn, columnMetadata) ||
795
                tableColumn.precision !== columnMetadata.precision ||
796
                tableColumn.scale !== columnMetadata.scale ||
797
                // || tableColumn.comment !== columnMetadata.comment || // todo
798
                tableColumn.isGenerated !== columnMetadata.isGenerated ||
799
                (!tableColumn.isGenerated &&
800
                    this.lowerDefaultValueIfNecessary(
801
                        this.normalizeDefault(columnMetadata),
802
                    ) !==
803
                        this.lowerDefaultValueIfNecessary(
804
                            tableColumn.default,
805
                        )) || // we included check for generated here, because generated columns already can have default values
806
                tableColumn.isPrimary !== columnMetadata.isPrimary ||
807
                tableColumn.isNullable !== columnMetadata.isNullable ||
808
                tableColumn.asExpression !== columnMetadata.asExpression ||
809
                tableColumn.generatedType !== columnMetadata.generatedType ||
810
                tableColumn.isUnique !==
811
                    this.normalizeIsUnique(columnMetadata) ||
812
                (tableColumn.enum &&
813
                    columnMetadata.enum &&
814
                    !OrmUtils.isArraysEqual(
815
                        tableColumn.enum,
816
                        columnMetadata.enum.map((val) => val + ""),
738✔
817
                    ))
818

819
            // DEBUG SECTION
820
            // if (isColumnChanged) {
821
            //     console.log("table:", columnMetadata.entityMetadata.tableName)
822
            //     console.log(
823
            //         "name:",
824
            //         tableColumn.name,
825
            //         columnMetadata.databaseName,
826
            //     )
827
            //     console.log(
828
            //         "type:",
829
            //         tableColumn.type,
830
            //         this.normalizeType(columnMetadata),
831
            //         this.compareColumnType(tableColumn, columnMetadata),
832
            //     )
833
            //     console.log(
834
            //         "length:",
835
            //         tableColumn.length,
836
            //         columnMetadata.length,
837
            //         this.compareColumnLength(tableColumn, columnMetadata),
838
            //     )
839
            //     console.log(
840
            //         "precision:",
841
            //         tableColumn.precision,
842
            //         columnMetadata.precision,
843
            //     )
844
            //     console.log("scale:", tableColumn.scale, columnMetadata.scale)
845
            //     console.log(
846
            //         "isGenerated:",
847
            //         tableColumn.isGenerated,
848
            //         columnMetadata.isGenerated,
849
            //     )
850
            //     console.log(
851
            //         "isGenerated 2:",
852
            //         !tableColumn.isGenerated &&
853
            //             this.lowerDefaultValueIfNecessary(
854
            //                 this.normalizeDefault(columnMetadata),
855
            //             ) !==
856
            //                 this.lowerDefaultValueIfNecessary(
857
            //                     tableColumn.default,
858
            //                 ),
859
            //     )
860
            //     console.log(
861
            //         "isPrimary:",
862
            //         tableColumn.isPrimary,
863
            //         columnMetadata.isPrimary,
864
            //     )
865
            //     console.log(
866
            //         "isNullable:",
867
            //         tableColumn.isNullable,
868
            //         columnMetadata.isNullable,
869
            //     )
870
            //     console.log(
871
            //         "asExpression:",
872
            //         tableColumn.asExpression,
873
            //         columnMetadata.asExpression,
874
            //     )
875
            //     console.log(
876
            //         "generatedType:",
877
            //         tableColumn.generatedType,
878
            //         columnMetadata.generatedType,
879
            //     )
880
            //     console.log(
881
            //         "isUnique:",
882
            //         tableColumn.isUnique,
883
            //         this.normalizeIsUnique(columnMetadata),
884
            //     )
885
            //     console.log("==========================================")
886
            // }
887

888
            return isColumnChanged
143,073✔
889
        })
890
    }
891

892
    /**
893
     * Returns true if driver supports RETURNING / OUTPUT statement.
894
     */
895
    isReturningSqlSupported(): boolean {
896
        if (
170,307!
897
            this.options.options &&
170,379✔
898
            this.options.options.disableOutputReturning
899
        ) {
900
            return false
×
901
        }
902
        return true
170,307✔
903
    }
904

905
    /**
906
     * Returns true if driver supports uuid values generation on its own.
907
     */
908
    isUUIDGenerationSupported(): boolean {
909
        return true
1,044✔
910
    }
911

912
    /**
913
     * Returns true if driver supports fulltext indices.
914
     */
915
    isFullTextColumnTypeSupported(): boolean {
916
        return false
405✔
917
    }
918

919
    /**
920
     * Creates an escaped parameter.
921
     */
922
    createParameter(parameterName: string, index: number): string {
923
        return this.parametersPrefix + index
313,146✔
924
    }
925

926
    // -------------------------------------------------------------------------
927
    // Public Methods
928
    // -------------------------------------------------------------------------
929

930
    /**
931
     * Sql server's parameters needs to be wrapped into special object with type information about this value.
932
     * This method wraps given value into MssqlParameter based on its column definition.
933
     */
934
    parametrizeValue(column: ColumnMetadata, value: any) {
935
        // if its already MssqlParameter then simply return it
936
        if (InstanceChecker.isMssqlParameter(value)) return value
228,051✔
937

938
        const normalizedType = this.normalizeType({ type: column.type })
228,033✔
939
        if (column.length) {
228,033✔
940
            return new MssqlParameter(
3,636✔
941
                value,
942
                normalizedType as any,
943
                column.length as any,
944
            )
945
        } else if (
224,397✔
946
            column.precision !== null &&
448,902✔
947
            column.precision !== undefined &&
948
            column.scale !== null &&
949
            column.scale !== undefined
950
        ) {
951
            return new MssqlParameter(
27✔
952
                value,
953
                normalizedType as any,
954
                column.precision,
955
                column.scale,
956
            )
957
        } else if (
224,370✔
958
            column.precision !== null &&
448,740✔
959
            column.precision !== undefined
960
        ) {
961
            return new MssqlParameter(
27✔
962
                value,
963
                normalizedType as any,
964
                column.precision,
965
            )
966
        } else if (column.scale !== null && column.scale !== undefined) {
224,343!
967
            return new MssqlParameter(
×
968
                value,
969
                normalizedType as any,
970
                column.scale,
971
            )
972
        }
973

974
        return new MssqlParameter(value, normalizedType as any)
224,343✔
975
    }
976

977
    /**
978
     * Recursively wraps values (including those inside FindOperators) into MssqlParameter instances,
979
     * ensuring correct type metadata is passed to the SQL Server driver.
980
     *
981
     * - If the value is a FindOperator containing an array, all elements are individually parametrized.
982
     * - If the value is a non-raw FindOperator, a transformation is applied to its internal value.
983
     * - Otherwise, the value is passed directly to parametrizeValue for wrapping.
984
     *
985
     * This ensures SQL Server receives properly typed parameters for queries involving operators like
986
     * In, MoreThan, Between, etc.
987
     */
988
    parametrizeValues(column: ColumnMetadata, value: any) {
989
        if (value instanceof FindOperator) {
6,246✔
990
            if (Array.isArray(value.value)) {
576✔
991
                for (let i = 0; i < value.value.length; i++) {
288✔
992
                    value.value[i] = this.parametrizeValues(
369✔
993
                        column,
994
                        value.value[i],
995
                    )
996
                }
997
            } else if (value.type !== "raw") {
288✔
998
                value.transformValue({
270✔
999
                    to: (v) => this.parametrizeValue(column, v),
270✔
NEW
1000
                    from: (v) => v,
×
1001
                })
1002
            }
1003
        } else {
1004
            value = this.parametrizeValue(column, value)
5,670✔
1005
        }
1006

1007
        return value
6,246✔
1008
    }
1009

1010
    /**
1011
     * Sql server's parameters needs to be wrapped into special object with type information about this value.
1012
     * This method wraps all values of the given object into MssqlParameter based on their column definitions in the given table.
1013
     */
1014
    parametrizeMap(tablePath: string, map: ObjectLiteral): ObjectLiteral {
1015
        // find metadata for the given table
1016
        if (!this.connection.hasMetadata(tablePath))
×
1017
            // if no metadata found then we can't proceed because we don't have columns and their types
1018
            return map
×
1019
        const metadata = this.connection.getMetadata(tablePath)
×
1020

1021
        return Object.keys(map).reduce((newMap, key) => {
×
1022
            const value = map[key]
×
1023

1024
            // find column metadata
1025
            const column = metadata.findColumnWithDatabaseName(key)
×
1026
            if (!column)
×
1027
                // if we didn't find a column then we can't proceed because we don't have a column type
1028
                return value
×
1029

1030
            newMap[key] = this.parametrizeValue(column, value)
×
1031
            return newMap
×
1032
        }, {} as ObjectLiteral)
1033
    }
1034

1035
    buildTableVariableDeclaration(
1036
        identifier: string,
1037
        columns: ColumnMetadata[],
1038
    ): string {
1039
        const outputColumns = columns.map((column) => {
24,093✔
1040
            return `${this.escape(column.databaseName)} ${this.createFullType(
30,807✔
1041
                new TableColumn({
1042
                    name: column.databaseName,
1043
                    type: this.normalizeType(column),
1044
                    length: column.length,
1045
                    isNullable: column.isNullable,
1046
                    isArray: column.isArray,
1047
                }),
1048
            )}`
1049
        })
1050

1051
        return `DECLARE ${identifier} TABLE (${outputColumns.join(", ")})`
24,093✔
1052
    }
1053

1054
    // -------------------------------------------------------------------------
1055
    // Protected Methods
1056
    // -------------------------------------------------------------------------
1057

1058
    /**
1059
     * If driver dependency is not given explicitly, then try to load it via "require".
1060
     */
1061
    protected loadDependencies(): void {
1062
        try {
4,293✔
1063
            const mssql = this.options.driver || PlatformTools.load("mssql")
4,293✔
1064
            this.mssql = mssql
4,293✔
1065
        } catch (e) {
1066
            // todo: better error for browser env
1067
            throw new DriverPackageNotInstalledError("SQL Server", "mssql")
×
1068
        }
1069
    }
1070

1071
    protected compareColumnType(
1072
        tableColumn: TableColumn,
1073
        columnMetadata: ColumnMetadata,
1074
    ): boolean {
1075
        // The Database Engine determines the data type of the computed column by applying the rules
1076
        // of data type precedence to the expressions specified in the formula.
1077
        if (columnMetadata.asExpression) return false
143,073✔
1078

1079
        return tableColumn.type !== this.normalizeType(columnMetadata)
142,785✔
1080
    }
1081

1082
    protected compareColumnLength(
1083
        tableColumn: TableColumn,
1084
        columnMetadata: ColumnMetadata,
1085
    ): boolean {
1086
        // The Database Engine determines the data type of the computed column by applying the rules
1087
        // of data type precedence to the expressions specified in the formula.
1088
        if (columnMetadata.asExpression) return false
143,028✔
1089

1090
        return (
142,740✔
1091
            tableColumn.length.toUpperCase() !==
1092
            this.getColumnLength(columnMetadata).toUpperCase()
1093
        )
1094
    }
1095

1096
    protected lowerDefaultValueIfNecessary(value: string | undefined) {
1097
        // SqlServer saves function calls in default value as lowercase https://github.com/typeorm/typeorm/issues/2733
1098
        if (!value) {
232,254✔
1099
            return value
214,164✔
1100
        }
1101
        return value
18,090✔
1102
            .split(`'`)
1103
            .map((v, i) => {
1104
                return i % 2 === 1 ? v : v.toLowerCase()
29,286✔
1105
            })
1106
            .join(`'`)
1107
    }
1108

1109
    /**
1110
     * Creates a new connection pool for a given database credentials.
1111
     */
1112
    protected createPool(
1113
        options: SqlServerConnectionOptions,
1114
        credentials: SqlServerConnectionCredentialsOptions,
1115
    ): Promise<any> {
1116
        credentials = Object.assign(
4,275✔
1117
            {},
1118
            credentials,
1119
            DriverUtils.buildDriverOptions(credentials),
1120
        ) // todo: do it better way
1121

1122
        // todo: credentials.domain is deprecation. remove it in future
1123
        const authentication = !credentials.domain
4,275!
1124
            ? credentials.authentication
1125
            : {
1126
                  type: "ntlm",
1127
                  options: {
1128
                      domain: credentials.domain,
1129
                      userName: credentials.username,
1130
                      password: credentials.password,
1131
                  },
1132
              }
1133
        // build connection options for the driver
1134
        const connectionOptions = Object.assign(
4,275✔
1135
            {},
1136
            {
1137
                connectionTimeout: this.options.connectionTimeout,
1138
                requestTimeout: this.options.requestTimeout,
1139
                stream: this.options.stream,
1140
                pool: this.options.pool,
1141
                options: this.options.options,
1142
            },
1143
            {
1144
                server: credentials.host,
1145
                database: credentials.database,
1146
                port: credentials.port,
1147
                user: credentials.username,
1148
                password: credentials.password,
1149
                authentication: authentication,
1150
            },
1151
            options.extra || {},
4,275!
1152
        )
1153

1154
        // set default useUTC option if it hasn't been set
1155
        if (!connectionOptions.options) {
4,275✔
1156
            connectionOptions.options = { useUTC: false }
4,257✔
1157
        } else if (!connectionOptions.options.useUTC) {
18✔
1158
            Object.assign(connectionOptions.options, { useUTC: false })
18✔
1159
        }
1160

1161
        // Match the next release of tedious for configuration options
1162
        // Also prevents warning messages.
1163
        Object.assign(connectionOptions.options, { enableArithAbort: true })
4,275✔
1164

1165
        // pooling is enabled either when its set explicitly to true,
1166
        // either when its not defined at all (e.g. enabled by default)
1167
        return new Promise<void>((ok, fail) => {
4,275✔
1168
            const pool = new this.mssql.ConnectionPool(connectionOptions)
4,275✔
1169

1170
            const { logger } = this.connection
4,275✔
1171

1172
            const poolErrorHandler =
1173
                (options.pool && options.pool.errorHandler) ||
4,275!
1174
                ((error: any) =>
1175
                    logger.log("warn", `MSSQL pool raised an error. ${error}`))
×
1176
            /**
1177
             * Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
1178
             * cause the hosting app to crash.
1179
             */
1180
            pool.on("error", poolErrorHandler)
4,275✔
1181

1182
            const connection = pool.connect((err: any) => {
4,275✔
1183
                if (err) return fail(err)
4,275!
1184
                ok(connection)
4,275✔
1185
            })
1186
        })
1187
    }
1188
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc