• 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.4
/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"
4✔
11
import { TypeORMError } from "../../error/TypeORMError"
4✔
12
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
13
import { PlatformTools } from "../../platform/PlatformTools"
4✔
14
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
4✔
15
import { ApplyValueTransformers } from "../../util/ApplyValueTransformers"
4✔
16
import { DateUtils } from "../../util/DateUtils"
4✔
17
import { OrmUtils } from "../../util/OrmUtils"
4✔
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"
4✔
24
import { ReplicationMode } from "../types/ReplicationMode"
25
import { DriverUtils } from "../DriverUtils"
4✔
26
import { View } from "../../schema-builder/view/View"
27
import { InstanceChecker } from "../../util/InstanceChecker"
4✔
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 {
4✔
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[] = []
×
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
×
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://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[] = [
×
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[] = []
×
147

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

153
    /**
154
     * Gets list of column data types that support length by a driver.
155
     */
156
    withLengthColumnTypes: ColumnType[] = [
×
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"]
×
168

169
    /**
170
     * Gets list of column data types that support scale by a driver.
171
     */
172
    withScaleColumnTypes: ColumnType[] = ["decimal"]
×
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 = {
×
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 = {
×
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
×
223

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

228
    dummyTableName = `SYS.DUMMY`
×
229

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

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

239
        this.database = DriverUtils.buildDriverOptions(this.options).database
×
240
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
×
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 = {
×
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
×
263
        if (this.options.schema) dbParams.currentSchema = this.options.schema
×
264
        if (this.options.encrypt) dbParams.encrypt = this.options.encrypt
×
265
        if (this.options.sslValidateCertificate)
×
266
            dbParams.validateCertificate = this.options.sslValidateCertificate
×
267
        if (this.options.key) dbParams.key = this.options.key
×
268
        if (this.options.cert) dbParams.cert = this.options.cert
×
269
        if (this.options.ca) dbParams.ca = this.options.ca
×
270

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

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

292
        const { logger } = this.connection
×
293

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

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

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

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

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

313
        await queryRunner.release()
×
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()
×
321
    }
322

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

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

339
    /**
340
     * Creates a query runner used to execute database queries.
341
     */
342
    createQueryRunner(mode: ReplicationMode) {
343
        return new SapQueryRunner(this, mode)
×
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(
×
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)
×
368
            return [sql, escapedParameters]
×
369

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

377
                const value: any = parameters[key]
×
378

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

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

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

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

406
    /**
407
     * Escapes a column name.
408
     */
409
    escape(columnName: string): string {
410
        return `"${columnName}"`
×
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]
×
419

420
        if (schema) {
×
421
            tablePath.unshift(schema)
×
422
        }
423

424
        return tablePath.join(".")
×
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
×
434
        const driverSchema = this.schema
×
435

436
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
×
437
            const parsed = this.parseTableName(target.name)
×
438

439
            return {
×
440
                database: target.database || parsed.database || driverDatabase,
×
441
                schema: target.schema || parsed.schema || driverSchema,
×
442
                tableName: parsed.tableName,
443
            }
444
        }
445

446
        if (InstanceChecker.isTableForeignKey(target)) {
×
447
            const parsed = this.parseTableName(target.referencedTableName)
×
448

449
            return {
×
450
                database:
451
                    target.referencedDatabase ||
×
452
                    parsed.database ||
453
                    driverDatabase,
454
                schema:
455
                    target.referencedSchema || parsed.schema || driverSchema,
×
456
                tableName: parsed.tableName,
457
            }
458
        }
459

460
        if (InstanceChecker.isEntityMetadata(target)) {
×
461
            // EntityMetadata tableName is never a path
462

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

470
        const parts = target.split(".")
×
471

472
        return {
×
473
            database: driverDatabase,
474
            schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
×
475
            tableName: parts.length > 1 ? parts[1] : parts[0],
×
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)
×
484
            value = ApplyValueTransformers.transformTo(
×
485
                columnMetadata.transformer,
486
                value,
487
            )
488

489
        if (value === null || value === undefined) return value
×
490

491
        if (columnMetadata.type === Boolean) {
×
492
            return value === true ? 1 : 0
×
493
        } else if (columnMetadata.type === "date") {
×
494
            return DateUtils.mixedDateToDateString(value)
×
495
        } else if (columnMetadata.type === "time") {
×
496
            return DateUtils.mixedDateToTimeString(value)
×
497
        } else if (
×
498
            columnMetadata.type === "timestamp" ||
×
499
            columnMetadata.type === Date
500
        ) {
501
            return DateUtils.mixedDateToDatetimeString(value, true)
×
502
        } else if (columnMetadata.type === "seconddate") {
×
503
            return DateUtils.mixedDateToDatetimeString(value, false)
×
504
        } else if (columnMetadata.type === "simple-array") {
×
505
            return DateUtils.simpleArrayToString(value)
×
506
        } else if (columnMetadata.type === "simple-json") {
×
507
            return DateUtils.simpleJsonToString(value)
×
508
        } else if (columnMetadata.type === "simple-enum") {
×
509
            return DateUtils.simpleEnumToString(value)
×
510
        } else if (columnMetadata.isArray) {
×
511
            return () => `ARRAY(${value.map((it: any) => `'${it}'`)})`
×
512
        }
513

514
        return value
×
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)
×
522
            return columnMetadata.transformer
×
523
                ? ApplyValueTransformers.transformFrom(
524
                      columnMetadata.transformer,
525
                      value,
526
                  )
527
                : value
528

529
        if (columnMetadata.type === Boolean) {
×
530
            value = value ? true : false
×
531
        } else if (
×
532
            columnMetadata.type === "timestamp" ||
×
533
            columnMetadata.type === "seconddate" ||
534
            columnMetadata.type === Date
535
        ) {
536
            value = DateUtils.normalizeHydratedDate(value)
×
537
        } else if (columnMetadata.type === "date") {
×
538
            value = DateUtils.mixedDateToDateString(value)
×
539
        } else if (columnMetadata.type === "time") {
×
540
            value = DateUtils.mixedTimeToString(value)
×
541
        } else if (columnMetadata.type === "simple-array") {
×
542
            value = DateUtils.stringToSimpleArray(value)
×
543
        } else if (columnMetadata.type === "simple-json") {
×
544
            value = DateUtils.stringToSimpleJson(value)
×
545
        } else if (columnMetadata.type === "simple-enum") {
×
546
            value = DateUtils.stringToSimpleEnum(value, columnMetadata)
×
547
        } else if (columnMetadata.type === Number) {
×
548
            // convert to number if number
549
            value = !isNaN(+value) ? parseInt(value) : value
×
550
        }
551

552
        if (columnMetadata.transformer)
×
553
            value = ApplyValueTransformers.transformFrom(
×
554
                columnMetadata.transformer,
555
                value,
556
            )
557

558
        return value
×
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") {
×
571
            return "integer"
×
572
        } else if (column.type === "dec") {
×
573
            return "decimal"
×
574
        } else if (column.type === "float") {
×
575
            const length =
576
                typeof column.length === "string"
×
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) {
×
582
                return "real"
×
583
            }
584

585
            return "double"
×
586
        } else if (column.type === String) {
×
587
            return "nvarchar"
×
588
        } else if (column.type === Date) {
×
589
            return "timestamp"
×
590
        } else if (column.type === Boolean) {
×
591
            return "boolean"
×
592
        } else if ((column.type as any) === Buffer) {
×
593
            return "blob"
×
594
        } else if (column.type === "uuid") {
×
595
            return "nvarchar"
×
596
        } else if (
×
597
            column.type === "simple-array" ||
×
598
            column.type === "simple-json"
599
        ) {
600
            return "nclob"
×
601
        } else if (column.type === "simple-enum") {
×
602
            return "nvarchar"
×
603
        }
604

605
        if (DriverUtils.isReleaseVersionOrGreater(this, "4.0")) {
×
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) || ""
×
621
    }
622

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

629
        if (typeof defaultValue === "number") {
×
630
            return `${defaultValue}`
×
631
        }
632

633
        if (typeof defaultValue === "boolean") {
×
634
            return defaultValue ? "true" : "false"
×
635
        }
636

637
        if (typeof defaultValue === "function") {
×
638
            return defaultValue()
×
639
        }
640

641
        if (typeof defaultValue === "string") {
×
642
            return `'${defaultValue}'`
×
643
        }
644

645
        if (defaultValue === null || defaultValue === undefined) {
×
646
            return undefined
×
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(
×
657
            (idx) =>
658
                idx.isUnique &&
×
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()
×
669

670
        if (column.generationStrategy === "uuid") return "36"
×
671

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

684
        return ""
×
685
    }
686

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

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

710
        if (column.isArray) type += " array"
×
711

712
        return type
×
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) {
×
722
            throw new TypeORMError("Driver not Connected")
×
723
        }
724

725
        return this.master.getConnection()
×
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(
×
742
            (map, generatedColumn) => {
743
                let value: any
744
                if (
×
745
                    generatedColumn.generationStrategy === "increment" &&
×
746
                    insertResult
747
                ) {
748
                    value = insertResult
×
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(
×
755
                    map,
756
                    generatedColumn.createValueMap(value),
757
                )
758
            },
759
            {} as ObjectLiteral,
760
        )
761

762
        return Object.keys(generatedMap).length > 0 ? generatedMap : undefined
×
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) => {
×
774
            const tableColumn = tableColumns.find(
×
775
                (c) => c.name === columnMetadata.databaseName,
×
776
            )
777
            if (!tableColumn) {
×
778
                // we don't need new columns, we only need exist and changed
779
                return false
×
780
            }
781

782
            const normalizedDefault = this.normalizeDefault(columnMetadata)
×
783

784
            return (
×
785
                tableColumn.name !== columnMetadata.databaseName ||
×
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
×
811
    }
812

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

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

827
    /**
828
     * Creates an escaped parameter.
829
     */
830
    createParameter(parameterName: string, index: number): string {
831
        return "?"
×
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 {
×
843
            const client = this.options.driver || PlatformTools.load("hdb-pool")
×
844
            this.client = client
×
845
        } catch (e) {
846
            // todo: better error for browser env
847
            throw new DriverPackageNotInstalledError("SAP Hana", "hdb-pool")
×
848
        }
849

850
        try {
×
851
            if (!this.options.hanaClientDriver) {
×
852
                PlatformTools.load("@sap/hana-client")
×
853
                this.streamClient = PlatformTools.load(
×
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
×
871

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

874
        return comment
×
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

© 2025 Coveralls, Inc