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

typeorm / typeorm / 14870110473

06 May 2025 09:30PM UTC coverage: 76.301% (-0.05%) from 76.346%
14870110473

push

github

web-flow
fix: update/delete/softDelete by criteria of condition objects (#10910)

Co-authored-by: maxbronnikov10 <maxbronnikov2004@gmail.com>

9180 of 12736 branches covered (72.08%)

Branch coverage included in aggregate %.

48 of 51 new or added lines in 2 files covered. (94.12%)

12 existing lines in 4 files now uncovered.

18818 of 23958 relevant lines covered (78.55%)

197423.2 hits per line

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

82.75
/src/driver/sap/SapDriver.ts
1
import {
2
    ColumnType,
3
    DataSource,
4
    EntityMetadata,
5
    ObjectLiteral,
6
    Table,
7
    TableColumn,
8
    TableForeignKey,
9
} from "../.."
10
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
40✔
11
import { TypeORMError } from "../../error/TypeORMError"
40✔
12
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
13
import { PlatformTools } from "../../platform/PlatformTools"
40✔
14
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
40✔
15
import { ApplyValueTransformers } from "../../util/ApplyValueTransformers"
40✔
16
import { DateUtils } from "../../util/DateUtils"
40✔
17
import { OrmUtils } from "../../util/OrmUtils"
40✔
18
import { Driver } from "../Driver"
19
import { CteCapabilities } from "../types/CteCapabilities"
20
import { DataTypeDefaults } from "../types/DataTypeDefaults"
21
import { MappedColumnTypes } from "../types/MappedColumnTypes"
22
import { SapConnectionOptions } from "./SapConnectionOptions"
23
import { SapQueryRunner } from "./SapQueryRunner"
40✔
24
import { ReplicationMode } from "../types/ReplicationMode"
25
import { DriverUtils } from "../DriverUtils"
40✔
26
import { View } from "../../schema-builder/view/View"
27
import { InstanceChecker } from "../../util/InstanceChecker"
40✔
28
import { UpsertType } from "../types/UpsertType"
29

30
/**
31
 * Organizes communication with SAP Hana DBMS.
32
 *
33
 * todo: looks like there is no built in support for connection pooling, we need to figure out something
34
 */
35
export class SapDriver implements Driver {
40✔
36
    // -------------------------------------------------------------------------
37
    // Public Properties
38
    // -------------------------------------------------------------------------
39

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

45
    /**
46
     * Hana Pool instance.
47
     */
48
    client: any
49

50
    /**
51
     * Hana Client streaming extension.
52
     */
53
    streamClient: any
54
    /**
55
     * Pool for master database.
56
     */
57
    master: any
58

59
    /**
60
     * Pool for slave databases.
61
     * Used in replication.
62
     */
63
    slaves: any[] = []
836✔
64

65
    // -------------------------------------------------------------------------
66
    // Public Implemented Properties
67
    // -------------------------------------------------------------------------
68

69
    /**
70
     * Connection options.
71
     */
72
    options: SapConnectionOptions
73

74
    /**
75
     * Version of SAP HANA. Requires a SQL query to the DB, so it is not always set
76
     */
77
    version?: string
78

79
    /**
80
     * Database name used to perform all write queries.
81
     */
82
    database?: string
83

84
    /**
85
     * Schema name used to perform all write queries.
86
     */
87
    schema?: string
88

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

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

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

104
    /**
105
     * Gets list of supported column data types by a driver.
106
     *
107
     * @see https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/20a1569875191014b507cf392724b7eb.html
108
     * @see https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/data-types
109
     */
110
    supportedDataTypes: ColumnType[] = [
836✔
111
        "tinyint",
112
        "smallint",
113
        "int", // typeorm alias for "integer"
114
        "integer",
115
        "bigint",
116
        "smalldecimal",
117
        "decimal",
118
        "dec", // typeorm alias for "decimal"
119
        "real",
120
        "double",
121
        "float", // database alias for "real" / "double"
122
        "date",
123
        "time",
124
        "seconddate",
125
        "timestamp",
126
        "boolean",
127
        "char", // not officially supported, in SAP HANA Cloud: alias for "nchar"
128
        "nchar", // not officially supported
129
        "varchar", // in SAP HANA Cloud: alias for "nvarchar"
130
        "nvarchar",
131
        "text", // removed in SAP HANA Cloud
132
        "alphanum", // removed in SAP HANA Cloud
133
        "shorttext", // removed in SAP HANA Cloud
134
        "array",
135
        "varbinary",
136
        "blob",
137
        "clob", // in SAP HANA Cloud: alias for "nclob"
138
        "nclob",
139
        "st_geometry",
140
        "st_point",
141
    ]
142

143
    /**
144
     * Returns type of upsert supported by driver if any
145
     */
146
    supportedUpsertTypes: UpsertType[] = []
836✔
147

148
    /**
149
     * Gets list of spatial column data types.
150
     */
151
    spatialTypes: ColumnType[] = ["st_geometry", "st_point"]
836✔
152

153
    /**
154
     * Gets list of column data types that support length by a driver.
155
     */
156
    withLengthColumnTypes: ColumnType[] = [
836✔
157
        "varchar",
158
        "nvarchar",
159
        "alphanum",
160
        "shorttext",
161
        "varbinary",
162
    ]
163

164
    /**
165
     * Gets list of column data types that support precision by a driver.
166
     */
167
    withPrecisionColumnTypes: ColumnType[] = ["decimal"]
836✔
168

169
    /**
170
     * Gets list of column data types that support scale by a driver.
171
     */
172
    withScaleColumnTypes: ColumnType[] = ["decimal"]
836✔
173

174
    /**
175
     * Orm has special columns and we need to know what database column types should be for those types.
176
     * Column types are driver dependant.
177
     */
178
    mappedDataTypes: MappedColumnTypes = {
836✔
179
        createDate: "timestamp",
180
        createDateDefault: "CURRENT_TIMESTAMP",
181
        updateDate: "timestamp",
182
        updateDateDefault: "CURRENT_TIMESTAMP",
183
        deleteDate: "timestamp",
184
        deleteDateNullable: true,
185
        version: "integer",
186
        treeLevel: "integer",
187
        migrationId: "integer",
188
        migrationName: "nvarchar",
189
        migrationTimestamp: "bigint",
190
        cacheId: "integer",
191
        cacheIdentifier: "nvarchar",
192
        cacheTime: "bigint",
193
        cacheDuration: "integer",
194
        cacheQuery: "nvarchar(5000)" as any,
195
        cacheResult: "nclob",
196
        metadataType: "nvarchar",
197
        metadataDatabase: "nvarchar",
198
        metadataSchema: "nvarchar",
199
        metadataTable: "nvarchar",
200
        metadataName: "nvarchar",
201
        metadataValue: "nvarchar(5000)" as any,
202
    }
203

204
    /**
205
     * Default values of length, precision and scale depends on column data type.
206
     * Used in the cases when length/precision/scale is not specified by user.
207
     */
208
    dataTypeDefaults: DataTypeDefaults = {
836✔
209
        char: { length: 1 },
210
        nchar: { length: 1 },
211
        varchar: { length: 255 },
212
        nvarchar: { length: 255 },
213
        shorttext: { length: 255 },
214
        varbinary: { length: 255 },
215
        decimal: { precision: 18, scale: 0 },
216
    }
217

218
    /**
219
     * Max length allowed by SAP HANA for aliases (identifiers).
220
     * @see https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.03/en-US/20a760537519101497e3cfe07b348f3c.html
221
     */
222
    maxAliasLength = 128
836✔
223

224
    cteCapabilities: CteCapabilities = {
836✔
225
        enabled: true,
226
    }
227

228
    dummyTableName = `SYS.DUMMY`
836✔
229

230
    // -------------------------------------------------------------------------
231
    // Constructor
232
    // -------------------------------------------------------------------------
233

234
    constructor(connection: DataSource) {
235
        this.connection = connection
836✔
236
        this.options = connection.options as SapConnectionOptions
836✔
237
        this.loadDependencies()
836✔
238

239
        this.database = DriverUtils.buildDriverOptions(this.options).database
836✔
240
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
836✔
241
    }
242

243
    // -------------------------------------------------------------------------
244
    // Public Implemented Methods
245
    // -------------------------------------------------------------------------
246

247
    /**
248
     * Performs connection to the database.
249
     * Based on pooling options, it can either create connection immediately,
250
     * either create a pool and create connection when needed.
251
     */
252
    async connect(): Promise<void> {
253
        // HANA connection info
254
        const dbParams = {
836✔
255
            hostName: this.options.host,
256
            port: this.options.port,
257
            userName: this.options.username,
258
            password: this.options.password,
259
            ...this.options.extra,
260
        }
261

262
        if (this.options.database) dbParams.databaseName = this.options.database
836!
263
        if (this.options.schema) dbParams.currentSchema = this.options.schema
836!
264
        if (this.options.encrypt) dbParams.encrypt = this.options.encrypt
836!
265
        if (this.options.sslValidateCertificate)
836!
266
            dbParams.validateCertificate = this.options.sslValidateCertificate
×
267
        if (this.options.key) dbParams.key = this.options.key
836!
268
        if (this.options.cert) dbParams.cert = this.options.cert
836!
269
        if (this.options.ca) dbParams.ca = this.options.ca
836!
270

271
        // pool options
272
        const options: any = {
836✔
273
            min:
274
                this.options.pool && this.options.pool.min
1,672!
275
                    ? this.options.pool.min
276
                    : 1,
277
            max:
278
                this.options.pool && this.options.pool.max
1,672!
279
                    ? this.options.pool.max
280
                    : 10,
281
        }
282

283
        if (this.options.pool && this.options.pool.checkInterval)
836!
284
            options.checkInterval = this.options.pool.checkInterval
×
285
        if (this.options.pool && this.options.pool.maxWaitingRequests)
836!
286
            options.maxWaitingRequests = this.options.pool.maxWaitingRequests
×
287
        if (this.options.pool && this.options.pool.requestTimeout)
836!
288
            options.requestTimeout = this.options.pool.requestTimeout
×
289
        if (this.options.pool && this.options.pool.idleTimeout)
836!
290
            options.idleTimeout = this.options.pool.idleTimeout
×
291

292
        const { logger } = this.connection
836✔
293

294
        const poolErrorHandler =
295
            options.poolErrorHandler ||
836✔
296
            ((error: any) =>
UNCOV
297
                logger.log("warn", `SAP Hana pool raised an error. ${error}`))
×
298
        this.client.eventEmitter.on("poolError", poolErrorHandler)
836✔
299

300
        // create the pool
301
        this.master = this.client.createPool(dbParams, options)
836✔
302

303
        const queryRunner = this.createQueryRunner("master")
836✔
304

305
        const { version, database } = await queryRunner.getDatabaseAndVersion()
836✔
306
        this.version = version
836✔
307
        this.database = database
836✔
308

309
        if (!this.schema) {
836✔
310
            this.schema = await queryRunner.getCurrentSchema()
836✔
311
        }
312

313
        await queryRunner.release()
836✔
314
    }
315

316
    /**
317
     * Makes any action after connection (e.g. create extensions in Postgres driver).
318
     */
319
    afterConnect(): Promise<void> {
320
        return Promise.resolve()
832✔
321
    }
322

323
    /**
324
     * Closes connection with the database.
325
     */
326
    async disconnect(): Promise<void> {
327
        const promise = this.master.clear()
836✔
328
        this.master = undefined
836✔
329
        return promise
836✔
330
    }
331

332
    /**
333
     * Creates a schema builder used to build and sync a schema.
334
     */
335
    createSchemaBuilder() {
336
        return new RdbmsSchemaBuilder(this.connection)
2,508✔
337
    }
338

339
    /**
340
     * Creates a query runner used to execute database queries.
341
     */
342
    createQueryRunner(mode: ReplicationMode) {
343
        return new SapQueryRunner(this, mode)
22,034✔
344
    }
345

346
    /**
347
     * Replaces parameters in the given sql with special escaping character
348
     * and an array of parameter names to be passed to a query.
349
     */
350
    escapeQueryWithParameters(
351
        sql: string,
352
        parameters: ObjectLiteral,
353
        nativeParameters: ObjectLiteral,
354
    ): [string, any[]] {
355
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
33,948✔
356
            (key) => {
357
                if (nativeParameters[key] instanceof Date)
×
358
                    return DateUtils.mixedDateToDatetimeString(
×
359
                        nativeParameters[key],
360
                        true,
361
                    )
362

363
                return nativeParameters[key]
×
364
            },
365
        )
366

367
        if (!parameters || !Object.keys(parameters).length)
33,948✔
368
            return [sql, escapedParameters]
1,268✔
369

370
        sql = sql.replace(
32,680✔
371
            /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
372
            (full, isArray: string, key: string): string => {
373
                if (!parameters.hasOwnProperty(key)) {
65,290!
374
                    return full
×
375
                }
376

377
                const value: any = parameters[key]
65,290✔
378

379
                if (isArray) {
65,290✔
380
                    return value
224✔
381
                        .map((v: any) => {
382
                            escapedParameters.push(v)
318✔
383
                            return this.createParameter(
318✔
384
                                key,
385
                                escapedParameters.length - 1,
386
                            )
387
                        })
388
                        .join(", ")
389
                }
390

391
                if (typeof value === "function") {
65,066!
392
                    return value()
×
393
                }
394

395
                if (value instanceof Date) {
65,066!
396
                    return DateUtils.mixedDateToDatetimeString(value, true)
×
397
                }
398

399
                escapedParameters.push(value)
65,066✔
400
                return this.createParameter(key, escapedParameters.length - 1)
65,066✔
401
            },
402
        ) // todo: make replace only in value statements, otherwise problems
403
        return [sql, escapedParameters]
32,680✔
404
    }
405

406
    /**
407
     * Escapes a column name.
408
     */
409
    escape(columnName: string): string {
410
        return `"${columnName}"`
2,851,744✔
411
    }
412

413
    /**
414
     * Build full table name with schema name and table name.
415
     * E.g. myDB.mySchema.myTable
416
     */
417
    buildTableName(tableName: string, schema?: string): string {
418
        const tablePath = [tableName]
613,196✔
419

420
        if (schema) {
613,196✔
421
            tablePath.unshift(schema)
598,956✔
422
        }
423

424
        return tablePath.join(".")
613,196✔
425
    }
426

427
    /**
428
     * Parse a target table name or other types and return a normalized table definition.
429
     */
430
    parseTableName(
431
        target: EntityMetadata | Table | View | TableForeignKey | string,
432
    ): { database?: string; schema?: string; tableName: string } {
433
        const driverDatabase = this.database
977,990✔
434
        const driverSchema = this.schema
977,990✔
435

436
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
977,990✔
437
            const parsed = this.parseTableName(target.name)
326,518✔
438

439
            return {
326,518✔
440
                database: target.database || parsed.database || driverDatabase,
617,984!
441
                schema: target.schema || parsed.schema || driverSchema,
617,818!
442
                tableName: parsed.tableName,
443
            }
444
        }
445

446
        if (InstanceChecker.isTableForeignKey(target)) {
651,472✔
447
            const parsed = this.parseTableName(target.referencedTableName)
6,042✔
448

449
            return {
6,042✔
450
                database:
451
                    target.referencedDatabase ||
11,596!
452
                    parsed.database ||
453
                    driverDatabase,
454
                schema:
455
                    target.referencedSchema || parsed.schema || driverSchema,
11,592!
456
                tableName: parsed.tableName,
457
            }
458
        }
459

460
        if (InstanceChecker.isEntityMetadata(target)) {
645,430✔
461
            // EntityMetadata tableName is never a path
462

463
            return {
304,650✔
464
                database: target.database || driverDatabase,
609,300✔
465
                schema: target.schema || driverSchema,
608,598✔
466
                tableName: target.tableName,
467
            }
468
        }
469

470
        const parts = target.split(".")
340,780✔
471

472
        return {
340,780✔
473
            database: driverDatabase,
474
            schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
1,015,576✔
475
            tableName: parts.length > 1 ? parts[1] : parts[0],
340,780✔
476
        }
477
    }
478

479
    /**
480
     * Prepares given value to a value to be persisted, based on its column type and metadata.
481
     */
482
    preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
483
        if (columnMetadata.transformer)
51,538✔
484
            value = ApplyValueTransformers.transformTo(
66✔
485
                columnMetadata.transformer,
486
                value,
487
            )
488

489
        if (value === null || value === undefined) return value
51,538✔
490

491
        if (columnMetadata.type === Boolean) {
47,428✔
492
            return value === true ? 1 : 0
4,002✔
493
        } else if (columnMetadata.type === "date") {
43,426✔
494
            return DateUtils.mixedDateToDateString(value)
6✔
495
        } else if (columnMetadata.type === "time") {
43,420✔
496
            return DateUtils.mixedDateToTimeString(value)
4✔
497
        } else if (
43,416✔
498
            columnMetadata.type === "timestamp" ||
86,814✔
499
            columnMetadata.type === Date
500
        ) {
501
            return DateUtils.mixedDateToDatetimeString(value, true)
48✔
502
        } else if (columnMetadata.type === "seconddate") {
43,368✔
503
            return DateUtils.mixedDateToDatetimeString(value, false)
2✔
504
        } else if (columnMetadata.type === "simple-array") {
43,366✔
505
            return DateUtils.simpleArrayToString(value)
2✔
506
        } else if (columnMetadata.type === "simple-json") {
43,364✔
507
            return DateUtils.simpleJsonToString(value)
8✔
508
        } else if (columnMetadata.type === "simple-enum") {
43,356!
509
            return DateUtils.simpleEnumToString(value)
×
510
        } else if (columnMetadata.isArray) {
43,356!
511
            return () => `ARRAY(${value.map((it: any) => `'${it}'`)})`
×
512
        }
513

514
        return value
43,356✔
515
    }
516

517
    /**
518
     * Prepares given value to a value to be persisted, based on its column type or metadata.
519
     */
520
    prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
521
        if (value === null || value === undefined)
124,480✔
522
            return columnMetadata.transformer
7,572✔
523
                ? ApplyValueTransformers.transformFrom(
524
                      columnMetadata.transformer,
525
                      value,
526
                  )
527
                : value
528

529
        if (columnMetadata.type === Boolean) {
116,908✔
530
            value = value ? true : false
1,414✔
531
        } else if (
115,494✔
532
            columnMetadata.type === "timestamp" ||
344,988✔
533
            columnMetadata.type === "seconddate" ||
534
            columnMetadata.type === Date
535
        ) {
536
            value = DateUtils.normalizeHydratedDate(value)
768✔
537
        } else if (columnMetadata.type === "date") {
114,726✔
538
            value = DateUtils.mixedDateToDateString(value)
6✔
539
        } else if (columnMetadata.type === "time") {
114,720✔
540
            value = DateUtils.mixedTimeToString(value)
4✔
541
        } else if (columnMetadata.type === "simple-array") {
114,716✔
542
            value = DateUtils.stringToSimpleArray(value)
2✔
543
        } else if (columnMetadata.type === "simple-json") {
114,714✔
544
            value = DateUtils.stringToSimpleJson(value)
8✔
545
        } else if (columnMetadata.type === "simple-enum") {
114,706!
546
            value = DateUtils.stringToSimpleEnum(value, columnMetadata)
×
547
        } else if (columnMetadata.type === Number) {
114,706✔
548
            // convert to number if number
549
            value = !isNaN(+value) ? parseInt(value) : value
25,234!
550
        }
551

552
        if (columnMetadata.transformer)
116,908✔
553
            value = ApplyValueTransformers.transformFrom(
118✔
554
                columnMetadata.transformer,
555
                value,
556
            )
557

558
        return value
116,908✔
559
    }
560

561
    /**
562
     * Creates a database type from a given column metadata.
563
     */
564
    normalizeType(column: {
565
        type?: ColumnType
566
        length?: number | string
567
        precision?: number | null
568
        scale?: number
569
    }): string {
570
        if (column.type === Number || column.type === "int") {
62,034✔
571
            return "integer"
31,812✔
572
        } else if (column.type === "dec") {
30,222✔
573
            return "decimal"
28✔
574
        } else if (column.type === "float") {
30,194✔
575
            const length =
576
                typeof column.length === "string"
34!
577
                    ? parseInt(column.length)
578
                    : column.length
579

580
            // https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/4ee2f261e9c44003807d08ccc2e249ac.html
581
            if (length && length < 25) {
34!
582
                return "real"
×
583
            }
584

585
            return "double"
34✔
586
        } else if (column.type === String) {
30,160✔
587
            return "nvarchar"
23,798✔
588
        } else if (column.type === Date) {
6,362✔
589
            return "timestamp"
120✔
590
        } else if (column.type === Boolean) {
6,242✔
591
            return "boolean"
1,708✔
592
        } else if ((column.type as any) === Buffer) {
4,534✔
593
            return "blob"
14✔
594
        } else if (column.type === "uuid") {
4,520✔
595
            return "nvarchar"
1,036✔
596
        } else if (
3,484✔
597
            column.type === "simple-array" ||
6,954✔
598
            column.type === "simple-json"
599
        ) {
600
            return "nclob"
50✔
601
        } else if (column.type === "simple-enum") {
3,434!
602
            return "nvarchar"
×
603
        }
604

605
        if (DriverUtils.isReleaseVersionOrGreater(this, "4.0")) {
3,434!
606
            // SAP HANA Cloud deprecated / removed these data types
607
            if (
×
608
                column.type === "varchar" ||
×
609
                column.type === "alphanum" ||
610
                column.type === "shorttext"
611
            ) {
612
                return "nvarchar"
×
613
            } else if (column.type === "text" || column.type === "clob") {
×
614
                return "nclob"
×
615
            } else if (column.type === "char") {
×
616
                return "nchar"
×
617
            }
618
        }
619

620
        return (column.type as string) || ""
3,434!
621
    }
622

623
    /**
624
     * Normalizes "default" value of the column.
625
     */
626
    normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
627
        const defaultValue = columnMetadata.default
54,102✔
628

629
        if (typeof defaultValue === "number") {
54,102✔
630
            return `${defaultValue}`
606✔
631
        }
632

633
        if (typeof defaultValue === "boolean") {
53,496✔
634
            return defaultValue ? "true" : "false"
44✔
635
        }
636

637
        if (typeof defaultValue === "function") {
53,452✔
638
            return defaultValue()
1,354✔
639
        }
640

641
        if (typeof defaultValue === "string") {
52,098✔
642
            return `'${defaultValue}'`
1,108✔
643
        }
644

645
        if (defaultValue === null || defaultValue === undefined) {
50,990✔
646
            return undefined
50,990✔
647
        }
648

649
        return `${defaultValue}`
×
650
    }
651

652
    /**
653
     * Normalizes "isUnique" value of the column.
654
     */
655
    normalizeIsUnique(column: ColumnMetadata): boolean {
656
        return column.entityMetadata.indices.some(
54,784✔
657
            (idx) =>
658
                idx.isUnique &&
33,498✔
659
                idx.columns.length === 1 &&
660
                idx.columns[0] === column,
661
        )
662
    }
663

664
    /**
665
     * Returns default column lengths, which is required on column creation.
666
     */
667
    getColumnLength(column: ColumnMetadata | TableColumn): string {
668
        if (column.length) return column.length.toString()
65,288✔
669

670
        if (column.generationStrategy === "uuid") return "36"
42,920✔
671

672
        switch (column.type) {
42,650✔
673
            case "varchar":
674
            case "nvarchar":
675
            case "shorttext":
676
            case String:
677
                return "255"
11,020✔
678
            case "alphanum":
679
                return "127"
8✔
680
            case "varbinary":
681
                return "255"
8✔
682
        }
683

684
        return ""
31,614✔
685
    }
686

687
    /**
688
     * Creates column type definition including length, precision and scale
689
     */
690
    createFullType(column: TableColumn): string {
691
        let type = column.type
26,442✔
692

693
        // used 'getColumnLength()' method, because SqlServer sets `varchar` and `nvarchar` length to 1 by default.
694
        if (this.getColumnLength(column)) {
26,442✔
695
            type += `(${this.getColumnLength(column)})`
10,600✔
696
        } else if (
15,842✔
697
            column.precision !== null &&
31,708✔
698
            column.precision !== undefined &&
699
            column.scale !== null &&
700
            column.scale !== undefined
701
        ) {
702
            type += `(${column.precision},${column.scale})`
12✔
703
        } else if (
15,830!
704
            column.precision !== null &&
31,660✔
705
            column.precision !== undefined
706
        ) {
707
            type += `(${column.precision})`
×
708
        }
709

710
        if (column.isArray) type += " array"
26,442!
711

712
        return type
26,442✔
713
    }
714

715
    /**
716
     * Obtains a new database connection to a master server.
717
     * Used for replication.
718
     * If replication is not setup then returns default connection's database connection.
719
     */
720
    obtainMasterConnection(): Promise<any> {
721
        if (!this.master) {
21,166!
722
            throw new TypeORMError("Driver not Connected")
×
723
        }
724

725
        return this.master.getConnection()
21,166✔
726
    }
727

728
    /**
729
     * Obtains a new database connection to a slave server.
730
     * Used for replication.
731
     * If replication is not setup then returns master (default) connection's database connection.
732
     */
733
    obtainSlaveConnection(): Promise<any> {
734
        return this.obtainMasterConnection()
×
735
    }
736

737
    /**
738
     * Creates generated map of values generated or returned by database after INSERT query.
739
     */
740
    createGeneratedMap(metadata: EntityMetadata, insertResult: ObjectLiteral) {
741
        const generatedMap = metadata.generatedColumns.reduce(
18,112✔
742
            (map, generatedColumn) => {
743
                let value: any
744
                if (
6,620✔
745
                    generatedColumn.generationStrategy === "increment" &&
13,008✔
746
                    insertResult
747
                ) {
748
                    value = insertResult
6,388✔
749
                    // } else if (generatedColumn.generationStrategy === "uuid") {
750
                    //     console.log("getting db value:", generatedColumn.databaseName);
751
                    //     value = generatedColumn.getEntityValue(uuidMap);
752
                }
753

754
                return OrmUtils.mergeDeep(
6,620✔
755
                    map,
756
                    generatedColumn.createValueMap(value),
757
                )
758
            },
759
            {} as ObjectLiteral,
760
        )
761

762
        return Object.keys(generatedMap).length > 0 ? generatedMap : undefined
18,112✔
763
    }
764

765
    /**
766
     * Differentiate columns of this table and columns from the given column metadatas columns
767
     * and returns only changed.
768
     */
769
    findChangedColumns(
770
        tableColumns: TableColumn[],
771
        columnMetadatas: ColumnMetadata[],
772
    ): ColumnMetadata[] {
773
        return columnMetadatas.filter((columnMetadata) => {
8,840✔
774
            const tableColumn = tableColumns.find(
27,388✔
775
                (c) => c.name === columnMetadata.databaseName,
70,234✔
776
            )
777
            if (!tableColumn) {
27,388✔
778
                // we don't need new columns, we only need exist and changed
779
                return false
6✔
780
            }
781

782
            const normalizedDefault = this.normalizeDefault(columnMetadata)
27,382✔
783

784
            return (
27,382✔
785
                tableColumn.name !== columnMetadata.databaseName ||
350,870✔
786
                tableColumn.type !== this.normalizeType(columnMetadata) ||
787
                (columnMetadata.length &&
788
                    tableColumn.length !==
789
                        this.getColumnLength(columnMetadata)) ||
790
                tableColumn.precision !== columnMetadata.precision ||
791
                tableColumn.scale !== columnMetadata.scale ||
792
                tableColumn.comment !==
793
                    this.escapeComment(columnMetadata.comment) ||
794
                (!tableColumn.isGenerated &&
795
                    normalizedDefault !== tableColumn.default) || // we included check for generated here, because generated columns already can have default values
796
                tableColumn.isPrimary !== columnMetadata.isPrimary ||
797
                tableColumn.isNullable !== columnMetadata.isNullable ||
798
                tableColumn.isUnique !==
799
                    this.normalizeIsUnique(columnMetadata) ||
800
                (columnMetadata.generationStrategy !== "uuid" &&
801
                    tableColumn.isGenerated !== columnMetadata.isGenerated)
802
            )
803
        })
804
    }
805

806
    /**
807
     * Returns true if driver supports RETURNING / OUTPUT statement.
808
     */
809
    isReturningSqlSupported(): boolean {
810
        return false
37,830✔
811
    }
812

813
    /**
814
     * Returns true if driver supports uuid values generation on its own.
815
     */
816
    isUUIDGenerationSupported(): boolean {
817
        return false
232✔
818
    }
819

820
    /**
821
     * Returns true if driver supports fulltext indices.
822
     */
823
    isFullTextColumnTypeSupported(): boolean {
824
        return !DriverUtils.isReleaseVersionOrGreater(this, "4.0")
180✔
825
    }
826

827
    /**
828
     * Creates an escaped parameter.
829
     */
830
    createParameter(parameterName: string, index: number): string {
831
        return "?"
65,716✔
832
    }
833

834
    // -------------------------------------------------------------------------
835
    // Protected Methods
836
    // -------------------------------------------------------------------------
837

838
    /**
839
     * If driver dependency is not given explicitly, then try to load it via "require".
840
     */
841
    protected loadDependencies(): void {
842
        try {
836✔
843
            const client = this.options.driver || PlatformTools.load("hdb-pool")
836✔
844
            this.client = client
836✔
845
        } catch (e) {
846
            // todo: better error for browser env
847
            throw new DriverPackageNotInstalledError("SAP Hana", "hdb-pool")
×
848
        }
849

850
        try {
836✔
851
            if (!this.options.hanaClientDriver) {
836✔
852
                PlatformTools.load("@sap/hana-client")
836✔
853
                this.streamClient = PlatformTools.load(
836✔
854
                    "@sap/hana-client/extension/Stream",
855
                )
856
            }
857
        } catch (e) {
858
            // todo: better error for browser env
859
            throw new DriverPackageNotInstalledError(
×
860
                "SAP Hana",
861
                "@sap/hana-client",
862
            )
863
        }
864
    }
865

866
    /**
867
     * Escapes a given comment.
868
     */
869
    protected escapeComment(comment?: string) {
870
        if (!comment) return comment
27,356✔
871

872
        comment = comment.replace(/\u0000/g, "") // Null bytes aren't allowed in comments
126✔
873

874
        return comment
126✔
875
    }
876
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc