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

typeorm / typeorm / 16010787164

01 Jul 2025 09:43PM UTC coverage: 76.381% (+0.02%) from 76.363%
16010787164

push

github

web-flow
feat(sap): use the native driver for connection pooling (#11520)

* feat(sap): use the native driver for connection pooling

* Add pool error handler

9306 of 12880 branches covered (72.25%)

Branch coverage included in aggregate %.

41 of 63 new or added lines in 2 files covered. (65.08%)

3 existing lines in 2 files now uncovered.

19004 of 24184 relevant lines covered (78.58%)

119360.12 hits per line

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

83.94
/src/driver/sap/SapQueryRunner.ts
1
import { promisify } from "node:util"
24✔
2
import { ObjectLiteral } from "../../common/ObjectLiteral"
3
import { QueryFailedError, TypeORMError } from "../../error"
24✔
4
import { QueryRunnerAlreadyReleasedError } from "../../error/QueryRunnerAlreadyReleasedError"
24✔
5
import { TransactionAlreadyStartedError } from "../../error/TransactionAlreadyStartedError"
24✔
6
import { TransactionNotStartedError } from "../../error/TransactionNotStartedError"
24✔
7
import { ReadStream } from "../../platform/PlatformTools"
8
import { BaseQueryRunner } from "../../query-runner/BaseQueryRunner"
24✔
9
import { QueryLock } from "../../query-runner/QueryLock"
24✔
10
import { QueryResult } from "../../query-runner/QueryResult"
24✔
11
import { QueryRunner } from "../../query-runner/QueryRunner"
12
import { TableIndexOptions } from "../../schema-builder/options/TableIndexOptions"
13
import { Table } from "../../schema-builder/table/Table"
24✔
14
import { TableCheck } from "../../schema-builder/table/TableCheck"
24✔
15
import { TableColumn } from "../../schema-builder/table/TableColumn"
24✔
16
import { TableExclusion } from "../../schema-builder/table/TableExclusion"
17
import { TableForeignKey } from "../../schema-builder/table/TableForeignKey"
24✔
18
import { TableIndex } from "../../schema-builder/table/TableIndex"
24✔
19
import { TableUnique } from "../../schema-builder/table/TableUnique"
24✔
20
import { View } from "../../schema-builder/view/View"
24✔
21
import { Broadcaster } from "../../subscriber/Broadcaster"
24✔
22
import { BroadcasterResult } from "../../subscriber/BroadcasterResult"
24✔
23
import { InstanceChecker } from "../../util/InstanceChecker"
24✔
24
import { OrmUtils } from "../../util/OrmUtils"
24✔
25
import { Query } from "../Query"
24✔
26
import { ColumnType } from "../types/ColumnTypes"
27
import { IsolationLevel } from "../types/IsolationLevel"
28
import { MetadataTableType } from "../types/MetadataTableType"
24✔
29
import { ReplicationMode } from "../types/ReplicationMode"
30
import { SapDriver } from "./SapDriver"
31

32
/**
33
 * Runs queries on a single SQL Server database connection.
34
 */
35
export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
24✔
36
    // -------------------------------------------------------------------------
37
    // Public Implemented Properties
38
    // -------------------------------------------------------------------------
39

40
    /**
41
     * Database driver used by connection.
42
     */
43
    driver: SapDriver
44

45
    // -------------------------------------------------------------------------
46
    // Protected Properties
47
    // -------------------------------------------------------------------------
48

49
    /**
50
     * Promise used to obtain a database connection from a pool for a first time.
51
     */
52
    protected databaseConnectionPromise: Promise<any>
53

54
    private lock: QueryLock = new QueryLock()
22,426✔
55

56
    // -------------------------------------------------------------------------
57
    // Constructor
58
    // -------------------------------------------------------------------------
59

60
    constructor(driver: SapDriver, mode: ReplicationMode) {
61
        super()
22,426✔
62
        this.driver = driver
22,426✔
63
        this.connection = driver.connection
22,426✔
64
        this.broadcaster = new Broadcaster(this)
22,426✔
65
        this.mode = mode
22,426✔
66
    }
67

68
    // -------------------------------------------------------------------------
69
    // Public Methods
70
    // -------------------------------------------------------------------------
71

72
    /**
73
     * Creates/uses database connection from the connection pool to perform further operations.
74
     * Returns obtained database connection.
75
     */
76
    async connect(): Promise<any> {
77
        if (this.databaseConnection) return this.databaseConnection
130,716✔
78

79
        this.databaseConnection = await this.driver.obtainMasterConnection()
21,546✔
80

81
        return this.databaseConnection
21,544✔
82
    }
83

84
    /**
85
     * Releases used database connection.
86
     * You cannot use query runner methods once its released.
87
     */
88
    async release(): Promise<void> {
89
        this.isReleased = true
22,424✔
90

91
        if (this.databaseConnection) {
22,424✔
92
            // return the connection back to the pool
93
            try {
21,542✔
94
                await promisify(this.databaseConnection.disconnect).call(
21,542✔
95
                    this.databaseConnection,
96
                )
97
            } catch (error) {
NEW
98
                this.driver.poolErrorHandler(error)
×
NEW
99
                throw error
×
100
            }
101
        }
102
    }
103

104
    /**
105
     * Starts transaction.
106
     */
107
    async startTransaction(isolationLevel?: IsolationLevel): Promise<void> {
108
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
16,936!
109

110
        if (
16,936✔
111
            this.isTransactionActive &&
16,942✔
112
            this.driver.transactionSupport === "simple"
113
        )
114
            throw new TransactionAlreadyStartedError()
6✔
115

116
        await this.broadcaster.broadcast("BeforeTransactionStart")
16,930✔
117

118
        this.isTransactionActive = true
16,930✔
119

120
        /**
121
         * Disable AUTOCOMMIT while running transaction.
122
         *  Otherwise, COMMIT/ROLLBACK doesn't work in autocommit mode.
123
         */
124
        await this.setAutoCommit({ status: "off" })
16,930✔
125

126
        if (isolationLevel) {
16,930✔
127
            await this.query(
10✔
128
                `SET TRANSACTION ISOLATION LEVEL ${isolationLevel || ""}`,
10!
129
            )
130
        }
131

132
        await this.broadcaster.broadcast("AfterTransactionStart")
16,930✔
133
    }
134

135
    /**
136
     * Commits transaction.
137
     * Error will be thrown if transaction was not started.
138
     */
139
    async commitTransaction(): Promise<void> {
140
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
16,890!
141

142
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
16,890✔
143

144
        await this.broadcaster.broadcast("BeforeTransactionCommit")
16,888✔
145

146
        await this.query("COMMIT")
16,888✔
147
        this.isTransactionActive = false
16,888✔
148

149
        await this.setAutoCommit({ status: "on" })
16,888✔
150
        await this.broadcaster.broadcast("AfterTransactionCommit")
16,888✔
151
    }
152

153
    /**
154
     * Rollbacks transaction.
155
     * Error will be thrown if transaction was not started.
156
     */
157
    async rollbackTransaction(): Promise<void> {
158
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
48!
159

160
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
48✔
161

162
        await this.broadcaster.broadcast("BeforeTransactionRollback")
42✔
163

164
        await this.query("ROLLBACK")
42✔
165
        this.isTransactionActive = false
42✔
166

167
        await this.setAutoCommit({ status: "on" })
42✔
168
        await this.broadcaster.broadcast("AfterTransactionRollback")
42✔
169
    }
170

171
    /**
172
     * @description Switches on/off AUTOCOMMIT mode
173
     * @link https://help.sap.com/docs/HANA_SERVICE_CF/7c78579ce9b14a669c1f3295b0d8ca16/d538d11053bd4f3f847ec5ce817a3d4c.html?locale=en-US
174
     */
175
    async setAutoCommit(options: { status: "on" | "off" }) {
176
        const connection = await this.connect()
33,860✔
177
        connection.setAutoCommit(options.status === "on")
33,860✔
178

179
        const query = `SET TRANSACTION AUTOCOMMIT DDL ${options.status.toUpperCase()}`
33,860✔
180
        this.driver.connection.logger.logQuery(query, [], this)
33,860✔
181
        try {
33,860✔
182
            await promisify(connection.exec).call(connection, query)
33,860✔
183
        } catch (error) {
184
            throw new QueryFailedError(query, [], error)
×
185
        }
186
    }
187

188
    /**
189
     * Executes a given SQL query.
190
     */
191
    async query(
192
        query: string,
193
        parameters?: any[],
194
        useStructuredResult = false,
62,522✔
195
    ): Promise<any> {
196
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
96,850!
197

198
        const release = await this.lock.acquire()
96,850✔
199

200
        const databaseConnection = await this.connect()
96,850✔
201

202
        let statement: any
203
        const result = new QueryResult()
96,850✔
204

205
        this.driver.connection.logger.logQuery(query, parameters, this)
96,850✔
206
        await this.broadcaster.broadcast("BeforeQuery", query, parameters)
96,850✔
207

208
        const broadcasterResult = new BroadcasterResult()
96,850✔
209

210
        try {
96,850✔
211
            const queryStartTime = Date.now()
96,850✔
212
            const isInsertQuery = query.substr(0, 11) === "INSERT INTO"
96,850✔
213

214
            if (parameters?.some(Array.isArray)) {
96,850!
215
                statement = await promisify(databaseConnection.prepare).call(
×
216
                    databaseConnection,
217
                    query,
218
                )
219
            }
220

221
            let raw: any
222
            try {
96,850✔
223
                raw = statement
96,850!
224
                    ? await promisify(statement.exec).call(
225
                          statement,
226
                          parameters,
227
                      )
228
                    : await promisify(databaseConnection.exec).call(
229
                          databaseConnection,
230
                          query,
231
                          parameters,
232
                          {},
233
                      )
234
            } catch (err) {
235
                throw new QueryFailedError(query, parameters, err)
12✔
236
            }
237

238
            // log slow queries if maxQueryExecution time is set
239
            const maxQueryExecutionTime =
240
                this.driver.connection.options.maxQueryExecutionTime
96,838✔
241
            const queryEndTime = Date.now()
96,838✔
242
            const queryExecutionTime = queryEndTime - queryStartTime
96,838✔
243

244
            this.broadcaster.broadcastAfterQueryEvent(
96,838✔
245
                broadcasterResult,
246
                query,
247
                parameters,
248
                true,
249
                queryExecutionTime,
250
                raw,
251
                undefined,
252
            )
253

254
            if (
96,838!
255
                maxQueryExecutionTime &&
96,838!
256
                queryExecutionTime > maxQueryExecutionTime
257
            ) {
258
                this.driver.connection.logger.logQuerySlow(
×
259
                    queryExecutionTime,
260
                    query,
261
                    parameters,
262
                    this,
263
                )
264
            }
265

266
            if (typeof raw === "number") {
96,838✔
267
                result.affected = raw
37,438✔
268
            } else if (Array.isArray(raw)) {
59,400✔
269
                result.records = raw
31,510✔
270
            }
271

272
            result.raw = raw
96,838✔
273

274
            if (isInsertQuery) {
96,838✔
275
                const lastIdQuery = `SELECT CURRENT_IDENTITY_VALUE() FROM "SYS"."DUMMY"`
18,914✔
276
                this.driver.connection.logger.logQuery(lastIdQuery, [], this)
18,914✔
277
                try {
18,914✔
278
                    const identityValueResult: [
279
                        { "CURRENT_IDENTITY_VALUE()": unknown },
280
                    ] = await promisify(databaseConnection.exec).call(
18,914✔
281
                        databaseConnection,
282
                        lastIdQuery,
283
                    )
284

285
                    result.raw =
18,914✔
286
                        identityValueResult[0]["CURRENT_IDENTITY_VALUE()"]
287
                    result.records = identityValueResult
18,914✔
288
                } catch (error) {
NEW
289
                    throw new QueryFailedError(lastIdQuery, [], error)
×
290
                }
291
            }
292
        } catch (err) {
293
            this.driver.connection.logger.logQueryError(
12✔
294
                err,
295
                query,
296
                parameters,
297
                this,
298
            )
299
            this.broadcaster.broadcastAfterQueryEvent(
12✔
300
                broadcasterResult,
301
                query,
302
                parameters,
303
                false,
304
                undefined,
305
                undefined,
306
                err,
307
            )
308
            throw err
12✔
309
        } finally {
310
            // Never forget to drop the statement we reserved
311
            if (statement?.drop) {
96,850!
NEW
312
                await promisify(statement.drop).call(statement)
×
313
            }
314

315
            await broadcasterResult.wait()
96,850✔
316

317
            // Always release the lock.
318
            release()
96,850✔
319
        }
320

321
        if (useStructuredResult) {
96,838✔
322
            return result
34,318✔
323
        } else {
324
            return result.raw
62,520✔
325
        }
326
    }
327

328
    /**
329
     * Returns raw data stream.
330
     */
331
    async stream(
332
        query: string,
333
        parameters?: any[],
334
        onEnd?: Function,
335
        onError?: Function,
336
    ): Promise<ReadStream> {
337
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
2!
338

339
        const release = await this.lock.acquire()
2✔
340
        let statement: any
341
        let resultSet: any
342

343
        const cleanup = async () => {
2✔
NEW
344
            const originalStatement = statement
×
NEW
345
            const originalResultSet = resultSet
×
NEW
346
            statement = null
×
NEW
347
            resultSet = null
×
NEW
348
            if (originalResultSet) {
×
NEW
349
                await promisify(originalResultSet.close).call(originalResultSet)
×
350
            }
NEW
351
            if (originalStatement) {
×
NEW
352
                await promisify(originalStatement.drop).call(originalStatement)
×
353
            }
UNCOV
354
            release()
×
355
        }
356

357
        try {
2✔
358
            const databaseConnection = await this.connect()
2✔
359
            this.driver.connection.logger.logQuery(query, parameters, this)
2✔
360

361
            statement = await promisify(databaseConnection.prepare).call(
2✔
362
                databaseConnection,
363
                query,
364
            )
365
            resultSet = await promisify(statement.executeQuery).call(
2✔
366
                statement,
367
                parameters,
368
            )
369

370
            const stream =
371
                this.driver.streamClient.createObjectStream(resultSet)
2✔
372

373
            if (onEnd) {
2!
NEW
374
                stream.on("end", onEnd)
×
375
            }
376
            stream.on("error", (error: Error) => {
2✔
UNCOV
377
                this.driver.connection.logger.logQueryError(
×
378
                    error,
379
                    query,
380
                    parameters,
381
                    this,
382
                )
383
                onError?.(error)
×
384
            })
385
            stream.on("close", cleanup)
2✔
386

387
            return stream
2✔
388
        } catch (error) {
389
            this.driver.connection.logger.logQueryError(
×
390
                error,
391
                query,
392
                parameters,
393
                this,
394
            )
395
            await cleanup()
×
396
            throw new QueryFailedError(query, parameters, error)
×
397
        }
398
    }
399

400
    /**
401
     * Returns all available database names including system databases.
402
     */
403
    async getDatabases(): Promise<string[]> {
404
        const results: ObjectLiteral[] = await this.query(
×
405
            `SELECT DATABASE_NAME FROM "SYS"."M_DATABASES"`,
406
        )
407
        return results.map((result) => result["DATABASE_NAME"])
×
408
    }
409

410
    /**
411
     * Returns all available schema names including system schemas.
412
     * If database parameter specified, returns schemas of that database.
413
     */
414
    async getSchemas(database?: string): Promise<string[]> {
415
        const query = database
6!
416
            ? `SELECT * FROM "${database}"."SYS"."SCHEMAS"`
417
            : `SELECT * FROM "SYS"."SCHEMAS"`
418
        const results: ObjectLiteral[] = await this.query(query)
6✔
419
        return results.map((result) => result["SCHEMA_NAME"])
134✔
420
    }
421

422
    /**
423
     * Checks if database with the given name exist.
424
     */
425
    async hasDatabase(database: string): Promise<boolean> {
426
        const databases = await this.getDatabases()
×
427
        return databases.indexOf(database) !== -1
×
428
    }
429

430
    /**
431
     * Returns current database.
432
     */
433
    async getCurrentDatabase(): Promise<string> {
434
        const currentDBQuery: [{ dbName: string }] = await this.query(
2,914✔
435
            `SELECT "DATABASE_NAME" AS "dbName" FROM "SYS"."M_DATABASE"`,
436
        )
437

438
        return currentDBQuery[0].dbName
2,914✔
439
    }
440

441
    /**
442
     * Returns the database server version.
443
     */
444
    async getDatabaseAndVersion(): Promise<{
445
        database: string
446
        version: string
447
    }> {
448
        const currentDBQuery: [{ database: string; version: string }] =
449
            await this.query(
850✔
450
                `SELECT  "DATABASE_NAME" AS "database", "VERSION" AS "version" FROM "SYS"."M_DATABASE"`,
451
            )
452

453
        return currentDBQuery[0]
850✔
454
    }
455

456
    /**
457
     * Checks if schema with the given name exist.
458
     */
459
    async hasSchema(schema: string): Promise<boolean> {
460
        const schemas = await this.getSchemas()
6✔
461
        return schemas.indexOf(schema) !== -1
6✔
462
    }
463

464
    /**
465
     * Returns current schema.
466
     */
467
    async getCurrentSchema(): Promise<string> {
468
        const currentSchemaQuery: [{ schemaName: string }] = await this.query(
3,764✔
469
            `SELECT CURRENT_SCHEMA AS "schemaName" FROM "SYS"."DUMMY"`,
470
        )
471

472
        return currentSchemaQuery[0].schemaName
3,764✔
473
    }
474

475
    /**
476
     * Checks if table with the given name exist in the database.
477
     */
478
    async hasTable(tableOrName: Table | string): Promise<boolean> {
479
        const parsedTableName = this.driver.parseTableName(tableOrName)
2,624✔
480

481
        if (!parsedTableName.schema) {
2,624!
482
            parsedTableName.schema = await this.getCurrentSchema()
×
483
        }
484

485
        const sql = `SELECT COUNT(*) as "hasTable" FROM "SYS"."TABLES" WHERE "SCHEMA_NAME" = '${parsedTableName.schema}' AND "TABLE_NAME" = '${parsedTableName.tableName}'`
2,624✔
486
        const result: [{ hasTable: number }] = await this.query(sql)
2,624✔
487

488
        return result[0].hasTable > 0
2,624✔
489
    }
490

491
    /**
492
     * Checks if column with the given name exist in the given table.
493
     */
494
    async hasColumn(
495
        tableOrName: Table | string,
496
        columnName: string,
497
    ): Promise<boolean> {
498
        const parsedTableName = this.driver.parseTableName(tableOrName)
8✔
499

500
        if (!parsedTableName.schema) {
8!
501
            parsedTableName.schema = await this.getCurrentSchema()
×
502
        }
503

504
        const sql = `SELECT COUNT(*) as "hasColumn" FROM "SYS"."TABLE_COLUMNS" WHERE "SCHEMA_NAME" = '${parsedTableName.schema}' AND "TABLE_NAME" = '${parsedTableName.tableName}' AND "COLUMN_NAME" = '${columnName}'`
8✔
505
        const result: [{ hasColumn: number }] = await this.query(sql)
8✔
506

507
        return result[0].hasColumn > 0
8✔
508
    }
509

510
    /**
511
     * Creates a new database.
512
     */
513
    async createDatabase(
514
        database: string,
515
        ifNotExist?: boolean,
516
    ): Promise<void> {
517
        return Promise.resolve()
×
518
    }
519

520
    /**
521
     * Drops database.
522
     */
523
    async dropDatabase(database: string, ifExist?: boolean): Promise<void> {
524
        return Promise.resolve()
4✔
525
    }
526

527
    /**
528
     * Creates a new table schema.
529
     */
530
    async createSchema(
531
        schemaPath: string,
532
        ifNotExist?: boolean,
533
    ): Promise<void> {
534
        const schema =
535
            schemaPath.indexOf(".") === -1
12!
536
                ? schemaPath
537
                : schemaPath.split(".")[1]
538

539
        let exist = false
12✔
540
        if (ifNotExist) {
12✔
541
            const result = await this.query(
12✔
542
                `SELECT * FROM "SYS"."SCHEMAS" WHERE "SCHEMA_NAME" = '${schema}'`,
543
            )
544
            exist = !!result.length
12✔
545
        }
546
        if (!ifNotExist || (ifNotExist && !exist)) {
12✔
547
            const up = `CREATE SCHEMA "${schema}"`
10✔
548
            const down = `DROP SCHEMA "${schema}" CASCADE`
10✔
549
            await this.executeQueries(new Query(up), new Query(down))
10✔
550
        }
551
    }
552

553
    /**
554
     * Drops table schema
555
     */
556
    async dropSchema(
557
        schemaPath: string,
558
        ifExist?: boolean,
559
        isCascade?: boolean,
560
    ): Promise<void> {
561
        const schema =
562
            schemaPath.indexOf(".") === -1
2!
563
                ? schemaPath
564
                : schemaPath.split(".")[0]
565
        let exist = false
2✔
566
        if (ifExist) {
2!
567
            const result = await this.query(
×
568
                `SELECT * FROM "SYS"."SCHEMAS" WHERE "SCHEMA_NAME" = '${schema}'`,
569
            )
570
            exist = !!result.length
×
571
        }
572
        if (!ifExist || (ifExist && exist)) {
2!
573
            const up = `DROP SCHEMA "${schema}" ${isCascade ? "CASCADE" : ""}`
2!
574
            const down = `CREATE SCHEMA "${schema}"`
2✔
575
            await this.executeQueries(new Query(up), new Query(down))
2✔
576
        }
577
    }
578

579
    /**
580
     * Creates a new table.
581
     */
582
    async createTable(
583
        table: Table,
584
        ifNotExist: boolean = false,
42✔
585
        createForeignKeys: boolean = true,
76✔
586
        createIndices: boolean = true,
8,520✔
587
    ): Promise<void> {
588
        if (ifNotExist) {
8,520✔
589
            const isTableExist = await this.hasTable(table)
34✔
590
            if (isTableExist) return Promise.resolve()
34✔
591
        }
592
        const upQueries: Query[] = []
8,518✔
593
        const downQueries: Query[] = []
8,518✔
594

595
        upQueries.push(this.createTableSql(table, createForeignKeys))
8,518✔
596
        downQueries.push(this.dropTableSql(table))
8,518✔
597

598
        // if createForeignKeys is true, we must drop created foreign keys in down query.
599
        // createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
600
        if (createForeignKeys)
8,518✔
601
            table.foreignKeys.forEach((foreignKey) =>
74✔
602
                downQueries.push(this.dropForeignKeySql(table, foreignKey)),
8✔
603
            )
604

605
        if (createIndices) {
8,518✔
606
            table.indices.forEach((index) => {
8,518✔
607
                // new index may be passed without name. In this case we generate index name manually.
608
                if (!index.name)
4,688✔
609
                    index.name = this.connection.namingStrategy.indexName(
10✔
610
                        table,
611
                        index.columnNames,
612
                        index.where,
613
                    )
614
                upQueries.push(this.createIndexSql(table, index))
4,688✔
615
                downQueries.push(this.dropIndexSql(table, index))
4,688✔
616
            })
617
        }
618

619
        await this.executeQueries(upQueries, downQueries)
8,518✔
620
    }
621

622
    /**
623
     * Drops the table.
624
     */
625
    async dropTable(
626
        tableOrName: Table | string,
627
        ifExist?: boolean,
628
        dropForeignKeys: boolean = true,
8✔
629
        dropIndices: boolean = true,
16✔
630
    ): Promise<void> {
631
        if (ifExist) {
16✔
632
            const isTableExist = await this.hasTable(tableOrName)
8✔
633
            if (!isTableExist) return Promise.resolve()
8!
634
        }
635

636
        // if dropTable called with dropForeignKeys = true, we must create foreign keys in down query.
637
        const createForeignKeys: boolean = dropForeignKeys
16✔
638
        const table = InstanceChecker.isTable(tableOrName)
16✔
639
            ? tableOrName
640
            : await this.getCachedTable(tableOrName)
641
        const upQueries: Query[] = []
16✔
642
        const downQueries: Query[] = []
16✔
643

644
        // It needs because if table does not exist and dropForeignKeys or dropIndices is true, we don't need
645
        // to perform drop queries for foreign keys and indices.
646

647
        if (dropIndices) {
16✔
648
            table.indices.forEach((index) => {
16✔
649
                upQueries.push(this.dropIndexSql(table, index))
6✔
650
                downQueries.push(this.createIndexSql(table, index))
6✔
651
            })
652
        }
653

654
        // if dropForeignKeys is true, we just drop the table, otherwise we also drop table foreign keys.
655
        // createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
656
        if (dropForeignKeys)
16✔
657
            table.foreignKeys.forEach((foreignKey) =>
8✔
658
                upQueries.push(this.dropForeignKeySql(table, foreignKey)),
4✔
659
            )
660

661
        upQueries.push(this.dropTableSql(table))
16✔
662
        downQueries.push(this.createTableSql(table, createForeignKeys))
16✔
663

664
        await this.executeQueries(upQueries, downQueries)
16✔
665
    }
666

667
    /**
668
     * Creates a new view.
669
     */
670
    async createView(
671
        view: View,
672
        syncWithMetadata: boolean = false,
×
673
    ): Promise<void> {
674
        const upQueries: Query[] = []
12✔
675
        const downQueries: Query[] = []
12✔
676
        upQueries.push(this.createViewSql(view))
12✔
677
        if (syncWithMetadata)
12✔
678
            upQueries.push(await this.insertViewDefinitionSql(view))
12✔
679
        downQueries.push(this.dropViewSql(view))
12✔
680
        if (syncWithMetadata)
12✔
681
            downQueries.push(await this.deleteViewDefinitionSql(view))
12✔
682
        await this.executeQueries(upQueries, downQueries)
12✔
683
    }
684

685
    /**
686
     * Drops the view.
687
     */
688
    async dropView(target: View | string): Promise<void> {
689
        const viewName = InstanceChecker.isView(target) ? target.name : target
×
690
        const view = await this.getCachedView(viewName)
×
691

692
        const upQueries: Query[] = []
×
693
        const downQueries: Query[] = []
×
694
        upQueries.push(await this.deleteViewDefinitionSql(view))
×
695
        upQueries.push(this.dropViewSql(view))
×
696
        downQueries.push(await this.insertViewDefinitionSql(view))
×
697
        downQueries.push(this.createViewSql(view))
×
698
        await this.executeQueries(upQueries, downQueries)
×
699
    }
700

701
    /**
702
     * Renames a table.
703
     */
704
    async renameTable(
705
        oldTableOrName: Table | string,
706
        newTableName: string,
707
    ): Promise<void> {
708
        const upQueries: Query[] = []
10✔
709
        const downQueries: Query[] = []
10✔
710
        const oldTable = InstanceChecker.isTable(oldTableOrName)
10✔
711
            ? oldTableOrName
712
            : await this.getCachedTable(oldTableOrName)
713
        const newTable = oldTable.clone()
10✔
714

715
        const { schema: schemaName, tableName: oldTableName } =
716
            this.driver.parseTableName(oldTable)
10✔
717

718
        newTable.name = schemaName
10!
719
            ? `${schemaName}.${newTableName}`
720
            : newTableName
721

722
        // rename table
723
        upQueries.push(
10✔
724
            new Query(
725
                `RENAME TABLE ${this.escapePath(oldTable)} TO ${this.escapePath(
726
                    newTable,
727
                )}`,
728
            ),
729
        )
730
        downQueries.push(
10✔
731
            new Query(
732
                `RENAME TABLE ${this.escapePath(newTable)} TO ${this.escapePath(
733
                    oldTable,
734
                )}`,
735
            ),
736
        )
737

738
        // drop old FK's. Foreign keys must be dropped before the primary keys are dropped
739
        newTable.foreignKeys.forEach((foreignKey) => {
10✔
740
            upQueries.push(this.dropForeignKeySql(newTable, foreignKey))
2✔
741
            downQueries.push(this.createForeignKeySql(newTable, foreignKey))
2✔
742
        })
743

744
        // SAP HANA does not allow to drop PK's which is referenced by foreign keys.
745
        // To avoid this, we must drop all referential foreign keys and recreate them later
746
        const referencedForeignKeySql = `SELECT * FROM "SYS"."REFERENTIAL_CONSTRAINTS" WHERE "REFERENCED_SCHEMA_NAME" = '${schemaName}' AND "REFERENCED_TABLE_NAME" = '${oldTableName}'`
10✔
747
        const dbForeignKeys: ObjectLiteral[] = await this.query(
10✔
748
            referencedForeignKeySql,
749
        )
750
        let referencedForeignKeys: TableForeignKey[] = []
10✔
751
        const referencedForeignKeyTableMapping: {
752
            tableName: string
753
            fkName: string
754
        }[] = []
10✔
755
        if (dbForeignKeys.length > 0) {
10✔
756
            referencedForeignKeys = dbForeignKeys.map((dbForeignKey) => {
6✔
757
                const foreignKeys = dbForeignKeys.filter(
6✔
758
                    (dbFk) =>
759
                        dbFk["CONSTRAINT_NAME"] ===
6✔
760
                        dbForeignKey["CONSTRAINT_NAME"],
761
                )
762

763
                referencedForeignKeyTableMapping.push({
6✔
764
                    tableName: `${dbForeignKey["SCHEMA_NAME"]}.${dbForeignKey["TABLE_NAME"]}`,
765
                    fkName: dbForeignKey["CONSTRAINT_NAME"],
766
                })
767
                return new TableForeignKey({
6✔
768
                    name: dbForeignKey["CONSTRAINT_NAME"],
769
                    columnNames: foreignKeys.map((dbFk) => dbFk["COLUMN_NAME"]),
6✔
770
                    referencedDatabase: newTable.database,
771
                    referencedSchema: newTable.schema,
772
                    referencedTableName: newTable.name, // we use renamed table name
773
                    referencedColumnNames: foreignKeys.map(
774
                        (dbFk) => dbFk["REFERENCED_COLUMN_NAME"],
6✔
775
                    ),
776
                    onDelete:
777
                        dbForeignKey["DELETE_RULE"] === "RESTRICT"
6!
778
                            ? "NO ACTION"
779
                            : dbForeignKey["DELETE_RULE"],
780
                    onUpdate:
781
                        dbForeignKey["UPDATE_RULE"] === "RESTRICT"
6!
782
                            ? "NO ACTION"
783
                            : dbForeignKey["UPDATE_RULE"],
784
                    deferrable: dbForeignKey["CHECK_TIME"].replace("_", " "), // "CHECK_TIME" is "INITIALLY_IMMEDIATE" or "INITIALLY DEFERRED"
785
                })
786
            })
787

788
            // drop referenced foreign keys
789
            referencedForeignKeys.forEach((foreignKey) => {
6✔
790
                const mapping = referencedForeignKeyTableMapping.find(
6✔
791
                    (it) => it.fkName === foreignKey.name,
6✔
792
                )
793
                upQueries.push(
6✔
794
                    this.dropForeignKeySql(mapping!.tableName, foreignKey),
795
                )
796
                downQueries.push(
6✔
797
                    this.createForeignKeySql(mapping!.tableName, foreignKey),
798
                )
799
            })
800
        }
801

802
        // rename primary key constraint
803
        if (newTable.primaryColumns.length > 0) {
10✔
804
            const columnNames = newTable.primaryColumns.map(
10✔
805
                (column) => column.name,
10✔
806
            )
807
            const columnNamesString = columnNames
10✔
808
                .map((columnName) => `"${columnName}"`)
10✔
809
                .join(", ")
810

811
            const oldPkName = this.connection.namingStrategy.primaryKeyName(
10✔
812
                oldTable,
813
                columnNames,
814
            )
815
            const newPkName = this.connection.namingStrategy.primaryKeyName(
10✔
816
                newTable,
817
                columnNames,
818
            )
819

820
            // drop old PK
821
            upQueries.push(
10✔
822
                new Query(
823
                    `ALTER TABLE ${this.escapePath(
824
                        newTable,
825
                    )} DROP CONSTRAINT "${oldPkName}"`,
826
                ),
827
            )
828
            downQueries.push(
10✔
829
                new Query(
830
                    `ALTER TABLE ${this.escapePath(
831
                        newTable,
832
                    )} ADD CONSTRAINT "${oldPkName}" PRIMARY KEY (${columnNamesString})`,
833
                ),
834
            )
835

836
            // create new PK
837
            upQueries.push(
10✔
838
                new Query(
839
                    `ALTER TABLE ${this.escapePath(
840
                        newTable,
841
                    )} ADD CONSTRAINT "${newPkName}" PRIMARY KEY (${columnNamesString})`,
842
                ),
843
            )
844
            downQueries.push(
10✔
845
                new Query(
846
                    `ALTER TABLE ${this.escapePath(
847
                        newTable,
848
                    )} DROP CONSTRAINT "${newPkName}"`,
849
                ),
850
            )
851
        }
852

853
        // recreate foreign keys with new constraint names
854
        newTable.foreignKeys.forEach((foreignKey) => {
10✔
855
            // replace constraint name
856
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
2✔
857
                newTable,
858
                foreignKey.columnNames,
859
                this.getTablePath(foreignKey),
860
                foreignKey.referencedColumnNames,
861
            )
862

863
            // create new FK's
864
            upQueries.push(this.createForeignKeySql(newTable, foreignKey))
2✔
865
            downQueries.push(this.dropForeignKeySql(newTable, foreignKey))
2✔
866
        })
867

868
        // restore referenced foreign keys
869
        referencedForeignKeys.forEach((foreignKey) => {
10✔
870
            const mapping = referencedForeignKeyTableMapping.find(
6✔
871
                (it) => it.fkName === foreignKey.name,
6✔
872
            )
873
            upQueries.push(
6✔
874
                this.createForeignKeySql(mapping!.tableName, foreignKey),
875
            )
876
            downQueries.push(
6✔
877
                this.dropForeignKeySql(mapping!.tableName, foreignKey),
878
            )
879
        })
880

881
        // rename index constraints
882
        newTable.indices.forEach((index) => {
10✔
883
            // build new constraint name
884
            const newIndexName = this.connection.namingStrategy.indexName(
8✔
885
                newTable,
886
                index.columnNames,
887
                index.where,
888
            )
889

890
            // drop old index
891
            upQueries.push(this.dropIndexSql(newTable, index))
8✔
892
            downQueries.push(this.createIndexSql(newTable, index))
8✔
893

894
            // replace constraint name
895
            index.name = newIndexName
8✔
896

897
            // create new index
898
            upQueries.push(this.createIndexSql(newTable, index))
8✔
899
            downQueries.push(this.dropIndexSql(newTable, index))
8✔
900
        })
901

902
        await this.executeQueries(upQueries, downQueries)
10✔
903

904
        // rename old table and replace it in cached tabled;
905
        oldTable.name = newTable.name
10✔
906
        this.replaceCachedTable(oldTable, newTable)
10✔
907
    }
908

909
    /**
910
     * Creates a new column from the column in the table.
911
     */
912
    async addColumn(
913
        tableOrName: Table | string,
914
        column: TableColumn,
915
    ): Promise<void> {
916
        const table = InstanceChecker.isTable(tableOrName)
52✔
917
            ? tableOrName
918
            : await this.getCachedTable(tableOrName)
919
        const parsedTableName = this.driver.parseTableName(table)
52✔
920

921
        if (!parsedTableName.schema) {
52!
922
            parsedTableName.schema = await this.getCurrentSchema()
×
923
        }
924

925
        const clonedTable = table.clone()
52✔
926
        const upQueries: Query[] = []
52✔
927
        const downQueries: Query[] = []
52✔
928

929
        upQueries.push(new Query(this.addColumnSql(table, column)))
52✔
930
        downQueries.push(new Query(this.dropColumnSql(table, column)))
52✔
931

932
        // create or update primary key constraint
933
        if (column.isPrimary) {
52✔
934
            const primaryColumns = clonedTable.primaryColumns
14✔
935
            // if table already have primary key, me must drop it and recreate again
936
            if (primaryColumns.length > 0) {
14✔
937
                // SAP HANA does not allow to drop PK's which is referenced by foreign keys.
938
                // To avoid this, we must drop all referential foreign keys and recreate them later
939
                const referencedForeignKeySql = `SELECT * FROM "SYS"."REFERENTIAL_CONSTRAINTS" WHERE "REFERENCED_SCHEMA_NAME" = '${parsedTableName.schema}' AND "REFERENCED_TABLE_NAME" = '${parsedTableName.tableName}'`
4✔
940
                const dbForeignKeys: ObjectLiteral[] = await this.query(
4✔
941
                    referencedForeignKeySql,
942
                )
943
                let referencedForeignKeys: TableForeignKey[] = []
4✔
944
                const referencedForeignKeyTableMapping: {
945
                    tableName: string
946
                    fkName: string
947
                }[] = []
4✔
948
                if (dbForeignKeys.length > 0) {
4✔
949
                    referencedForeignKeys = dbForeignKeys.map(
2✔
950
                        (dbForeignKey) => {
951
                            const foreignKeys = dbForeignKeys.filter(
2✔
952
                                (dbFk) =>
953
                                    dbFk["CONSTRAINT_NAME"] ===
2✔
954
                                    dbForeignKey["CONSTRAINT_NAME"],
955
                            )
956

957
                            referencedForeignKeyTableMapping.push({
2✔
958
                                tableName: `${dbForeignKey["SCHEMA_NAME"]}.${dbForeignKey["TABLE_NAME"]}`,
959
                                fkName: dbForeignKey["CONSTRAINT_NAME"],
960
                            })
961
                            return new TableForeignKey({
2✔
962
                                name: dbForeignKey["CONSTRAINT_NAME"],
963
                                columnNames: foreignKeys.map(
964
                                    (dbFk) => dbFk["COLUMN_NAME"],
2✔
965
                                ),
966
                                referencedDatabase: table.database,
967
                                referencedSchema: table.schema,
968
                                referencedTableName: table.name,
969
                                referencedColumnNames: foreignKeys.map(
970
                                    (dbFk) => dbFk["REFERENCED_COLUMN_NAME"],
2✔
971
                                ),
972
                                onDelete:
973
                                    dbForeignKey["DELETE_RULE"] === "RESTRICT"
2!
974
                                        ? "NO ACTION"
975
                                        : dbForeignKey["DELETE_RULE"],
976
                                onUpdate:
977
                                    dbForeignKey["UPDATE_RULE"] === "RESTRICT"
2!
978
                                        ? "NO ACTION"
979
                                        : dbForeignKey["UPDATE_RULE"],
980
                                deferrable: dbForeignKey["CHECK_TIME"].replace(
981
                                    "_",
982
                                    " ",
983
                                ),
984
                            })
985
                        },
986
                    )
987

988
                    // drop referenced foreign keys
989
                    referencedForeignKeys.forEach((foreignKey) => {
2✔
990
                        const mapping = referencedForeignKeyTableMapping.find(
2✔
991
                            (it) => it.fkName === foreignKey.name,
2✔
992
                        )
993
                        upQueries.push(
2✔
994
                            this.dropForeignKeySql(
995
                                mapping!.tableName,
996
                                foreignKey,
997
                            ),
998
                        )
999
                        downQueries.push(
2✔
1000
                            this.createForeignKeySql(
1001
                                mapping!.tableName,
1002
                                foreignKey,
1003
                            ),
1004
                        )
1005
                    })
1006
                }
1007

1008
                const pkName = this.connection.namingStrategy.primaryKeyName(
4✔
1009
                    clonedTable,
1010
                    primaryColumns.map((column) => column.name),
4✔
1011
                )
1012
                const columnNames = primaryColumns
4✔
1013
                    .map((column) => `"${column.name}"`)
4✔
1014
                    .join(", ")
1015
                upQueries.push(
4✔
1016
                    new Query(
1017
                        `ALTER TABLE ${this.escapePath(
1018
                            table,
1019
                        )} DROP CONSTRAINT "${pkName}"`,
1020
                    ),
1021
                )
1022
                downQueries.push(
4✔
1023
                    new Query(
1024
                        `ALTER TABLE ${this.escapePath(
1025
                            table,
1026
                        )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1027
                    ),
1028
                )
1029

1030
                // restore referenced foreign keys
1031
                referencedForeignKeys.forEach((foreignKey) => {
4✔
1032
                    const mapping = referencedForeignKeyTableMapping.find(
2✔
1033
                        (it) => it.fkName === foreignKey.name,
2✔
1034
                    )
1035
                    upQueries.push(
2✔
1036
                        this.createForeignKeySql(
1037
                            mapping!.tableName,
1038
                            foreignKey,
1039
                        ),
1040
                    )
1041
                    downQueries.push(
2✔
1042
                        this.dropForeignKeySql(mapping!.tableName, foreignKey),
1043
                    )
1044
                })
1045
            }
1046

1047
            primaryColumns.push(column)
14✔
1048
            const pkName = this.connection.namingStrategy.primaryKeyName(
14✔
1049
                clonedTable,
1050
                primaryColumns.map((column) => column.name),
18✔
1051
            )
1052
            const columnNames = primaryColumns
14✔
1053
                .map((column) => `"${column.name}"`)
18✔
1054
                .join(", ")
1055
            upQueries.push(
14✔
1056
                new Query(
1057
                    `ALTER TABLE ${this.escapePath(
1058
                        table,
1059
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1060
                ),
1061
            )
1062
            downQueries.push(
14✔
1063
                new Query(
1064
                    `ALTER TABLE ${this.escapePath(
1065
                        table,
1066
                    )} DROP CONSTRAINT "${pkName}"`,
1067
                ),
1068
            )
1069
        }
1070

1071
        // create column index
1072
        const columnIndex = clonedTable.indices.find(
52✔
1073
            (index) =>
1074
                index.columnNames.length === 1 &&
50✔
1075
                index.columnNames[0] === column.name,
1076
        )
1077
        if (columnIndex) {
52!
1078
            upQueries.push(this.createIndexSql(table, columnIndex))
×
1079
            downQueries.push(this.dropIndexSql(table, columnIndex))
×
1080
        } else if (column.isUnique) {
52✔
1081
            const uniqueIndex = new TableIndex({
6✔
1082
                name: this.connection.namingStrategy.indexName(table, [
1083
                    column.name,
1084
                ]),
1085
                columnNames: [column.name],
1086
                isUnique: true,
1087
            })
1088
            clonedTable.indices.push(uniqueIndex)
6✔
1089
            clonedTable.uniques.push(
6✔
1090
                new TableUnique({
1091
                    name: uniqueIndex.name,
1092
                    columnNames: uniqueIndex.columnNames,
1093
                }),
1094
            )
1095
            upQueries.push(this.createIndexSql(table, uniqueIndex))
6✔
1096
            downQueries.push(this.dropIndexSql(table, uniqueIndex))
6✔
1097
        }
1098

1099
        await this.executeQueries(upQueries, downQueries)
52✔
1100

1101
        clonedTable.addColumn(column)
50✔
1102
        this.replaceCachedTable(table, clonedTable)
50✔
1103
    }
1104

1105
    /**
1106
     * Creates a new columns from the column in the table.
1107
     */
1108
    async addColumns(
1109
        tableOrName: Table | string,
1110
        columns: TableColumn[],
1111
    ): Promise<void> {
1112
        for (const column of columns) {
8✔
1113
            await this.addColumn(tableOrName, column)
10✔
1114
        }
1115
    }
1116

1117
    /**
1118
     * Renames column in the given table.
1119
     */
1120
    async renameColumn(
1121
        tableOrName: Table | string,
1122
        oldTableColumnOrName: TableColumn | string,
1123
        newTableColumnOrName: TableColumn | string,
1124
    ): Promise<void> {
1125
        const table = InstanceChecker.isTable(tableOrName)
28✔
1126
            ? tableOrName
1127
            : await this.getCachedTable(tableOrName)
1128
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
28✔
1129
            ? oldTableColumnOrName
1130
            : table.columns.find((c) => c.name === oldTableColumnOrName)
14✔
1131
        if (!oldColumn)
28!
1132
            throw new TypeORMError(
×
1133
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
1134
            )
1135

1136
        let newColumn: TableColumn | undefined = undefined
28✔
1137
        if (InstanceChecker.isTableColumn(newTableColumnOrName)) {
28✔
1138
            newColumn = newTableColumnOrName
20✔
1139
        } else {
1140
            newColumn = oldColumn.clone()
8✔
1141
            newColumn.name = newTableColumnOrName
8✔
1142
        }
1143

1144
        await this.changeColumn(table, oldColumn, newColumn)
28✔
1145
    }
1146

1147
    /**
1148
     * Changes a column in the table.
1149
     */
1150
    async changeColumn(
1151
        tableOrName: Table | string,
1152
        oldTableColumnOrName: TableColumn | string,
1153
        newColumn: TableColumn,
1154
    ): Promise<void> {
1155
        const table = InstanceChecker.isTable(tableOrName)
86!
1156
            ? tableOrName
1157
            : await this.getCachedTable(tableOrName)
1158
        let clonedTable = table.clone()
86✔
1159
        const upQueries: Query[] = []
86✔
1160
        const downQueries: Query[] = []
86✔
1161

1162
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
86!
1163
            ? oldTableColumnOrName
1164
            : table.columns.find(
1165
                  (column) => column.name === oldTableColumnOrName,
×
1166
              )
1167
        if (!oldColumn)
86!
1168
            throw new TypeORMError(
×
1169
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
1170
            )
1171

1172
        if (
86✔
1173
            (newColumn.isGenerated !== oldColumn.isGenerated &&
242✔
1174
                newColumn.generationStrategy !== "uuid") ||
1175
            newColumn.type !== oldColumn.type ||
1176
            newColumn.length !== oldColumn.length
1177
        ) {
1178
            // SQL Server does not support changing of IDENTITY column, so we must drop column and recreate it again.
1179
            // Also, we recreate column if column type changed
1180
            await this.dropColumn(table, oldColumn)
36✔
1181
            await this.addColumn(table, newColumn)
36✔
1182

1183
            // update cloned table
1184
            clonedTable = table.clone()
36✔
1185
        } else {
1186
            if (newColumn.name !== oldColumn.name) {
50✔
1187
                // rename column
1188
                upQueries.push(
14✔
1189
                    new Query(
1190
                        `RENAME COLUMN ${this.escapePath(table)}."${
1191
                            oldColumn.name
1192
                        }" TO "${newColumn.name}"`,
1193
                    ),
1194
                )
1195
                downQueries.push(
14✔
1196
                    new Query(
1197
                        `RENAME COLUMN ${this.escapePath(table)}."${
1198
                            newColumn.name
1199
                        }" TO "${oldColumn.name}"`,
1200
                    ),
1201
                )
1202

1203
                if (oldColumn.isPrimary === true) {
14✔
1204
                    const primaryColumns = clonedTable.primaryColumns
2✔
1205

1206
                    // build old primary constraint name
1207
                    const columnNames = primaryColumns.map(
2✔
1208
                        (column) => column.name,
2✔
1209
                    )
1210
                    const oldPkName =
1211
                        this.connection.namingStrategy.primaryKeyName(
2✔
1212
                            clonedTable,
1213
                            columnNames,
1214
                        )
1215

1216
                    // replace old column name with new column name
1217
                    columnNames.splice(columnNames.indexOf(oldColumn.name), 1)
2✔
1218
                    columnNames.push(newColumn.name)
2✔
1219
                    const columnNamesString = columnNames
2✔
1220
                        .map((columnName) => `"${columnName}"`)
2✔
1221
                        .join(", ")
1222

1223
                    // drop old PK
1224
                    upQueries.push(
2✔
1225
                        new Query(
1226
                            `ALTER TABLE ${this.escapePath(
1227
                                clonedTable,
1228
                            )} DROP CONSTRAINT "${oldPkName}"`,
1229
                        ),
1230
                    )
1231
                    downQueries.push(
2✔
1232
                        new Query(
1233
                            `ALTER TABLE ${this.escapePath(
1234
                                clonedTable,
1235
                            )} ADD CONSTRAINT "${oldPkName}" PRIMARY KEY (${columnNamesString})`,
1236
                        ),
1237
                    )
1238

1239
                    // build new primary constraint name
1240
                    const newPkName =
1241
                        this.connection.namingStrategy.primaryKeyName(
2✔
1242
                            clonedTable,
1243
                            columnNames,
1244
                        )
1245

1246
                    // create new PK
1247
                    upQueries.push(
2✔
1248
                        new Query(
1249
                            `ALTER TABLE ${this.escapePath(
1250
                                clonedTable,
1251
                            )} ADD CONSTRAINT "${newPkName}" PRIMARY KEY (${columnNamesString})`,
1252
                        ),
1253
                    )
1254
                    downQueries.push(
2✔
1255
                        new Query(
1256
                            `ALTER TABLE ${this.escapePath(
1257
                                clonedTable,
1258
                            )} DROP CONSTRAINT "${newPkName}"`,
1259
                        ),
1260
                    )
1261
                }
1262

1263
                // rename index constraints
1264
                clonedTable.findColumnIndices(oldColumn).forEach((index) => {
14✔
1265
                    // build new constraint name
1266
                    index.columnNames.splice(
8✔
1267
                        index.columnNames.indexOf(oldColumn.name),
1268
                        1,
1269
                    )
1270
                    index.columnNames.push(newColumn.name)
8✔
1271
                    const newIndexName =
1272
                        this.connection.namingStrategy.indexName(
8✔
1273
                            clonedTable,
1274
                            index.columnNames,
1275
                            index.where,
1276
                        )
1277

1278
                    // drop old index
1279
                    upQueries.push(this.dropIndexSql(clonedTable, index))
8✔
1280
                    downQueries.push(this.createIndexSql(clonedTable, index))
8✔
1281

1282
                    // replace constraint name
1283
                    index.name = newIndexName
8✔
1284

1285
                    // create new index
1286
                    upQueries.push(this.createIndexSql(clonedTable, index))
8✔
1287
                    downQueries.push(this.dropIndexSql(clonedTable, index))
8✔
1288
                })
1289

1290
                // rename foreign key constraints
1291
                clonedTable
14✔
1292
                    .findColumnForeignKeys(oldColumn)
1293
                    .forEach((foreignKey) => {
1294
                        // build new constraint name
1295
                        foreignKey.columnNames.splice(
2✔
1296
                            foreignKey.columnNames.indexOf(oldColumn.name),
1297
                            1,
1298
                        )
1299
                        foreignKey.columnNames.push(newColumn.name)
2✔
1300
                        const newForeignKeyName =
1301
                            this.connection.namingStrategy.foreignKeyName(
2✔
1302
                                clonedTable,
1303
                                foreignKey.columnNames,
1304
                                this.getTablePath(foreignKey),
1305
                                foreignKey.referencedColumnNames,
1306
                            )
1307

1308
                        upQueries.push(
2✔
1309
                            this.dropForeignKeySql(clonedTable, foreignKey),
1310
                        )
1311
                        downQueries.push(
2✔
1312
                            this.createForeignKeySql(clonedTable, foreignKey),
1313
                        )
1314

1315
                        // replace constraint name
1316
                        foreignKey.name = newForeignKeyName
2✔
1317

1318
                        // create new FK's
1319
                        upQueries.push(
2✔
1320
                            this.createForeignKeySql(clonedTable, foreignKey),
1321
                        )
1322
                        downQueries.push(
2✔
1323
                            this.dropForeignKeySql(clonedTable, foreignKey),
1324
                        )
1325
                    })
1326

1327
                // rename check constraints
1328
                clonedTable.findColumnChecks(oldColumn).forEach((check) => {
14✔
1329
                    // build new constraint name
1330
                    check.columnNames!.splice(
×
1331
                        check.columnNames!.indexOf(oldColumn.name),
1332
                        1,
1333
                    )
1334
                    check.columnNames!.push(newColumn.name)
×
1335
                    const newCheckName =
1336
                        this.connection.namingStrategy.checkConstraintName(
×
1337
                            clonedTable,
1338
                            check.expression!,
1339
                        )
1340

1341
                    upQueries.push(
×
1342
                        this.dropCheckConstraintSql(clonedTable, check),
1343
                    )
1344
                    downQueries.push(
×
1345
                        this.createCheckConstraintSql(clonedTable, check),
1346
                    )
1347

1348
                    // replace constraint name
1349
                    check.name = newCheckName
×
1350

1351
                    upQueries.push(
×
1352
                        this.createCheckConstraintSql(clonedTable, check),
1353
                    )
1354
                    downQueries.push(
×
1355
                        this.dropCheckConstraintSql(clonedTable, check),
1356
                    )
1357
                })
1358

1359
                // rename old column in the Table object
1360
                const oldTableColumn = clonedTable.columns.find(
14✔
1361
                    (column) => column.name === oldColumn.name,
36✔
1362
                )
1363
                clonedTable.columns[
14✔
1364
                    clonedTable.columns.indexOf(oldTableColumn!)
1365
                ].name = newColumn.name
1366
                oldColumn.name = newColumn.name
14✔
1367
            }
1368

1369
            if (this.isColumnChanged(oldColumn, newColumn, true)) {
50✔
1370
                upQueries.push(
8✔
1371
                    new Query(
1372
                        `ALTER TABLE ${this.escapePath(
1373
                            table,
1374
                        )} ALTER (${this.buildCreateColumnSql(
1375
                            newColumn,
1376
                            !(
1377
                                oldColumn.default === null ||
16✔
1378
                                oldColumn.default === undefined
1379
                            ),
1380
                            !oldColumn.isNullable,
1381
                        )})`,
1382
                    ),
1383
                )
1384
                downQueries.push(
8✔
1385
                    new Query(
1386
                        `ALTER TABLE ${this.escapePath(
1387
                            table,
1388
                        )} ALTER (${this.buildCreateColumnSql(
1389
                            oldColumn,
1390
                            !(
1391
                                newColumn.default === null ||
16✔
1392
                                newColumn.default === undefined
1393
                            ),
1394
                            !newColumn.isNullable,
1395
                        )})`,
1396
                    ),
1397
                )
1398
            } else if (oldColumn.comment !== newColumn.comment) {
42✔
1399
                upQueries.push(
6✔
1400
                    new Query(
1401
                        `COMMENT ON COLUMN ${this.escapePath(table)}."${
1402
                            oldColumn.name
1403
                        }" IS ${this.escapeComment(newColumn.comment)}`,
1404
                    ),
1405
                )
1406
                downQueries.push(
6✔
1407
                    new Query(
1408
                        `COMMENT ON COLUMN ${this.escapePath(table)}."${
1409
                            newColumn.name
1410
                        }" IS ${this.escapeComment(oldColumn.comment)}`,
1411
                    ),
1412
                )
1413
            }
1414

1415
            if (newColumn.isPrimary !== oldColumn.isPrimary) {
50✔
1416
                const primaryColumns = clonedTable.primaryColumns
6✔
1417

1418
                // if primary column state changed, we must always drop existed constraint.
1419
                if (primaryColumns.length > 0) {
6✔
1420
                    const pkName =
1421
                        this.connection.namingStrategy.primaryKeyName(
6✔
1422
                            clonedTable,
1423
                            primaryColumns.map((column) => column.name),
8✔
1424
                        )
1425
                    const columnNames = primaryColumns
6✔
1426
                        .map((column) => `"${column.name}"`)
8✔
1427
                        .join(", ")
1428
                    upQueries.push(
6✔
1429
                        new Query(
1430
                            `ALTER TABLE ${this.escapePath(
1431
                                table,
1432
                            )} DROP CONSTRAINT "${pkName}"`,
1433
                        ),
1434
                    )
1435
                    downQueries.push(
6✔
1436
                        new Query(
1437
                            `ALTER TABLE ${this.escapePath(
1438
                                table,
1439
                            )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1440
                        ),
1441
                    )
1442
                }
1443

1444
                if (newColumn.isPrimary === true) {
6✔
1445
                    primaryColumns.push(newColumn)
2✔
1446
                    // update column in table
1447
                    const column = clonedTable.columns.find(
2✔
1448
                        (column) => column.name === newColumn.name,
6✔
1449
                    )
1450
                    column!.isPrimary = true
2✔
1451
                    const pkName =
1452
                        this.connection.namingStrategy.primaryKeyName(
2✔
1453
                            clonedTable,
1454
                            primaryColumns.map((column) => column.name),
4✔
1455
                        )
1456
                    const columnNames = primaryColumns
2✔
1457
                        .map((column) => `"${column.name}"`)
4✔
1458
                        .join(", ")
1459
                    upQueries.push(
2✔
1460
                        new Query(
1461
                            `ALTER TABLE ${this.escapePath(
1462
                                table,
1463
                            )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1464
                        ),
1465
                    )
1466
                    downQueries.push(
2✔
1467
                        new Query(
1468
                            `ALTER TABLE ${this.escapePath(
1469
                                table,
1470
                            )} DROP CONSTRAINT "${pkName}"`,
1471
                        ),
1472
                    )
1473
                } else {
1474
                    const primaryColumn = primaryColumns.find(
4✔
1475
                        (c) => c.name === newColumn.name,
4✔
1476
                    )
1477
                    primaryColumns.splice(
4✔
1478
                        primaryColumns.indexOf(primaryColumn!),
1479
                        1,
1480
                    )
1481

1482
                    // update column in table
1483
                    const column = clonedTable.columns.find(
4✔
1484
                        (column) => column.name === newColumn.name,
12✔
1485
                    )
1486
                    column!.isPrimary = false
4✔
1487

1488
                    // if we have another primary keys, we must recreate constraint.
1489
                    if (primaryColumns.length > 0) {
4✔
1490
                        const pkName =
1491
                            this.connection.namingStrategy.primaryKeyName(
2✔
1492
                                clonedTable,
1493
                                primaryColumns.map((column) => column.name),
2✔
1494
                            )
1495
                        const columnNames = primaryColumns
2✔
1496
                            .map((column) => `"${column.name}"`)
2✔
1497
                            .join(", ")
1498
                        upQueries.push(
2✔
1499
                            new Query(
1500
                                `ALTER TABLE ${this.escapePath(
1501
                                    table,
1502
                                )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1503
                            ),
1504
                        )
1505
                        downQueries.push(
2✔
1506
                            new Query(
1507
                                `ALTER TABLE ${this.escapePath(
1508
                                    table,
1509
                                )} DROP CONSTRAINT "${pkName}"`,
1510
                            ),
1511
                        )
1512
                    }
1513
                }
1514
            }
1515

1516
            if (newColumn.isUnique !== oldColumn.isUnique) {
50✔
1517
                if (newColumn.isUnique === true) {
2!
1518
                    const uniqueIndex = new TableIndex({
2✔
1519
                        name: this.connection.namingStrategy.indexName(table, [
1520
                            newColumn.name,
1521
                        ]),
1522
                        columnNames: [newColumn.name],
1523
                        isUnique: true,
1524
                    })
1525
                    clonedTable.indices.push(uniqueIndex)
2✔
1526
                    clonedTable.uniques.push(
2✔
1527
                        new TableUnique({
1528
                            name: uniqueIndex.name,
1529
                            columnNames: uniqueIndex.columnNames,
1530
                        }),
1531
                    )
1532
                    upQueries.push(this.createIndexSql(table, uniqueIndex))
2✔
1533
                    downQueries.push(this.dropIndexSql(table, uniqueIndex))
2✔
1534
                } else {
1535
                    const uniqueIndex = clonedTable.indices.find((index) => {
×
1536
                        return (
×
1537
                            index.columnNames.length === 1 &&
×
1538
                            index.isUnique === true &&
1539
                            !!index.columnNames.find(
1540
                                (columnName) => columnName === newColumn.name,
×
1541
                            )
1542
                        )
1543
                    })
1544
                    clonedTable.indices.splice(
×
1545
                        clonedTable.indices.indexOf(uniqueIndex!),
1546
                        1,
1547
                    )
1548

1549
                    const tableUnique = clonedTable.uniques.find(
×
1550
                        (unique) => unique.name === uniqueIndex!.name,
×
1551
                    )
1552
                    clonedTable.uniques.splice(
×
1553
                        clonedTable.uniques.indexOf(tableUnique!),
1554
                        1,
1555
                    )
1556

1557
                    upQueries.push(this.dropIndexSql(table, uniqueIndex!))
×
1558
                    downQueries.push(this.createIndexSql(table, uniqueIndex!))
×
1559
                }
1560
            }
1561

1562
            await this.executeQueries(upQueries, downQueries)
50✔
1563
            this.replaceCachedTable(table, clonedTable)
50✔
1564
        }
1565
    }
1566

1567
    /**
1568
     * Changes a column in the table.
1569
     */
1570
    async changeColumns(
1571
        tableOrName: Table | string,
1572
        changedColumns: { newColumn: TableColumn; oldColumn: TableColumn }[],
1573
    ): Promise<void> {
1574
        for (const { oldColumn, newColumn } of changedColumns) {
36✔
1575
            await this.changeColumn(tableOrName, oldColumn, newColumn)
46✔
1576
        }
1577
    }
1578

1579
    /**
1580
     * Drops column in the table.
1581
     */
1582
    async dropColumn(
1583
        tableOrName: Table | string,
1584
        columnOrName: TableColumn | string,
1585
    ): Promise<void> {
1586
        const table = InstanceChecker.isTable(tableOrName)
60✔
1587
            ? tableOrName
1588
            : await this.getCachedTable(tableOrName)
1589
        const parsedTableName = this.driver.parseTableName(table)
60✔
1590

1591
        if (!parsedTableName.schema) {
60!
1592
            parsedTableName.schema = await this.getCurrentSchema()
×
1593
        }
1594

1595
        const column = InstanceChecker.isTableColumn(columnOrName)
60✔
1596
            ? columnOrName
1597
            : table.findColumnByName(columnOrName)
1598
        if (!column)
60✔
1599
            throw new TypeORMError(
2✔
1600
                `Column "${columnOrName}" was not found in table "${table.name}"`,
1601
            )
1602

1603
        const clonedTable = table.clone()
58✔
1604
        const upQueries: Query[] = []
58✔
1605
        const downQueries: Query[] = []
58✔
1606

1607
        // drop primary key constraint
1608
        if (column.isPrimary) {
58✔
1609
            // SAP HANA does not allow to drop PK's which is referenced by foreign keys.
1610
            // To avoid this, we must drop all referential foreign keys and recreate them later
1611
            const referencedForeignKeySql = `SELECT * FROM "SYS"."REFERENTIAL_CONSTRAINTS" WHERE "REFERENCED_SCHEMA_NAME" = '${parsedTableName.schema}' AND "REFERENCED_TABLE_NAME" = '${parsedTableName.tableName}'`
14✔
1612
            const dbForeignKeys: ObjectLiteral[] = await this.query(
14✔
1613
                referencedForeignKeySql,
1614
            )
1615
            let referencedForeignKeys: TableForeignKey[] = []
14✔
1616
            const referencedForeignKeyTableMapping: {
1617
                tableName: string
1618
                fkName: string
1619
            }[] = []
14✔
1620
            if (dbForeignKeys.length > 0) {
14✔
1621
                referencedForeignKeys = dbForeignKeys.map((dbForeignKey) => {
4✔
1622
                    const foreignKeys = dbForeignKeys.filter(
4✔
1623
                        (dbFk) =>
1624
                            dbFk["CONSTRAINT_NAME"] ===
4✔
1625
                            dbForeignKey["CONSTRAINT_NAME"],
1626
                    )
1627

1628
                    referencedForeignKeyTableMapping.push({
4✔
1629
                        tableName: `${dbForeignKey["SCHEMA_NAME"]}.${dbForeignKey["TABLE_NAME"]}`,
1630
                        fkName: dbForeignKey["CONSTRAINT_NAME"],
1631
                    })
1632
                    return new TableForeignKey({
4✔
1633
                        name: dbForeignKey["CONSTRAINT_NAME"],
1634
                        columnNames: foreignKeys.map(
1635
                            (dbFk) => dbFk["COLUMN_NAME"],
4✔
1636
                        ),
1637
                        referencedDatabase: table.database,
1638
                        referencedSchema: table.schema,
1639
                        referencedTableName: table.name,
1640
                        referencedColumnNames: foreignKeys.map(
1641
                            (dbFk) => dbFk["REFERENCED_COLUMN_NAME"],
4✔
1642
                        ),
1643
                        onDelete:
1644
                            dbForeignKey["DELETE_RULE"] === "RESTRICT"
4!
1645
                                ? "NO ACTION"
1646
                                : dbForeignKey["DELETE_RULE"],
1647
                        onUpdate:
1648
                            dbForeignKey["UPDATE_RULE"] === "RESTRICT"
4!
1649
                                ? "NO ACTION"
1650
                                : dbForeignKey["UPDATE_RULE"],
1651
                        deferrable: dbForeignKey["CHECK_TIME"].replace(
1652
                            "_",
1653
                            " ",
1654
                        ),
1655
                    })
1656
                })
1657

1658
                // drop referenced foreign keys
1659
                referencedForeignKeys.forEach((foreignKey) => {
4✔
1660
                    const mapping = referencedForeignKeyTableMapping.find(
4✔
1661
                        (it) => it.fkName === foreignKey.name,
4✔
1662
                    )
1663
                    upQueries.push(
4✔
1664
                        this.dropForeignKeySql(mapping!.tableName, foreignKey),
1665
                    )
1666
                    downQueries.push(
4✔
1667
                        this.createForeignKeySql(
1668
                            mapping!.tableName,
1669
                            foreignKey,
1670
                        ),
1671
                    )
1672
                })
1673
            }
1674

1675
            const pkName = this.connection.namingStrategy.primaryKeyName(
14✔
1676
                clonedTable,
1677
                clonedTable.primaryColumns.map((column) => column.name),
16✔
1678
            )
1679
            const columnNames = clonedTable.primaryColumns
14✔
1680
                .map((primaryColumn) => `"${primaryColumn.name}"`)
16✔
1681
                .join(", ")
1682
            upQueries.push(
14✔
1683
                new Query(
1684
                    `ALTER TABLE ${this.escapePath(
1685
                        clonedTable,
1686
                    )} DROP CONSTRAINT "${pkName}"`,
1687
                ),
1688
            )
1689
            downQueries.push(
14✔
1690
                new Query(
1691
                    `ALTER TABLE ${this.escapePath(
1692
                        clonedTable,
1693
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1694
                ),
1695
            )
1696

1697
            // update column in table
1698
            const tableColumn = clonedTable.findColumnByName(column.name)
14✔
1699
            tableColumn!.isPrimary = false
14✔
1700

1701
            // if primary key have multiple columns, we must recreate it without dropped column
1702
            if (clonedTable.primaryColumns.length > 0) {
14✔
1703
                const pkName = this.connection.namingStrategy.primaryKeyName(
2✔
1704
                    clonedTable,
1705
                    clonedTable.primaryColumns.map((column) => column.name),
2✔
1706
                )
1707
                const columnNames = clonedTable.primaryColumns
2✔
1708
                    .map((primaryColumn) => `"${primaryColumn.name}"`)
2✔
1709
                    .join(", ")
1710
                upQueries.push(
2✔
1711
                    new Query(
1712
                        `ALTER TABLE ${this.escapePath(
1713
                            clonedTable,
1714
                        )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1715
                    ),
1716
                )
1717
                downQueries.push(
2✔
1718
                    new Query(
1719
                        `ALTER TABLE ${this.escapePath(
1720
                            clonedTable,
1721
                        )} DROP CONSTRAINT "${pkName}"`,
1722
                    ),
1723
                )
1724
            }
1725

1726
            // restore referenced foreign keys
1727
            referencedForeignKeys.forEach((foreignKey) => {
14✔
1728
                const mapping = referencedForeignKeyTableMapping.find(
4✔
1729
                    (it) => it.fkName === foreignKey.name,
4✔
1730
                )
1731
                upQueries.push(
4✔
1732
                    this.createForeignKeySql(mapping!.tableName, foreignKey),
1733
                )
1734
                downQueries.push(
4✔
1735
                    this.dropForeignKeySql(mapping!.tableName, foreignKey),
1736
                )
1737
            })
1738
        }
1739

1740
        // drop column index
1741
        const columnIndex = clonedTable.indices.find(
58✔
1742
            (index) =>
1743
                index.columnNames.length === 1 &&
54✔
1744
                index.columnNames[0] === column.name,
1745
        )
1746
        if (columnIndex) {
58✔
1747
            clonedTable.indices.splice(
6✔
1748
                clonedTable.indices.indexOf(columnIndex),
1749
                1,
1750
            )
1751
            upQueries.push(this.dropIndexSql(table, columnIndex))
6✔
1752
            downQueries.push(this.createIndexSql(table, columnIndex))
6✔
1753
        } else if (column.isUnique) {
52✔
1754
            // we splice constraints both from table uniques and indices.
1755
            const uniqueName =
1756
                this.connection.namingStrategy.uniqueConstraintName(table, [
2✔
1757
                    column.name,
1758
                ])
1759
            const foundUnique = clonedTable.uniques.find(
2✔
1760
                (unique) => unique.name === uniqueName,
×
1761
            )
1762
            if (foundUnique) {
2!
1763
                clonedTable.uniques.splice(
×
1764
                    clonedTable.uniques.indexOf(foundUnique),
1765
                    1,
1766
                )
1767
                upQueries.push(this.dropIndexSql(table, uniqueName))
×
1768
                downQueries.push(
×
1769
                    new Query(
1770
                        `CREATE UNIQUE INDEX "${uniqueName}" ON ${this.escapePath(
1771
                            table,
1772
                        )} ("${column.name}")`,
1773
                    ),
1774
                )
1775
            }
1776

1777
            const indexName = this.connection.namingStrategy.indexName(table, [
2✔
1778
                column.name,
1779
            ])
1780
            const foundIndex = clonedTable.indices.find(
2✔
1781
                (index) => index.name === indexName,
×
1782
            )
1783
            if (foundIndex) {
2!
1784
                clonedTable.indices.splice(
×
1785
                    clonedTable.indices.indexOf(foundIndex),
1786
                    1,
1787
                )
1788
                upQueries.push(this.dropIndexSql(table, indexName))
×
1789
                downQueries.push(
×
1790
                    new Query(
1791
                        `CREATE UNIQUE INDEX "${indexName}" ON ${this.escapePath(
1792
                            table,
1793
                        )} ("${column.name}")`,
1794
                    ),
1795
                )
1796
            }
1797
        }
1798

1799
        // drop column check
1800
        const columnCheck = clonedTable.checks.find(
58✔
1801
            (check) =>
1802
                !!check.columnNames &&
24✔
1803
                check.columnNames.length === 1 &&
1804
                check.columnNames[0] === column.name,
1805
        )
1806
        if (columnCheck) {
58!
1807
            clonedTable.checks.splice(
×
1808
                clonedTable.checks.indexOf(columnCheck),
1809
                1,
1810
            )
1811
            upQueries.push(this.dropCheckConstraintSql(table, columnCheck))
×
1812
            downQueries.push(this.createCheckConstraintSql(table, columnCheck))
×
1813
        }
1814

1815
        upQueries.push(new Query(this.dropColumnSql(table, column)))
58✔
1816
        downQueries.push(new Query(this.addColumnSql(table, column)))
58✔
1817

1818
        await this.executeQueries(upQueries, downQueries)
58✔
1819

1820
        clonedTable.removeColumn(column)
58✔
1821
        this.replaceCachedTable(table, clonedTable)
58✔
1822
    }
1823

1824
    /**
1825
     * Drops the columns in the table.
1826
     */
1827
    async dropColumns(
1828
        tableOrName: Table | string,
1829
        columns: TableColumn[] | string[],
1830
    ): Promise<void> {
1831
        for (const column of columns) {
8✔
1832
            await this.dropColumn(tableOrName, column)
18✔
1833
        }
1834
    }
1835

1836
    /**
1837
     * Creates a new primary key.
1838
     */
1839
    async createPrimaryKey(
1840
        tableOrName: Table | string,
1841
        columnNames: string[],
1842
    ): Promise<void> {
1843
        const table = InstanceChecker.isTable(tableOrName)
4!
1844
            ? tableOrName
1845
            : await this.getCachedTable(tableOrName)
1846
        const clonedTable = table.clone()
4✔
1847

1848
        const up = this.createPrimaryKeySql(table, columnNames)
4✔
1849

1850
        // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names.
1851
        clonedTable.columns.forEach((column) => {
4✔
1852
            if (columnNames.find((columnName) => columnName === column.name))
14✔
1853
                column.isPrimary = true
6✔
1854
        })
1855
        const down = this.dropPrimaryKeySql(clonedTable)
4✔
1856

1857
        await this.executeQueries(up, down)
4✔
1858
        this.replaceCachedTable(table, clonedTable)
4✔
1859
    }
1860

1861
    /**
1862
     * Updates composite primary keys.
1863
     */
1864
    async updatePrimaryKeys(
1865
        tableOrName: Table | string,
1866
        columns: TableColumn[],
1867
    ): Promise<void> {
1868
        const table = InstanceChecker.isTable(tableOrName)
6!
1869
            ? tableOrName
1870
            : await this.getCachedTable(tableOrName)
1871
        const parsedTableName = this.driver.parseTableName(table)
6✔
1872

1873
        if (!parsedTableName.schema) {
6!
1874
            parsedTableName.schema = await this.getCurrentSchema()
×
1875
        }
1876

1877
        const clonedTable = table.clone()
6✔
1878
        const columnNames = columns.map((column) => column.name)
12✔
1879
        const upQueries: Query[] = []
6✔
1880
        const downQueries: Query[] = []
6✔
1881

1882
        // SAP HANA does not allow to drop PK's which is referenced by foreign keys.
1883
        // To avoid this, we must drop all referential foreign keys and recreate them later
1884
        const referencedForeignKeySql = `SELECT * FROM "SYS"."REFERENTIAL_CONSTRAINTS" WHERE "REFERENCED_SCHEMA_NAME" = '${parsedTableName.schema}' AND "REFERENCED_TABLE_NAME" = '${parsedTableName.tableName}'`
6✔
1885
        const dbForeignKeys: ObjectLiteral[] = await this.query(
6✔
1886
            referencedForeignKeySql,
1887
        )
1888
        let referencedForeignKeys: TableForeignKey[] = []
6✔
1889
        const referencedForeignKeyTableMapping: {
1890
            tableName: string
1891
            fkName: string
1892
        }[] = []
6✔
1893
        if (dbForeignKeys.length > 0) {
6✔
1894
            referencedForeignKeys = dbForeignKeys.map((dbForeignKey) => {
2✔
1895
                const foreignKeys = dbForeignKeys.filter(
2✔
1896
                    (dbFk) =>
1897
                        dbFk["CONSTRAINT_NAME"] ===
2✔
1898
                        dbForeignKey["CONSTRAINT_NAME"],
1899
                )
1900

1901
                referencedForeignKeyTableMapping.push({
2✔
1902
                    tableName: `${dbForeignKey["SCHEMA_NAME"]}.${dbForeignKey["TABLE_NAME"]}`,
1903
                    fkName: dbForeignKey["CONSTRAINT_NAME"],
1904
                })
1905
                return new TableForeignKey({
2✔
1906
                    name: dbForeignKey["CONSTRAINT_NAME"],
1907
                    columnNames: foreignKeys.map((dbFk) => dbFk["COLUMN_NAME"]),
2✔
1908
                    referencedDatabase: table.database,
1909
                    referencedSchema: table.schema,
1910
                    referencedTableName: table.name,
1911
                    referencedColumnNames: foreignKeys.map(
1912
                        (dbFk) => dbFk["REFERENCED_COLUMN_NAME"],
2✔
1913
                    ),
1914
                    onDelete:
1915
                        dbForeignKey["DELETE_RULE"] === "RESTRICT"
2!
1916
                            ? "NO ACTION"
1917
                            : dbForeignKey["DELETE_RULE"],
1918
                    onUpdate:
1919
                        dbForeignKey["UPDATE_RULE"] === "RESTRICT"
2!
1920
                            ? "NO ACTION"
1921
                            : dbForeignKey["UPDATE_RULE"],
1922
                    deferrable: dbForeignKey["CHECK_TIME"].replace("_", " "),
1923
                })
1924
            })
1925

1926
            // drop referenced foreign keys
1927
            referencedForeignKeys.forEach((foreignKey) => {
2✔
1928
                const mapping = referencedForeignKeyTableMapping.find(
2✔
1929
                    (it) => it.fkName === foreignKey.name,
2✔
1930
                )
1931
                upQueries.push(
2✔
1932
                    this.dropForeignKeySql(mapping!.tableName, foreignKey),
1933
                )
1934
                downQueries.push(
2✔
1935
                    this.createForeignKeySql(mapping!.tableName, foreignKey),
1936
                )
1937
            })
1938
        }
1939

1940
        // if table already have primary columns, we must drop them.
1941
        const primaryColumns = clonedTable.primaryColumns
6✔
1942
        if (primaryColumns.length > 0) {
6✔
1943
            const pkName = this.connection.namingStrategy.primaryKeyName(
6✔
1944
                clonedTable,
1945
                primaryColumns.map((column) => column.name),
6✔
1946
            )
1947
            const columnNamesString = primaryColumns
6✔
1948
                .map((column) => `"${column.name}"`)
6✔
1949
                .join(", ")
1950
            upQueries.push(
6✔
1951
                new Query(
1952
                    `ALTER TABLE ${this.escapePath(
1953
                        table,
1954
                    )} DROP CONSTRAINT "${pkName}"`,
1955
                ),
1956
            )
1957
            downQueries.push(
6✔
1958
                new Query(
1959
                    `ALTER TABLE ${this.escapePath(
1960
                        table,
1961
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
1962
                ),
1963
            )
1964
        }
1965

1966
        // update columns in table.
1967
        clonedTable.columns
6✔
1968
            .filter((column) => columnNames.indexOf(column.name) !== -1)
24✔
1969
            .forEach((column) => (column.isPrimary = true))
12✔
1970

1971
        const pkName = this.connection.namingStrategy.primaryKeyName(
6✔
1972
            clonedTable,
1973
            columnNames,
1974
        )
1975
        const columnNamesString = columnNames
6✔
1976
            .map((columnName) => `"${columnName}"`)
12✔
1977
            .join(", ")
1978
        upQueries.push(
6✔
1979
            new Query(
1980
                `ALTER TABLE ${this.escapePath(
1981
                    table,
1982
                )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
1983
            ),
1984
        )
1985
        downQueries.push(
6✔
1986
            new Query(
1987
                `ALTER TABLE ${this.escapePath(
1988
                    table,
1989
                )} DROP CONSTRAINT "${pkName}"`,
1990
            ),
1991
        )
1992

1993
        // restore referenced foreign keys
1994
        referencedForeignKeys.forEach((foreignKey) => {
6✔
1995
            const mapping = referencedForeignKeyTableMapping.find(
2✔
1996
                (it) => it.fkName === foreignKey.name,
2✔
1997
            )
1998
            upQueries.push(
2✔
1999
                this.createForeignKeySql(mapping!.tableName, foreignKey),
2000
            )
2001
            downQueries.push(
2✔
2002
                this.dropForeignKeySql(mapping!.tableName, foreignKey),
2003
            )
2004
        })
2005

2006
        await this.executeQueries(upQueries, downQueries)
6✔
2007
        this.replaceCachedTable(table, clonedTable)
6✔
2008
    }
2009

2010
    /**
2011
     * Drops a primary key.
2012
     */
2013
    async dropPrimaryKey(tableOrName: Table | string): Promise<void> {
2014
        const table = InstanceChecker.isTable(tableOrName)
6!
2015
            ? tableOrName
2016
            : await this.getCachedTable(tableOrName)
2017
        const parsedTableName = this.driver.parseTableName(table)
6✔
2018

2019
        if (!parsedTableName.schema) {
6!
2020
            parsedTableName.schema = await this.getCurrentSchema()
×
2021
        }
2022

2023
        const upQueries: Query[] = []
6✔
2024
        const downQueries: Query[] = []
6✔
2025

2026
        // SAP HANA does not allow to drop PK's which is referenced by foreign keys.
2027
        // To avoid this, we must drop all referential foreign keys and recreate them later
2028
        const referencedForeignKeySql = `SELECT * FROM "SYS"."REFERENTIAL_CONSTRAINTS" WHERE "REFERENCED_SCHEMA_NAME" = '${parsedTableName.schema}' AND "REFERENCED_TABLE_NAME" = '${parsedTableName.tableName}'`
6✔
2029
        const dbForeignKeys: ObjectLiteral[] = await this.query(
6✔
2030
            referencedForeignKeySql,
2031
        )
2032
        let referencedForeignKeys: TableForeignKey[] = []
6✔
2033
        const referencedForeignKeyTableMapping: {
2034
            tableName: string
2035
            fkName: string
2036
        }[] = []
6✔
2037
        if (dbForeignKeys.length > 0) {
6!
2038
            referencedForeignKeys = dbForeignKeys.map((dbForeignKey) => {
×
2039
                const foreignKeys = dbForeignKeys.filter(
×
2040
                    (dbFk) =>
2041
                        dbFk["CONSTRAINT_NAME"] ===
×
2042
                        dbForeignKey["CONSTRAINT_NAME"],
2043
                )
2044

2045
                referencedForeignKeyTableMapping.push({
×
2046
                    tableName: `${dbForeignKey["SCHEMA_NAME"]}.${dbForeignKey["TABLE_NAME"]}`,
2047
                    fkName: dbForeignKey["CONSTRAINT_NAME"],
2048
                })
2049
                return new TableForeignKey({
×
2050
                    name: dbForeignKey["CONSTRAINT_NAME"],
2051
                    columnNames: foreignKeys.map((dbFk) => dbFk["COLUMN_NAME"]),
×
2052
                    referencedDatabase: table.database,
2053
                    referencedSchema: table.schema,
2054
                    referencedTableName: table.name,
2055
                    referencedColumnNames: foreignKeys.map(
2056
                        (dbFk) => dbFk["REFERENCED_COLUMN_NAME"],
×
2057
                    ),
2058
                    onDelete:
2059
                        dbForeignKey["DELETE_RULE"] === "RESTRICT"
×
2060
                            ? "NO ACTION"
2061
                            : dbForeignKey["DELETE_RULE"],
2062
                    onUpdate:
2063
                        dbForeignKey["UPDATE_RULE"] === "RESTRICT"
×
2064
                            ? "NO ACTION"
2065
                            : dbForeignKey["UPDATE_RULE"],
2066
                    deferrable: dbForeignKey["CHECK_TIME"].replace("_", " "),
2067
                })
2068
            })
2069

2070
            // drop referenced foreign keys
2071
            referencedForeignKeys.forEach((foreignKey) => {
×
2072
                const mapping = referencedForeignKeyTableMapping.find(
×
2073
                    (it) => it.fkName === foreignKey.name,
×
2074
                )
2075
                upQueries.push(
×
2076
                    this.dropForeignKeySql(mapping!.tableName, foreignKey),
2077
                )
2078
                downQueries.push(
×
2079
                    this.createForeignKeySql(mapping!.tableName, foreignKey),
2080
                )
2081
            })
2082
        }
2083

2084
        upQueries.push(this.dropPrimaryKeySql(table))
6✔
2085
        downQueries.push(
6✔
2086
            this.createPrimaryKeySql(
2087
                table,
2088
                table.primaryColumns.map((column) => column.name),
6✔
2089
            ),
2090
        )
2091

2092
        // restore referenced foreign keys
2093
        referencedForeignKeys.forEach((foreignKey) => {
6✔
2094
            const mapping = referencedForeignKeyTableMapping.find(
×
2095
                (it) => it.fkName === foreignKey.name,
×
2096
            )
2097
            upQueries.push(
×
2098
                this.createForeignKeySql(mapping!.tableName, foreignKey),
2099
            )
2100
            downQueries.push(
×
2101
                this.dropForeignKeySql(mapping!.tableName, foreignKey),
2102
            )
2103
        })
2104

2105
        await this.executeQueries(upQueries, downQueries)
6✔
2106
        table.primaryColumns.forEach((column) => {
6✔
2107
            column.isPrimary = false
6✔
2108
        })
2109
    }
2110

2111
    /**
2112
     * Creates a new unique constraint.
2113
     */
2114
    async createUniqueConstraint(
2115
        tableOrName: Table | string,
2116
        uniqueConstraint: TableUnique,
2117
    ): Promise<void> {
2118
        throw new TypeORMError(
×
2119
            `SAP HANA does not support unique constraints. Use unique index instead.`,
2120
        )
2121
    }
2122

2123
    /**
2124
     * Creates a new unique constraints.
2125
     */
2126
    async createUniqueConstraints(
2127
        tableOrName: Table | string,
2128
        uniqueConstraints: TableUnique[],
2129
    ): Promise<void> {
2130
        throw new TypeORMError(
×
2131
            `SAP HANA does not support unique constraints. Use unique index instead.`,
2132
        )
2133
    }
2134

2135
    /**
2136
     * Drops unique constraint.
2137
     */
2138
    async dropUniqueConstraint(
2139
        tableOrName: Table | string,
2140
        uniqueOrName: TableUnique | string,
2141
    ): Promise<void> {
2142
        throw new TypeORMError(
×
2143
            `SAP HANA does not support unique constraints. Use unique index instead.`,
2144
        )
2145
    }
2146

2147
    /**
2148
     * Drops an unique constraints.
2149
     */
2150
    async dropUniqueConstraints(
2151
        tableOrName: Table | string,
2152
        uniqueConstraints: TableUnique[],
2153
    ): Promise<void> {
2154
        throw new TypeORMError(
×
2155
            `SAP HANA does not support unique constraints. Use unique index instead.`,
2156
        )
2157
    }
2158

2159
    /**
2160
     * Creates a new check constraint.
2161
     */
2162
    async createCheckConstraint(
2163
        tableOrName: Table | string,
2164
        checkConstraint: TableCheck,
2165
    ): Promise<void> {
2166
        const table = InstanceChecker.isTable(tableOrName)
10✔
2167
            ? tableOrName
2168
            : await this.getCachedTable(tableOrName)
2169

2170
        // new unique constraint may be passed without name. In this case we generate unique name manually.
2171
        if (!checkConstraint.name)
10✔
2172
            checkConstraint.name =
6✔
2173
                this.connection.namingStrategy.checkConstraintName(
2174
                    table,
2175
                    checkConstraint.expression!,
2176
                )
2177

2178
        const up = this.createCheckConstraintSql(table, checkConstraint)
10✔
2179
        const down = this.dropCheckConstraintSql(table, checkConstraint)
10✔
2180
        await this.executeQueries(up, down)
10✔
2181
        table.addCheckConstraint(checkConstraint)
10✔
2182
    }
2183

2184
    /**
2185
     * Creates a new check constraints.
2186
     */
2187
    async createCheckConstraints(
2188
        tableOrName: Table | string,
2189
        checkConstraints: TableCheck[],
2190
    ): Promise<void> {
2191
        const promises = checkConstraints.map((checkConstraint) =>
4✔
2192
            this.createCheckConstraint(tableOrName, checkConstraint),
4✔
2193
        )
2194
        await Promise.all(promises)
4✔
2195
    }
2196

2197
    /**
2198
     * Drops check constraint.
2199
     */
2200
    async dropCheckConstraint(
2201
        tableOrName: Table | string,
2202
        checkOrName: TableCheck | string,
2203
    ): Promise<void> {
2204
        const table = InstanceChecker.isTable(tableOrName)
6!
2205
            ? tableOrName
2206
            : await this.getCachedTable(tableOrName)
2207
        const checkConstraint = InstanceChecker.isTableCheck(checkOrName)
6!
2208
            ? checkOrName
2209
            : table.checks.find((c) => c.name === checkOrName)
×
2210
        if (!checkConstraint)
6!
2211
            throw new TypeORMError(
×
2212
                `Supplied check constraint was not found in table ${table.name}`,
2213
            )
2214

2215
        const up = this.dropCheckConstraintSql(table, checkConstraint)
6✔
2216
        const down = this.createCheckConstraintSql(table, checkConstraint)
6✔
2217
        await this.executeQueries(up, down)
6✔
2218
        table.removeCheckConstraint(checkConstraint)
6✔
2219
    }
2220

2221
    /**
2222
     * Drops check constraints.
2223
     */
2224
    async dropCheckConstraints(
2225
        tableOrName: Table | string,
2226
        checkConstraints: TableCheck[],
2227
    ): Promise<void> {
2228
        const promises = checkConstraints.map((checkConstraint) =>
4✔
2229
            this.dropCheckConstraint(tableOrName, checkConstraint),
4✔
2230
        )
2231
        await Promise.all(promises)
4✔
2232
    }
2233

2234
    /**
2235
     * Creates a new exclusion constraint.
2236
     */
2237
    async createExclusionConstraint(
2238
        tableOrName: Table | string,
2239
        exclusionConstraint: TableExclusion,
2240
    ): Promise<void> {
2241
        throw new TypeORMError(
×
2242
            `SAP HANA does not support exclusion constraints.`,
2243
        )
2244
    }
2245

2246
    /**
2247
     * Creates a new exclusion constraints.
2248
     */
2249
    async createExclusionConstraints(
2250
        tableOrName: Table | string,
2251
        exclusionConstraints: TableExclusion[],
2252
    ): Promise<void> {
2253
        throw new TypeORMError(
×
2254
            `SAP HANA does not support exclusion constraints.`,
2255
        )
2256
    }
2257

2258
    /**
2259
     * Drops exclusion constraint.
2260
     */
2261
    async dropExclusionConstraint(
2262
        tableOrName: Table | string,
2263
        exclusionOrName: TableExclusion | string,
2264
    ): Promise<void> {
2265
        throw new TypeORMError(
×
2266
            `SAP HANA does not support exclusion constraints.`,
2267
        )
2268
    }
2269

2270
    /**
2271
     * Drops exclusion constraints.
2272
     */
2273
    async dropExclusionConstraints(
2274
        tableOrName: Table | string,
2275
        exclusionConstraints: TableExclusion[],
2276
    ): Promise<void> {
2277
        throw new TypeORMError(
×
2278
            `SAP HANA does not support exclusion constraints.`,
2279
        )
2280
    }
2281

2282
    /**
2283
     * Creates a new foreign key.
2284
     */
2285
    async createForeignKey(
2286
        tableOrName: Table | string,
2287
        foreignKey: TableForeignKey,
2288
    ): Promise<void> {
2289
        const table = InstanceChecker.isTable(tableOrName)
5,536✔
2290
            ? tableOrName
2291
            : await this.getCachedTable(tableOrName)
2292

2293
        // new FK may be passed without name. In this case we generate FK name manually.
2294
        if (!foreignKey.name)
5,536✔
2295
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
2✔
2296
                table,
2297
                foreignKey.columnNames,
2298
                this.getTablePath(foreignKey),
2299
                foreignKey.referencedColumnNames,
2300
            )
2301

2302
        const up = this.createForeignKeySql(table, foreignKey)
5,536✔
2303
        const down = this.dropForeignKeySql(table, foreignKey)
5,536✔
2304
        await this.executeQueries(up, down)
5,536✔
2305
        table.addForeignKey(foreignKey)
5,536✔
2306
    }
2307

2308
    /**
2309
     * Creates a new foreign keys.
2310
     */
2311
    async createForeignKeys(
2312
        tableOrName: Table | string,
2313
        foreignKeys: TableForeignKey[],
2314
    ): Promise<void> {
2315
        const promises = foreignKeys.map((foreignKey) =>
3,164✔
2316
            this.createForeignKey(tableOrName, foreignKey),
5,534✔
2317
        )
2318
        await Promise.all(promises)
3,164✔
2319
    }
2320

2321
    /**
2322
     * Drops a foreign key from the table.
2323
     */
2324
    async dropForeignKey(
2325
        tableOrName: Table | string,
2326
        foreignKeyOrName: TableForeignKey | string,
2327
    ): Promise<void> {
2328
        const table = InstanceChecker.isTable(tableOrName)
22!
2329
            ? tableOrName
2330
            : await this.getCachedTable(tableOrName)
2331
        const foreignKey = InstanceChecker.isTableForeignKey(foreignKeyOrName)
22!
2332
            ? foreignKeyOrName
2333
            : table.foreignKeys.find((fk) => fk.name === foreignKeyOrName)
×
2334
        if (!foreignKey)
22!
2335
            throw new TypeORMError(
×
2336
                `Supplied foreign key was not found in table ${table.name}`,
2337
            )
2338

2339
        const up = this.dropForeignKeySql(table, foreignKey)
22✔
2340
        const down = this.createForeignKeySql(table, foreignKey)
22✔
2341
        await this.executeQueries(up, down)
22✔
2342
        table.removeForeignKey(foreignKey)
22✔
2343
    }
2344

2345
    /**
2346
     * Drops a foreign keys from the table.
2347
     */
2348
    async dropForeignKeys(
2349
        tableOrName: Table | string,
2350
        foreignKeys: TableForeignKey[],
2351
    ): Promise<void> {
2352
        const promises = foreignKeys.map((foreignKey) =>
20✔
2353
            this.dropForeignKey(tableOrName, foreignKey),
20✔
2354
        )
2355
        await Promise.all(promises)
20✔
2356
    }
2357

2358
    /**
2359
     * Creates a new index.
2360
     */
2361
    async createIndex(
2362
        tableOrName: Table | string,
2363
        index: TableIndex,
2364
    ): Promise<void> {
2365
        const table = InstanceChecker.isTable(tableOrName)
28✔
2366
            ? tableOrName
2367
            : await this.getCachedTable(tableOrName)
2368

2369
        // new index may be passed without name. In this case we generate index name manually.
2370
        if (!index.name) index.name = this.generateIndexName(table, index)
28✔
2371

2372
        const up = this.createIndexSql(table, index)
28✔
2373
        const down = this.dropIndexSql(table, index)
28✔
2374
        await this.executeQueries(up, down)
28✔
2375
        table.addIndex(index)
28✔
2376
    }
2377

2378
    /**
2379
     * Creates a new indices
2380
     */
2381
    async createIndices(
2382
        tableOrName: Table | string,
2383
        indices: TableIndex[],
2384
    ): Promise<void> {
2385
        const promises = indices.map((index) =>
20✔
2386
            this.createIndex(tableOrName, index),
20✔
2387
        )
2388
        await Promise.all(promises)
20✔
2389
    }
2390

2391
    /**
2392
     * Drops an index.
2393
     */
2394
    async dropIndex(
2395
        tableOrName: Table | string,
2396
        indexOrName: TableIndex | string,
2397
    ): Promise<void> {
2398
        const table = InstanceChecker.isTable(tableOrName)
30✔
2399
            ? tableOrName
2400
            : await this.getCachedTable(tableOrName)
2401
        const index = InstanceChecker.isTableIndex(indexOrName)
30!
2402
            ? indexOrName
2403
            : table.indices.find((i) => i.name === indexOrName)
×
2404
        if (!index)
30!
2405
            throw new TypeORMError(
×
2406
                `Supplied index ${indexOrName} was not found in table ${table.name}`,
2407
            )
2408

2409
        // old index may be passed without name. In this case we generate index name manually.
2410
        if (!index.name) index.name = this.generateIndexName(table, index)
30✔
2411

2412
        const up = this.dropIndexSql(table, index)
30✔
2413
        const down = this.createIndexSql(table, index)
30✔
2414
        await this.executeQueries(up, down)
30✔
2415
        table.removeIndex(index)
30✔
2416
    }
2417

2418
    /**
2419
     * Drops an indices from the table.
2420
     */
2421
    async dropIndices(
2422
        tableOrName: Table | string,
2423
        indices: TableIndex[],
2424
    ): Promise<void> {
2425
        const promises = indices.map((index) =>
6✔
2426
            this.dropIndex(tableOrName, index),
6✔
2427
        )
2428
        await Promise.all(promises)
6✔
2429
    }
2430

2431
    /**
2432
     * Clears all table contents.
2433
     * Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
2434
     */
2435
    async clearTable(tablePath: string): Promise<void> {
2436
        await this.query(`TRUNCATE TABLE ${this.escapePath(tablePath)}`)
4✔
2437
    }
2438

2439
    /**
2440
     * Removes all tables from the currently connected database.
2441
     */
2442
    async clearDatabase(): Promise<void> {
2443
        const schemas: string[] = []
2,436✔
2444
        this.connection.entityMetadatas
2,436✔
2445
            .filter((metadata) => metadata.schema)
8,774✔
2446
            .forEach((metadata) => {
2447
                const isSchemaExist = !!schemas.find(
4✔
2448
                    (schema) => schema === metadata.schema,
2✔
2449
                )
2450
                if (!isSchemaExist) schemas.push(metadata.schema!)
4✔
2451
            })
2452

2453
        schemas.push(this.driver.options.schema || "current_schema")
2,436✔
2454
        const schemaNamesString = schemas
2,436✔
2455
            .map((name) => {
2456
                return name === "current_schema" ? name : "'" + name + "'"
2,440✔
2457
            })
2458
            .join(", ")
2459

2460
        const isAnotherTransactionActive = this.isTransactionActive
2,436✔
2461
        if (!isAnotherTransactionActive) await this.startTransaction()
2,436✔
2462
        try {
2,436✔
2463
            // const selectViewDropsQuery = `SELECT 'DROP VIEW IF EXISTS "' || schemaname || '"."' || viewname || '" CASCADE;' as "query" ` +
2464
            //     `FROM "pg_views" WHERE "schemaname" IN (${schemaNamesString}) AND "viewname" NOT IN ('geography_columns', 'geometry_columns', 'raster_columns', 'raster_overviews')`;
2465
            // const dropViewQueries: ObjectLiteral[] = await this.query(selectViewDropsQuery);
2466
            // await Promise.all(dropViewQueries.map(q => this.query(q["query"])));
2467

2468
            // ignore spatial_ref_sys; it's a special table supporting PostGIS
2469
            const selectTableDropsQuery = `SELECT 'DROP TABLE "' || schema_name || '"."' || table_name || '" CASCADE;' as "query" FROM "SYS"."TABLES" WHERE "SCHEMA_NAME" IN (${schemaNamesString}) AND "TABLE_NAME" NOT IN ('SYS_AFL_GENERATOR_PARAMETERS') AND "IS_COLUMN_TABLE" = 'TRUE'`
2,436✔
2470
            const dropTableQueries: ObjectLiteral[] = await this.query(
2,436✔
2471
                selectTableDropsQuery,
2472
            )
2473
            await Promise.all(
2,436✔
2474
                dropTableQueries.map((q) => this.query(q["query"])),
8,474✔
2475
            )
2476

2477
            if (!isAnotherTransactionActive) await this.commitTransaction()
2,436✔
2478
        } catch (error) {
2479
            try {
×
2480
                // we throw original error even if rollback thrown an error
2481
                if (!isAnotherTransactionActive)
×
2482
                    await this.rollbackTransaction()
×
2483
            } catch (rollbackError) {}
2484
            throw error
×
2485
        }
2486
    }
2487

2488
    // -------------------------------------------------------------------------
2489
    // Protected Methods
2490
    // -------------------------------------------------------------------------
2491

2492
    protected async loadViews(viewNames?: string[]): Promise<View[]> {
2493
        const hasTable = await this.hasTable(this.getTypeormMetadataTableName())
2,536✔
2494
        if (!hasTable) {
2,536✔
2495
            return []
2,528✔
2496
        }
2497

2498
        if (!viewNames) {
8!
2499
            viewNames = []
×
2500
        }
2501

2502
        const currentDatabase = await this.getCurrentDatabase()
8✔
2503
        const currentSchema = await this.getCurrentSchema()
8✔
2504

2505
        const viewsCondition = viewNames
8✔
2506
            .map((viewName) => {
2507
                let { schema, tableName: name } =
2508
                    this.driver.parseTableName(viewName)
16✔
2509

2510
                if (!schema) {
16!
2511
                    schema = currentSchema
×
2512
                }
2513

2514
                return `("t"."schema" = '${schema}' AND "t"."name" = '${name}')`
16✔
2515
            })
2516
            .join(" OR ")
2517

2518
        const query = `SELECT "t".* FROM ${this.escapePath(
8✔
2519
            this.getTypeormMetadataTableName(),
2520
        )} "t" WHERE "t"."type" = '${MetadataTableType.VIEW}' ${
2521
            viewsCondition ? `AND (${viewsCondition})` : ""
8!
2522
        }`
2523
        const dbViews = await this.query(query)
8✔
2524
        return dbViews.map((dbView: any) => {
8✔
2525
            const view = new View()
4✔
2526
            const schema =
2527
                dbView["schema"] === currentSchema &&
4!
2528
                !this.driver.options.schema
2529
                    ? undefined
2530
                    : dbView["schema"]
2531
            view.database = currentDatabase
4✔
2532
            view.schema = dbView["schema"]
4✔
2533
            view.name = this.driver.buildTableName(dbView["name"], schema)
4✔
2534
            view.expression = dbView["value"]
4✔
2535
            return view
4✔
2536
        })
2537
    }
2538

2539
    /**
2540
     * Loads all tables (with given names) from the database and creates a Table from them.
2541
     */
2542
    protected async loadTables(tableNames?: string[]): Promise<Table[]> {
2543
        if (tableNames && tableNames.length === 0) {
2,918✔
2544
            return []
12✔
2545
        }
2546

2547
        const currentSchema = await this.getCurrentSchema()
2,906✔
2548
        const currentDatabase = await this.getCurrentDatabase()
2,906✔
2549

2550
        const dbTables: { SCHEMA_NAME: string; TABLE_NAME: string }[] = []
2,906✔
2551

2552
        if (!tableNames) {
2,906!
2553
            const tablesSql = `SELECT "SCHEMA_NAME", "TABLE_NAME" FROM "SYS"."TABLES"`
×
2554

2555
            dbTables.push(...(await this.query(tablesSql)))
×
2556
        } else {
2557
            const tablesCondition = tableNames
2,906✔
2558
                .map((tableName) => {
2559
                    let [schema, name] = tableName.split(".")
9,292✔
2560
                    if (!name) {
9,292✔
2561
                        name = schema
354✔
2562
                        schema = this.driver.options.schema || currentSchema
354✔
2563
                    }
2564
                    return `("SCHEMA_NAME" = '${schema}' AND "TABLE_NAME" = '${name}')`
9,292✔
2565
                })
2566
                .join(" OR ")
2567

2568
            const tablesSql =
2569
                `SELECT "SCHEMA_NAME", "TABLE_NAME" FROM "SYS"."TABLES" WHERE ` +
2,906✔
2570
                tablesCondition
2571

2572
            dbTables.push(...(await this.query(tablesSql)))
2,906✔
2573
        }
2574

2575
        // if tables were not found in the db, no need to proceed
2576
        if (dbTables.length === 0) return []
2,906✔
2577

2578
        const columnsCondition = dbTables
454✔
2579
            .map(({ SCHEMA_NAME, TABLE_NAME }) => {
2580
                return `("SCHEMA_NAME" = '${SCHEMA_NAME}' AND "TABLE_NAME" = '${TABLE_NAME}')`
798✔
2581
            })
2582
            .join(" OR ")
2583
        const columnsSql =
2584
            `SELECT * FROM "SYS"."TABLE_COLUMNS" WHERE ` +
454✔
2585
            columnsCondition +
2586
            ` ORDER BY "POSITION"`
2587

2588
        const constraintsCondition = dbTables
454✔
2589
            .map(({ SCHEMA_NAME, TABLE_NAME }) => {
2590
                return `("SCHEMA_NAME" = '${SCHEMA_NAME}' AND "TABLE_NAME" = '${TABLE_NAME}')`
798✔
2591
            })
2592
            .join(" OR ")
2593
        const constraintsSql = `SELECT * FROM "SYS"."CONSTRAINTS" WHERE (${constraintsCondition}) ORDER BY "POSITION"`
454✔
2594

2595
        const indicesCondition = dbTables
454✔
2596
            .map(({ SCHEMA_NAME, TABLE_NAME }) => {
2597
                return `("I"."SCHEMA_NAME" = '${SCHEMA_NAME}' AND "I"."TABLE_NAME" = '${TABLE_NAME}')`
798✔
2598
            })
2599
            .join(" OR ")
2600
        // excluding primary key and autogenerated fulltext indices
2601
        const indicesSql =
2602
            `SELECT "I"."INDEX_TYPE", "I"."SCHEMA_NAME", "I"."TABLE_NAME", "I"."INDEX_NAME", "IC"."COLUMN_NAME", "I"."CONSTRAINT" ` +
454✔
2603
            `FROM "SYS"."INDEXES" "I" INNER JOIN "SYS"."INDEX_COLUMNS" "IC" ON "IC"."INDEX_OID" = "I"."INDEX_OID" ` +
2604
            `WHERE (${indicesCondition}) AND ("I"."CONSTRAINT" IS NULL OR "I"."CONSTRAINT" != 'PRIMARY KEY') AND "I"."INDEX_NAME" NOT LIKE '%_SYS_FULLTEXT_%' ORDER BY "IC"."POSITION"`
2605

2606
        const foreignKeysCondition = dbTables
454✔
2607
            .map(({ SCHEMA_NAME, TABLE_NAME }) => {
2608
                return `("SCHEMA_NAME" = '${SCHEMA_NAME}' AND "TABLE_NAME" = '${TABLE_NAME}')`
798✔
2609
            })
2610
            .join(" OR ")
2611
        const foreignKeysSql = `SELECT * FROM "SYS"."REFERENTIAL_CONSTRAINTS" WHERE (${foreignKeysCondition}) ORDER BY "POSITION"`
454✔
2612
        const [
2613
            dbColumns,
2614
            dbConstraints,
2615
            dbIndices,
2616
            dbForeignKeys,
2617
        ]: ObjectLiteral[][] = await Promise.all([
454✔
2618
            this.query(columnsSql),
2619
            this.query(constraintsSql),
2620
            this.query(indicesSql),
2621
            this.query(foreignKeysSql),
2622
        ])
2623

2624
        // create tables for loaded tables
2625
        return dbTables.map((dbTable) => {
454✔
2626
            const table = new Table()
798✔
2627
            const getSchemaFromKey = (dbObject: any, key: string) => {
798✔
2628
                return dbObject[key] === currentSchema &&
1,100!
2629
                    (!this.driver.options.schema ||
2630
                        this.driver.options.schema === currentSchema)
2631
                    ? undefined
2632
                    : dbObject[key]
2633
            }
2634

2635
            // We do not need to join schema name, when database is by default.
2636
            const schema = getSchemaFromKey(dbTable, "SCHEMA_NAME")
798✔
2637
            table.database = currentDatabase
798✔
2638
            table.schema = dbTable["SCHEMA_NAME"]
798✔
2639
            table.name = this.driver.buildTableName(
798✔
2640
                dbTable["TABLE_NAME"],
2641
                schema,
2642
            )
2643

2644
            // create columns from the loaded columns
2645
            table.columns = dbColumns
798✔
2646
                .filter(
2647
                    (dbColumn) =>
2648
                        dbColumn["TABLE_NAME"] === dbTable["TABLE_NAME"] &&
10,292✔
2649
                        dbColumn["SCHEMA_NAME"] === dbTable["SCHEMA_NAME"],
2650
                )
2651
                .map((dbColumn) => {
2652
                    const columnConstraints = dbConstraints.filter(
2,772✔
2653
                        (dbConstraint) =>
2654
                            dbConstraint["TABLE_NAME"] ===
18,268✔
2655
                                dbColumn["TABLE_NAME"] &&
2656
                            dbConstraint["SCHEMA_NAME"] ===
2657
                                dbColumn["SCHEMA_NAME"] &&
2658
                            dbConstraint["COLUMN_NAME"] ===
2659
                                dbColumn["COLUMN_NAME"],
2660
                    )
2661

2662
                    const columnUniqueIndices = dbIndices.filter((dbIndex) => {
2,772✔
2663
                        return (
7,542✔
2664
                            dbIndex["TABLE_NAME"] === dbTable["TABLE_NAME"] &&
16,076✔
2665
                            dbIndex["SCHEMA_NAME"] === dbTable["SCHEMA_NAME"] &&
2666
                            dbIndex["COLUMN_NAME"] ===
2667
                                dbColumn["COLUMN_NAME"] &&
2668
                            dbIndex["CONSTRAINT"] &&
2669
                            dbIndex["CONSTRAINT"].indexOf("UNIQUE") !== -1
2670
                        )
2671
                    })
2672

2673
                    const tableMetadata = this.connection.entityMetadatas.find(
2,772✔
2674
                        (metadata) =>
2675
                            this.getTablePath(table) ===
8,894✔
2676
                            this.getTablePath(metadata),
2677
                    )
2678
                    const hasIgnoredIndex =
2679
                        columnUniqueIndices.length > 0 &&
2,772✔
2680
                        tableMetadata &&
2681
                        tableMetadata.indices.some((index) => {
2682
                            return columnUniqueIndices.some((uniqueIndex) => {
1,118✔
2683
                                return (
1,130✔
2684
                                    index.name === uniqueIndex["INDEX_NAME"] &&
1,674✔
2685
                                    index.synchronize === false
2686
                                )
2687
                            })
2688
                        })
2689

2690
                    const isConstraintComposite = columnUniqueIndices.every(
2,772✔
2691
                        (uniqueIndex) => {
2692
                            return dbIndices.some(
604✔
2693
                                (dbIndex) =>
2694
                                    dbIndex["INDEX_NAME"] ===
1,725✔
2695
                                        uniqueIndex["INDEX_NAME"] &&
2696
                                    dbIndex["COLUMN_NAME"] !==
2697
                                        dbColumn["COLUMN_NAME"],
2698
                            )
2699
                        },
2700
                    )
2701

2702
                    const tableColumn = new TableColumn()
2,772✔
2703
                    tableColumn.name = dbColumn["COLUMN_NAME"]
2,772✔
2704
                    tableColumn.type = dbColumn["DATA_TYPE_NAME"].toLowerCase()
2,772✔
2705

2706
                    if (
2,772✔
2707
                        tableColumn.type === "dec" ||
5,544✔
2708
                        tableColumn.type === "decimal"
2709
                    ) {
2710
                        // If one of these properties was set, and another was not, Postgres sets '0' in to unspecified property
2711
                        // we set 'undefined' in to unspecified property to avoid changing column on sync
2712
                        if (
8!
2713
                            dbColumn["LENGTH"] !== null &&
16✔
2714
                            !this.isDefaultColumnPrecision(
2715
                                table,
2716
                                tableColumn,
2717
                                dbColumn["LENGTH"],
2718
                            )
2719
                        ) {
2720
                            tableColumn.precision = dbColumn["LENGTH"]
8✔
2721
                        } else if (
×
2722
                            dbColumn["SCALE"] !== null &&
×
2723
                            !this.isDefaultColumnScale(
2724
                                table,
2725
                                tableColumn,
2726
                                dbColumn["SCALE"],
2727
                            )
2728
                        ) {
2729
                            tableColumn.precision = undefined
×
2730
                        }
2731
                        if (
8✔
2732
                            dbColumn["SCALE"] !== null &&
12✔
2733
                            !this.isDefaultColumnScale(
2734
                                table,
2735
                                tableColumn,
2736
                                dbColumn["SCALE"],
2737
                            )
2738
                        ) {
2739
                            tableColumn.scale = dbColumn["SCALE"]
4✔
2740
                        } else if (
4✔
2741
                            dbColumn["LENGTH"] !== null &&
8✔
2742
                            !this.isDefaultColumnPrecision(
2743
                                table,
2744
                                tableColumn,
2745
                                dbColumn["LENGTH"],
2746
                            )
2747
                        ) {
2748
                            tableColumn.scale = undefined
4✔
2749
                        }
2750
                    }
2751

2752
                    if (dbColumn["DATA_TYPE_NAME"].toLowerCase() === "array") {
2,772!
2753
                        tableColumn.isArray = true
×
2754
                        tableColumn.type =
×
2755
                            dbColumn["CS_DATA_TYPE_NAME"].toLowerCase()
2756
                    }
2757

2758
                    // check only columns that have length property
2759
                    if (
2,772✔
2760
                        this.driver.withLengthColumnTypes.indexOf(
4,272✔
2761
                            tableColumn.type as ColumnType,
2762
                        ) !== -1 &&
2763
                        dbColumn["LENGTH"]
2764
                    ) {
2765
                        const length = dbColumn["LENGTH"].toString()
1,500✔
2766
                        tableColumn.length = !this.isDefaultColumnLength(
1,500✔
2767
                            table,
2768
                            tableColumn,
2769
                            length,
2770
                        )
2771
                            ? length
2772
                            : ""
2773
                    }
2774
                    tableColumn.isUnique =
2,772✔
2775
                        columnUniqueIndices.length > 0 &&
3,970✔
2776
                        !hasIgnoredIndex &&
2777
                        !isConstraintComposite
2778
                    tableColumn.isNullable = dbColumn["IS_NULLABLE"] === "TRUE"
2,772✔
2779
                    tableColumn.isPrimary = !!columnConstraints.find(
2,772✔
2780
                        (constraint) => constraint["IS_PRIMARY_KEY"] === "TRUE",
1,412✔
2781
                    )
2782
                    tableColumn.isGenerated =
2,772✔
2783
                        dbColumn["GENERATION_TYPE"] === "ALWAYS AS IDENTITY"
2784
                    if (tableColumn.isGenerated)
2,772✔
2785
                        tableColumn.generationStrategy = "increment"
400✔
2786

2787
                    if (
2,772✔
2788
                        dbColumn["DEFAULT_VALUE"] === null ||
2,942✔
2789
                        dbColumn["DEFAULT_VALUE"] === undefined
2790
                    ) {
2791
                        tableColumn.default = undefined
2,602✔
2792
                    } else {
2793
                        if (
170✔
2794
                            tableColumn.type === "char" ||
684✔
2795
                            tableColumn.type === "nchar" ||
2796
                            tableColumn.type === "varchar" ||
2797
                            tableColumn.type === "nvarchar" ||
2798
                            tableColumn.type === "alphanum" ||
2799
                            tableColumn.type === "shorttext"
2800
                        ) {
2801
                            tableColumn.default = `'${dbColumn["DEFAULT_VALUE"]}'`
166✔
2802
                        } else if (tableColumn.type === "boolean") {
4!
2803
                            tableColumn.default =
×
2804
                                dbColumn["DEFAULT_VALUE"] === "1"
×
2805
                                    ? "true"
2806
                                    : "false"
2807
                        } else {
2808
                            tableColumn.default = dbColumn["DEFAULT_VALUE"]
4✔
2809
                        }
2810
                    }
2811
                    if (dbColumn["COMMENTS"]) {
2,772✔
2812
                        tableColumn.comment = dbColumn["COMMENTS"]
92✔
2813
                    }
2814
                    return tableColumn
2,772✔
2815
                })
2816

2817
            // find check constraints of table, group them by constraint name and build TableCheck.
2818
            const tableCheckConstraints = OrmUtils.uniq(
798✔
2819
                dbConstraints.filter(
2820
                    (dbConstraint) =>
2821
                        dbConstraint["TABLE_NAME"] === dbTable["TABLE_NAME"] &&
5,298✔
2822
                        dbConstraint["SCHEMA_NAME"] ===
2823
                            dbTable["SCHEMA_NAME"] &&
2824
                        dbConstraint["CHECK_CONDITION"] !== null &&
2825
                        dbConstraint["CHECK_CONDITION"] !== undefined,
2826
                ),
2827
                (dbConstraint) => dbConstraint["CONSTRAINT_NAME"],
166✔
2828
            )
2829

2830
            table.checks = tableCheckConstraints.map((constraint) => {
798✔
2831
                const checks = dbConstraints.filter(
160✔
2832
                    (dbC) =>
2833
                        dbC["CONSTRAINT_NAME"] ===
1,164✔
2834
                        constraint["CONSTRAINT_NAME"],
2835
                )
2836
                return new TableCheck({
160✔
2837
                    name: constraint["CONSTRAINT_NAME"],
2838
                    columnNames: checks.map((c) => c["COLUMN_NAME"]),
160✔
2839
                    expression: constraint["CHECK_CONDITION"],
2840
                })
2841
            })
2842

2843
            // find foreign key constraints of table, group them by constraint name and build TableForeignKey.
2844
            const tableForeignKeyConstraints = OrmUtils.uniq(
798✔
2845
                dbForeignKeys.filter(
2846
                    (dbForeignKey) =>
2847
                        dbForeignKey["TABLE_NAME"] === dbTable["TABLE_NAME"] &&
1,290✔
2848
                        dbForeignKey["SCHEMA_NAME"] === dbTable["SCHEMA_NAME"],
2849
                ),
2850
                (dbForeignKey) => dbForeignKey["CONSTRAINT_NAME"],
484✔
2851
            )
2852

2853
            table.foreignKeys = tableForeignKeyConstraints.map(
798✔
2854
                (dbForeignKey) => {
2855
                    const foreignKeys = dbForeignKeys.filter(
302✔
2856
                        (dbFk) =>
2857
                            dbFk["CONSTRAINT_NAME"] ===
834✔
2858
                            dbForeignKey["CONSTRAINT_NAME"],
2859
                    )
2860

2861
                    // if referenced table located in currently used schema, we don't need to concat schema name to table name.
2862
                    const schema = getSchemaFromKey(
302✔
2863
                        dbForeignKey,
2864
                        "REFERENCED_SCHEMA_NAME",
2865
                    )
2866
                    const referencedTableName = this.driver.buildTableName(
302✔
2867
                        dbForeignKey["REFERENCED_TABLE_NAME"],
2868
                        schema,
2869
                    )
2870

2871
                    return new TableForeignKey({
302✔
2872
                        name: dbForeignKey["CONSTRAINT_NAME"],
2873
                        columnNames: foreignKeys.map(
2874
                            (dbFk) => dbFk["COLUMN_NAME"],
310✔
2875
                        ),
2876
                        referencedDatabase: table.database,
2877
                        referencedSchema:
2878
                            dbForeignKey["REFERENCED_SCHEMA_NAME"],
2879
                        referencedTableName: referencedTableName,
2880
                        referencedColumnNames: foreignKeys.map(
2881
                            (dbFk) => dbFk["REFERENCED_COLUMN_NAME"],
310✔
2882
                        ),
2883
                        onDelete:
2884
                            dbForeignKey["DELETE_RULE"] === "RESTRICT"
302✔
2885
                                ? "NO ACTION"
2886
                                : dbForeignKey["DELETE_RULE"],
2887
                        onUpdate:
2888
                            dbForeignKey["UPDATE_RULE"] === "RESTRICT"
302✔
2889
                                ? "NO ACTION"
2890
                                : dbForeignKey["UPDATE_RULE"],
2891
                        deferrable: dbForeignKey["CHECK_TIME"].replace(
2892
                            "_",
2893
                            " ",
2894
                        ),
2895
                    })
2896
                },
2897
            )
2898

2899
            // find index constraints of table, group them by constraint name and build TableIndex.
2900
            const tableIndexConstraints = OrmUtils.uniq(
798✔
2901
                dbIndices.filter(
2902
                    (dbIndex) =>
2903
                        dbIndex["TABLE_NAME"] === dbTable["TABLE_NAME"] &&
2,128✔
2904
                        dbIndex["SCHEMA_NAME"] === dbTable["SCHEMA_NAME"],
2905
                ),
2906
                (dbIndex) => dbIndex["INDEX_NAME"],
1,328✔
2907
            )
2908

2909
            table.indices = tableIndexConstraints.map((constraint) => {
798✔
2910
                const indices = dbIndices.filter((index) => {
562✔
2911
                    return (
1,756✔
2912
                        index["SCHEMA_NAME"] === constraint["SCHEMA_NAME"] &&
4,924✔
2913
                        index["TABLE_NAME"] === constraint["TABLE_NAME"] &&
2914
                        index["INDEX_NAME"] === constraint["INDEX_NAME"]
2915
                    )
2916
                })
2917
                return new TableIndex(<TableIndexOptions>{
562✔
2918
                    table: table,
2919
                    name: constraint["INDEX_NAME"],
2920
                    columnNames: indices.map((i) => i["COLUMN_NAME"]),
760✔
2921
                    isUnique:
2922
                        constraint["CONSTRAINT"] &&
988✔
2923
                        constraint["CONSTRAINT"].indexOf("UNIQUE") !== -1,
2924
                    isFulltext: constraint["INDEX_TYPE"] === "FULLTEXT",
2925
                })
2926
            })
2927

2928
            return table
798✔
2929
        })
2930
    }
2931

2932
    /**
2933
     * Builds and returns SQL for create table.
2934
     */
2935
    protected createTableSql(table: Table, createForeignKeys?: boolean): Query {
2936
        const columnDefinitions = table.columns
8,534✔
2937
            .map((column) => this.buildCreateColumnSql(column))
26,482✔
2938
            .join(", ")
2939
        let sql = `CREATE TABLE ${this.escapePath(table)} (${columnDefinitions}`
8,534✔
2940

2941
        // we create unique indexes instead of unique constraints, because SAP HANA does not have unique constraints.
2942
        // if we mark column as Unique, it means that we create UNIQUE INDEX.
2943
        table.columns
8,534✔
2944
            .filter((column) => column.isUnique)
26,482✔
2945
            .forEach((column) => {
2946
                const isUniqueIndexExist = table.indices.some((index) => {
1,322✔
2947
                    return (
2,042✔
2948
                        index.columnNames.length === 1 &&
6,088✔
2949
                        !!index.isUnique &&
2950
                        index.columnNames.indexOf(column.name) !== -1
2951
                    )
2952
                })
2953
                const isUniqueConstraintExist = table.uniques.some((unique) => {
1,322✔
2954
                    return (
×
2955
                        unique.columnNames.length === 1 &&
×
2956
                        unique.columnNames.indexOf(column.name) !== -1
2957
                    )
2958
                })
2959
                if (!isUniqueIndexExist && !isUniqueConstraintExist)
1,322✔
2960
                    table.indices.push(
10✔
2961
                        new TableIndex({
2962
                            name: this.connection.namingStrategy.uniqueConstraintName(
2963
                                table,
2964
                                [column.name],
2965
                            ),
2966
                            columnNames: [column.name],
2967
                            isUnique: true,
2968
                        }),
2969
                    )
2970
            })
2971

2972
        // as SAP HANA does not have unique constraints, we must create table indices from table uniques and mark them as unique.
2973
        if (table.uniques.length > 0) {
8,534✔
2974
            table.uniques.forEach((unique) => {
50✔
2975
                const uniqueExist = table.indices.some(
50✔
2976
                    (index) => index.name === unique.name,
×
2977
                )
2978
                if (!uniqueExist) {
50✔
2979
                    table.indices.push(
50✔
2980
                        new TableIndex({
2981
                            name: unique.name,
2982
                            columnNames: unique.columnNames,
2983
                            isUnique: true,
2984
                        }),
2985
                    )
2986
                }
2987
            })
2988
        }
2989

2990
        if (table.checks.length > 0) {
8,534✔
2991
            const checksSql = table.checks
124✔
2992
                .map((check) => {
2993
                    const checkName = check.name
124!
2994
                        ? check.name
2995
                        : this.connection.namingStrategy.checkConstraintName(
2996
                              table,
2997
                              check.expression!,
2998
                          )
2999
                    return `CONSTRAINT "${checkName}" CHECK (${check.expression})`
124✔
3000
                })
3001
                .join(", ")
3002

3003
            sql += `, ${checksSql}`
124✔
3004
        }
3005

3006
        if (table.foreignKeys.length > 0 && createForeignKeys) {
8,534✔
3007
            const foreignKeysSql = table.foreignKeys
10✔
3008
                .map((fk) => {
3009
                    const columnNames = fk.columnNames
12✔
3010
                        .map((columnName) => `"${columnName}"`)
14✔
3011
                        .join(", ")
3012
                    if (!fk.name)
12✔
3013
                        fk.name = this.connection.namingStrategy.foreignKeyName(
8✔
3014
                            table,
3015
                            fk.columnNames,
3016
                            this.getTablePath(fk),
3017
                            fk.referencedColumnNames,
3018
                        )
3019
                    const referencedColumnNames = fk.referencedColumnNames
12✔
3020
                        .map((columnName) => `"${columnName}"`)
14✔
3021
                        .join(", ")
3022

3023
                    let constraint = `CONSTRAINT "${
12✔
3024
                        fk.name
3025
                    }" FOREIGN KEY (${columnNames}) REFERENCES ${this.escapePath(
3026
                        this.getTablePath(fk),
3027
                    )} (${referencedColumnNames})`
3028
                    // SAP HANA does not have "NO ACTION" option for FK's
3029
                    if (fk.onDelete) {
12✔
3030
                        const onDelete =
3031
                            fk.onDelete === "NO ACTION"
4!
3032
                                ? "RESTRICT"
3033
                                : fk.onDelete
3034
                        constraint += ` ON DELETE ${onDelete}`
4✔
3035
                    }
3036
                    if (fk.onUpdate) {
12✔
3037
                        const onUpdate =
3038
                            fk.onUpdate === "NO ACTION"
4!
3039
                                ? "RESTRICT"
3040
                                : fk.onUpdate
3041
                        constraint += ` ON UPDATE ${onUpdate}`
4✔
3042
                    }
3043
                    if (fk.deferrable) {
12✔
3044
                        constraint += ` ${fk.deferrable}`
4✔
3045
                    }
3046

3047
                    return constraint
12✔
3048
                })
3049
                .join(", ")
3050

3051
            sql += `, ${foreignKeysSql}`
10✔
3052
        }
3053

3054
        const primaryColumns = table.columns.filter(
8,534✔
3055
            (column) => column.isPrimary,
26,482✔
3056
        )
3057
        if (primaryColumns.length > 0) {
8,534✔
3058
            const primaryKeyName =
3059
                this.connection.namingStrategy.primaryKeyName(
8,526✔
3060
                    table,
3061
                    primaryColumns.map((column) => column.name),
10,688✔
3062
                )
3063
            const columnNames = primaryColumns
8,526✔
3064
                .map((column) => `"${column.name}"`)
10,688✔
3065
                .join(", ")
3066
            sql += `, CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNames})`
8,526✔
3067
        }
3068

3069
        sql += `)`
8,534✔
3070

3071
        return new Query(sql)
8,534✔
3072
    }
3073

3074
    /**
3075
     * Builds drop table sql.
3076
     */
3077
    protected dropTableSql(
3078
        tableOrName: Table | string,
3079
        ifExist?: boolean,
3080
    ): Query {
3081
        const query = ifExist
8,534!
3082
            ? `DROP TABLE IF EXISTS ${this.escapePath(tableOrName)}`
3083
            : `DROP TABLE ${this.escapePath(tableOrName)}`
3084
        return new Query(query)
8,534✔
3085
    }
3086

3087
    protected createViewSql(view: View): Query {
3088
        if (typeof view.expression === "string") {
12!
3089
            return new Query(
×
3090
                `CREATE VIEW ${this.escapePath(view)} AS ${view.expression}`,
3091
            )
3092
        } else {
3093
            return new Query(
12✔
3094
                `CREATE VIEW ${this.escapePath(view)} AS ${view
3095
                    .expression(this.connection)
3096
                    .getQuery()}`,
3097
            )
3098
        }
3099
    }
3100

3101
    protected async insertViewDefinitionSql(view: View): Promise<Query> {
3102
        let { schema, tableName: name } = this.driver.parseTableName(view)
12✔
3103

3104
        if (!schema) {
12!
3105
            schema = await this.getCurrentSchema()
×
3106
        }
3107

3108
        const expression =
3109
            typeof view.expression === "string"
12!
3110
                ? view.expression.trim()
3111
                : view.expression(this.connection).getQuery()
3112
        return this.insertTypeormMetadataSql({
12✔
3113
            type: MetadataTableType.VIEW,
3114
            schema: schema,
3115
            name: name,
3116
            value: expression,
3117
        })
3118
    }
3119

3120
    /**
3121
     * Builds drop view sql.
3122
     */
3123
    protected dropViewSql(viewOrPath: View | string): Query {
3124
        return new Query(`DROP VIEW ${this.escapePath(viewOrPath)}`)
12✔
3125
    }
3126

3127
    /**
3128
     * Builds remove view sql.
3129
     */
3130
    protected async deleteViewDefinitionSql(
3131
        viewOrPath: View | string,
3132
    ): Promise<Query> {
3133
        let { schema, tableName: name } = this.driver.parseTableName(viewOrPath)
12✔
3134

3135
        if (!schema) {
12!
3136
            schema = await this.getCurrentSchema()
×
3137
        }
3138

3139
        return this.deleteTypeormMetadataSql({
12✔
3140
            type: MetadataTableType.VIEW,
3141
            schema,
3142
            name,
3143
        })
3144
    }
3145

3146
    protected addColumnSql(table: Table, column: TableColumn): string {
3147
        return `ALTER TABLE ${this.escapePath(
110✔
3148
            table,
3149
        )} ADD (${this.buildCreateColumnSql(column)})`
3150
    }
3151

3152
    protected dropColumnSql(table: Table, column: TableColumn): string {
3153
        return `ALTER TABLE ${this.escapePath(table)} DROP ("${column.name}")`
110✔
3154
    }
3155

3156
    /**
3157
     * Builds create index sql.
3158
     */
3159
    protected createIndexSql(table: Table, index: TableIndex): Query {
3160
        const columns = index.columnNames
4,798✔
3161
            .map((columnName) => `"${columnName}"`)
5,794✔
3162
            .join(", ")
3163
        let indexType = ""
4,798✔
3164
        if (index.isUnique) {
4,798✔
3165
            indexType += "UNIQUE "
1,844✔
3166
        }
3167
        if (index.isFulltext && this.driver.isFullTextColumnTypeSupported()) {
4,798✔
3168
            indexType += "FULLTEXT "
4✔
3169
        }
3170

3171
        return new Query(
4,798✔
3172
            `CREATE ${indexType}INDEX "${index.name}" ON ${this.escapePath(
3173
                table,
3174
            )} (${columns}) ${index.where ? "WHERE " + index.where : ""}`,
4,798!
3175
        )
3176
    }
3177

3178
    /**
3179
     * Builds drop index sql.
3180
     */
3181
    protected dropIndexSql(
3182
        table: Table,
3183
        indexOrName: TableIndex | string,
3184
    ): Query {
3185
        const indexName = InstanceChecker.isTableIndex(indexOrName)
4,798!
3186
            ? indexOrName.name
3187
            : indexOrName
3188
        const parsedTableName = this.driver.parseTableName(table)
4,798✔
3189

3190
        if (!parsedTableName.schema) {
4,798!
3191
            return new Query(`DROP INDEX "${indexName}"`)
×
3192
        } else {
3193
            return new Query(
4,798✔
3194
                `DROP INDEX "${parsedTableName.schema}"."${indexName}"`,
3195
            )
3196
        }
3197
    }
3198

3199
    /**
3200
     * Builds create primary key sql.
3201
     */
3202
    protected createPrimaryKeySql(table: Table, columnNames: string[]): Query {
3203
        const primaryKeyName = this.connection.namingStrategy.primaryKeyName(
10✔
3204
            table,
3205
            columnNames,
3206
        )
3207
        const columnNamesString = columnNames
10✔
3208
            .map((columnName) => `"${columnName}"`)
12✔
3209
            .join(", ")
3210
        return new Query(
10✔
3211
            `ALTER TABLE ${this.escapePath(
3212
                table,
3213
            )} ADD CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNamesString})`,
3214
        )
3215
    }
3216

3217
    /**
3218
     * Builds drop primary key sql.
3219
     */
3220
    protected dropPrimaryKeySql(table: Table): Query {
3221
        const columnNames = table.primaryColumns.map((column) => column.name)
12✔
3222
        const primaryKeyName = this.connection.namingStrategy.primaryKeyName(
10✔
3223
            table,
3224
            columnNames,
3225
        )
3226
        return new Query(
10✔
3227
            `ALTER TABLE ${this.escapePath(
3228
                table,
3229
            )} DROP CONSTRAINT "${primaryKeyName}"`,
3230
        )
3231
    }
3232

3233
    /**
3234
     * Builds create check constraint sql.
3235
     */
3236
    protected createCheckConstraintSql(
3237
        table: Table,
3238
        checkConstraint: TableCheck,
3239
    ): Query {
3240
        return new Query(
16✔
3241
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
3242
                checkConstraint.name
3243
            }" CHECK (${checkConstraint.expression})`,
3244
        )
3245
    }
3246

3247
    /**
3248
     * Builds drop check constraint sql.
3249
     */
3250
    protected dropCheckConstraintSql(
3251
        table: Table,
3252
        checkOrName: TableCheck | string,
3253
    ): Query {
3254
        const checkName = InstanceChecker.isTableCheck(checkOrName)
16!
3255
            ? checkOrName.name
3256
            : checkOrName
3257
        return new Query(
16✔
3258
            `ALTER TABLE ${this.escapePath(
3259
                table,
3260
            )} DROP CONSTRAINT "${checkName}"`,
3261
        )
3262
    }
3263

3264
    /**
3265
     * Builds create foreign key sql.
3266
     */
3267
    protected createForeignKeySql(
3268
        tableOrName: Table | string,
3269
        foreignKey: TableForeignKey,
3270
    ): Query {
3271
        const columnNames = foreignKey.columnNames
5,594✔
3272
            .map((column) => `"` + column + `"`)
6,380✔
3273
            .join(", ")
3274
        const referencedColumnNames = foreignKey.referencedColumnNames
5,594✔
3275
            .map((column) => `"` + column + `"`)
6,380✔
3276
            .join(",")
3277
        let sql =
3278
            `ALTER TABLE ${this.escapePath(tableOrName)} ADD CONSTRAINT "${
5,594✔
3279
                foreignKey.name
3280
            }" FOREIGN KEY (${columnNames}) ` +
3281
            `REFERENCES ${this.escapePath(
3282
                this.getTablePath(foreignKey),
3283
            )}(${referencedColumnNames})`
3284

3285
        // SAP HANA does not have "NO ACTION" option for FK's
3286
        if (foreignKey.onDelete) {
5,594✔
3287
            const onDelete =
3288
                foreignKey.onDelete === "NO ACTION"
5,594✔
3289
                    ? "RESTRICT"
3290
                    : foreignKey.onDelete
3291
            sql += ` ON DELETE ${onDelete}`
5,594✔
3292
        }
3293
        if (foreignKey.onUpdate) {
5,594✔
3294
            const onUpdate =
3295
                foreignKey.onUpdate === "NO ACTION"
5,592✔
3296
                    ? "RESTRICT"
3297
                    : foreignKey.onUpdate
3298
            sql += ` ON UPDATE ${onUpdate}`
5,592✔
3299
        }
3300

3301
        if (foreignKey.deferrable) {
5,594✔
3302
            sql += ` ${foreignKey.deferrable}`
66✔
3303
        }
3304

3305
        return new Query(sql)
5,594✔
3306
    }
3307

3308
    /**
3309
     * Builds drop foreign key sql.
3310
     */
3311
    protected dropForeignKeySql(
3312
        tableOrName: Table | string,
3313
        foreignKeyOrName: TableForeignKey | string,
3314
    ): Query {
3315
        const foreignKeyName = InstanceChecker.isTableForeignKey(
5,606!
3316
            foreignKeyOrName,
3317
        )
3318
            ? foreignKeyOrName.name
3319
            : foreignKeyOrName
3320
        return new Query(
5,606✔
3321
            `ALTER TABLE ${this.escapePath(
3322
                tableOrName,
3323
            )} DROP CONSTRAINT "${foreignKeyName}"`,
3324
        )
3325
    }
3326

3327
    /**
3328
     * Escapes a given comment so it's safe to include in a query.
3329
     */
3330
    protected escapeComment(comment?: string) {
3331
        if (!comment) {
90✔
3332
            return "NULL"
8✔
3333
        }
3334

3335
        comment = comment.replace(/'/g, "''").replace(/\u0000/g, "") // Null bytes aren't allowed in comments
82✔
3336

3337
        return `'${comment}'`
82✔
3338
    }
3339

3340
    /**
3341
     * Escapes given table or view path.
3342
     */
3343
    protected escapePath(target: Table | View | string): string {
3344
        const { schema, tableName } = this.driver.parseTableName(target)
39,236✔
3345

3346
        if (schema) {
39,236✔
3347
            return `"${schema}"."${tableName}"`
39,236✔
3348
        }
3349

3350
        return `"${tableName}"`
×
3351
    }
3352

3353
    /**
3354
     * Builds a query for create column.
3355
     */
3356
    protected buildCreateColumnSql(
3357
        column: TableColumn,
3358
        explicitDefault?: boolean,
3359
        explicitNullable?: boolean,
3360
    ) {
3361
        let c =
3362
            `"${column.name}" ` + this.connection.driver.createFullType(column)
26,608✔
3363
        if (column.default !== undefined && column.default !== null) {
26,608✔
3364
            c += " DEFAULT " + column.default
1,204✔
3365
        } else if (explicitDefault) {
25,404✔
3366
            c += " DEFAULT NULL"
2✔
3367
        }
3368
        if (!column.isGenerated) {
26,608✔
3369
            // NOT NULL is not supported with GENERATED
3370
            if (column.isNullable !== true) c += " NOT NULL"
21,448✔
3371
            else if (explicitNullable) c += " NULL"
4,164✔
3372
        }
3373
        if (
26,608✔
3374
            column.isGenerated === true &&
31,768✔
3375
            column.generationStrategy === "increment"
3376
        ) {
3377
            c += " GENERATED ALWAYS AS IDENTITY"
4,904✔
3378
        }
3379
        if (column.comment) {
26,608✔
3380
            c += ` COMMENT ${this.escapeComment(column.comment)}`
78✔
3381
        }
3382

3383
        return c
26,608✔
3384
    }
3385

3386
    /**
3387
     * Change table comment.
3388
     */
3389
    changeTableComment(
3390
        tableOrName: Table | string,
3391
        comment?: string,
3392
    ): Promise<void> {
3393
        throw new TypeORMError(
×
3394
            `spa driver does not support change table comment.`,
3395
        )
3396
    }
3397
}
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