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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

2.76
/src/driver/sqlserver/SqlServerDriver.ts
1
import { Driver } from "../Driver"
2
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
4✔
3
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
4✔
4
import { DriverUtils } from "../DriverUtils"
4✔
5
import { CteCapabilities } from "../types/CteCapabilities"
6
import { SqlServerQueryRunner } from "./SqlServerQueryRunner"
4✔
7
import { ObjectLiteral } from "../../common/ObjectLiteral"
8
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
9
import { DateUtils } from "../../util/DateUtils"
4✔
10
import { PlatformTools } from "../../platform/PlatformTools"
4✔
11
import { DataSource } from "../../data-source/DataSource"
12
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
4✔
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"
4✔
18
import { TableColumn } from "../../schema-builder/table/TableColumn"
4✔
19
import { SqlServerConnectionCredentialsOptions } from "./SqlServerConnectionCredentialsOptions"
20
import { EntityMetadata } from "../../metadata/EntityMetadata"
21
import { OrmUtils } from "../../util/OrmUtils"
4✔
22
import { ApplyValueTransformers } from "../../util/ApplyValueTransformers"
4✔
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"
4✔
28
import { InstanceChecker } from "../../util/InstanceChecker"
4✔
29
import { UpsertType } from "../types/UpsertType"
30

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

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

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

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

54
    /**
55
     * Pool for slave databases.
56
     * Used in replication.
57
     */
58
    slaves: any[] = []
×
59

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

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

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

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

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

89
    /**
90
     * Indicates if replication is enabled.
91
     */
92
    isReplicated: boolean = false
×
93

94
    /**
95
     * Indicates if tree tables are supported by this driver.
96
     */
97
    treeSupport = true
×
98

99
    /**
100
     * Represent transaction support by this driver
101
     */
102
    transactionSupport = "simple" as const
×
103

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

146
    /**
147
     * Returns type of upsert supported by driver if any
148
     */
149
    supportedUpsertTypes: UpsertType[] = []
×
150

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

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

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

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

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

214
    /**
215
     * The prefix used for the parameters
216
     */
217
    parametersPrefix: string = "@"
×
218

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

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

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

249
    // -------------------------------------------------------------------------
250
    // Constructor
251
    // -------------------------------------------------------------------------
252

253
    constructor(connection: DataSource) {
254
        this.connection = connection
×
255
        this.options = connection.options as SqlServerConnectionOptions
×
256
        this.isReplicated = this.options.replication ? true : false
×
257

258
        // load mssql package
259
        this.loadDependencies()
×
260

261
        this.database = DriverUtils.buildDriverOptions(
×
262
            this.options.replication
×
263
                ? this.options.replication.master
264
                : this.options,
265
        ).database
266
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
×
267

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

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

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

302
        if (!this.database || !this.searchSchema) {
×
303
            const queryRunner = this.createQueryRunner("master")
×
304

305
            if (!this.database) {
×
306
                this.database = await queryRunner.getCurrentDatabase()
×
307
            }
308

309
            if (!this.searchSchema) {
×
310
                this.searchSchema = await queryRunner.getCurrentSchema()
×
311
            }
312

313
            await queryRunner.release()
×
314
        }
315

316
        if (!this.schema) {
×
317
            this.schema = this.searchSchema
×
318
        }
319
    }
320

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

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

335
        await this.closePool(this.master)
×
336
        await Promise.all(this.slaves.map((slave) => this.closePool(slave)))
×
337
        this.master = undefined
×
338
        this.slaves = []
×
339
    }
340

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

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

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

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

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

387
                if (parameterIndexMap.has(key)) {
×
388
                    return this.parametersPrefix + parameterIndexMap.get(key)
×
389
                }
390

391
                const value: any = parameters[key]
×
392

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

405
                if (typeof value === "function") {
×
406
                    return value()
×
407
                }
408

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

417
    /**
418
     * Escapes a column name.
419
     */
420
    escape(columnName: string): string {
421
        return `"${columnName}"`
×
422
    }
423

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

435
        if (schema) {
×
436
            tablePath.unshift(schema)
×
437
        }
438

439
        if (database) {
×
440
            if (!schema) {
×
441
                tablePath.unshift("")
×
442
            }
443

444
            tablePath.unshift(database)
×
445
        }
446

447
        return tablePath.join(".")
×
448
    }
449

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

459
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
×
460
            const parsed = this.parseTableName(target.name)
×
461

462
            return {
×
463
                database: target.database || parsed.database || driverDatabase,
×
464
                schema: target.schema || parsed.schema || driverSchema,
×
465
                tableName: parsed.tableName,
466
            }
467
        }
468

469
        if (InstanceChecker.isTableForeignKey(target)) {
×
470
            const parsed = this.parseTableName(target.referencedTableName)
×
471

472
            return {
×
473
                database:
474
                    target.referencedDatabase ||
×
475
                    parsed.database ||
476
                    driverDatabase,
477
                schema:
478
                    target.referencedSchema || parsed.schema || driverSchema,
×
479
                tableName: parsed.tableName,
480
            }
481
        }
482

483
        if (InstanceChecker.isEntityMetadata(target)) {
×
484
            // EntityMetadata tableName is never a path
485

486
            return {
×
487
                database: target.database || driverDatabase,
×
488
                schema: target.schema || driverSchema,
×
489
                tableName: target.tableName,
490
            }
491
        }
492

493
        const parts = target.split(".")
×
494

495
        if (parts.length === 3) {
×
496
            return {
×
497
                database: parts[0] || driverDatabase,
×
498
                schema: parts[1] || driverSchema,
×
499
                tableName: parts[2],
500
            }
501
        } else if (parts.length === 2) {
×
502
            return {
×
503
                database: driverDatabase,
504
                schema: parts[0],
505
                tableName: parts[1],
506
            }
507
        } else {
508
            return {
×
509
                database: driverDatabase,
510
                schema: driverSchema,
511
                tableName: target,
512
            }
513
        }
514
    }
515

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

526
        if (value === null || value === undefined) return value
×
527

528
        if (columnMetadata.type === Boolean) {
×
529
            return value === true ? 1 : 0
×
530
        } else if (columnMetadata.type === "date") {
×
531
            return DateUtils.mixedDateToDate(value)
×
532
        } else if (columnMetadata.type === "time") {
×
533
            return DateUtils.mixedTimeToDate(value)
×
534
        } else if (
×
535
            columnMetadata.type === "datetime" ||
×
536
            columnMetadata.type === "smalldatetime" ||
537
            columnMetadata.type === Date
538
        ) {
539
            return DateUtils.mixedDateToDate(value, false, false)
×
540
        } else if (
×
541
            columnMetadata.type === "datetime2" ||
×
542
            columnMetadata.type === "datetimeoffset"
543
        ) {
544
            return DateUtils.mixedDateToDate(value, false, true)
×
545
        } else if (columnMetadata.type === "simple-array") {
×
546
            return DateUtils.simpleArrayToString(value)
×
547
        } else if (columnMetadata.type === "simple-json") {
×
548
            return DateUtils.simpleJsonToString(value)
×
549
        } else if (columnMetadata.type === "simple-enum") {
×
550
            return DateUtils.simpleEnumToString(value)
×
551
        }
552

553
        return value
×
554
    }
555

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

568
        if (columnMetadata.type === Boolean) {
×
569
            value = value ? true : false
×
570
        } else if (
×
571
            columnMetadata.type === "datetime" ||
×
572
            columnMetadata.type === Date ||
573
            columnMetadata.type === "datetime2" ||
574
            columnMetadata.type === "smalldatetime" ||
575
            columnMetadata.type === "datetimeoffset"
576
        ) {
577
            value = DateUtils.normalizeHydratedDate(value)
×
578
        } else if (columnMetadata.type === "date") {
×
579
            value = DateUtils.mixedDateToDateString(value)
×
580
        } else if (columnMetadata.type === "time") {
×
581
            value = DateUtils.mixedTimeToString(value)
×
582
        } else if (columnMetadata.type === "simple-array") {
×
583
            value = DateUtils.stringToSimpleArray(value)
×
584
        } else if (columnMetadata.type === "simple-json") {
×
585
            value = DateUtils.stringToSimpleJson(value)
×
586
        } else if (columnMetadata.type === "simple-enum") {
×
587
            value = DateUtils.stringToSimpleEnum(value, columnMetadata)
×
588
        } else if (columnMetadata.type === Number) {
×
589
            // convert to number if number
590
            value = !isNaN(+value) ? parseInt(value) : value
×
591
        }
592

593
        if (columnMetadata.transformer)
×
594
            value = ApplyValueTransformers.transformFrom(
×
595
                columnMetadata.transformer,
596
                value,
597
            )
598

599
        return value
×
600
    }
601

602
    /**
603
     * Creates a database type from a given column metadata.
604
     */
605
    normalizeType(column: {
606
        type?: ColumnType
607
        length?: number | string
608
        precision?: number | null
609
        scale?: number
610
    }): string {
611
        if (column.type === Number || column.type === "integer") {
×
612
            return "int"
×
613
        } else if (column.type === String) {
×
614
            return "nvarchar"
×
615
        } else if (column.type === Date) {
×
616
            return "datetime"
×
617
        } else if (column.type === Boolean) {
×
618
            return "bit"
×
619
        } else if ((column.type as any) === Buffer) {
×
620
            return "binary"
×
621
        } else if (column.type === "uuid") {
×
622
            return "uniqueidentifier"
×
623
        } else if (
×
624
            column.type === "simple-array" ||
×
625
            column.type === "simple-json"
626
        ) {
627
            return "ntext"
×
628
        } else if (column.type === "simple-enum") {
×
629
            return "nvarchar"
×
630
        } else if (column.type === "dec") {
×
631
            return "decimal"
×
632
        } else if (column.type === "double precision") {
×
633
            return "float"
×
634
        } else if (column.type === "rowversion") {
×
635
            return "timestamp" // the rowversion type's name in SQL server metadata is timestamp
×
636
        } else {
637
            return (column.type as string) || ""
×
638
        }
639
    }
640

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

647
        if (typeof defaultValue === "number") {
×
648
            return `${defaultValue}`
×
649
        }
650

651
        if (typeof defaultValue === "boolean") {
×
652
            return defaultValue ? "1" : "0"
×
653
        }
654

655
        if (typeof defaultValue === "function") {
×
656
            const value = defaultValue()
×
657
            if (value.toUpperCase() === "CURRENT_TIMESTAMP") {
×
658
                return "getdate()"
×
659
            }
660
            return value
×
661
        }
662

663
        if (typeof defaultValue === "string") {
×
664
            return `'${defaultValue}'`
×
665
        }
666

667
        if (defaultValue === undefined || defaultValue === null) {
×
668
            return undefined
×
669
        }
670

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

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

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

689
        if (
×
690
            column.type === "varchar" ||
×
691
            column.type === "nvarchar" ||
692
            column.type === String
693
        )
694
            return "255"
×
695

696
        return ""
×
697
    }
698

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

707
        let type = column.type
×
708

709
        // used 'getColumnLength()' method, because SqlServer sets `varchar` and `nvarchar` length to 1 by default.
710
        if (this.getColumnLength(column)) {
×
711
            type += `(${this.getColumnLength(column)})`
×
712
        } else if (
×
713
            column.precision !== null &&
×
714
            column.precision !== undefined &&
715
            column.scale !== null &&
716
            column.scale !== undefined
717
        ) {
718
            type += `(${column.precision},${column.scale})`
×
719
        } else if (
×
720
            column.precision !== null &&
×
721
            column.precision !== undefined
722
        ) {
723
            type += `(${column.precision})`
×
724
        }
725

726
        if (column.isArray) type += " array"
×
727

728
        return type
×
729
    }
730

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

741
        return Promise.resolve(this.master)
×
742
    }
743

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

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

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

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

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

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

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

887
            return isColumnChanged
×
888
        })
889
    }
890

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

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

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

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

925
    // -------------------------------------------------------------------------
926
    // Public Methods
927
    // -------------------------------------------------------------------------
928

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

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

973
        return new MssqlParameter(value, normalizedType as any)
×
974
    }
975

976
    /**
977
     * Sql server's parameters needs to be wrapped into special object with type information about this value.
978
     * This method wraps all values of the given object into MssqlParameter based on their column definitions in the given table.
979
     */
980
    parametrizeMap(tablePath: string, map: ObjectLiteral): ObjectLiteral {
981
        // find metadata for the given table
982
        if (!this.connection.hasMetadata(tablePath))
×
983
            // if no metadata found then we can't proceed because we don't have columns and their types
984
            return map
×
985
        const metadata = this.connection.getMetadata(tablePath)
×
986

987
        return Object.keys(map).reduce((newMap, key) => {
×
988
            const value = map[key]
×
989

990
            // find column metadata
991
            const column = metadata.findColumnWithDatabaseName(key)
×
992
            if (!column)
×
993
                // if we didn't find a column then we can't proceed because we don't have a column type
994
                return value
×
995

996
            newMap[key] = this.parametrizeValue(column, value)
×
997
            return newMap
×
998
        }, {} as ObjectLiteral)
999
    }
1000

1001
    buildTableVariableDeclaration(
1002
        identifier: string,
1003
        columns: ColumnMetadata[],
1004
    ): string {
1005
        const outputColumns = columns.map((column) => {
×
1006
            return `${this.escape(column.databaseName)} ${this.createFullType(
×
1007
                new TableColumn({
1008
                    name: column.databaseName,
1009
                    type: this.normalizeType(column),
1010
                    length: column.length,
1011
                    isNullable: column.isNullable,
1012
                    isArray: column.isArray,
1013
                }),
1014
            )}`
1015
        })
1016

1017
        return `DECLARE ${identifier} TABLE (${outputColumns.join(", ")})`
×
1018
    }
1019

1020
    // -------------------------------------------------------------------------
1021
    // Protected Methods
1022
    // -------------------------------------------------------------------------
1023

1024
    /**
1025
     * If driver dependency is not given explicitly, then try to load it via "require".
1026
     */
1027
    protected loadDependencies(): void {
1028
        try {
×
1029
            const mssql = this.options.driver || PlatformTools.load("mssql")
×
1030
            this.mssql = mssql
×
1031
        } catch (e) {
1032
            // todo: better error for browser env
1033
            throw new DriverPackageNotInstalledError("SQL Server", "mssql")
×
1034
        }
1035
    }
1036

1037
    protected compareColumnType(
1038
        tableColumn: TableColumn,
1039
        columnMetadata: ColumnMetadata,
1040
    ): boolean {
1041
        // The Database Engine determines the data type of the computed column by applying the rules
1042
        // of data type precedence to the expressions specified in the formula.
1043
        if (columnMetadata.asExpression) return false
×
1044

1045
        return tableColumn.type !== this.normalizeType(columnMetadata)
×
1046
    }
1047

1048
    protected compareColumnLength(
1049
        tableColumn: TableColumn,
1050
        columnMetadata: ColumnMetadata,
1051
    ): boolean {
1052
        // The Database Engine determines the data type of the computed column by applying the rules
1053
        // of data type precedence to the expressions specified in the formula.
1054
        if (columnMetadata.asExpression) return false
×
1055

1056
        return (
×
1057
            tableColumn.length.toUpperCase() !==
1058
            this.getColumnLength(columnMetadata).toUpperCase()
1059
        )
1060
    }
1061

1062
    protected lowerDefaultValueIfNecessary(value: string | undefined) {
1063
        // SqlServer saves function calls in default value as lowercase https://github.com/typeorm/typeorm/issues/2733
1064
        if (!value) {
×
1065
            return value
×
1066
        }
1067
        return value
×
1068
            .split(`'`)
1069
            .map((v, i) => {
1070
                return i % 2 === 1 ? v : v.toLowerCase()
×
1071
            })
1072
            .join(`'`)
1073
    }
1074

1075
    /**
1076
     * Creates a new connection pool for a given database credentials.
1077
     */
1078
    protected createPool(
1079
        options: SqlServerConnectionOptions,
1080
        credentials: SqlServerConnectionCredentialsOptions,
1081
    ): Promise<any> {
1082
        credentials = Object.assign(
×
1083
            {},
1084
            credentials,
1085
            DriverUtils.buildDriverOptions(credentials),
1086
        ) // todo: do it better way
1087

1088
        // todo: credentials.domain is deprecation. remove it in future
1089
        const authentication = !credentials.domain
×
1090
            ? credentials.authentication
1091
            : {
1092
                  type: "ntlm",
1093
                  options: {
1094
                      domain: credentials.domain,
1095
                      userName: credentials.username,
1096
                      password: credentials.password,
1097
                  },
1098
              }
1099
        // build connection options for the driver
1100
        const connectionOptions = Object.assign(
×
1101
            {},
1102
            {
1103
                connectionTimeout: this.options.connectionTimeout,
1104
                requestTimeout: this.options.requestTimeout,
1105
                stream: this.options.stream,
1106
                pool: this.options.pool,
1107
                options: this.options.options,
1108
            },
1109
            {
1110
                server: credentials.host,
1111
                database: credentials.database,
1112
                port: credentials.port,
1113
                user: credentials.username,
1114
                password: credentials.password,
1115
                authentication: authentication,
1116
            },
1117
            options.extra || {},
×
1118
        )
1119

1120
        // set default useUTC option if it hasn't been set
1121
        if (!connectionOptions.options) {
×
1122
            connectionOptions.options = { useUTC: false }
×
1123
        } else if (!connectionOptions.options.useUTC) {
×
1124
            Object.assign(connectionOptions.options, { useUTC: false })
×
1125
        }
1126

1127
        // Match the next release of tedious for configuration options
1128
        // Also prevents warning messages.
1129
        Object.assign(connectionOptions.options, { enableArithAbort: true })
×
1130

1131
        // pooling is enabled either when its set explicitly to true,
1132
        // either when its not defined at all (e.g. enabled by default)
1133
        return new Promise<void>((ok, fail) => {
×
1134
            const pool = new this.mssql.ConnectionPool(connectionOptions)
×
1135

1136
            const { logger } = this.connection
×
1137

1138
            const poolErrorHandler =
1139
                (options.pool && options.pool.errorHandler) ||
×
1140
                ((error: any) =>
1141
                    logger.log("warn", `MSSQL pool raised an error. ${error}`))
×
1142
            /**
1143
             * Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
1144
             * cause the hosting app to crash.
1145
             */
1146
            pool.on("error", poolErrorHandler)
×
1147

1148
            const connection = pool.connect((err: any) => {
×
1149
                if (err) return fail(err)
×
1150
                ok(connection)
×
1151
            })
1152
        })
1153
    }
1154
}
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

© 2025 Coveralls, Inc