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

typeorm / typeorm / 15219332477

23 May 2025 09:13PM UTC coverage: 17.216% (-59.1%) from 76.346%
15219332477

Pull #11332

github

naorpeled
cr comments - move if block
Pull Request #11332: feat: add new undefined and null behavior flags

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

0 of 31 new or added lines in 3 files covered. (0.0%)

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

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

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

32
/**
33
 * Organizes communication with SQL Server DBMS.
34
 */
35
export class SqlServerDriver implements Driver {
1✔
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
     */
UNCOV
59
    slaves: any[] = []
×
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
     */
UNCOV
93
    isReplicated: boolean = false
×
94

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

100
    /**
101
     * Represent transaction support by this driver
102
     */
UNCOV
103
    transactionSupport = "simple" as const
×
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
     */
UNCOV
110
    supportedDataTypes: ColumnType[] = [
×
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
     */
UNCOV
150
    supportedUpsertTypes: UpsertType[] = []
×
151

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

157
    /**
158
     * Gets list of column data types that support length by a driver.
159
     */
UNCOV
160
    withLengthColumnTypes: ColumnType[] = [
×
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
     */
UNCOV
172
    withPrecisionColumnTypes: ColumnType[] = [
×
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
     */
UNCOV
183
    withScaleColumnTypes: ColumnType[] = ["decimal", "numeric"]
×
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
     */
UNCOV
189
    mappedDataTypes: MappedColumnTypes = {
×
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
     */
UNCOV
218
    parametersPrefix: string = "@"
×
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
     */
UNCOV
224
    dataTypeDefaults: DataTypeDefaults = {
×
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

UNCOV
238
    cteCapabilities: CteCapabilities = {
×
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
     */
UNCOV
248
    maxAliasLength = 128
×
249

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

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

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

UNCOV
262
        this.database = DriverUtils.buildDriverOptions(
×
263
            this.options.replication
×
264
                ? this.options.replication.master
265
                : this.options,
266
        ).database
UNCOV
267
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
×
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> {
UNCOV
289
        if (this.options.replication) {
×
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 {
UNCOV
300
            this.master = await this.createPool(this.options, this.options)
×
301
        }
302

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

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

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

UNCOV
314
            await queryRunner.release()
×
315
        }
316

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

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

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

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

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

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

358
    /**
359
     * Creates a query runner used to execute database queries.
360
     */
361
    createQueryRunner(mode: ReplicationMode) {
UNCOV
362
        return new SqlServerQueryRunner(this, mode)
×
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[]] {
UNCOV
374
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
×
375
            (key) => nativeParameters[key],
×
376
        )
UNCOV
377
        if (!parameters || !Object.keys(parameters).length)
×
UNCOV
378
            return [sql, escapedParameters]
×
379

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

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

UNCOV
392
                const value: any = parameters[key]
×
393

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

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

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

418
    /**
419
     * Escapes a column name.
420
     */
421
    escape(columnName: string): string {
UNCOV
422
        return `"${columnName}"`
×
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 {
UNCOV
434
        const tablePath = [tableName]
×
435

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

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

UNCOV
445
            tablePath.unshift(database)
×
446
        }
447

UNCOV
448
        return tablePath.join(".")
×
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 } {
UNCOV
457
        const driverDatabase = this.database
×
UNCOV
458
        const driverSchema = this.schema
×
459

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

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

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

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

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

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

UNCOV
494
        const parts = target.split(".")
×
495

UNCOV
496
        if (parts.length === 3) {
×
UNCOV
497
            return {
×
498
                database: parts[0] || driverDatabase,
×
499
                schema: parts[1] || driverSchema,
×
500
                tableName: parts[2],
501
            }
UNCOV
502
        } else if (parts.length === 2) {
×
UNCOV
503
            return {
×
504
                database: driverDatabase,
505
                schema: parts[0],
506
                tableName: parts[1],
507
            }
508
        } else {
UNCOV
509
            return {
×
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 {
UNCOV
521
        if (columnMetadata.transformer)
×
UNCOV
522
            value = ApplyValueTransformers.transformTo(
×
523
                columnMetadata.transformer,
524
                value,
525
            )
526

UNCOV
527
        if (value === null || value === undefined) return value
×
528

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

UNCOV
554
        return value
×
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 {
UNCOV
561
        if (value === null || value === undefined)
×
UNCOV
562
            return columnMetadata.transformer
×
563
                ? ApplyValueTransformers.transformFrom(
564
                      columnMetadata.transformer,
565
                      value,
566
                  )
567
                : value
568

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

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

UNCOV
600
        return value
×
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 {
UNCOV
612
        if (column.type === Number || column.type === "integer") {
×
UNCOV
613
            return "int"
×
UNCOV
614
        } else if (column.type === String) {
×
UNCOV
615
            return "nvarchar"
×
UNCOV
616
        } else if (column.type === Date) {
×
UNCOV
617
            return "datetime"
×
UNCOV
618
        } else if (column.type === Boolean) {
×
UNCOV
619
            return "bit"
×
UNCOV
620
        } else if ((column.type as any) === Buffer) {
×
UNCOV
621
            return "binary"
×
UNCOV
622
        } else if (column.type === "uuid") {
×
UNCOV
623
            return "uniqueidentifier"
×
UNCOV
624
        } else if (
×
625
            column.type === "simple-array" ||
×
626
            column.type === "simple-json"
627
        ) {
UNCOV
628
            return "ntext"
×
UNCOV
629
        } else if (column.type === "simple-enum") {
×
UNCOV
630
            return "nvarchar"
×
UNCOV
631
        } else if (column.type === "dec") {
×
UNCOV
632
            return "decimal"
×
UNCOV
633
        } else if (column.type === "double precision") {
×
UNCOV
634
            return "float"
×
UNCOV
635
        } else if (column.type === "rowversion") {
×
UNCOV
636
            return "timestamp" // the rowversion type's name in SQL server metadata is timestamp
×
637
        } else {
UNCOV
638
            return (column.type as string) || ""
×
639
        }
640
    }
641

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

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

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

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

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

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

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

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

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

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

UNCOV
697
        return ""
×
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.
UNCOV
706
        if (column.asExpression) return ""
×
707

UNCOV
708
        let type = column.type
×
709

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

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

UNCOV
729
        return type
×
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> {
UNCOV
738
        if (!this.master) {
×
739
            return Promise.reject(new TypeORMError("Driver not Connected"))
×
740
        }
741

UNCOV
742
        return Promise.resolve(this.master)
×
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> {
UNCOV
751
        if (!this.slaves.length) return this.obtainMasterConnection()
×
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) {
UNCOV
761
        if (!insertResult) return undefined
×
762

UNCOV
763
        return Object.keys(insertResult).reduce((map, key) => {
×
UNCOV
764
            const column = metadata.findColumnWithDatabaseName(key)
×
UNCOV
765
            if (column) {
×
UNCOV
766
                OrmUtils.mergeDeep(
×
767
                    map,
768
                    column.createValueMap(
769
                        this.prepareHydratedValue(insertResult[key], column),
770
                    ),
771
                )
772
            }
UNCOV
773
            return map
×
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[] {
UNCOV
785
        return columnMetadatas.filter((columnMetadata) => {
×
UNCOV
786
            const tableColumn = tableColumns.find(
×
UNCOV
787
                (c) => c.name === columnMetadata.databaseName,
×
788
            )
UNCOV
789
            if (!tableColumn) return false // we don't need new columns, we only need exist and changed
×
790

791
            const isColumnChanged =
UNCOV
792
                tableColumn.name !== columnMetadata.databaseName ||
×
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,
UNCOV
816
                        columnMetadata.enum.map((val) => val + ""),
×
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

UNCOV
888
            return isColumnChanged
×
889
        })
890
    }
891

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

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

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

919
    /**
920
     * Creates an escaped parameter.
921
     */
922
    createParameter(parameterName: string, index: number): string {
UNCOV
923
        return this.parametersPrefix + index
×
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
UNCOV
936
        if (InstanceChecker.isMssqlParameter(value)) return value
×
937

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

UNCOV
974
        return new MssqlParameter(value, normalizedType as any)
×
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) {
UNCOV
989
        if (value instanceof FindOperator) {
×
UNCOV
990
            if (value.type !== "raw") {
×
UNCOV
991
                value.transformValue({
×
UNCOV
992
                    to: (v) => this.parametrizeValues(column, v),
×
993
                    from: (v) => v,
×
994
                })
995
            }
996

UNCOV
997
            return value
×
998
        }
999

UNCOV
1000
        return this.parametrizeValue(column, value)
×
1001
    }
1002

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

1014
        return Object.keys(map).reduce((newMap, key) => {
×
1015
            const value = map[key]
×
1016

1017
            // find column metadata
1018
            const column = metadata.findColumnWithDatabaseName(key)
×
1019
            if (!column)
×
1020
                // if we didn't find a column then we can't proceed because we don't have a column type
1021
                return value
×
1022

1023
            newMap[key] = this.parametrizeValue(column, value)
×
1024
            return newMap
×
1025
        }, {} as ObjectLiteral)
1026
    }
1027

1028
    buildTableVariableDeclaration(
1029
        identifier: string,
1030
        columns: ColumnMetadata[],
1031
    ): string {
UNCOV
1032
        const outputColumns = columns.map((column) => {
×
UNCOV
1033
            return `${this.escape(column.databaseName)} ${this.createFullType(
×
1034
                new TableColumn({
1035
                    name: column.databaseName,
1036
                    type: this.normalizeType(column),
1037
                    length: column.length,
1038
                    isNullable: column.isNullable,
1039
                    isArray: column.isArray,
1040
                }),
1041
            )}`
1042
        })
1043

UNCOV
1044
        return `DECLARE ${identifier} TABLE (${outputColumns.join(", ")})`
×
1045
    }
1046

1047
    // -------------------------------------------------------------------------
1048
    // Protected Methods
1049
    // -------------------------------------------------------------------------
1050

1051
    /**
1052
     * If driver dependency is not given explicitly, then try to load it via "require".
1053
     */
1054
    protected loadDependencies(): void {
UNCOV
1055
        try {
×
UNCOV
1056
            const mssql = this.options.driver || PlatformTools.load("mssql")
×
UNCOV
1057
            this.mssql = mssql
×
1058
        } catch (e) {
1059
            // todo: better error for browser env
1060
            throw new DriverPackageNotInstalledError("SQL Server", "mssql")
×
1061
        }
1062
    }
1063

1064
    protected compareColumnType(
1065
        tableColumn: TableColumn,
1066
        columnMetadata: ColumnMetadata,
1067
    ): boolean {
1068
        // The Database Engine determines the data type of the computed column by applying the rules
1069
        // of data type precedence to the expressions specified in the formula.
UNCOV
1070
        if (columnMetadata.asExpression) return false
×
1071

UNCOV
1072
        return tableColumn.type !== this.normalizeType(columnMetadata)
×
1073
    }
1074

1075
    protected compareColumnLength(
1076
        tableColumn: TableColumn,
1077
        columnMetadata: ColumnMetadata,
1078
    ): boolean {
1079
        // The Database Engine determines the data type of the computed column by applying the rules
1080
        // of data type precedence to the expressions specified in the formula.
UNCOV
1081
        if (columnMetadata.asExpression) return false
×
1082

UNCOV
1083
        return (
×
1084
            tableColumn.length.toUpperCase() !==
1085
            this.getColumnLength(columnMetadata).toUpperCase()
1086
        )
1087
    }
1088

1089
    protected lowerDefaultValueIfNecessary(value: string | undefined) {
1090
        // SqlServer saves function calls in default value as lowercase https://github.com/typeorm/typeorm/issues/2733
UNCOV
1091
        if (!value) {
×
UNCOV
1092
            return value
×
1093
        }
UNCOV
1094
        return value
×
1095
            .split(`'`)
1096
            .map((v, i) => {
UNCOV
1097
                return i % 2 === 1 ? v : v.toLowerCase()
×
1098
            })
1099
            .join(`'`)
1100
    }
1101

1102
    /**
1103
     * Creates a new connection pool for a given database credentials.
1104
     */
1105
    protected createPool(
1106
        options: SqlServerConnectionOptions,
1107
        credentials: SqlServerConnectionCredentialsOptions,
1108
    ): Promise<any> {
UNCOV
1109
        credentials = Object.assign(
×
1110
            {},
1111
            credentials,
1112
            DriverUtils.buildDriverOptions(credentials),
1113
        ) // todo: do it better way
1114

1115
        // todo: credentials.domain is deprecation. remove it in future
UNCOV
1116
        const authentication = !credentials.domain
×
1117
            ? credentials.authentication
1118
            : {
1119
                  type: "ntlm",
1120
                  options: {
1121
                      domain: credentials.domain,
1122
                      userName: credentials.username,
1123
                      password: credentials.password,
1124
                  },
1125
              }
1126
        // build connection options for the driver
UNCOV
1127
        const connectionOptions = Object.assign(
×
1128
            {},
1129
            {
1130
                connectionTimeout: this.options.connectionTimeout,
1131
                requestTimeout: this.options.requestTimeout,
1132
                stream: this.options.stream,
1133
                pool: this.options.pool,
1134
                options: this.options.options,
1135
            },
1136
            {
1137
                server: credentials.host,
1138
                database: credentials.database,
1139
                port: credentials.port,
1140
                user: credentials.username,
1141
                password: credentials.password,
1142
                authentication: authentication,
1143
            },
1144
            options.extra || {},
×
1145
        )
1146

1147
        // set default useUTC option if it hasn't been set
UNCOV
1148
        if (!connectionOptions.options) {
×
UNCOV
1149
            connectionOptions.options = { useUTC: false }
×
UNCOV
1150
        } else if (!connectionOptions.options.useUTC) {
×
UNCOV
1151
            Object.assign(connectionOptions.options, { useUTC: false })
×
1152
        }
1153

1154
        // Match the next release of tedious for configuration options
1155
        // Also prevents warning messages.
UNCOV
1156
        Object.assign(connectionOptions.options, { enableArithAbort: true })
×
1157

1158
        // pooling is enabled either when its set explicitly to true,
1159
        // either when its not defined at all (e.g. enabled by default)
UNCOV
1160
        return new Promise<void>((ok, fail) => {
×
UNCOV
1161
            const pool = new this.mssql.ConnectionPool(connectionOptions)
×
1162

UNCOV
1163
            const { logger } = this.connection
×
1164

1165
            const poolErrorHandler =
UNCOV
1166
                (options.pool && options.pool.errorHandler) ||
×
1167
                ((error: any) =>
1168
                    logger.log("warn", `MSSQL pool raised an error. ${error}`))
×
1169
            /**
1170
             * Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
1171
             * cause the hosting app to crash.
1172
             */
UNCOV
1173
            pool.on("error", poolErrorHandler)
×
1174

UNCOV
1175
            const connection = pool.connect((err: any) => {
×
UNCOV
1176
                if (err) return fail(err)
×
UNCOV
1177
                ok(connection)
×
1178
            })
1179
        })
1180
    }
1181
}
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