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

typeorm / typeorm / 15805338492

22 Jun 2025 09:51AM UTC coverage: 76.344% (-0.07%) from 76.418%
15805338492

push

github

web-flow
fix: add stricter type-checking and improve event loop handling (#11540)

* refactor: minor type improvements

* chore: add type-checked eslint rules

* fix: enable no-misused-promises

* fix: enable no-floating-promises

* fix: enable await-thenable

* fix: enable require-await

* fix: enable no-misused-new

* refactor: enable no-namespace

* refactor: enable tseslint eslint recommended

* code review

9281 of 12872 branches covered (72.1%)

Branch coverage included in aggregate %.

117 of 210 new or added lines in 18 files covered. (55.71%)

19 existing lines in 9 files now uncovered.

18996 of 24167 relevant lines covered (78.6%)

119161.42 hits per line

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

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

31
/**
32
 * Runs queries on a single postgres database connection.
33
 */
34
export class PostgresQueryRunner
24✔
35
    extends BaseQueryRunner
36
    implements QueryRunner
37
{
38
    // -------------------------------------------------------------------------
39
    // Public Implemented Properties
40
    // -------------------------------------------------------------------------
41

42
    /**
43
     * Database driver used by connection.
44
     */
45
    driver: PostgresDriver
46

47
    // -------------------------------------------------------------------------
48
    // Protected Properties
49
    // -------------------------------------------------------------------------
50

51
    /**
52
     * Promise used to obtain a database connection for a first time.
53
     */
54
    protected databaseConnectionPromise: Promise<any>
55

56
    /**
57
     * Special callback provided by a driver used to release a created connection.
58
     */
59
    protected releaseCallback?: (err: any) => void
60

61
    // -------------------------------------------------------------------------
62
    // Constructor
63
    // -------------------------------------------------------------------------
64

65
    constructor(driver: PostgresDriver, mode: ReplicationMode) {
66
        super()
56,224✔
67
        this.driver = driver
56,224✔
68
        this.connection = driver.connection
56,224✔
69
        this.mode = mode
56,224✔
70
        this.broadcaster = new Broadcaster(this)
56,224✔
71
    }
72

73
    // -------------------------------------------------------------------------
74
    // Public Methods
75
    // -------------------------------------------------------------------------
76

77
    /**
78
     * Creates/uses database connection from the connection pool to perform further operations.
79
     * Returns obtained database connection.
80
     */
81
    connect(): Promise<any> {
82
        if (this.databaseConnection)
299,280✔
83
            return Promise.resolve(this.databaseConnection)
245,436✔
84

85
        if (this.databaseConnectionPromise)
53,844✔
86
            return this.databaseConnectionPromise
240✔
87

88
        if (this.mode === "slave" && this.driver.isReplicated) {
53,604✔
89
            this.databaseConnectionPromise = this.driver
12✔
90
                .obtainSlaveConnection()
91
                .then(([connection, release]: any[]) => {
92
                    this.driver.connectedQueryRunners.push(this)
12✔
93
                    this.databaseConnection = connection
12✔
94

95
                    const onErrorCallback = (err: Error) =>
12✔
96
                        this.releasePostgresConnection(err)
×
97
                    this.releaseCallback = (err?: Error) => {
12✔
98
                        this.databaseConnection.removeListener(
12✔
99
                            "error",
100
                            onErrorCallback,
101
                        )
102
                        release(err)
12✔
103
                    }
104
                    this.databaseConnection.on("error", onErrorCallback)
12✔
105

106
                    return this.databaseConnection
12✔
107
                })
108
        } else {
109
            // master
110
            this.databaseConnectionPromise = this.driver
53,592✔
111
                .obtainMasterConnection()
112
                .then(([connection, release]: any[]) => {
113
                    this.driver.connectedQueryRunners.push(this)
53,588✔
114
                    this.databaseConnection = connection
53,588✔
115

116
                    const onErrorCallback = (err: Error) =>
53,588✔
117
                        this.releasePostgresConnection(err)
×
118
                    this.releaseCallback = (err?: Error) => {
53,588✔
119
                        this.databaseConnection.removeListener(
53,588✔
120
                            "error",
121
                            onErrorCallback,
122
                        )
123
                        release(err)
53,588✔
124
                    }
125
                    this.databaseConnection.on("error", onErrorCallback)
53,588✔
126

127
                    return this.databaseConnection
53,588✔
128
                })
129
        }
130

131
        return this.databaseConnectionPromise
53,604✔
132
    }
133

134
    /**
135
     * Release a connection back to the pool, optionally specifying an Error to release with.
136
     * Per pg-pool documentation this will prevent the pool from re-using the broken connection.
137
     */
138
    private async releasePostgresConnection(err?: Error) {
139
        if (this.isReleased) {
56,224!
UNCOV
140
            return
×
141
        }
142

143
        this.isReleased = true
56,224✔
144
        if (this.releaseCallback) {
56,224✔
145
            this.releaseCallback(err)
53,600✔
146
            this.releaseCallback = undefined
53,600✔
147
        }
148

149
        const index = this.driver.connectedQueryRunners.indexOf(this)
56,224✔
150

151
        if (index !== -1) {
56,224✔
152
            this.driver.connectedQueryRunners.splice(index, 1)
53,600✔
153
        }
154
    }
155

156
    /**
157
     * Releases used database connection.
158
     * You cannot use query runner methods once its released.
159
     */
160
    release(): Promise<void> {
161
        return this.releasePostgresConnection()
56,224✔
162
    }
163

164
    /**
165
     * Starts transaction.
166
     */
167
    async startTransaction(isolationLevel?: IsolationLevel): Promise<void> {
168
        this.isTransactionActive = true
40,476✔
169
        try {
40,476✔
170
            await this.broadcaster.broadcast("BeforeTransactionStart")
40,476✔
171
        } catch (err) {
172
            this.isTransactionActive = false
×
173
            throw err
×
174
        }
175

176
        if (this.transactionDepth === 0) {
40,476✔
177
            await this.query("START TRANSACTION")
40,412✔
178
            if (isolationLevel) {
40,412✔
179
                await this.query(
24✔
180
                    "SET TRANSACTION ISOLATION LEVEL " + isolationLevel,
181
                )
182
            }
183
        } else {
184
            await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
64✔
185
        }
186
        this.transactionDepth += 1
40,476✔
187

188
        await this.broadcaster.broadcast("AfterTransactionStart")
40,476✔
189
    }
190

191
    /**
192
     * Commits transaction.
193
     * Error will be thrown if transaction was not started.
194
     */
195
    async commitTransaction(): Promise<void> {
196
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
40,332!
197

198
        await this.broadcaster.broadcast("BeforeTransactionCommit")
40,332✔
199

200
        if (this.transactionDepth > 1) {
40,332✔
201
            await this.query(
40✔
202
                `RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
203
            )
204
        } else {
205
            await this.query("COMMIT")
40,292✔
206
            this.isTransactionActive = false
40,288✔
207
        }
208
        this.transactionDepth -= 1
40,328✔
209

210
        await this.broadcaster.broadcast("AfterTransactionCommit")
40,328✔
211
    }
212

213
    /**
214
     * Rollbacks transaction.
215
     * Error will be thrown if transaction was not started.
216
     */
217
    async rollbackTransaction(): Promise<void> {
218
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
148!
219

220
        await this.broadcaster.broadcast("BeforeTransactionRollback")
148✔
221

222
        if (this.transactionDepth > 1) {
148✔
223
            await this.query(
24✔
224
                `ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
225
            )
226
        } else {
227
            await this.query("ROLLBACK")
124✔
228
            this.isTransactionActive = false
124✔
229
        }
230
        this.transactionDepth -= 1
148✔
231

232
        await this.broadcaster.broadcast("AfterTransactionRollback")
148✔
233
    }
234

235
    /**
236
     * Executes a given SQL query.
237
     */
238
    async query(
239
        query: string,
240
        parameters?: any[],
241
        useStructuredResult: boolean = false,
234,020✔
242
    ): Promise<any> {
243
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
299,264!
244

245
        const databaseConnection = await this.connect()
299,264✔
246

247
        this.driver.connection.logger.logQuery(query, parameters, this)
299,260✔
248
        await this.broadcaster.broadcast("BeforeQuery", query, parameters)
299,260✔
249

250
        const broadcasterResult = new BroadcasterResult()
299,260✔
251

252
        try {
299,260✔
253
            const queryStartTime = Date.now()
299,260✔
254
            const raw = await databaseConnection.query(query, parameters)
299,260✔
255
            // log slow queries if maxQueryExecution time is set
256
            const maxQueryExecutionTime =
257
                this.driver.options.maxQueryExecutionTime
299,196✔
258
            const queryEndTime = Date.now()
299,196✔
259
            const queryExecutionTime = queryEndTime - queryStartTime
299,196✔
260

261
            this.broadcaster.broadcastAfterQueryEvent(
299,196✔
262
                broadcasterResult,
263
                query,
264
                parameters,
265
                true,
266
                queryExecutionTime,
267
                raw,
268
                undefined,
269
            )
270

271
            if (
299,196!
272
                maxQueryExecutionTime &&
299,196!
273
                queryExecutionTime > maxQueryExecutionTime
274
            )
275
                this.driver.connection.logger.logQuerySlow(
×
276
                    queryExecutionTime,
277
                    query,
278
                    parameters,
279
                    this,
280
                )
281

282
            const result = new QueryResult()
299,196✔
283
            if (raw) {
299,196✔
284
                if (raw.hasOwnProperty("rows")) {
299,196✔
285
                    result.records = raw.rows
299,048✔
286
                }
287

288
                if (raw.hasOwnProperty("rowCount")) {
299,196✔
289
                    result.affected = raw.rowCount
299,048✔
290
                }
291

292
                switch (raw.command) {
299,196✔
293
                    case "DELETE":
294
                    case "UPDATE":
295
                        // for UPDATE and DELETE query additionally return number of affected rows
296
                        result.raw = [raw.rows, raw.rowCount]
4,284✔
297
                        break
4,284✔
298
                    default:
299
                        result.raw = raw.rows
294,912✔
300
                }
301

302
                if (!useStructuredResult) {
299,196✔
303
                    return result.raw
234,012✔
304
                }
305
            }
306

307
            return result
65,184✔
308
        } catch (err) {
309
            this.driver.connection.logger.logQueryError(
64✔
310
                err,
311
                query,
312
                parameters,
313
                this,
314
            )
315
            this.broadcaster.broadcastAfterQueryEvent(
64✔
316
                broadcasterResult,
317
                query,
318
                parameters,
319
                false,
320
                undefined,
321
                undefined,
322
                err,
323
            )
324

325
            throw new QueryFailedError(query, parameters, err)
64✔
326
        } finally {
327
            await broadcasterResult.wait()
299,260✔
328
        }
329
    }
330

331
    /**
332
     * Returns raw data stream.
333
     */
334
    async stream(
335
        query: string,
336
        parameters?: any[],
337
        onEnd?: Function,
338
        onError?: Function,
339
    ): Promise<ReadStream> {
340
        const QueryStream = this.driver.loadStreamDependency()
8✔
341
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
8!
342

343
        const databaseConnection = await this.connect()
8✔
344
        this.driver.connection.logger.logQuery(query, parameters, this)
8✔
345
        const stream = databaseConnection.query(
8✔
346
            new QueryStream(query, parameters),
347
        )
348
        if (onEnd) stream.on("end", onEnd)
8✔
349
        if (onError) stream.on("error", onError)
8✔
350

351
        return stream
8✔
352
    }
353

354
    /**
355
     * Returns all available database names including system databases.
356
     */
357
    async getDatabases(): Promise<string[]> {
358
        return Promise.resolve([])
×
359
    }
360

361
    /**
362
     * Returns all available schema names including system schemas.
363
     * If database parameter specified, returns schemas of that database.
364
     */
365
    async getSchemas(database?: string): Promise<string[]> {
366
        return Promise.resolve([])
×
367
    }
368

369
    /**
370
     * Checks if database with the given name exist.
371
     */
372
    async hasDatabase(database: string): Promise<boolean> {
373
        const result = await this.query(
20✔
374
            `SELECT * FROM pg_database WHERE datname='${database}';`,
375
        )
376
        return result.length ? true : false
20✔
377
    }
378

379
    /**
380
     * Loads currently using database
381
     */
382
    async getCurrentDatabase(): Promise<string> {
383
        const query = await this.query(`SELECT * FROM current_database()`)
9,024✔
384
        return query[0]["current_database"]
9,024✔
385
    }
386

387
    /**
388
     * Checks if schema with the given name exist.
389
     */
390
    async hasSchema(schema: string): Promise<boolean> {
391
        const result = await this.query(
12✔
392
            `SELECT * FROM "information_schema"."schemata" WHERE "schema_name" = '${schema}'`,
393
        )
394
        return result.length ? true : false
12✔
395
    }
396

397
    /**
398
     * Loads currently using database schema
399
     */
400
    async getCurrentSchema(): Promise<string> {
401
        const query = await this.query(`SELECT * FROM current_schema()`)
12,044✔
402
        return query[0]["current_schema"]
12,044✔
403
    }
404

405
    /**
406
     * Checks if table with the given name exist in the database.
407
     */
408
    async hasTable(tableOrName: Table | string): Promise<boolean> {
409
        const parsedTableName = this.driver.parseTableName(tableOrName)
7,784✔
410

411
        if (!parsedTableName.schema) {
7,784!
412
            parsedTableName.schema = await this.getCurrentSchema()
×
413
        }
414

415
        const sql = `SELECT * FROM "information_schema"."tables" WHERE "table_schema" = '${parsedTableName.schema}' AND "table_name" = '${parsedTableName.tableName}'`
7,784✔
416
        const result = await this.query(sql)
7,784✔
417
        return result.length ? true : false
7,784✔
418
    }
419

420
    /**
421
     * Checks if column with the given name exist in the given table.
422
     */
423
    async hasColumn(
424
        tableOrName: Table | string,
425
        columnName: string,
426
    ): Promise<boolean> {
427
        const parsedTableName = this.driver.parseTableName(tableOrName)
16✔
428

429
        if (!parsedTableName.schema) {
16!
430
            parsedTableName.schema = await this.getCurrentSchema()
×
431
        }
432

433
        const sql = `SELECT * FROM "information_schema"."columns" WHERE "table_schema" = '${parsedTableName.schema}' AND "table_name" = '${parsedTableName.tableName}' AND "column_name" = '${columnName}'`
16✔
434
        const result = await this.query(sql)
16✔
435
        return result.length ? true : false
16✔
436
    }
437

438
    /**
439
     * Creates a new database.
440
     * Note: Postgres does not support database creation inside a transaction block.
441
     */
442
    async createDatabase(
443
        database: string,
444
        ifNotExist?: boolean,
445
    ): Promise<void> {
446
        if (ifNotExist) {
8✔
447
            const databaseAlreadyExists = await this.hasDatabase(database)
8✔
448

449
            if (databaseAlreadyExists) return Promise.resolve()
8!
450
        }
451

452
        const up = `CREATE DATABASE "${database}"`
8✔
453
        const down = `DROP DATABASE "${database}"`
8✔
454
        await this.executeQueries(new Query(up), new Query(down))
8✔
455
    }
456

457
    /**
458
     * Drops database.
459
     * Note: Postgres does not support database dropping inside a transaction block.
460
     */
461
    async dropDatabase(database: string, ifExist?: boolean): Promise<void> {
462
        const up = ifExist
12✔
463
            ? `DROP DATABASE IF EXISTS "${database}"`
464
            : `DROP DATABASE "${database}"`
465
        const down = `CREATE DATABASE "${database}"`
12✔
466
        await this.executeQueries(new Query(up), new Query(down))
12✔
467
    }
468

469
    /**
470
     * Creates a new table schema.
471
     */
472
    async createSchema(
473
        schemaPath: string,
474
        ifNotExist?: boolean,
475
    ): Promise<void> {
476
        const schema =
477
            schemaPath.indexOf(".") === -1
156!
478
                ? schemaPath
479
                : schemaPath.split(".")[1]
480

481
        const up = ifNotExist
156✔
482
            ? `CREATE SCHEMA IF NOT EXISTS "${schema}"`
483
            : `CREATE SCHEMA "${schema}"`
484
        const down = `DROP SCHEMA "${schema}" CASCADE`
156✔
485
        await this.executeQueries(new Query(up), new Query(down))
156✔
486
    }
487

488
    /**
489
     * Drops table schema.
490
     */
491
    async dropSchema(
492
        schemaPath: string,
493
        ifExist?: boolean,
494
        isCascade?: boolean,
495
    ): Promise<void> {
496
        const schema =
497
            schemaPath.indexOf(".") === -1
100!
498
                ? schemaPath
499
                : schemaPath.split(".")[1]
500

501
        const up = ifExist
100✔
502
            ? `DROP SCHEMA IF EXISTS "${schema}" ${isCascade ? "CASCADE" : ""}`
96!
503
            : `DROP SCHEMA "${schema}" ${isCascade ? "CASCADE" : ""}`
4!
504
        const down = `CREATE SCHEMA "${schema}"`
100✔
505
        await this.executeQueries(new Query(up), new Query(down))
100✔
506
    }
507

508
    /**
509
     * Creates a new table.
510
     */
511
    async createTable(
512
        table: Table,
513
        ifNotExist: boolean = false,
184✔
514
        createForeignKeys: boolean = true,
372✔
515
        createIndices: boolean = true,
21,828✔
516
    ): Promise<void> {
517
        if (ifNotExist) {
21,828✔
518
            const isTableExist = await this.hasTable(table)
188✔
519
            if (isTableExist) return Promise.resolve()
188✔
520
        }
521
        const upQueries: Query[] = []
21,804✔
522
        const downQueries: Query[] = []
21,804✔
523

524
        // if table have column with ENUM type, we must create this type in postgres.
525
        const enumColumns = table.columns.filter(
21,804✔
526
            (column) => column.type === "enum" || column.type === "simple-enum",
70,908✔
527
        )
528
        const createdEnumTypes: string[] = []
21,804✔
529
        for (const column of enumColumns) {
21,804✔
530
            // TODO: Should also check if values of existing type matches expected ones
531
            const hasEnum = await this.hasEnumType(table, column)
904✔
532
            const enumName = this.buildEnumName(table, column)
904✔
533

534
            // if enum with the same "enumName" is defined more then once, me must prevent double creation
535
            if (!hasEnum && createdEnumTypes.indexOf(enumName) === -1) {
904✔
536
                createdEnumTypes.push(enumName)
860✔
537
                upQueries.push(this.createEnumTypeSql(table, column, enumName))
860✔
538
                downQueries.push(this.dropEnumTypeSql(table, column, enumName))
860✔
539
            }
540
        }
541

542
        // if table have column with generated type, we must add the expression to the metadata table
543
        const generatedColumns = table.columns.filter(
21,804✔
544
            (column) =>
545
                column.generatedType === "STORED" && column.asExpression,
70,908✔
546
        )
547
        for (const column of generatedColumns) {
21,804✔
548
            const tableNameWithSchema = (
76✔
549
                await this.getTableNameWithSchema(table.name)
550
            ).split(".")
551
            const tableName = tableNameWithSchema[1]
76✔
552
            const schema = tableNameWithSchema[0]
76✔
553

554
            const insertQuery = this.insertTypeormMetadataSql({
76✔
555
                database: this.driver.database,
556
                schema,
557
                table: tableName,
558
                type: MetadataTableType.GENERATED_COLUMN,
559
                name: column.name,
560
                value: column.asExpression,
561
            })
562

563
            const deleteQuery = this.deleteTypeormMetadataSql({
76✔
564
                database: this.driver.database,
565
                schema,
566
                table: tableName,
567
                type: MetadataTableType.GENERATED_COLUMN,
568
                name: column.name,
569
            })
570

571
            upQueries.push(insertQuery)
76✔
572
            downQueries.push(deleteQuery)
76✔
573
        }
574

575
        upQueries.push(this.createTableSql(table, createForeignKeys))
21,804✔
576
        downQueries.push(this.dropTableSql(table))
21,804✔
577

578
        // if createForeignKeys is true, we must drop created foreign keys in down query.
579
        // createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
580
        if (createForeignKeys)
21,804✔
581
            table.foreignKeys.forEach((foreignKey) =>
348✔
582
                downQueries.push(this.dropForeignKeySql(table, foreignKey)),
12✔
583
            )
584

585
        if (createIndices) {
21,804✔
586
            table.indices.forEach((index) => {
21,804✔
587
                // new index may be passed without name. In this case we generate index name manually.
588
                if (!index.name)
7,884✔
589
                    index.name = this.connection.namingStrategy.indexName(
12✔
590
                        table,
591
                        index.columnNames,
592
                        index.where,
593
                    )
594
                upQueries.push(this.createIndexSql(table, index))
7,884✔
595
                downQueries.push(this.dropIndexSql(table, index))
7,884✔
596
            })
597
        }
598

599
        if (table.comment) {
21,804✔
600
            upQueries.push(
12✔
601
                new Query(
602
                    "COMMENT ON TABLE " +
603
                        this.escapePath(table) +
604
                        " IS '" +
605
                        table.comment +
606
                        "'",
607
                ),
608
            )
609
            downQueries.push(
12✔
610
                new Query(
611
                    "COMMENT ON TABLE " + this.escapePath(table) + " IS NULL",
612
                ),
613
            )
614
        }
615

616
        await this.executeQueries(upQueries, downQueries)
21,804✔
617
    }
618

619
    /**
620
     * Drops the table.
621
     */
622
    async dropTable(
623
        target: Table | string,
624
        ifExist?: boolean,
625
        dropForeignKeys: boolean = true,
28✔
626
        dropIndices: boolean = true,
44✔
627
    ): Promise<void> {
628
        // It needs because if table does not exist and dropForeignKeys or dropIndices is true, we don't need
629
        // to perform drop queries for foreign keys and indices.
630
        if (ifExist) {
44✔
631
            const isTableExist = await this.hasTable(target)
16✔
632
            if (!isTableExist) return Promise.resolve()
16!
633
        }
634

635
        // if dropTable called with dropForeignKeys = true, we must create foreign keys in down query.
636
        const createForeignKeys: boolean = dropForeignKeys
44✔
637
        const tablePath = this.getTablePath(target)
44✔
638
        const table = await this.getCachedTable(tablePath)
44✔
639
        const upQueries: Query[] = []
44✔
640
        const downQueries: Query[] = []
44✔
641

642
        if (dropIndices) {
44✔
643
            table.indices.forEach((index) => {
44✔
644
                upQueries.push(this.dropIndexSql(table, index))
4✔
645
                downQueries.push(this.createIndexSql(table, index))
4✔
646
            })
647
        }
648

649
        if (dropForeignKeys)
44✔
650
            table.foreignKeys.forEach((foreignKey) =>
28✔
651
                upQueries.push(this.dropForeignKeySql(table, foreignKey)),
8✔
652
            )
653

654
        upQueries.push(this.dropTableSql(table))
44✔
655
        downQueries.push(this.createTableSql(table, createForeignKeys))
44✔
656

657
        // if table had columns with generated type, we must remove the expression from the metadata table
658
        const generatedColumns = table.columns.filter(
44✔
659
            (column) => column.generatedType && column.asExpression,
160✔
660
        )
661
        for (const column of generatedColumns) {
44✔
662
            const tableNameWithSchema = (
12✔
663
                await this.getTableNameWithSchema(table.name)
664
            ).split(".")
665
            const tableName = tableNameWithSchema[1]
12✔
666
            const schema = tableNameWithSchema[0]
12✔
667

668
            const deleteQuery = this.deleteTypeormMetadataSql({
12✔
669
                database: this.driver.database,
670
                schema,
671
                table: tableName,
672
                type: MetadataTableType.GENERATED_COLUMN,
673
                name: column.name,
674
            })
675

676
            const insertQuery = this.insertTypeormMetadataSql({
12✔
677
                database: this.driver.database,
678
                schema,
679
                table: tableName,
680
                type: MetadataTableType.GENERATED_COLUMN,
681
                name: column.name,
682
                value: column.asExpression,
683
            })
684

685
            upQueries.push(deleteQuery)
12✔
686
            downQueries.push(insertQuery)
12✔
687
        }
688

689
        await this.executeQueries(upQueries, downQueries)
44✔
690
    }
691

692
    /**
693
     * Creates a new view.
694
     */
695
    async createView(
696
        view: View,
697
        syncWithMetadata: boolean = false,
4✔
698
    ): Promise<void> {
699
        const upQueries: Query[] = []
168✔
700
        const downQueries: Query[] = []
168✔
701
        upQueries.push(this.createViewSql(view))
168✔
702
        if (syncWithMetadata)
168✔
703
            upQueries.push(await this.insertViewDefinitionSql(view))
164✔
704
        downQueries.push(this.dropViewSql(view))
168✔
705
        if (syncWithMetadata)
168✔
706
            downQueries.push(await this.deleteViewDefinitionSql(view))
164✔
707
        await this.executeQueries(upQueries, downQueries)
168✔
708
    }
709

710
    /**
711
     * Drops the view.
712
     */
713
    async dropView(target: View | string): Promise<void> {
714
        const viewName = InstanceChecker.isView(target) ? target.name : target
32!
715
        const view = await this.getCachedView(viewName)
32✔
716

717
        const upQueries: Query[] = []
32✔
718
        const downQueries: Query[] = []
32✔
719
        upQueries.push(await this.deleteViewDefinitionSql(view))
32✔
720
        upQueries.push(this.dropViewSql(view))
32✔
721
        downQueries.push(await this.insertViewDefinitionSql(view))
32✔
722
        downQueries.push(this.createViewSql(view))
32✔
723
        await this.executeQueries(upQueries, downQueries)
32✔
724
    }
725

726
    /**
727
     * Renames the given table.
728
     */
729
    async renameTable(
730
        oldTableOrName: Table | string,
731
        newTableName: string,
732
    ): Promise<void> {
733
        const upQueries: Query[] = []
76✔
734
        const downQueries: Query[] = []
76✔
735
        const oldTable = InstanceChecker.isTable(oldTableOrName)
76✔
736
            ? oldTableOrName
737
            : await this.getCachedTable(oldTableOrName)
738
        const newTable = oldTable.clone()
76✔
739

740
        const { schema: schemaName, tableName: oldTableName } =
741
            this.driver.parseTableName(oldTable)
76✔
742

743
        newTable.name = schemaName
76!
744
            ? `${schemaName}.${newTableName}`
745
            : newTableName
746

747
        upQueries.push(
76✔
748
            new Query(
749
                `ALTER TABLE ${this.escapePath(
750
                    oldTable,
751
                )} RENAME TO "${newTableName}"`,
752
            ),
753
        )
754
        downQueries.push(
76✔
755
            new Query(
756
                `ALTER TABLE ${this.escapePath(
757
                    newTable,
758
                )} RENAME TO "${oldTableName}"`,
759
            ),
760
        )
761

762
        // rename column primary key constraint if it has default constraint name
763
        if (
76✔
764
            newTable.primaryColumns.length > 0 &&
152✔
765
            !newTable.primaryColumns[0].primaryKeyConstraintName
766
        ) {
767
            const columnNames = newTable.primaryColumns.map(
60✔
768
                (column) => column.name,
68✔
769
            )
770

771
            const oldPkName = this.connection.namingStrategy.primaryKeyName(
60✔
772
                oldTable,
773
                columnNames,
774
            )
775

776
            const newPkName = this.connection.namingStrategy.primaryKeyName(
60✔
777
                newTable,
778
                columnNames,
779
            )
780

781
            upQueries.push(
60✔
782
                new Query(
783
                    `ALTER TABLE ${this.escapePath(
784
                        newTable,
785
                    )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
786
                ),
787
            )
788
            downQueries.push(
60✔
789
                new Query(
790
                    `ALTER TABLE ${this.escapePath(
791
                        newTable,
792
                    )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
793
                ),
794
            )
795
        }
796

797
        // rename sequences
798
        newTable.columns.map((col) => {
76✔
799
            if (col.isGenerated && col.generationStrategy === "increment") {
192✔
800
                const sequencePath = this.buildSequencePath(oldTable, col.name)
52✔
801
                const sequenceName = this.buildSequenceName(oldTable, col.name)
52✔
802

803
                const newSequencePath = this.buildSequencePath(
52✔
804
                    newTable,
805
                    col.name,
806
                )
807
                const newSequenceName = this.buildSequenceName(
52✔
808
                    newTable,
809
                    col.name,
810
                )
811

812
                const up = `ALTER SEQUENCE ${this.escapePath(
52✔
813
                    sequencePath,
814
                )} RENAME TO "${newSequenceName}"`
815
                const down = `ALTER SEQUENCE ${this.escapePath(
52✔
816
                    newSequencePath,
817
                )} RENAME TO "${sequenceName}"`
818

819
                upQueries.push(new Query(up))
52✔
820
                downQueries.push(new Query(down))
52✔
821
            }
822
        })
823

824
        // rename unique constraints
825
        newTable.uniques.forEach((unique) => {
76✔
826
            const oldUniqueName =
827
                this.connection.namingStrategy.uniqueConstraintName(
28✔
828
                    oldTable,
829
                    unique.columnNames,
830
                )
831

832
            // Skip renaming if Unique has user defined constraint name
833
            if (unique.name !== oldUniqueName) return
28✔
834

835
            // build new constraint name
836
            const newUniqueName =
837
                this.connection.namingStrategy.uniqueConstraintName(
12✔
838
                    newTable,
839
                    unique.columnNames,
840
                )
841

842
            // build queries
843
            upQueries.push(
12✔
844
                new Query(
845
                    `ALTER TABLE ${this.escapePath(
846
                        newTable,
847
                    )} RENAME CONSTRAINT "${
848
                        unique.name
849
                    }" TO "${newUniqueName}"`,
850
                ),
851
            )
852
            downQueries.push(
12✔
853
                new Query(
854
                    `ALTER TABLE ${this.escapePath(
855
                        newTable,
856
                    )} RENAME CONSTRAINT "${newUniqueName}" TO "${
857
                        unique.name
858
                    }"`,
859
                ),
860
            )
861

862
            // replace constraint name
863
            unique.name = newUniqueName
12✔
864
        })
865

866
        // rename index constraints
867
        newTable.indices.forEach((index) => {
76✔
868
            const oldIndexName = this.connection.namingStrategy.indexName(
36✔
869
                oldTable,
870
                index.columnNames,
871
                index.where,
872
            )
873

874
            // Skip renaming if Index has user defined constraint name
875
            if (index.name !== oldIndexName) return
36✔
876

877
            // build new constraint name
878
            const { schema } = this.driver.parseTableName(newTable)
20✔
879
            const newIndexName = this.connection.namingStrategy.indexName(
20✔
880
                newTable,
881
                index.columnNames,
882
                index.where,
883
            )
884

885
            // build queries
886
            const up = schema
20!
887
                ? `ALTER INDEX "${schema}"."${index.name}" RENAME TO "${newIndexName}"`
888
                : `ALTER INDEX "${index.name}" RENAME TO "${newIndexName}"`
889
            const down = schema
20!
890
                ? `ALTER INDEX "${schema}"."${newIndexName}" RENAME TO "${index.name}"`
891
                : `ALTER INDEX "${newIndexName}" RENAME TO "${index.name}"`
892
            upQueries.push(new Query(up))
20✔
893
            downQueries.push(new Query(down))
20✔
894

895
            // replace constraint name
896
            index.name = newIndexName
20✔
897
        })
898

899
        // rename foreign key constraints
900
        newTable.foreignKeys.forEach((foreignKey) => {
76✔
901
            const oldForeignKeyName =
902
                this.connection.namingStrategy.foreignKeyName(
36✔
903
                    oldTable,
904
                    foreignKey.columnNames,
905
                    this.getTablePath(foreignKey),
906
                    foreignKey.referencedColumnNames,
907
                )
908

909
            // Skip renaming if foreign key has user defined constraint name
910
            if (foreignKey.name !== oldForeignKeyName) return
36✔
911

912
            // build new constraint name
913
            const newForeignKeyName =
914
                this.connection.namingStrategy.foreignKeyName(
4✔
915
                    newTable,
916
                    foreignKey.columnNames,
917
                    this.getTablePath(foreignKey),
918
                    foreignKey.referencedColumnNames,
919
                )
920

921
            // build queries
922
            upQueries.push(
4✔
923
                new Query(
924
                    `ALTER TABLE ${this.escapePath(
925
                        newTable,
926
                    )} RENAME CONSTRAINT "${
927
                        foreignKey.name
928
                    }" TO "${newForeignKeyName}"`,
929
                ),
930
            )
931
            downQueries.push(
4✔
932
                new Query(
933
                    `ALTER TABLE ${this.escapePath(
934
                        newTable,
935
                    )} RENAME CONSTRAINT "${newForeignKeyName}" TO "${
936
                        foreignKey.name
937
                    }"`,
938
                ),
939
            )
940

941
            // replace constraint name
942
            foreignKey.name = newForeignKeyName
4✔
943
        })
944

945
        // rename ENUM types
946
        const enumColumns = newTable.columns.filter(
76✔
947
            (column) => column.type === "enum" || column.type === "simple-enum",
192✔
948
        )
949
        for (const column of enumColumns) {
76✔
950
            // skip renaming for user-defined enum name
951
            if (column.enumName) continue
20✔
952

953
            const oldEnumType = await this.getUserDefinedTypeName(
16✔
954
                oldTable,
955
                column,
956
            )
957
            upQueries.push(
16✔
958
                new Query(
959
                    `ALTER TYPE "${oldEnumType.schema}"."${
960
                        oldEnumType.name
961
                    }" RENAME TO ${this.buildEnumName(
962
                        newTable,
963
                        column,
964
                        false,
965
                    )}`,
966
                ),
967
            )
968
            downQueries.push(
16✔
969
                new Query(
970
                    `ALTER TYPE ${this.buildEnumName(
971
                        newTable,
972
                        column,
973
                    )} RENAME TO "${oldEnumType.name}"`,
974
                ),
975
            )
976
        }
977
        await this.executeQueries(upQueries, downQueries)
76✔
978
    }
979

980
    /**
981
     * Creates a new column from the column in the table.
982
     */
983
    async addColumn(
984
        tableOrName: Table | string,
985
        column: TableColumn,
986
    ): Promise<void> {
987
        const table = InstanceChecker.isTable(tableOrName)
152✔
988
            ? tableOrName
989
            : await this.getCachedTable(tableOrName)
990
        const clonedTable = table.clone()
152✔
991
        const upQueries: Query[] = []
152✔
992
        const downQueries: Query[] = []
152✔
993

994
        if (column.type === "enum" || column.type === "simple-enum") {
152✔
995
            const hasEnum = await this.hasEnumType(table, column)
20✔
996
            if (!hasEnum) {
20✔
997
                upQueries.push(this.createEnumTypeSql(table, column))
20✔
998
                downQueries.push(this.dropEnumTypeSql(table, column))
20✔
999
            }
1000
        }
1001

1002
        upQueries.push(
152✔
1003
            new Query(
1004
                `ALTER TABLE ${this.escapePath(
1005
                    table,
1006
                )} ADD ${this.buildCreateColumnSql(table, column)}`,
1007
            ),
1008
        )
1009
        downQueries.push(
152✔
1010
            new Query(
1011
                `ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${
1012
                    column.name
1013
                }"`,
1014
            ),
1015
        )
1016

1017
        // create or update primary key constraint
1018
        if (column.isPrimary) {
152✔
1019
            const primaryColumns = clonedTable.primaryColumns
12✔
1020
            // if table already have primary key, me must drop it and recreate again
1021
            if (primaryColumns.length > 0) {
12✔
1022
                const pkName = primaryColumns[0].primaryKeyConstraintName
4!
1023
                    ? primaryColumns[0].primaryKeyConstraintName
1024
                    : this.connection.namingStrategy.primaryKeyName(
1025
                          clonedTable,
1026
                          primaryColumns.map((column) => column.name),
4✔
1027
                      )
1028

1029
                const columnNames = primaryColumns
4✔
1030
                    .map((column) => `"${column.name}"`)
4✔
1031
                    .join(", ")
1032

1033
                upQueries.push(
4✔
1034
                    new Query(
1035
                        `ALTER TABLE ${this.escapePath(
1036
                            table,
1037
                        )} DROP CONSTRAINT "${pkName}"`,
1038
                    ),
1039
                )
1040
                downQueries.push(
4✔
1041
                    new Query(
1042
                        `ALTER TABLE ${this.escapePath(
1043
                            table,
1044
                        )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1045
                    ),
1046
                )
1047
            }
1048

1049
            primaryColumns.push(column)
12✔
1050
            const pkName = primaryColumns[0].primaryKeyConstraintName
12!
1051
                ? primaryColumns[0].primaryKeyConstraintName
1052
                : this.connection.namingStrategy.primaryKeyName(
1053
                      clonedTable,
1054
                      primaryColumns.map((column) => column.name),
16✔
1055
                  )
1056

1057
            const columnNames = primaryColumns
12✔
1058
                .map((column) => `"${column.name}"`)
16✔
1059
                .join(", ")
1060

1061
            upQueries.push(
12✔
1062
                new Query(
1063
                    `ALTER TABLE ${this.escapePath(
1064
                        table,
1065
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1066
                ),
1067
            )
1068
            downQueries.push(
12✔
1069
                new Query(
1070
                    `ALTER TABLE ${this.escapePath(
1071
                        table,
1072
                    )} DROP CONSTRAINT "${pkName}"`,
1073
                ),
1074
            )
1075
        }
1076

1077
        // create column index
1078
        const columnIndex = clonedTable.indices.find(
152✔
1079
            (index) =>
1080
                index.columnNames.length === 1 &&
4✔
1081
                index.columnNames[0] === column.name,
1082
        )
1083
        if (columnIndex) {
152!
1084
            upQueries.push(this.createIndexSql(table, columnIndex))
×
1085
            downQueries.push(this.dropIndexSql(table, columnIndex))
×
1086
        }
1087

1088
        // create unique constraint
1089
        if (column.isUnique) {
152✔
1090
            const uniqueConstraint = new TableUnique({
12✔
1091
                name: this.connection.namingStrategy.uniqueConstraintName(
1092
                    table,
1093
                    [column.name],
1094
                ),
1095
                columnNames: [column.name],
1096
            })
1097
            clonedTable.uniques.push(uniqueConstraint)
12✔
1098
            upQueries.push(
12✔
1099
                new Query(
1100
                    `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
1101
                        uniqueConstraint.name
1102
                    }" UNIQUE ("${column.name}")`,
1103
                ),
1104
            )
1105
            downQueries.push(
12✔
1106
                new Query(
1107
                    `ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${
1108
                        uniqueConstraint.name
1109
                    }"`,
1110
                ),
1111
            )
1112
        }
1113

1114
        if (column.generatedType === "STORED" && column.asExpression) {
152✔
1115
            const tableNameWithSchema = (
24✔
1116
                await this.getTableNameWithSchema(table.name)
1117
            ).split(".")
1118
            const tableName = tableNameWithSchema[1]
24✔
1119
            const schema = tableNameWithSchema[0]
24✔
1120

1121
            const insertQuery = this.insertTypeormMetadataSql({
24✔
1122
                database: this.driver.database,
1123
                schema,
1124
                table: tableName,
1125
                type: MetadataTableType.GENERATED_COLUMN,
1126
                name: column.name,
1127
                value: column.asExpression,
1128
            })
1129

1130
            const deleteQuery = this.deleteTypeormMetadataSql({
24✔
1131
                database: this.driver.database,
1132
                schema,
1133
                table: tableName,
1134
                type: MetadataTableType.GENERATED_COLUMN,
1135
                name: column.name,
1136
            })
1137

1138
            upQueries.push(insertQuery)
24✔
1139
            downQueries.push(deleteQuery)
24✔
1140
        }
1141

1142
        // create column's comment
1143
        if (column.comment) {
152!
1144
            upQueries.push(
×
1145
                new Query(
1146
                    `COMMENT ON COLUMN ${this.escapePath(table)}."${
1147
                        column.name
1148
                    }" IS ${this.escapeComment(column.comment)}`,
1149
                ),
1150
            )
1151
            downQueries.push(
×
1152
                new Query(
1153
                    `COMMENT ON COLUMN ${this.escapePath(table)}."${
1154
                        column.name
1155
                    }" IS ${this.escapeComment(column.comment)}`,
1156
                ),
1157
            )
1158
        }
1159

1160
        await this.executeQueries(upQueries, downQueries)
152✔
1161

1162
        clonedTable.addColumn(column)
148✔
1163
        this.replaceCachedTable(table, clonedTable)
148✔
1164
    }
1165

1166
    /**
1167
     * Creates a new columns from the column in the table.
1168
     */
1169
    async addColumns(
1170
        tableOrName: Table | string,
1171
        columns: TableColumn[],
1172
    ): Promise<void> {
1173
        for (const column of columns) {
16✔
1174
            await this.addColumn(tableOrName, column)
20✔
1175
        }
1176
    }
1177

1178
    /**
1179
     * Renames column in the given table.
1180
     */
1181
    async renameColumn(
1182
        tableOrName: Table | string,
1183
        oldTableColumnOrName: TableColumn | string,
1184
        newTableColumnOrName: TableColumn | string,
1185
    ): Promise<void> {
1186
        const table = InstanceChecker.isTable(tableOrName)
64✔
1187
            ? tableOrName
1188
            : await this.getCachedTable(tableOrName)
1189
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
64✔
1190
            ? oldTableColumnOrName
1191
            : table.columns.find((c) => c.name === oldTableColumnOrName)
44✔
1192
        if (!oldColumn)
64!
1193
            throw new TypeORMError(
×
1194
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
1195
            )
1196

1197
        let newColumn
1198
        if (InstanceChecker.isTableColumn(newTableColumnOrName)) {
64✔
1199
            newColumn = newTableColumnOrName
44✔
1200
        } else {
1201
            newColumn = oldColumn.clone()
20✔
1202
            newColumn.name = newTableColumnOrName
20✔
1203
        }
1204

1205
        return this.changeColumn(table, oldColumn, newColumn)
64✔
1206
    }
1207

1208
    /**
1209
     * Changes a column in the table.
1210
     */
1211
    async changeColumn(
1212
        tableOrName: Table | string,
1213
        oldTableColumnOrName: TableColumn | string,
1214
        newColumn: TableColumn,
1215
    ): Promise<void> {
1216
        const table = InstanceChecker.isTable(tableOrName)
488!
1217
            ? tableOrName
1218
            : await this.getCachedTable(tableOrName)
1219
        let clonedTable = table.clone()
488✔
1220
        const upQueries: Query[] = []
488✔
1221
        const downQueries: Query[] = []
488✔
1222
        let defaultValueChanged = false
488✔
1223

1224
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
488!
1225
            ? oldTableColumnOrName
1226
            : table.columns.find(
1227
                  (column) => column.name === oldTableColumnOrName,
×
1228
              )
1229
        if (!oldColumn)
488!
1230
            throw new TypeORMError(
×
1231
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
1232
            )
1233

1234
        if (
488✔
1235
            oldColumn.type !== newColumn.type ||
2,536✔
1236
            oldColumn.length !== newColumn.length ||
1237
            newColumn.isArray !== oldColumn.isArray ||
1238
            (!oldColumn.generatedType &&
1239
                newColumn.generatedType === "STORED") ||
1240
            (oldColumn.asExpression !== newColumn.asExpression &&
1241
                newColumn.generatedType === "STORED")
1242
        ) {
1243
            // To avoid data conversion, we just recreate column
1244
            await this.dropColumn(table, oldColumn)
100✔
1245
            await this.addColumn(table, newColumn)
100✔
1246

1247
            // update cloned table
1248
            clonedTable = table.clone()
100✔
1249
        } else {
1250
            if (oldColumn.name !== newColumn.name) {
388✔
1251
                // rename column
1252
                upQueries.push(
100✔
1253
                    new Query(
1254
                        `ALTER TABLE ${this.escapePath(table)} RENAME COLUMN "${
1255
                            oldColumn.name
1256
                        }" TO "${newColumn.name}"`,
1257
                    ),
1258
                )
1259
                downQueries.push(
100✔
1260
                    new Query(
1261
                        `ALTER TABLE ${this.escapePath(table)} RENAME COLUMN "${
1262
                            newColumn.name
1263
                        }" TO "${oldColumn.name}"`,
1264
                    ),
1265
                )
1266

1267
                // rename ENUM type
1268
                if (
100✔
1269
                    oldColumn.type === "enum" ||
192✔
1270
                    oldColumn.type === "simple-enum"
1271
                ) {
1272
                    const oldEnumType = await this.getUserDefinedTypeName(
8✔
1273
                        table,
1274
                        oldColumn,
1275
                    )
1276
                    upQueries.push(
8✔
1277
                        new Query(
1278
                            `ALTER TYPE "${oldEnumType.schema}"."${
1279
                                oldEnumType.name
1280
                            }" RENAME TO ${this.buildEnumName(
1281
                                table,
1282
                                newColumn,
1283
                                false,
1284
                            )}`,
1285
                        ),
1286
                    )
1287
                    downQueries.push(
8✔
1288
                        new Query(
1289
                            `ALTER TYPE ${this.buildEnumName(
1290
                                table,
1291
                                newColumn,
1292
                            )} RENAME TO "${oldEnumType.name}"`,
1293
                        ),
1294
                    )
1295
                }
1296

1297
                // rename column primary key constraint
1298
                if (
100✔
1299
                    oldColumn.isPrimary === true &&
128✔
1300
                    !oldColumn.primaryKeyConstraintName
1301
                ) {
1302
                    const primaryColumns = clonedTable.primaryColumns
20✔
1303

1304
                    // build old primary constraint name
1305
                    const columnNames = primaryColumns.map(
20✔
1306
                        (column) => column.name,
36✔
1307
                    )
1308
                    const oldPkName =
1309
                        this.connection.namingStrategy.primaryKeyName(
20✔
1310
                            clonedTable,
1311
                            columnNames,
1312
                        )
1313

1314
                    // replace old column name with new column name
1315
                    columnNames.splice(columnNames.indexOf(oldColumn.name), 1)
20✔
1316
                    columnNames.push(newColumn.name)
20✔
1317

1318
                    // build new primary constraint name
1319
                    const newPkName =
1320
                        this.connection.namingStrategy.primaryKeyName(
20✔
1321
                            clonedTable,
1322
                            columnNames,
1323
                        )
1324

1325
                    upQueries.push(
20✔
1326
                        new Query(
1327
                            `ALTER TABLE ${this.escapePath(
1328
                                table,
1329
                            )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
1330
                        ),
1331
                    )
1332
                    downQueries.push(
20✔
1333
                        new Query(
1334
                            `ALTER TABLE ${this.escapePath(
1335
                                table,
1336
                            )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
1337
                        ),
1338
                    )
1339
                }
1340

1341
                // rename column sequence
1342
                if (
100!
1343
                    oldColumn.isGenerated === true &&
100!
1344
                    newColumn.generationStrategy === "increment"
1345
                ) {
1346
                    const sequencePath = this.buildSequencePath(
×
1347
                        table,
1348
                        oldColumn.name,
1349
                    )
1350
                    const sequenceName = this.buildSequenceName(
×
1351
                        table,
1352
                        oldColumn.name,
1353
                    )
1354

1355
                    const newSequencePath = this.buildSequencePath(
×
1356
                        table,
1357
                        newColumn.name,
1358
                    )
1359
                    const newSequenceName = this.buildSequenceName(
×
1360
                        table,
1361
                        newColumn.name,
1362
                    )
1363

1364
                    const up = `ALTER SEQUENCE ${this.escapePath(
×
1365
                        sequencePath,
1366
                    )} RENAME TO "${newSequenceName}"`
1367
                    const down = `ALTER SEQUENCE ${this.escapePath(
×
1368
                        newSequencePath,
1369
                    )} RENAME TO "${sequenceName}"`
1370

1371
                    upQueries.push(new Query(up))
×
1372
                    downQueries.push(new Query(down))
×
1373
                }
1374

1375
                // rename unique constraints
1376
                clonedTable.findColumnUniques(oldColumn).forEach((unique) => {
100✔
1377
                    const oldUniqueName =
1378
                        this.connection.namingStrategy.uniqueConstraintName(
32✔
1379
                            clonedTable,
1380
                            unique.columnNames,
1381
                        )
1382

1383
                    // Skip renaming if Unique has user defined constraint name
1384
                    if (unique.name !== oldUniqueName) return
32✔
1385

1386
                    // build new constraint name
1387
                    unique.columnNames.splice(
16✔
1388
                        unique.columnNames.indexOf(oldColumn.name),
1389
                        1,
1390
                    )
1391
                    unique.columnNames.push(newColumn.name)
16✔
1392
                    const newUniqueName =
1393
                        this.connection.namingStrategy.uniqueConstraintName(
16✔
1394
                            clonedTable,
1395
                            unique.columnNames,
1396
                        )
1397

1398
                    // build queries
1399
                    upQueries.push(
16✔
1400
                        new Query(
1401
                            `ALTER TABLE ${this.escapePath(
1402
                                table,
1403
                            )} RENAME CONSTRAINT "${
1404
                                unique.name
1405
                            }" TO "${newUniqueName}"`,
1406
                        ),
1407
                    )
1408
                    downQueries.push(
16✔
1409
                        new Query(
1410
                            `ALTER TABLE ${this.escapePath(
1411
                                table,
1412
                            )} RENAME CONSTRAINT "${newUniqueName}" TO "${
1413
                                unique.name
1414
                            }"`,
1415
                        ),
1416
                    )
1417

1418
                    // replace constraint name
1419
                    unique.name = newUniqueName
16✔
1420
                })
1421

1422
                // rename index constraints
1423
                clonedTable.findColumnIndices(oldColumn).forEach((index) => {
100✔
1424
                    const oldIndexName =
1425
                        this.connection.namingStrategy.indexName(
28✔
1426
                            clonedTable,
1427
                            index.columnNames,
1428
                            index.where,
1429
                        )
1430

1431
                    // Skip renaming if Index has user defined constraint name
1432
                    if (index.name !== oldIndexName) return
28✔
1433

1434
                    // build new constraint name
1435
                    index.columnNames.splice(
20✔
1436
                        index.columnNames.indexOf(oldColumn.name),
1437
                        1,
1438
                    )
1439
                    index.columnNames.push(newColumn.name)
20✔
1440
                    const { schema } = this.driver.parseTableName(table)
20✔
1441
                    const newIndexName =
1442
                        this.connection.namingStrategy.indexName(
20✔
1443
                            clonedTable,
1444
                            index.columnNames,
1445
                            index.where,
1446
                        )
1447

1448
                    // build queries
1449
                    const up = schema
20!
1450
                        ? `ALTER INDEX "${schema}"."${index.name}" RENAME TO "${newIndexName}"`
1451
                        : `ALTER INDEX "${index.name}" RENAME TO "${newIndexName}"`
1452
                    const down = schema
20!
1453
                        ? `ALTER INDEX "${schema}"."${newIndexName}" RENAME TO "${index.name}"`
1454
                        : `ALTER INDEX "${newIndexName}" RENAME TO "${index.name}"`
1455

1456
                    upQueries.push(new Query(up))
20✔
1457
                    downQueries.push(new Query(down))
20✔
1458

1459
                    // replace constraint name
1460
                    index.name = newIndexName
20✔
1461
                })
1462

1463
                // rename foreign key constraints
1464
                clonedTable
100✔
1465
                    .findColumnForeignKeys(oldColumn)
1466
                    .forEach((foreignKey) => {
1467
                        const foreignKeyName =
1468
                            this.connection.namingStrategy.foreignKeyName(
36✔
1469
                                clonedTable,
1470
                                foreignKey.columnNames,
1471
                                this.getTablePath(foreignKey),
1472
                                foreignKey.referencedColumnNames,
1473
                            )
1474

1475
                        // Skip renaming if foreign key has user defined constraint name
1476
                        if (foreignKey.name !== foreignKeyName) return
36✔
1477

1478
                        // build new constraint name
1479
                        foreignKey.columnNames.splice(
4✔
1480
                            foreignKey.columnNames.indexOf(oldColumn.name),
1481
                            1,
1482
                        )
1483
                        foreignKey.columnNames.push(newColumn.name)
4✔
1484
                        const newForeignKeyName =
1485
                            this.connection.namingStrategy.foreignKeyName(
4✔
1486
                                clonedTable,
1487
                                foreignKey.columnNames,
1488
                                this.getTablePath(foreignKey),
1489
                                foreignKey.referencedColumnNames,
1490
                            )
1491

1492
                        // build queries
1493
                        upQueries.push(
4✔
1494
                            new Query(
1495
                                `ALTER TABLE ${this.escapePath(
1496
                                    table,
1497
                                )} RENAME CONSTRAINT "${
1498
                                    foreignKey.name
1499
                                }" TO "${newForeignKeyName}"`,
1500
                            ),
1501
                        )
1502
                        downQueries.push(
4✔
1503
                            new Query(
1504
                                `ALTER TABLE ${this.escapePath(
1505
                                    table,
1506
                                )} RENAME CONSTRAINT "${newForeignKeyName}" TO "${
1507
                                    foreignKey.name
1508
                                }"`,
1509
                            ),
1510
                        )
1511

1512
                        // replace constraint name
1513
                        foreignKey.name = newForeignKeyName
4✔
1514
                    })
1515

1516
                // rename old column in the Table object
1517
                const oldTableColumn = clonedTable.columns.find(
100✔
1518
                    (column) => column.name === oldColumn.name,
216✔
1519
                )
1520
                clonedTable.columns[
100✔
1521
                    clonedTable.columns.indexOf(oldTableColumn!)
1522
                ].name = newColumn.name
1523
                oldColumn.name = newColumn.name
100✔
1524
            }
1525

1526
            if (
388!
1527
                newColumn.precision !== oldColumn.precision ||
776✔
1528
                newColumn.scale !== oldColumn.scale
1529
            ) {
1530
                upQueries.push(
×
1531
                    new Query(
1532
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
1533
                            newColumn.name
1534
                        }" TYPE ${this.driver.createFullType(newColumn)}`,
1535
                    ),
1536
                )
1537
                downQueries.push(
×
1538
                    new Query(
1539
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
1540
                            newColumn.name
1541
                        }" TYPE ${this.driver.createFullType(oldColumn)}`,
1542
                    ),
1543
                )
1544
            }
1545

1546
            if (
388✔
1547
                (newColumn.type === "enum" ||
868!
1548
                    newColumn.type === "simple-enum") &&
1549
                (oldColumn.type === "enum" ||
1550
                    oldColumn.type === "simple-enum") &&
1551
                (!OrmUtils.isArraysEqual(newColumn.enum!, oldColumn.enum!) ||
1552
                    newColumn.enumName !== oldColumn.enumName)
1553
            ) {
1554
                const arraySuffix = newColumn.isArray ? "[]" : ""
52✔
1555

1556
                // "public"."new_enum"
1557
                const newEnumName = this.buildEnumName(table, newColumn)
52✔
1558

1559
                // "public"."old_enum"
1560
                const oldEnumName = this.buildEnumName(table, oldColumn)
52✔
1561

1562
                // "old_enum"
1563
                const oldEnumNameWithoutSchema = this.buildEnumName(
52✔
1564
                    table,
1565
                    oldColumn,
1566
                    false,
1567
                )
1568

1569
                //"public"."old_enum_old"
1570
                const oldEnumNameWithSchema_old = this.buildEnumName(
52✔
1571
                    table,
1572
                    oldColumn,
1573
                    true,
1574
                    false,
1575
                    true,
1576
                )
1577

1578
                //"old_enum_old"
1579
                const oldEnumNameWithoutSchema_old = this.buildEnumName(
52✔
1580
                    table,
1581
                    oldColumn,
1582
                    false,
1583
                    false,
1584
                    true,
1585
                )
1586

1587
                // rename old ENUM
1588
                upQueries.push(
52✔
1589
                    new Query(
1590
                        `ALTER TYPE ${oldEnumName} RENAME TO ${oldEnumNameWithoutSchema_old}`,
1591
                    ),
1592
                )
1593
                downQueries.push(
52✔
1594
                    new Query(
1595
                        `ALTER TYPE ${oldEnumNameWithSchema_old} RENAME TO ${oldEnumNameWithoutSchema}`,
1596
                    ),
1597
                )
1598

1599
                // create new ENUM
1600
                upQueries.push(
52✔
1601
                    this.createEnumTypeSql(table, newColumn, newEnumName),
1602
                )
1603
                downQueries.push(
52✔
1604
                    this.dropEnumTypeSql(table, newColumn, newEnumName),
1605
                )
1606

1607
                // if column have default value, we must drop it to avoid issues with type casting
1608
                if (
52✔
1609
                    oldColumn.default !== null &&
104✔
1610
                    oldColumn.default !== undefined
1611
                ) {
1612
                    // mark default as changed to prevent double update
1613
                    defaultValueChanged = true
20✔
1614
                    upQueries.push(
20✔
1615
                        new Query(
1616
                            `ALTER TABLE ${this.escapePath(
1617
                                table,
1618
                            )} ALTER COLUMN "${oldColumn.name}" DROP DEFAULT`,
1619
                        ),
1620
                    )
1621
                    downQueries.push(
20✔
1622
                        new Query(
1623
                            `ALTER TABLE ${this.escapePath(
1624
                                table,
1625
                            )} ALTER COLUMN "${oldColumn.name}" SET DEFAULT ${
1626
                                oldColumn.default
1627
                            }`,
1628
                        ),
1629
                    )
1630
                }
1631

1632
                // build column types
1633
                const upType = `${newEnumName}${arraySuffix} USING "${newColumn.name}"::"text"::${newEnumName}${arraySuffix}`
52✔
1634
                const downType = `${oldEnumNameWithSchema_old}${arraySuffix} USING "${newColumn.name}"::"text"::${oldEnumNameWithSchema_old}${arraySuffix}`
52✔
1635

1636
                // update column to use new type
1637
                upQueries.push(
52✔
1638
                    new Query(
1639
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
1640
                            newColumn.name
1641
                        }" TYPE ${upType}`,
1642
                    ),
1643
                )
1644
                downQueries.push(
52✔
1645
                    new Query(
1646
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
1647
                            newColumn.name
1648
                        }" TYPE ${downType}`,
1649
                    ),
1650
                )
1651

1652
                // restore column default or create new one
1653
                if (
52✔
1654
                    newColumn.default !== null &&
104✔
1655
                    newColumn.default !== undefined
1656
                ) {
1657
                    upQueries.push(
20✔
1658
                        new Query(
1659
                            `ALTER TABLE ${this.escapePath(
1660
                                table,
1661
                            )} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${
1662
                                newColumn.default
1663
                            }`,
1664
                        ),
1665
                    )
1666
                    downQueries.push(
20✔
1667
                        new Query(
1668
                            `ALTER TABLE ${this.escapePath(
1669
                                table,
1670
                            )} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`,
1671
                        ),
1672
                    )
1673
                }
1674

1675
                // remove old ENUM
1676
                upQueries.push(
52✔
1677
                    this.dropEnumTypeSql(
1678
                        table,
1679
                        oldColumn,
1680
                        oldEnumNameWithSchema_old,
1681
                    ),
1682
                )
1683
                downQueries.push(
52✔
1684
                    this.createEnumTypeSql(
1685
                        table,
1686
                        oldColumn,
1687
                        oldEnumNameWithSchema_old,
1688
                    ),
1689
                )
1690
            }
1691

1692
            if (oldColumn.isNullable !== newColumn.isNullable) {
388✔
1693
                if (newColumn.isNullable) {
8✔
1694
                    upQueries.push(
4✔
1695
                        new Query(
1696
                            `ALTER TABLE ${this.escapePath(
1697
                                table,
1698
                            )} ALTER COLUMN "${oldColumn.name}" DROP NOT NULL`,
1699
                        ),
1700
                    )
1701
                    downQueries.push(
4✔
1702
                        new Query(
1703
                            `ALTER TABLE ${this.escapePath(
1704
                                table,
1705
                            )} ALTER COLUMN "${oldColumn.name}" SET NOT NULL`,
1706
                        ),
1707
                    )
1708
                } else {
1709
                    upQueries.push(
4✔
1710
                        new Query(
1711
                            `ALTER TABLE ${this.escapePath(
1712
                                table,
1713
                            )} ALTER COLUMN "${oldColumn.name}" SET NOT NULL`,
1714
                        ),
1715
                    )
1716
                    downQueries.push(
4✔
1717
                        new Query(
1718
                            `ALTER TABLE ${this.escapePath(
1719
                                table,
1720
                            )} ALTER COLUMN "${oldColumn.name}" DROP NOT NULL`,
1721
                        ),
1722
                    )
1723
                }
1724
            }
1725

1726
            if (oldColumn.comment !== newColumn.comment) {
388✔
1727
                upQueries.push(
20✔
1728
                    new Query(
1729
                        `COMMENT ON COLUMN ${this.escapePath(table)}."${
1730
                            oldColumn.name
1731
                        }" IS ${this.escapeComment(newColumn.comment)}`,
1732
                    ),
1733
                )
1734
                downQueries.push(
20✔
1735
                    new Query(
1736
                        `COMMENT ON COLUMN ${this.escapePath(table)}."${
1737
                            newColumn.name
1738
                        }" IS ${this.escapeComment(oldColumn.comment)}`,
1739
                    ),
1740
                )
1741
            }
1742

1743
            if (newColumn.isPrimary !== oldColumn.isPrimary) {
388✔
1744
                const primaryColumns = clonedTable.primaryColumns
16✔
1745

1746
                // if primary column state changed, we must always drop existed constraint.
1747
                if (primaryColumns.length > 0) {
16✔
1748
                    const pkName = primaryColumns[0].primaryKeyConstraintName
12!
1749
                        ? primaryColumns[0].primaryKeyConstraintName
1750
                        : this.connection.namingStrategy.primaryKeyName(
1751
                              clonedTable,
1752
                              primaryColumns.map((column) => column.name),
16✔
1753
                          )
1754

1755
                    const columnNames = primaryColumns
12✔
1756
                        .map((column) => `"${column.name}"`)
16✔
1757
                        .join(", ")
1758

1759
                    upQueries.push(
12✔
1760
                        new Query(
1761
                            `ALTER TABLE ${this.escapePath(
1762
                                table,
1763
                            )} DROP CONSTRAINT "${pkName}"`,
1764
                        ),
1765
                    )
1766
                    downQueries.push(
12✔
1767
                        new Query(
1768
                            `ALTER TABLE ${this.escapePath(
1769
                                table,
1770
                            )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1771
                        ),
1772
                    )
1773
                }
1774

1775
                if (newColumn.isPrimary === true) {
16✔
1776
                    primaryColumns.push(newColumn)
8✔
1777
                    // update column in table
1778
                    const column = clonedTable.columns.find(
8✔
1779
                        (column) => column.name === newColumn.name,
16✔
1780
                    )
1781
                    column!.isPrimary = true
8✔
1782
                    const pkName = primaryColumns[0].primaryKeyConstraintName
8!
1783
                        ? primaryColumns[0].primaryKeyConstraintName
1784
                        : this.connection.namingStrategy.primaryKeyName(
1785
                              clonedTable,
1786
                              primaryColumns.map((column) => column.name),
12✔
1787
                          )
1788

1789
                    const columnNames = primaryColumns
8✔
1790
                        .map((column) => `"${column.name}"`)
12✔
1791
                        .join(", ")
1792

1793
                    upQueries.push(
8✔
1794
                        new Query(
1795
                            `ALTER TABLE ${this.escapePath(
1796
                                table,
1797
                            )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1798
                        ),
1799
                    )
1800
                    downQueries.push(
8✔
1801
                        new Query(
1802
                            `ALTER TABLE ${this.escapePath(
1803
                                table,
1804
                            )} DROP CONSTRAINT "${pkName}"`,
1805
                        ),
1806
                    )
1807
                } else {
1808
                    const primaryColumn = primaryColumns.find(
8✔
1809
                        (c) => c.name === newColumn.name,
8✔
1810
                    )
1811
                    primaryColumns.splice(
8✔
1812
                        primaryColumns.indexOf(primaryColumn!),
1813
                        1,
1814
                    )
1815

1816
                    // update column in table
1817
                    const column = clonedTable.columns.find(
8✔
1818
                        (column) => column.name === newColumn.name,
8✔
1819
                    )
1820
                    column!.isPrimary = false
8✔
1821

1822
                    // if we have another primary keys, we must recreate constraint.
1823
                    if (primaryColumns.length > 0) {
8✔
1824
                        const pkName = primaryColumns[0]
4!
1825
                            .primaryKeyConstraintName
1826
                            ? primaryColumns[0].primaryKeyConstraintName
1827
                            : this.connection.namingStrategy.primaryKeyName(
1828
                                  clonedTable,
1829
                                  primaryColumns.map((column) => column.name),
4✔
1830
                              )
1831

1832
                        const columnNames = primaryColumns
4✔
1833
                            .map((column) => `"${column.name}"`)
4✔
1834
                            .join(", ")
1835

1836
                        upQueries.push(
4✔
1837
                            new Query(
1838
                                `ALTER TABLE ${this.escapePath(
1839
                                    table,
1840
                                )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
1841
                            ),
1842
                        )
1843
                        downQueries.push(
4✔
1844
                            new Query(
1845
                                `ALTER TABLE ${this.escapePath(
1846
                                    table,
1847
                                )} DROP CONSTRAINT "${pkName}"`,
1848
                            ),
1849
                        )
1850
                    }
1851
                }
1852
            }
1853

1854
            if (newColumn.isUnique !== oldColumn.isUnique) {
388✔
1855
                if (newColumn.isUnique === true) {
4!
1856
                    const uniqueConstraint = new TableUnique({
4✔
1857
                        name: this.connection.namingStrategy.uniqueConstraintName(
1858
                            table,
1859
                            [newColumn.name],
1860
                        ),
1861
                        columnNames: [newColumn.name],
1862
                    })
1863
                    clonedTable.uniques.push(uniqueConstraint)
4✔
1864
                    upQueries.push(
4✔
1865
                        new Query(
1866
                            `ALTER TABLE ${this.escapePath(
1867
                                table,
1868
                            )} ADD CONSTRAINT "${
1869
                                uniqueConstraint.name
1870
                            }" UNIQUE ("${newColumn.name}")`,
1871
                        ),
1872
                    )
1873
                    downQueries.push(
4✔
1874
                        new Query(
1875
                            `ALTER TABLE ${this.escapePath(
1876
                                table,
1877
                            )} DROP CONSTRAINT "${uniqueConstraint.name}"`,
1878
                        ),
1879
                    )
1880
                } else {
1881
                    const uniqueConstraint = clonedTable.uniques.find(
×
1882
                        (unique) => {
1883
                            return (
×
1884
                                unique.columnNames.length === 1 &&
×
1885
                                !!unique.columnNames.find(
1886
                                    (columnName) =>
1887
                                        columnName === newColumn.name,
×
1888
                                )
1889
                            )
1890
                        },
1891
                    )
1892
                    clonedTable.uniques.splice(
×
1893
                        clonedTable.uniques.indexOf(uniqueConstraint!),
1894
                        1,
1895
                    )
1896
                    upQueries.push(
×
1897
                        new Query(
1898
                            `ALTER TABLE ${this.escapePath(
1899
                                table,
1900
                            )} DROP CONSTRAINT "${uniqueConstraint!.name}"`,
1901
                        ),
1902
                    )
1903
                    downQueries.push(
×
1904
                        new Query(
1905
                            `ALTER TABLE ${this.escapePath(
1906
                                table,
1907
                            )} ADD CONSTRAINT "${
1908
                                uniqueConstraint!.name
1909
                            }" UNIQUE ("${newColumn.name}")`,
1910
                        ),
1911
                    )
1912
                }
1913
            }
1914

1915
            if (oldColumn.isGenerated !== newColumn.isGenerated) {
388✔
1916
                // if old column was "generated", we should clear defaults
1917
                if (oldColumn.isGenerated) {
48✔
1918
                    if (oldColumn.generationStrategy === "uuid") {
20✔
1919
                        upQueries.push(
8✔
1920
                            new Query(
1921
                                `ALTER TABLE ${this.escapePath(
1922
                                    table,
1923
                                )} ALTER COLUMN "${
1924
                                    oldColumn.name
1925
                                }" DROP DEFAULT`,
1926
                            ),
1927
                        )
1928
                        downQueries.push(
8✔
1929
                            new Query(
1930
                                `ALTER TABLE ${this.escapePath(
1931
                                    table,
1932
                                )} ALTER COLUMN "${
1933
                                    oldColumn.name
1934
                                }" SET DEFAULT ${this.driver.uuidGenerator}`,
1935
                            ),
1936
                        )
1937
                    } else if (oldColumn.generationStrategy === "increment") {
12✔
1938
                        upQueries.push(
12✔
1939
                            new Query(
1940
                                `ALTER TABLE ${this.escapePath(
1941
                                    table,
1942
                                )} ALTER COLUMN "${
1943
                                    newColumn.name
1944
                                }" DROP DEFAULT`,
1945
                            ),
1946
                        )
1947
                        downQueries.push(
12✔
1948
                            new Query(
1949
                                `ALTER TABLE ${this.escapePath(
1950
                                    table,
1951
                                )} ALTER COLUMN "${
1952
                                    newColumn.name
1953
                                }" SET DEFAULT nextval('${this.escapePath(
1954
                                    this.buildSequencePath(table, newColumn),
1955
                                )}')`,
1956
                            ),
1957
                        )
1958

1959
                        upQueries.push(
12✔
1960
                            new Query(
1961
                                `DROP SEQUENCE ${this.escapePath(
1962
                                    this.buildSequencePath(table, newColumn),
1963
                                )}`,
1964
                            ),
1965
                        )
1966
                        downQueries.push(
12✔
1967
                            new Query(
1968
                                `CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(
1969
                                    this.buildSequencePath(table, newColumn),
1970
                                )} OWNED BY ${this.escapePath(table)}."${
1971
                                    newColumn.name
1972
                                }"`,
1973
                            ),
1974
                        )
1975
                    }
1976
                }
1977

1978
                if (newColumn.generationStrategy === "uuid") {
48✔
1979
                    if (newColumn.isGenerated === true) {
8!
1980
                        upQueries.push(
8✔
1981
                            new Query(
1982
                                `ALTER TABLE ${this.escapePath(
1983
                                    table,
1984
                                )} ALTER COLUMN "${
1985
                                    newColumn.name
1986
                                }" SET DEFAULT ${this.driver.uuidGenerator}`,
1987
                            ),
1988
                        )
1989
                        downQueries.push(
8✔
1990
                            new Query(
1991
                                `ALTER TABLE ${this.escapePath(
1992
                                    table,
1993
                                )} ALTER COLUMN "${
1994
                                    newColumn.name
1995
                                }" DROP DEFAULT`,
1996
                            ),
1997
                        )
1998
                    } else {
1999
                        upQueries.push(
×
2000
                            new Query(
2001
                                `ALTER TABLE ${this.escapePath(
2002
                                    table,
2003
                                )} ALTER COLUMN "${
2004
                                    newColumn.name
2005
                                }" DROP DEFAULT`,
2006
                            ),
2007
                        )
2008
                        downQueries.push(
×
2009
                            new Query(
2010
                                `ALTER TABLE ${this.escapePath(
2011
                                    table,
2012
                                )} ALTER COLUMN "${
2013
                                    newColumn.name
2014
                                }" SET DEFAULT ${this.driver.uuidGenerator}`,
2015
                            ),
2016
                        )
2017
                    }
2018
                } else if (newColumn.generationStrategy === "increment") {
40✔
2019
                    if (newColumn.isGenerated === true) {
20!
2020
                        upQueries.push(
20✔
2021
                            new Query(
2022
                                `CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(
2023
                                    this.buildSequencePath(table, newColumn),
2024
                                )} OWNED BY ${this.escapePath(table)}."${
2025
                                    newColumn.name
2026
                                }"`,
2027
                            ),
2028
                        )
2029
                        downQueries.push(
20✔
2030
                            new Query(
2031
                                `DROP SEQUENCE ${this.escapePath(
2032
                                    this.buildSequencePath(table, newColumn),
2033
                                )}`,
2034
                            ),
2035
                        )
2036

2037
                        upQueries.push(
20✔
2038
                            new Query(
2039
                                `ALTER TABLE ${this.escapePath(
2040
                                    table,
2041
                                )} ALTER COLUMN "${
2042
                                    newColumn.name
2043
                                }" SET DEFAULT nextval('${this.escapePath(
2044
                                    this.buildSequencePath(table, newColumn),
2045
                                )}')`,
2046
                            ),
2047
                        )
2048
                        downQueries.push(
20✔
2049
                            new Query(
2050
                                `ALTER TABLE ${this.escapePath(
2051
                                    table,
2052
                                )} ALTER COLUMN "${
2053
                                    newColumn.name
2054
                                }" DROP DEFAULT`,
2055
                            ),
2056
                        )
2057
                    } else {
2058
                        upQueries.push(
×
2059
                            new Query(
2060
                                `ALTER TABLE ${this.escapePath(
2061
                                    table,
2062
                                )} ALTER COLUMN "${
2063
                                    newColumn.name
2064
                                }" DROP DEFAULT`,
2065
                            ),
2066
                        )
2067
                        downQueries.push(
×
2068
                            new Query(
2069
                                `ALTER TABLE ${this.escapePath(
2070
                                    table,
2071
                                )} ALTER COLUMN "${
2072
                                    newColumn.name
2073
                                }" SET DEFAULT nextval('${this.escapePath(
2074
                                    this.buildSequencePath(table, newColumn),
2075
                                )}')`,
2076
                            ),
2077
                        )
2078

2079
                        upQueries.push(
×
2080
                            new Query(
2081
                                `DROP SEQUENCE ${this.escapePath(
2082
                                    this.buildSequencePath(table, newColumn),
2083
                                )}`,
2084
                            ),
2085
                        )
2086
                        downQueries.push(
×
2087
                            new Query(
2088
                                `CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(
2089
                                    this.buildSequencePath(table, newColumn),
2090
                                )} OWNED BY ${this.escapePath(table)}."${
2091
                                    newColumn.name
2092
                                }"`,
2093
                            ),
2094
                        )
2095
                    }
2096
                }
2097
            }
2098

2099
            // the default might have changed when the enum changed
2100
            if (
388✔
2101
                newColumn.default !== oldColumn.default &&
420✔
2102
                !defaultValueChanged
2103
            ) {
2104
                if (
20!
2105
                    newColumn.default !== null &&
40✔
2106
                    newColumn.default !== undefined
2107
                ) {
2108
                    upQueries.push(
20✔
2109
                        new Query(
2110
                            `ALTER TABLE ${this.escapePath(
2111
                                table,
2112
                            )} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${
2113
                                newColumn.default
2114
                            }`,
2115
                        ),
2116
                    )
2117

2118
                    if (
20✔
2119
                        oldColumn.default !== null &&
40✔
2120
                        oldColumn.default !== undefined
2121
                    ) {
2122
                        downQueries.push(
12✔
2123
                            new Query(
2124
                                `ALTER TABLE ${this.escapePath(
2125
                                    table,
2126
                                )} ALTER COLUMN "${
2127
                                    newColumn.name
2128
                                }" SET DEFAULT ${oldColumn.default}`,
2129
                            ),
2130
                        )
2131
                    } else {
2132
                        downQueries.push(
8✔
2133
                            new Query(
2134
                                `ALTER TABLE ${this.escapePath(
2135
                                    table,
2136
                                )} ALTER COLUMN "${
2137
                                    newColumn.name
2138
                                }" DROP DEFAULT`,
2139
                            ),
2140
                        )
2141
                    }
2142
                } else if (
×
2143
                    oldColumn.default !== null &&
×
2144
                    oldColumn.default !== undefined
2145
                ) {
2146
                    upQueries.push(
×
2147
                        new Query(
2148
                            `ALTER TABLE ${this.escapePath(
2149
                                table,
2150
                            )} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`,
2151
                        ),
2152
                    )
2153
                    downQueries.push(
×
2154
                        new Query(
2155
                            `ALTER TABLE ${this.escapePath(
2156
                                table,
2157
                            )} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${
2158
                                oldColumn.default
2159
                            }`,
2160
                        ),
2161
                    )
2162
                }
2163
            }
2164

2165
            if (
388!
2166
                (newColumn.spatialFeatureType || "").toLowerCase() !==
1,552✔
2167
                    (oldColumn.spatialFeatureType || "").toLowerCase() ||
776✔
2168
                newColumn.srid !== oldColumn.srid
2169
            ) {
2170
                upQueries.push(
×
2171
                    new Query(
2172
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
2173
                            newColumn.name
2174
                        }" TYPE ${this.driver.createFullType(newColumn)}`,
2175
                    ),
2176
                )
2177
                downQueries.push(
×
2178
                    new Query(
2179
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
2180
                            newColumn.name
2181
                        }" TYPE ${this.driver.createFullType(oldColumn)}`,
2182
                    ),
2183
                )
2184
            }
2185

2186
            // update column collation
2187
            if (newColumn.collation !== oldColumn.collation) {
388✔
2188
                upQueries.push(
8✔
2189
                    new Query(
2190
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
2191
                            newColumn.name
2192
                        }" TYPE ${newColumn.type} COLLATE "${
2193
                            newColumn.collation
2194
                        }"`,
2195
                    ),
2196
                )
2197

2198
                const oldCollation = oldColumn.collation
8!
2199
                    ? `"${oldColumn.collation}"`
2200
                    : `pg_catalog."default"` // if there's no old collation, use default
2201

2202
                downQueries.push(
8✔
2203
                    new Query(
2204
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
2205
                            newColumn.name
2206
                        }" TYPE ${newColumn.type} COLLATE ${oldCollation}`,
2207
                    ),
2208
                )
2209
            }
2210

2211
            if (newColumn.generatedType !== oldColumn.generatedType) {
388✔
2212
                // Convert generated column data to normal column
2213
                if (
8✔
2214
                    !newColumn.generatedType ||
8!
2215
                    newColumn.generatedType === "VIRTUAL"
2216
                ) {
2217
                    // We can copy the generated data to the new column
2218
                    const tableNameWithSchema = (
8✔
2219
                        await this.getTableNameWithSchema(table.name)
2220
                    ).split(".")
2221
                    const tableName = tableNameWithSchema[1]
8✔
2222
                    const schema = tableNameWithSchema[0]
8✔
2223

2224
                    upQueries.push(
8✔
2225
                        new Query(
2226
                            `ALTER TABLE ${this.escapePath(
2227
                                table,
2228
                            )} RENAME COLUMN "${oldColumn.name}" TO "TEMP_OLD_${
2229
                                oldColumn.name
2230
                            }"`,
2231
                        ),
2232
                    )
2233
                    upQueries.push(
8✔
2234
                        new Query(
2235
                            `ALTER TABLE ${this.escapePath(
2236
                                table,
2237
                            )} ADD ${this.buildCreateColumnSql(
2238
                                table,
2239
                                newColumn,
2240
                            )}`,
2241
                        ),
2242
                    )
2243
                    upQueries.push(
8✔
2244
                        new Query(
2245
                            `UPDATE ${this.escapePath(table)} SET "${
2246
                                newColumn.name
2247
                            }" = "TEMP_OLD_${oldColumn.name}"`,
2248
                        ),
2249
                    )
2250
                    upQueries.push(
8✔
2251
                        new Query(
2252
                            `ALTER TABLE ${this.escapePath(
2253
                                table,
2254
                            )} DROP COLUMN "TEMP_OLD_${oldColumn.name}"`,
2255
                        ),
2256
                    )
2257
                    upQueries.push(
8✔
2258
                        this.deleteTypeormMetadataSql({
2259
                            database: this.driver.database,
2260
                            schema,
2261
                            table: tableName,
2262
                            type: MetadataTableType.GENERATED_COLUMN,
2263
                            name: oldColumn.name,
2264
                        }),
2265
                    )
2266
                    // However, we can't copy it back on downgrade. It needs to regenerate.
2267
                    downQueries.push(
8✔
2268
                        this.insertTypeormMetadataSql({
2269
                            database: this.driver.database,
2270
                            schema,
2271
                            table: tableName,
2272
                            type: MetadataTableType.GENERATED_COLUMN,
2273
                            name: oldColumn.name,
2274
                            value: oldColumn.asExpression,
2275
                        }),
2276
                    )
2277
                    downQueries.push(
8✔
2278
                        new Query(
2279
                            `ALTER TABLE ${this.escapePath(
2280
                                table,
2281
                            )} ADD ${this.buildCreateColumnSql(
2282
                                table,
2283
                                oldColumn,
2284
                            )}`,
2285
                        ),
2286
                    )
2287
                    downQueries.push(
8✔
2288
                        new Query(
2289
                            `ALTER TABLE ${this.escapePath(
2290
                                table,
2291
                            )} DROP COLUMN "${newColumn.name}"`,
2292
                        ),
2293
                    )
2294
                    // downQueries.push(
2295
                    //     this.deleteTypeormMetadataSql({
2296
                    //         database: this.driver.database,
2297
                    //         schema,
2298
                    //         table: tableName,
2299
                    //         type: MetadataTableType.GENERATED_COLUMN,
2300
                    //         name: newColumn.name,
2301
                    //     }),
2302
                    // )
2303
                }
2304
            }
2305
        }
2306

2307
        await this.executeQueries(upQueries, downQueries)
488✔
2308
        this.replaceCachedTable(table, clonedTable)
488✔
2309
    }
2310

2311
    /**
2312
     * Changes a column in the table.
2313
     */
2314
    async changeColumns(
2315
        tableOrName: Table | string,
2316
        changedColumns: { newColumn: TableColumn; oldColumn: TableColumn }[],
2317
    ): Promise<void> {
2318
        for (const { oldColumn, newColumn } of changedColumns) {
232✔
2319
            await this.changeColumn(tableOrName, oldColumn, newColumn)
308✔
2320
        }
2321
    }
2322

2323
    /**
2324
     * Drops column in the table.
2325
     */
2326
    async dropColumn(
2327
        tableOrName: Table | string,
2328
        columnOrName: TableColumn | string,
2329
    ): Promise<void> {
2330
        const table = InstanceChecker.isTable(tableOrName)
168✔
2331
            ? tableOrName
2332
            : await this.getCachedTable(tableOrName)
2333
        const column = InstanceChecker.isTableColumn(columnOrName)
168✔
2334
            ? columnOrName
2335
            : table.findColumnByName(columnOrName)
2336
        if (!column)
168✔
2337
            throw new TypeORMError(
4✔
2338
                `Column "${columnOrName}" was not found in table "${table.name}"`,
2339
            )
2340

2341
        const clonedTable = table.clone()
164✔
2342
        const upQueries: Query[] = []
164✔
2343
        const downQueries: Query[] = []
164✔
2344

2345
        // drop primary key constraint
2346
        if (column.isPrimary) {
164✔
2347
            const pkName = column.primaryKeyConstraintName
16!
2348
                ? column.primaryKeyConstraintName
2349
                : this.connection.namingStrategy.primaryKeyName(
2350
                      clonedTable,
2351
                      clonedTable.primaryColumns.map((column) => column.name),
16✔
2352
                  )
2353

2354
            const columnNames = clonedTable.primaryColumns
16✔
2355
                .map((primaryColumn) => `"${primaryColumn.name}"`)
16✔
2356
                .join(", ")
2357

2358
            upQueries.push(
16✔
2359
                new Query(
2360
                    `ALTER TABLE ${this.escapePath(
2361
                        clonedTable,
2362
                    )} DROP CONSTRAINT "${pkName}"`,
2363
                ),
2364
            )
2365
            downQueries.push(
16✔
2366
                new Query(
2367
                    `ALTER TABLE ${this.escapePath(
2368
                        clonedTable,
2369
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
2370
                ),
2371
            )
2372

2373
            // update column in table
2374
            const tableColumn = clonedTable.findColumnByName(column.name)
16✔
2375
            tableColumn!.isPrimary = false
16✔
2376

2377
            // if primary key have multiple columns, we must recreate it without dropped column
2378
            if (clonedTable.primaryColumns.length > 0) {
16!
2379
                const pkName = clonedTable.primaryColumns[0]
×
2380
                    .primaryKeyConstraintName
2381
                    ? clonedTable.primaryColumns[0].primaryKeyConstraintName
2382
                    : this.connection.namingStrategy.primaryKeyName(
2383
                          clonedTable,
2384
                          clonedTable.primaryColumns.map(
2385
                              (column) => column.name,
×
2386
                          ),
2387
                      )
2388

2389
                const columnNames = clonedTable.primaryColumns
×
2390
                    .map((primaryColumn) => `"${primaryColumn.name}"`)
×
2391
                    .join(", ")
2392

2393
                upQueries.push(
×
2394
                    new Query(
2395
                        `ALTER TABLE ${this.escapePath(
2396
                            clonedTable,
2397
                        )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
2398
                    ),
2399
                )
2400
                downQueries.push(
×
2401
                    new Query(
2402
                        `ALTER TABLE ${this.escapePath(
2403
                            clonedTable,
2404
                        )} DROP CONSTRAINT "${pkName}"`,
2405
                    ),
2406
                )
2407
            }
2408
        }
2409

2410
        // drop column index
2411
        const columnIndex = clonedTable.indices.find(
164✔
2412
            (index) =>
2413
                index.columnNames.length === 1 &&
12✔
2414
                index.columnNames[0] === column.name,
2415
        )
2416
        if (columnIndex) {
164!
2417
            clonedTable.indices.splice(
×
2418
                clonedTable.indices.indexOf(columnIndex),
2419
                1,
2420
            )
2421
            upQueries.push(this.dropIndexSql(table, columnIndex))
×
2422
            downQueries.push(this.createIndexSql(table, columnIndex))
×
2423
        }
2424

2425
        // drop column check
2426
        const columnCheck = clonedTable.checks.find(
164✔
2427
            (check) =>
2428
                !!check.columnNames &&
56✔
2429
                check.columnNames.length === 1 &&
2430
                check.columnNames[0] === column.name,
2431
        )
2432
        if (columnCheck) {
164✔
2433
            clonedTable.checks.splice(
8✔
2434
                clonedTable.checks.indexOf(columnCheck),
2435
                1,
2436
            )
2437
            upQueries.push(this.dropCheckConstraintSql(table, columnCheck))
8✔
2438
            downQueries.push(this.createCheckConstraintSql(table, columnCheck))
8✔
2439
        }
2440

2441
        // drop column unique
2442
        const columnUnique = clonedTable.uniques.find(
164✔
2443
            (unique) =>
2444
                unique.columnNames.length === 1 &&
103✔
2445
                unique.columnNames[0] === column.name,
2446
        )
2447
        if (columnUnique) {
164✔
2448
            clonedTable.uniques.splice(
12✔
2449
                clonedTable.uniques.indexOf(columnUnique),
2450
                1,
2451
            )
2452
            upQueries.push(this.dropUniqueConstraintSql(table, columnUnique))
12✔
2453
            downQueries.push(
12✔
2454
                this.createUniqueConstraintSql(table, columnUnique),
2455
            )
2456
        }
2457

2458
        upQueries.push(
164✔
2459
            new Query(
2460
                `ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${
2461
                    column.name
2462
                }"`,
2463
            ),
2464
        )
2465
        downQueries.push(
164✔
2466
            new Query(
2467
                `ALTER TABLE ${this.escapePath(
2468
                    table,
2469
                )} ADD ${this.buildCreateColumnSql(table, column)}`,
2470
            ),
2471
        )
2472

2473
        // drop enum type
2474
        if (column.type === "enum" || column.type === "simple-enum") {
164✔
2475
            const hasEnum = await this.hasEnumType(table, column)
20✔
2476
            if (hasEnum) {
20✔
2477
                const enumType = await this.getUserDefinedTypeName(
20✔
2478
                    table,
2479
                    column,
2480
                )
2481
                const escapedEnumName = `"${enumType.schema}"."${enumType.name}"`
20✔
2482
                upQueries.push(
20✔
2483
                    this.dropEnumTypeSql(table, column, escapedEnumName),
2484
                )
2485
                downQueries.push(
20✔
2486
                    this.createEnumTypeSql(table, column, escapedEnumName),
2487
                )
2488
            }
2489
        }
2490

2491
        if (column.generatedType === "STORED") {
164✔
2492
            const tableNameWithSchema = (
12✔
2493
                await this.getTableNameWithSchema(table.name)
2494
            ).split(".")
2495
            const tableName = tableNameWithSchema[1]
12✔
2496
            const schema = tableNameWithSchema[0]
12✔
2497
            const deleteQuery = this.deleteTypeormMetadataSql({
12✔
2498
                database: this.driver.database,
2499
                schema,
2500
                table: tableName,
2501
                type: MetadataTableType.GENERATED_COLUMN,
2502
                name: column.name,
2503
            })
2504
            const insertQuery = this.insertTypeormMetadataSql({
12✔
2505
                database: this.driver.database,
2506
                schema,
2507
                table: tableName,
2508
                type: MetadataTableType.GENERATED_COLUMN,
2509
                name: column.name,
2510
                value: column.asExpression,
2511
            })
2512

2513
            upQueries.push(deleteQuery)
12✔
2514
            downQueries.push(insertQuery)
12✔
2515
        }
2516

2517
        await this.executeQueries(upQueries, downQueries)
164✔
2518

2519
        clonedTable.removeColumn(column)
164✔
2520
        this.replaceCachedTable(table, clonedTable)
164✔
2521
    }
2522

2523
    /**
2524
     * Drops the columns in the table.
2525
     */
2526
    async dropColumns(
2527
        tableOrName: Table | string,
2528
        columns: TableColumn[] | string[],
2529
    ): Promise<void> {
2530
        for (const column of columns) {
24✔
2531
            await this.dropColumn(tableOrName, column)
44✔
2532
        }
2533
    }
2534

2535
    /**
2536
     * Creates a new primary key.
2537
     */
2538
    async createPrimaryKey(
2539
        tableOrName: Table | string,
2540
        columnNames: string[],
2541
        constraintName?: string,
2542
    ): Promise<void> {
2543
        const table = InstanceChecker.isTable(tableOrName)
8!
2544
            ? tableOrName
2545
            : await this.getCachedTable(tableOrName)
2546
        const clonedTable = table.clone()
8✔
2547

2548
        const up = this.createPrimaryKeySql(table, columnNames, constraintName)
8✔
2549

2550
        // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names.
2551
        clonedTable.columns.forEach((column) => {
8✔
2552
            if (columnNames.find((columnName) => columnName === column.name))
28✔
2553
                column.isPrimary = true
12✔
2554
        })
2555
        const down = this.dropPrimaryKeySql(clonedTable)
8✔
2556

2557
        await this.executeQueries(up, down)
8✔
2558
        this.replaceCachedTable(table, clonedTable)
8✔
2559
    }
2560

2561
    /**
2562
     * Updates composite primary keys.
2563
     */
2564
    async updatePrimaryKeys(
2565
        tableOrName: Table | string,
2566
        columns: TableColumn[],
2567
    ): Promise<void> {
2568
        const table = InstanceChecker.isTable(tableOrName)
12!
2569
            ? tableOrName
2570
            : await this.getCachedTable(tableOrName)
2571
        const clonedTable = table.clone()
12✔
2572
        const columnNames = columns.map((column) => column.name)
24✔
2573
        const upQueries: Query[] = []
12✔
2574
        const downQueries: Query[] = []
12✔
2575

2576
        // if table already have primary columns, we must drop them.
2577
        const primaryColumns = clonedTable.primaryColumns
12✔
2578
        if (primaryColumns.length > 0) {
12✔
2579
            const pkName = primaryColumns[0].primaryKeyConstraintName
12!
2580
                ? primaryColumns[0].primaryKeyConstraintName
2581
                : this.connection.namingStrategy.primaryKeyName(
2582
                      clonedTable,
2583
                      primaryColumns.map((column) => column.name),
12✔
2584
                  )
2585

2586
            const columnNamesString = primaryColumns
12✔
2587
                .map((column) => `"${column.name}"`)
12✔
2588
                .join(", ")
2589

2590
            upQueries.push(
12✔
2591
                new Query(
2592
                    `ALTER TABLE ${this.escapePath(
2593
                        table,
2594
                    )} DROP CONSTRAINT "${pkName}"`,
2595
                ),
2596
            )
2597
            downQueries.push(
12✔
2598
                new Query(
2599
                    `ALTER TABLE ${this.escapePath(
2600
                        table,
2601
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
2602
                ),
2603
            )
2604
        }
2605

2606
        // update columns in table.
2607
        clonedTable.columns
12✔
2608
            .filter((column) => columnNames.indexOf(column.name) !== -1)
48✔
2609
            .forEach((column) => (column.isPrimary = true))
24✔
2610

2611
        const pkName = primaryColumns[0]?.primaryKeyConstraintName
12!
2612
            ? primaryColumns[0].primaryKeyConstraintName
2613
            : this.connection.namingStrategy.primaryKeyName(
2614
                  clonedTable,
2615
                  columnNames,
2616
              )
2617

2618
        const columnNamesString = columnNames
12✔
2619
            .map((columnName) => `"${columnName}"`)
24✔
2620
            .join(", ")
2621

2622
        upQueries.push(
12✔
2623
            new Query(
2624
                `ALTER TABLE ${this.escapePath(
2625
                    table,
2626
                )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
2627
            ),
2628
        )
2629
        downQueries.push(
12✔
2630
            new Query(
2631
                `ALTER TABLE ${this.escapePath(
2632
                    table,
2633
                )} DROP CONSTRAINT "${pkName}"`,
2634
            ),
2635
        )
2636

2637
        await this.executeQueries(upQueries, downQueries)
12✔
2638
        this.replaceCachedTable(table, clonedTable)
12✔
2639
    }
2640

2641
    /**
2642
     * Drops a primary key.
2643
     */
2644
    async dropPrimaryKey(
2645
        tableOrName: Table | string,
2646
        constraintName?: string,
2647
    ): Promise<void> {
2648
        const table = InstanceChecker.isTable(tableOrName)
12!
2649
            ? tableOrName
2650
            : await this.getCachedTable(tableOrName)
2651
        const up = this.dropPrimaryKeySql(table)
12✔
2652
        const down = this.createPrimaryKeySql(
12✔
2653
            table,
2654
            table.primaryColumns.map((column) => column.name),
12✔
2655
            constraintName,
2656
        )
2657
        await this.executeQueries(up, down)
12✔
2658
        table.primaryColumns.forEach((column) => {
12✔
2659
            column.isPrimary = false
12✔
2660
        })
2661
    }
2662

2663
    /**
2664
     * Creates new unique constraint.
2665
     */
2666
    async createUniqueConstraint(
2667
        tableOrName: Table | string,
2668
        uniqueConstraint: TableUnique,
2669
    ): Promise<void> {
2670
        const table = InstanceChecker.isTable(tableOrName)
24✔
2671
            ? tableOrName
2672
            : await this.getCachedTable(tableOrName)
2673

2674
        // new unique constraint may be passed without name. In this case we generate unique name manually.
2675
        if (!uniqueConstraint.name)
24✔
2676
            uniqueConstraint.name =
8✔
2677
                this.connection.namingStrategy.uniqueConstraintName(
2678
                    table,
2679
                    uniqueConstraint.columnNames,
2680
                )
2681

2682
        const up = this.createUniqueConstraintSql(table, uniqueConstraint)
24✔
2683
        const down = this.dropUniqueConstraintSql(table, uniqueConstraint)
24✔
2684
        await this.executeQueries(up, down)
24✔
2685
        table.addUniqueConstraint(uniqueConstraint)
24✔
2686
    }
2687

2688
    /**
2689
     * Creates new unique constraints.
2690
     */
2691
    async createUniqueConstraints(
2692
        tableOrName: Table | string,
2693
        uniqueConstraints: TableUnique[],
2694
    ): Promise<void> {
2695
        for (const uniqueConstraint of uniqueConstraints) {
16✔
2696
            await this.createUniqueConstraint(tableOrName, uniqueConstraint)
16✔
2697
        }
2698
    }
2699

2700
    /**
2701
     * Drops unique constraint.
2702
     */
2703
    async dropUniqueConstraint(
2704
        tableOrName: Table | string,
2705
        uniqueOrName: TableUnique | string,
2706
    ): Promise<void> {
2707
        const table = InstanceChecker.isTable(tableOrName)
24!
2708
            ? tableOrName
2709
            : await this.getCachedTable(tableOrName)
2710
        const uniqueConstraint = InstanceChecker.isTableUnique(uniqueOrName)
24!
2711
            ? uniqueOrName
2712
            : table.uniques.find((u) => u.name === uniqueOrName)
×
2713
        if (!uniqueConstraint)
24!
2714
            throw new TypeORMError(
×
2715
                `Supplied unique constraint was not found in table ${table.name}`,
2716
            )
2717

2718
        const up = this.dropUniqueConstraintSql(table, uniqueConstraint)
24✔
2719
        const down = this.createUniqueConstraintSql(table, uniqueConstraint)
24✔
2720
        await this.executeQueries(up, down)
24✔
2721
        table.removeUniqueConstraint(uniqueConstraint)
24✔
2722
    }
2723

2724
    /**
2725
     * Drops unique constraints.
2726
     */
2727
    async dropUniqueConstraints(
2728
        tableOrName: Table | string,
2729
        uniqueConstraints: TableUnique[],
2730
    ): Promise<void> {
2731
        for (const uniqueConstraint of uniqueConstraints) {
20✔
2732
            await this.dropUniqueConstraint(tableOrName, uniqueConstraint)
20✔
2733
        }
2734
    }
2735

2736
    /**
2737
     * Creates new check constraint.
2738
     */
2739
    async createCheckConstraint(
2740
        tableOrName: Table | string,
2741
        checkConstraint: TableCheck,
2742
    ): Promise<void> {
2743
        const table = InstanceChecker.isTable(tableOrName)
20✔
2744
            ? tableOrName
2745
            : await this.getCachedTable(tableOrName)
2746

2747
        // new unique constraint may be passed without name. In this case we generate unique name manually.
2748
        if (!checkConstraint.name)
20✔
2749
            checkConstraint.name =
12✔
2750
                this.connection.namingStrategy.checkConstraintName(
2751
                    table,
2752
                    checkConstraint.expression!,
2753
                )
2754

2755
        const up = this.createCheckConstraintSql(table, checkConstraint)
20✔
2756
        const down = this.dropCheckConstraintSql(table, checkConstraint)
20✔
2757
        await this.executeQueries(up, down)
20✔
2758
        table.addCheckConstraint(checkConstraint)
20✔
2759
    }
2760

2761
    /**
2762
     * Creates new check constraints.
2763
     */
2764
    async createCheckConstraints(
2765
        tableOrName: Table | string,
2766
        checkConstraints: TableCheck[],
2767
    ): Promise<void> {
2768
        const promises = checkConstraints.map((checkConstraint) =>
8✔
2769
            this.createCheckConstraint(tableOrName, checkConstraint),
8✔
2770
        )
2771
        await Promise.all(promises)
8✔
2772
    }
2773

2774
    /**
2775
     * Drops check constraint.
2776
     */
2777
    async dropCheckConstraint(
2778
        tableOrName: Table | string,
2779
        checkOrName: TableCheck | string,
2780
    ): Promise<void> {
2781
        const table = InstanceChecker.isTable(tableOrName)
12!
2782
            ? tableOrName
2783
            : await this.getCachedTable(tableOrName)
2784
        const checkConstraint = InstanceChecker.isTableCheck(checkOrName)
12!
2785
            ? checkOrName
2786
            : table.checks.find((c) => c.name === checkOrName)
×
2787
        if (!checkConstraint)
12!
2788
            throw new TypeORMError(
×
2789
                `Supplied check constraint was not found in table ${table.name}`,
2790
            )
2791

2792
        const up = this.dropCheckConstraintSql(table, checkConstraint)
12✔
2793
        const down = this.createCheckConstraintSql(table, checkConstraint)
12✔
2794
        await this.executeQueries(up, down)
12✔
2795
        table.removeCheckConstraint(checkConstraint)
12✔
2796
    }
2797

2798
    /**
2799
     * Drops check constraints.
2800
     */
2801
    async dropCheckConstraints(
2802
        tableOrName: Table | string,
2803
        checkConstraints: TableCheck[],
2804
    ): Promise<void> {
2805
        const promises = checkConstraints.map((checkConstraint) =>
8✔
2806
            this.dropCheckConstraint(tableOrName, checkConstraint),
8✔
2807
        )
2808
        await Promise.all(promises)
8✔
2809
    }
2810

2811
    /**
2812
     * Creates new exclusion constraint.
2813
     */
2814
    async createExclusionConstraint(
2815
        tableOrName: Table | string,
2816
        exclusionConstraint: TableExclusion,
2817
    ): Promise<void> {
2818
        const table = InstanceChecker.isTable(tableOrName)
16✔
2819
            ? tableOrName
2820
            : await this.getCachedTable(tableOrName)
2821

2822
        // new unique constraint may be passed without name. In this case we generate unique name manually.
2823
        if (!exclusionConstraint.name)
16✔
2824
            exclusionConstraint.name =
8✔
2825
                this.connection.namingStrategy.exclusionConstraintName(
2826
                    table,
2827
                    exclusionConstraint.expression!,
2828
                )
2829

2830
        const up = this.createExclusionConstraintSql(table, exclusionConstraint)
16✔
2831
        const down = this.dropExclusionConstraintSql(table, exclusionConstraint)
16✔
2832
        await this.executeQueries(up, down)
16✔
2833
        table.addExclusionConstraint(exclusionConstraint)
16✔
2834
    }
2835

2836
    /**
2837
     * Creates new exclusion constraints.
2838
     */
2839
    async createExclusionConstraints(
2840
        tableOrName: Table | string,
2841
        exclusionConstraints: TableExclusion[],
2842
    ): Promise<void> {
2843
        const promises = exclusionConstraints.map((exclusionConstraint) =>
12✔
2844
            this.createExclusionConstraint(tableOrName, exclusionConstraint),
16✔
2845
        )
2846
        await Promise.all(promises)
12✔
2847
    }
2848

2849
    /**
2850
     * Drops exclusion constraint.
2851
     */
2852
    async dropExclusionConstraint(
2853
        tableOrName: Table | string,
2854
        exclusionOrName: TableExclusion | string,
2855
    ): Promise<void> {
2856
        const table = InstanceChecker.isTable(tableOrName)
12!
2857
            ? tableOrName
2858
            : await this.getCachedTable(tableOrName)
2859
        const exclusionConstraint = InstanceChecker.isTableExclusion(
12!
2860
            exclusionOrName,
2861
        )
2862
            ? exclusionOrName
2863
            : table.exclusions.find((c) => c.name === exclusionOrName)
×
2864
        if (!exclusionConstraint)
12!
2865
            throw new TypeORMError(
×
2866
                `Supplied exclusion constraint was not found in table ${table.name}`,
2867
            )
2868

2869
        const up = this.dropExclusionConstraintSql(table, exclusionConstraint)
12✔
2870
        const down = this.createExclusionConstraintSql(
12✔
2871
            table,
2872
            exclusionConstraint,
2873
        )
2874
        await this.executeQueries(up, down)
12✔
2875
        table.removeExclusionConstraint(exclusionConstraint)
12✔
2876
    }
2877

2878
    /**
2879
     * Drops exclusion constraints.
2880
     */
2881
    async dropExclusionConstraints(
2882
        tableOrName: Table | string,
2883
        exclusionConstraints: TableExclusion[],
2884
    ): Promise<void> {
2885
        const promises = exclusionConstraints.map((exclusionConstraint) =>
8✔
2886
            this.dropExclusionConstraint(tableOrName, exclusionConstraint),
8✔
2887
        )
2888
        await Promise.all(promises)
8✔
2889
    }
2890

2891
    /**
2892
     * Creates a new foreign key.
2893
     */
2894
    async createForeignKey(
2895
        tableOrName: Table | string,
2896
        foreignKey: TableForeignKey,
2897
    ): Promise<void> {
2898
        const table = InstanceChecker.isTable(tableOrName)
14,532✔
2899
            ? tableOrName
2900
            : await this.getCachedTable(tableOrName)
2901

2902
        // new FK may be passed without name. In this case we generate FK name manually.
2903
        if (!foreignKey.name)
14,532✔
2904
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
4✔
2905
                table,
2906
                foreignKey.columnNames,
2907
                this.getTablePath(foreignKey),
2908
                foreignKey.referencedColumnNames,
2909
            )
2910

2911
        const up = this.createForeignKeySql(table, foreignKey)
14,532✔
2912
        const down = this.dropForeignKeySql(table, foreignKey)
14,532✔
2913
        await this.executeQueries(up, down)
14,532✔
2914
        table.addForeignKey(foreignKey)
14,532✔
2915
    }
2916

2917
    /**
2918
     * Creates a new foreign keys.
2919
     */
2920
    async createForeignKeys(
2921
        tableOrName: Table | string,
2922
        foreignKeys: TableForeignKey[],
2923
    ): Promise<void> {
2924
        for (const foreignKey of foreignKeys) {
8,536✔
2925
            await this.createForeignKey(tableOrName, foreignKey)
14,528✔
2926
        }
2927
    }
2928

2929
    /**
2930
     * Drops a foreign key from the table.
2931
     */
2932
    async dropForeignKey(
2933
        tableOrName: Table | string,
2934
        foreignKeyOrName: TableForeignKey | string,
2935
    ): Promise<void> {
2936
        const table = InstanceChecker.isTable(tableOrName)
56!
2937
            ? tableOrName
2938
            : await this.getCachedTable(tableOrName)
2939
        const foreignKey = InstanceChecker.isTableForeignKey(foreignKeyOrName)
56!
2940
            ? foreignKeyOrName
2941
            : table.foreignKeys.find((fk) => fk.name === foreignKeyOrName)
×
2942
        if (!foreignKey)
56!
2943
            throw new TypeORMError(
×
2944
                `Supplied foreign key was not found in table ${table.name}`,
2945
            )
2946

2947
        if (!foreignKey.name) {
56!
2948
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
×
2949
                table,
2950
                foreignKey.columnNames,
2951
                this.getTablePath(foreignKey),
2952
                foreignKey.referencedColumnNames,
2953
            )
2954
        }
2955

2956
        const up = this.dropForeignKeySql(table, foreignKey)
56✔
2957
        const down = this.createForeignKeySql(table, foreignKey)
56✔
2958
        await this.executeQueries(up, down)
56✔
2959
        table.removeForeignKey(foreignKey)
56✔
2960
    }
2961

2962
    /**
2963
     * Drops a foreign keys from the table.
2964
     */
2965
    async dropForeignKeys(
2966
        tableOrName: Table | string,
2967
        foreignKeys: TableForeignKey[],
2968
    ): Promise<void> {
2969
        for (const foreignKey of foreignKeys) {
52✔
2970
            await this.dropForeignKey(tableOrName, foreignKey)
52✔
2971
        }
2972
    }
2973

2974
    /**
2975
     * Creates a new index.
2976
     */
2977
    async createIndex(
2978
        tableOrName: Table | string,
2979
        index: TableIndex,
2980
    ): Promise<void> {
2981
        const table = InstanceChecker.isTable(tableOrName)
48✔
2982
            ? tableOrName
2983
            : await this.getCachedTable(tableOrName)
2984

2985
        // new index may be passed without name. In this case we generate index name manually.
2986
        if (!index.name) index.name = this.generateIndexName(table, index)
48✔
2987

2988
        const up = this.createIndexSql(table, index)
48✔
2989
        const down = this.dropIndexSql(table, index)
48✔
2990
        await this.executeQueries(up, down)
48✔
2991
        table.addIndex(index)
48✔
2992
    }
2993

2994
    /**
2995
     * Create a new view index.
2996
     */
2997
    async createViewIndex(
2998
        viewOrName: View | string,
2999
        index: TableIndex,
3000
    ): Promise<void> {
3001
        const view = InstanceChecker.isView(viewOrName)
16!
3002
            ? viewOrName
3003
            : await this.getCachedView(viewOrName)
3004

3005
        // new index may be passed without name. In this case we generate index name manually.
3006
        if (!index.name) index.name = this.generateIndexName(view, index)
16!
3007

3008
        const up = this.createViewIndexSql(view, index)
16✔
3009
        const down = this.dropIndexSql(view, index)
16✔
3010
        await this.executeQueries(up, down)
16✔
3011
        view.addIndex(index)
16✔
3012
    }
3013

3014
    /**
3015
     * Creates a new indices
3016
     */
3017
    async createIndices(
3018
        tableOrName: Table | string,
3019
        indices: TableIndex[],
3020
    ): Promise<void> {
3021
        for (const index of indices) {
32✔
3022
            await this.createIndex(tableOrName, index)
32✔
3023
        }
3024
    }
3025

3026
    /**
3027
     * Creates new view indices
3028
     */
3029
    async createViewIndices(
3030
        viewOrName: View | string,
3031
        indices: TableIndex[],
3032
    ): Promise<void> {
3033
        for (const index of indices) {
16✔
3034
            await this.createViewIndex(viewOrName, index)
16✔
3035
        }
3036
    }
3037

3038
    /**
3039
     * Drops an index from the table.
3040
     */
3041
    async dropIndex(
3042
        tableOrName: Table | string,
3043
        indexOrName: TableIndex | string,
3044
    ): Promise<void> {
3045
        const table = InstanceChecker.isTable(tableOrName)
60✔
3046
            ? tableOrName
3047
            : await this.getCachedTable(tableOrName)
3048
        const index = InstanceChecker.isTableIndex(indexOrName)
60!
3049
            ? indexOrName
3050
            : table.indices.find((i) => i.name === indexOrName)
×
3051
        if (!index)
60!
3052
            throw new TypeORMError(
×
3053
                `Supplied index ${indexOrName} was not found in table ${table.name}`,
3054
            )
3055
        // old index may be passed without name. In this case we generate index name manually.
3056
        if (!index.name) index.name = this.generateIndexName(table, index)
60✔
3057

3058
        const up = this.dropIndexSql(table, index)
60✔
3059
        const down = this.createIndexSql(table, index)
60✔
3060
        await this.executeQueries(up, down)
60✔
3061
        table.removeIndex(index)
60✔
3062
    }
3063

3064
    /**
3065
     * Drops an index from a view.
3066
     */
3067
    async dropViewIndex(
3068
        viewOrName: View | string,
3069
        indexOrName: TableIndex | string,
3070
    ): Promise<void> {
3071
        const view = InstanceChecker.isView(viewOrName)
12!
3072
            ? viewOrName
3073
            : await this.getCachedView(viewOrName)
3074
        const index = InstanceChecker.isTableIndex(indexOrName)
12!
3075
            ? indexOrName
3076
            : view.indices.find((i) => i.name === indexOrName)
×
3077
        if (!index)
12!
3078
            throw new TypeORMError(
×
3079
                `Supplied index ${indexOrName} was not found in view ${view.name}`,
3080
            )
3081
        // old index may be passed without name. In this case we generate index name manually.
3082
        if (!index.name) index.name = this.generateIndexName(view, index)
12!
3083

3084
        const up = this.dropIndexSql(view, index)
12✔
3085
        const down = this.createViewIndexSql(view, index)
12✔
3086
        await this.executeQueries(up, down)
12✔
3087
        view.removeIndex(index)
12✔
3088
    }
3089

3090
    /**
3091
     * Drops an indices from the table.
3092
     */
3093
    async dropIndices(
3094
        tableOrName: Table | string,
3095
        indices: TableIndex[],
3096
    ): Promise<void> {
3097
        for (const index of indices) {
4✔
3098
            await this.dropIndex(tableOrName, index)
8✔
3099
        }
3100
    }
3101

3102
    /**
3103
     * Clears all table contents.
3104
     * Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
3105
     */
3106
    async clearTable(tableName: string): Promise<void> {
3107
        await this.query(`TRUNCATE TABLE ${this.escapePath(tableName)}`)
8✔
3108
    }
3109

3110
    /**
3111
     * Removes all tables from the currently connected database.
3112
     */
3113
    async clearDatabase(): Promise<void> {
3114
        const schemas: string[] = []
6,848✔
3115
        this.connection.entityMetadatas
6,848✔
3116
            .filter((metadata) => metadata.schema)
22,408✔
3117
            .forEach((metadata) => {
3118
                const isSchemaExist = !!schemas.find(
184✔
3119
                    (schema) => schema === metadata.schema,
116✔
3120
                )
3121
                if (!isSchemaExist) schemas.push(metadata.schema!)
184✔
3122
            })
3123
        schemas.push(this.driver.options.schema || "current_schema()")
6,848✔
3124
        const schemaNamesString = schemas
6,848✔
3125
            .map((name) => {
3126
                return name === "current_schema()" ? name : "'" + name + "'"
6,992✔
3127
            })
3128
            .join(", ")
3129

3130
        const isAnotherTransactionActive = this.isTransactionActive
6,848✔
3131
        if (!isAnotherTransactionActive) await this.startTransaction()
6,848✔
3132
        try {
6,848✔
3133
            // drop views
3134
            const selectViewDropsQuery =
3135
                `SELECT 'DROP VIEW IF EXISTS "' || schemaname || '"."' || viewname || '" CASCADE;' as "query" ` +
6,848✔
3136
                `FROM "pg_views" WHERE "schemaname" IN (${schemaNamesString}) AND "viewname" NOT IN ('geography_columns', 'geometry_columns', 'raster_columns', 'raster_overviews')`
3137
            const dropViewQueries: ObjectLiteral[] = await this.query(
6,848✔
3138
                selectViewDropsQuery,
3139
            )
3140
            await Promise.all(
6,848✔
3141
                dropViewQueries.map((q) => this.query(q["query"])),
96✔
3142
            )
3143

3144
            // drop materialized views
3145
            // Note: materialized views introduced in Postgres 9.3
3146
            if (DriverUtils.isReleaseVersionOrGreater(this.driver, "9.3")) {
6,848✔
3147
                const selectMatViewDropsQuery =
3148
                    `SELECT 'DROP MATERIALIZED VIEW IF EXISTS "' || schemaname || '"."' || matviewname || '" CASCADE;' as "query" ` +
6,848✔
3149
                    `FROM "pg_matviews" WHERE "schemaname" IN (${schemaNamesString})`
3150
                const dropMatViewQueries: ObjectLiteral[] = await this.query(
6,848✔
3151
                    selectMatViewDropsQuery,
3152
                )
3153
                await Promise.all(
6,848✔
3154
                    dropMatViewQueries.map((q) => this.query(q["query"])),
40✔
3155
                )
3156
            }
3157

3158
            // ignore spatial_ref_sys; it's a special table supporting PostGIS
3159
            // TODO generalize this as this.driver.ignoreTables
3160

3161
            // drop tables
3162
            const selectTableDropsQuery = `SELECT 'DROP TABLE IF EXISTS "' || schemaname || '"."' || tablename || '" CASCADE;' as "query" FROM "pg_tables" WHERE "schemaname" IN (${schemaNamesString}) AND "tablename" NOT IN ('spatial_ref_sys')`
6,848✔
3163
            const dropTableQueries: ObjectLiteral[] = await this.query(
6,848✔
3164
                selectTableDropsQuery,
3165
            )
3166
            await Promise.all(
6,848✔
3167
                dropTableQueries.map((q) => this.query(q["query"])),
21,500✔
3168
            )
3169

3170
            // drop enum types
3171
            await this.dropEnumTypes(schemaNamesString)
6,848✔
3172

3173
            if (!isAnotherTransactionActive) {
6,848✔
3174
                await this.commitTransaction()
6,844✔
3175
            }
3176
        } catch (error) {
3177
            try {
×
3178
                // we throw original error even if rollback thrown an error
3179
                if (!isAnotherTransactionActive) {
×
3180
                    await this.rollbackTransaction()
×
3181
                }
3182
            } catch {
3183
                // no-op
3184
            }
3185
            throw error
×
3186
        }
3187
    }
3188

3189
    // -------------------------------------------------------------------------
3190
    // Protected Methods
3191
    // -------------------------------------------------------------------------
3192

3193
    protected async loadViews(viewNames?: string[]): Promise<View[]> {
3194
        const hasTable = await this.hasTable(this.getTypeormMetadataTableName())
7,416✔
3195

3196
        if (!hasTable) return []
7,416✔
3197

3198
        if (!viewNames) {
196!
3199
            viewNames = []
×
3200
        }
3201

3202
        const currentDatabase = await this.getCurrentDatabase()
196✔
3203
        const currentSchema = await this.getCurrentSchema()
196✔
3204
        const viewsCondition =
3205
            viewNames.length === 0
196✔
3206
                ? "1=1"
3207
                : viewNames
3208
                      .map((tableName) => this.driver.parseTableName(tableName))
248✔
3209
                      .map(({ schema, tableName }) => {
3210
                          if (!schema) {
248!
3211
                              schema =
×
3212
                                  this.driver.options.schema || currentSchema
×
3213
                          }
3214

3215
                          return `("t"."schema" = '${schema}' AND "t"."name" = '${tableName}')`
248✔
3216
                      })
3217
                      .join(" OR ")
3218

3219
        const constraintsCondition =
3220
            viewNames.length === 0
196✔
3221
                ? "1=1"
3222
                : viewNames
3223
                      .map((tableName) => this.driver.parseTableName(tableName))
248✔
3224
                      .map(({ schema, tableName }) => {
3225
                          if (!schema) {
248!
3226
                              schema =
×
3227
                                  this.driver.options.schema || currentSchema
×
3228
                          }
3229

3230
                          return `("ns"."nspname" = '${schema}' AND "t"."relname" = '${tableName}')`
248✔
3231
                      })
3232
                      .join(" OR ")
3233

3234
        const indicesSql =
3235
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", ` +
196✔
3236
            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", ` +
3237
            `"types"."typname" AS "type_name" ` +
3238
            `FROM "pg_class" "t" ` +
3239
            `INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" ` +
3240
            `INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid"  AND "a"."attnum" = ANY ("ix"."indkey") ` +
3241
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" ` +
3242
            `INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" ` +
3243
            `INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" ` +
3244
            `LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" ` +
3245
            `WHERE "t"."relkind" IN ('m') AND "cnst"."contype" IS NULL AND (${constraintsCondition})`
3246

3247
        const query =
3248
            `SELECT "t".* FROM ${this.escapePath(
196✔
3249
                this.getTypeormMetadataTableName(),
3250
            )} "t" ` +
3251
            `INNER JOIN "pg_catalog"."pg_class" "c" ON "c"."relname" = "t"."name" ` +
3252
            `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "c"."relnamespace" AND "n"."nspname" = "t"."schema" ` +
3253
            `WHERE "t"."type" IN ('${MetadataTableType.VIEW}', '${
3254
                MetadataTableType.MATERIALIZED_VIEW
3255
            }') ${viewsCondition ? `AND (${viewsCondition})` : ""}`
196!
3256

3257
        const dbViews = await this.query(query)
196✔
3258
        const dbIndices: ObjectLiteral[] = await this.query(indicesSql)
196✔
3259
        return dbViews.map((dbView: any) => {
196✔
3260
            // find index constraints of table, group them by constraint name and build TableIndex.
3261
            const tableIndexConstraints = OrmUtils.uniq(
100✔
3262
                dbIndices.filter((dbIndex) => {
3263
                    return (
28✔
3264
                        dbIndex["table_name"] === dbView["name"] &&
56✔
3265
                        dbIndex["table_schema"] === dbView["schema"]
3266
                    )
3267
                }),
3268
                (dbIndex) => dbIndex["constraint_name"],
28✔
3269
            )
3270
            const view = new View()
100✔
3271
            const schema =
3272
                dbView["schema"] === currentSchema &&
100!
3273
                !this.driver.options.schema
3274
                    ? undefined
3275
                    : dbView["schema"]
3276
            view.database = currentDatabase
100✔
3277
            view.schema = dbView["schema"]
100✔
3278
            view.name = this.driver.buildTableName(dbView["name"], schema)
100✔
3279
            view.expression = dbView["value"]
100✔
3280
            view.materialized =
100✔
3281
                dbView["type"] === MetadataTableType.MATERIALIZED_VIEW
3282
            view.indices = tableIndexConstraints.map((constraint) => {
100✔
3283
                const indices = dbIndices.filter((index) => {
28✔
3284
                    return (
28✔
3285
                        index["table_schema"] === constraint["table_schema"] &&
84✔
3286
                        index["table_name"] === constraint["table_name"] &&
3287
                        index["constraint_name"] ===
3288
                            constraint["constraint_name"]
3289
                    )
3290
                })
3291
                return new TableIndex(<TableIndexOptions>{
28✔
3292
                    view: view,
3293
                    name: constraint["constraint_name"],
3294
                    columnNames: indices.map((i) => i["column_name"]),
28✔
3295
                    isUnique: constraint["is_unique"] === "TRUE",
3296
                    where: constraint["condition"],
3297
                    isFulltext: false,
3298
                })
3299
            })
3300
            return view
100✔
3301
        })
3302
    }
3303

3304
    /**
3305
     * Loads all tables (with given names) from the database and creates a Table from them.
3306
     */
3307
    protected async loadTables(tableNames?: string[]): Promise<Table[]> {
3308
        // if no tables given then no need to proceed
3309
        if (tableNames && tableNames.length === 0) {
8,948✔
3310
            return []
120✔
3311
        }
3312

3313
        const currentSchema = await this.getCurrentSchema()
8,828✔
3314
        const currentDatabase = await this.getCurrentDatabase()
8,828✔
3315

3316
        const dbTables: {
3317
            table_schema: string
3318
            table_name: string
3319
            table_comment: string
3320
        }[] = []
8,828✔
3321

3322
        if (!tableNames) {
8,828!
3323
            const tablesSql = `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables"`
×
3324
            dbTables.push(...(await this.query(tablesSql)))
×
3325
        } else {
3326
            const tablesCondition = tableNames
8,828✔
3327
                .map((tableName) => this.driver.parseTableName(tableName))
24,360✔
3328
                .map(({ schema, tableName }) => {
3329
                    return `("table_schema" = '${
24,360✔
3330
                        schema || currentSchema
24,360!
3331
                    }' AND "table_name" = '${tableName}')`
3332
                })
3333
                .join(" OR ")
3334

3335
            const tablesSql =
3336
                `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE ` +
8,828✔
3337
                tablesCondition
3338
            dbTables.push(...(await this.query(tablesSql)))
8,828✔
3339
        }
3340

3341
        // if tables were not found in the db, no need to proceed
3342
        if (dbTables.length === 0) {
8,828✔
3343
            return []
6,860✔
3344
        }
3345

3346
        /**
3347
         * Uses standard SQL information_schema.columns table and postgres-specific
3348
         * pg_catalog.pg_attribute table to get column information.
3349
         * @see https://stackoverflow.com/a/19541865
3350
         */
3351
        const columnsCondition = dbTables
1,968✔
3352
            .map(({ table_schema, table_name }) => {
3353
                return `("table_schema" = '${table_schema}' AND "table_name" = '${table_name}')`
2,788✔
3354
            })
3355
            .join(" OR ")
3356
        const columnsSql =
3357
            `SELECT columns.*, pg_catalog.col_description(('"' || table_catalog || '"."' || table_schema || '"."' || table_name || '"')::regclass::oid, ordinal_position) AS description, ` +
1,968✔
3358
            `('"' || "udt_schema" || '"."' || "udt_name" || '"')::"regtype" AS "regtype", pg_catalog.format_type("col_attr"."atttypid", "col_attr"."atttypmod") AS "format_type" ` +
3359
            `FROM "information_schema"."columns" ` +
3360
            `LEFT JOIN "pg_catalog"."pg_attribute" AS "col_attr" ON "col_attr"."attname" = "columns"."column_name" ` +
3361
            `AND "col_attr"."attrelid" = ( ` +
3362
            `SELECT "cls"."oid" FROM "pg_catalog"."pg_class" AS "cls" ` +
3363
            `LEFT JOIN "pg_catalog"."pg_namespace" AS "ns" ON "ns"."oid" = "cls"."relnamespace" ` +
3364
            `WHERE "cls"."relname" = "columns"."table_name" ` +
3365
            `AND "ns"."nspname" = "columns"."table_schema" ` +
3366
            `) ` +
3367
            `WHERE ` +
3368
            columnsCondition
3369

3370
        const constraintsCondition = dbTables
1,968✔
3371
            .map(({ table_schema, table_name }) => {
3372
                return `("ns"."nspname" = '${table_schema}' AND "t"."relname" = '${table_name}')`
2,788✔
3373
            })
3374
            .join(" OR ")
3375

3376
        const constraintsSql =
3377
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "cnst"."conname" AS "constraint_name", ` +
1,968✔
3378
            `pg_get_constraintdef("cnst"."oid") AS "expression", ` +
3379
            `CASE "cnst"."contype" WHEN 'p' THEN 'PRIMARY' WHEN 'u' THEN 'UNIQUE' WHEN 'c' THEN 'CHECK' WHEN 'x' THEN 'EXCLUDE' END AS "constraint_type", "a"."attname" AS "column_name" ` +
3380
            `FROM "pg_constraint" "cnst" ` +
3381
            `INNER JOIN "pg_class" "t" ON "t"."oid" = "cnst"."conrelid" ` +
3382
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "cnst"."connamespace" ` +
3383
            `LEFT JOIN "pg_attribute" "a" ON "a"."attrelid" = "cnst"."conrelid" AND "a"."attnum" = ANY ("cnst"."conkey") ` +
3384
            `WHERE "t"."relkind" IN ('r', 'p') AND (${constraintsCondition})`
3385

3386
        const indicesSql =
3387
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", ` +
1,968✔
3388
            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", ` +
3389
            `"types"."typname" AS "type_name", "am"."amname" AS "index_type" ` +
3390
            `FROM "pg_class" "t" ` +
3391
            `INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" ` +
3392
            `INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid"  AND "a"."attnum" = ANY ("ix"."indkey") ` +
3393
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" ` +
3394
            `INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" ` +
3395
            `INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" ` +
3396
            `INNER JOIN "pg_am" "am" ON "i"."relam" = "am"."oid" ` +
3397
            `LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" ` +
3398
            `WHERE "t"."relkind" IN ('r', 'p') AND "cnst"."contype" IS NULL AND (${constraintsCondition})`
3399

3400
        const foreignKeysCondition = dbTables
1,968✔
3401
            .map(({ table_schema, table_name }) => {
3402
                return `("ns"."nspname" = '${table_schema}' AND "cl"."relname" = '${table_name}')`
2,788✔
3403
            })
3404
            .join(" OR ")
3405

3406
        const hasRelispartitionColumn =
3407
            await this.hasSupportForPartitionedTables()
1,968✔
3408
        const isPartitionCondition = hasRelispartitionColumn
1,968!
3409
            ? ` AND "cl"."relispartition" = 'f'`
3410
            : ""
3411

3412
        const foreignKeysSql =
3413
            `SELECT "con"."conname" AS "constraint_name", "con"."nspname" AS "table_schema", "con"."relname" AS "table_name", "att2"."attname" AS "column_name", ` +
1,968✔
3414
            `"ns"."nspname" AS "referenced_table_schema", "cl"."relname" AS "referenced_table_name", "att"."attname" AS "referenced_column_name", "con"."confdeltype" AS "on_delete", ` +
3415
            `"con"."confupdtype" AS "on_update", "con"."condeferrable" AS "deferrable", "con"."condeferred" AS "deferred" ` +
3416
            `FROM ( ` +
3417
            `SELECT UNNEST ("con1"."conkey") AS "parent", UNNEST ("con1"."confkey") AS "child", "con1"."confrelid", "con1"."conrelid", "con1"."conname", "con1"."contype", "ns"."nspname", ` +
3418
            `"cl"."relname", "con1"."condeferrable", ` +
3419
            `CASE WHEN "con1"."condeferred" THEN 'INITIALLY DEFERRED' ELSE 'INITIALLY IMMEDIATE' END as condeferred, ` +
3420
            `CASE "con1"."confdeltype" WHEN 'a' THEN 'NO ACTION' WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' END as "confdeltype", ` +
3421
            `CASE "con1"."confupdtype" WHEN 'a' THEN 'NO ACTION' WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' END as "confupdtype" ` +
3422
            `FROM "pg_class" "cl" ` +
3423
            `INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" ` +
3424
            `INNER JOIN "pg_constraint" "con1" ON "con1"."conrelid" = "cl"."oid" ` +
3425
            `WHERE "con1"."contype" = 'f' AND (${foreignKeysCondition}) ` +
3426
            `) "con" ` +
3427
            `INNER JOIN "pg_attribute" "att" ON "att"."attrelid" = "con"."confrelid" AND "att"."attnum" = "con"."child" ` +
3428
            `INNER JOIN "pg_class" "cl" ON "cl"."oid" = "con"."confrelid" ${isPartitionCondition}` +
3429
            `INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" ` +
3430
            `INNER JOIN "pg_attribute" "att2" ON "att2"."attrelid" = "con"."conrelid" AND "att2"."attnum" = "con"."parent"`
3431

3432
        const [
3433
            dbColumns,
3434
            dbConstraints,
3435
            dbIndices,
3436
            dbForeignKeys,
3437
        ]: ObjectLiteral[][] = await Promise.all([
1,968✔
3438
            this.query(columnsSql),
3439
            this.query(constraintsSql),
3440
            this.query(indicesSql),
3441
            this.query(foreignKeysSql),
3442
        ])
3443

3444
        // create tables for loaded tables
3445
        return Promise.all(
1,968✔
3446
            dbTables.map(async (dbTable) => {
3447
                const table = new Table()
2,788✔
3448

3449
                const getSchemaFromKey = (dbObject: any, key: string) => {
2,788✔
3450
                    return dbObject[key] === currentSchema &&
3,668✔
3451
                        (!this.driver.options.schema ||
3452
                            this.driver.options.schema === currentSchema)
3453
                        ? undefined
3454
                        : dbObject[key]
3455
                }
3456
                // We do not need to join schema name, when database is by default.
3457
                const schema = getSchemaFromKey(dbTable, "table_schema")
2,788✔
3458
                table.database = currentDatabase
2,788✔
3459
                table.schema = dbTable["table_schema"]
2,788✔
3460
                table.comment = dbTable["table_comment"]
2,788✔
3461
                table.name = this.driver.buildTableName(
2,788✔
3462
                    dbTable["table_name"],
3463
                    schema,
3464
                )
3465

3466
                // create columns from the loaded columns
3467
                table.columns = await Promise.all(
2,788✔
3468
                    dbColumns
3469
                        .filter(
3470
                            (dbColumn) =>
3471
                                dbColumn["table_name"] ===
27,144✔
3472
                                    dbTable["table_name"] &&
3473
                                dbColumn["table_schema"] ===
3474
                                    dbTable["table_schema"],
3475
                        )
3476
                        .map(async (dbColumn) => {
3477
                            const columnConstraints = dbConstraints.filter(
10,136✔
3478
                                (dbConstraint) => {
3479
                                    return (
59,964✔
3480
                                        dbConstraint["table_name"] ===
110,068✔
3481
                                            dbColumn["table_name"] &&
3482
                                        dbConstraint["table_schema"] ===
3483
                                            dbColumn["table_schema"] &&
3484
                                        dbConstraint["column_name"] ===
3485
                                            dbColumn["column_name"]
3486
                                    )
3487
                                },
3488
                            )
3489

3490
                            const tableColumn = new TableColumn()
10,136✔
3491
                            tableColumn.name = dbColumn["column_name"]
10,136✔
3492
                            tableColumn.type = dbColumn["regtype"].toLowerCase()
10,136✔
3493

3494
                            if (
10,136✔
3495
                                tableColumn.type === "numeric" ||
40,416✔
3496
                                tableColumn.type === "numeric[]" ||
3497
                                tableColumn.type === "decimal" ||
3498
                                tableColumn.type === "float"
3499
                            ) {
3500
                                let numericPrecision =
3501
                                    dbColumn["numeric_precision"]
44✔
3502
                                let numericScale = dbColumn["numeric_scale"]
44✔
3503
                                if (dbColumn["data_type"] === "ARRAY") {
44✔
3504
                                    const numericSize = dbColumn[
4✔
3505
                                        "format_type"
3506
                                    ].match(
3507
                                        /^numeric\(([0-9]+),([0-9]+)\)\[\]$/,
3508
                                    )
3509
                                    if (numericSize) {
4✔
3510
                                        numericPrecision = +numericSize[1]
4✔
3511
                                        numericScale = +numericSize[2]
4✔
3512
                                    }
3513
                                }
3514
                                // If one of these properties was set, and another was not, Postgres sets '0' in to unspecified property
3515
                                // we set 'undefined' in to unspecified property to avoid changing column on sync
3516
                                if (
44✔
3517
                                    numericPrecision !== null &&
72✔
3518
                                    !this.isDefaultColumnPrecision(
3519
                                        table,
3520
                                        tableColumn,
3521
                                        numericPrecision,
3522
                                    )
3523
                                ) {
3524
                                    tableColumn.precision = numericPrecision
28✔
3525
                                } else if (
16!
3526
                                    numericScale !== null &&
16!
3527
                                    !this.isDefaultColumnScale(
3528
                                        table,
3529
                                        tableColumn,
3530
                                        numericScale,
3531
                                    )
3532
                                ) {
3533
                                    tableColumn.precision = undefined
×
3534
                                }
3535
                                if (
44✔
3536
                                    numericScale !== null &&
72✔
3537
                                    !this.isDefaultColumnScale(
3538
                                        table,
3539
                                        tableColumn,
3540
                                        numericScale,
3541
                                    )
3542
                                ) {
3543
                                    tableColumn.scale = numericScale
28✔
3544
                                } else if (
16!
3545
                                    numericPrecision !== null &&
16!
3546
                                    !this.isDefaultColumnPrecision(
3547
                                        table,
3548
                                        tableColumn,
3549
                                        numericPrecision,
3550
                                    )
3551
                                ) {
3552
                                    tableColumn.scale = undefined
×
3553
                                }
3554
                            }
3555

3556
                            if (
10,136✔
3557
                                tableColumn.type === "interval" ||
50,268✔
3558
                                tableColumn.type === "time without time zone" ||
3559
                                tableColumn.type === "time with time zone" ||
3560
                                tableColumn.type ===
3561
                                    "timestamp without time zone" ||
3562
                                tableColumn.type === "timestamp with time zone"
3563
                            ) {
3564
                                tableColumn.precision =
248✔
3565
                                    !this.isDefaultColumnPrecision(
248✔
3566
                                        table,
3567
                                        tableColumn,
3568
                                        dbColumn["datetime_precision"],
3569
                                    )
3570
                                        ? dbColumn["datetime_precision"]
3571
                                        : undefined
3572
                            }
3573

3574
                            // check if column has user-defined data type.
3575
                            // NOTE: if ENUM type defined with "array:true" it comes with ARRAY type instead of USER-DEFINED
3576
                            if (
10,136✔
3577
                                dbColumn["data_type"] === "USER-DEFINED" ||
19,660✔
3578
                                dbColumn["data_type"] === "ARRAY"
3579
                            ) {
3580
                                const { name } =
3581
                                    await this.getUserDefinedTypeName(
892✔
3582
                                        table,
3583
                                        tableColumn,
3584
                                    )
3585

3586
                                // check if `enumName` is specified by user
3587
                                const builtEnumName = this.buildEnumName(
892✔
3588
                                    table,
3589
                                    tableColumn,
3590
                                    false,
3591
                                    true,
3592
                                )
3593
                                const enumName =
3594
                                    builtEnumName !== name ? name : undefined
892✔
3595

3596
                                // check if type is ENUM
3597
                                const sql =
3598
                                    `SELECT "e"."enumlabel" AS "value" FROM "pg_enum" "e" ` +
892✔
3599
                                    `INNER JOIN "pg_type" "t" ON "t"."oid" = "e"."enumtypid" ` +
3600
                                    `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
3601
                                    `WHERE "n"."nspname" = '${
3602
                                        dbTable["table_schema"]
3603
                                    }' AND "t"."typname" = '${
3604
                                        enumName || name
1,484✔
3605
                                    }'`
3606
                                const results: ObjectLiteral[] =
3607
                                    await this.query(sql)
892✔
3608

3609
                                if (results.length) {
892✔
3610
                                    tableColumn.type = "enum"
772✔
3611
                                    tableColumn.enum = results.map(
772✔
3612
                                        (result) => result["value"],
2,484✔
3613
                                    )
3614
                                    tableColumn.enumName = enumName
772✔
3615
                                }
3616

3617
                                if (dbColumn["data_type"] === "ARRAY") {
892✔
3618
                                    tableColumn.isArray = true
280✔
3619
                                    const type = tableColumn.type.replace(
280✔
3620
                                        "[]",
3621
                                        "",
3622
                                    )
3623
                                    tableColumn.type =
280✔
3624
                                        this.connection.driver.normalizeType({
3625
                                            type: type,
3626
                                        })
3627
                                }
3628
                            }
3629

3630
                            if (
10,136✔
3631
                                tableColumn.type === "geometry" ||
20,232✔
3632
                                tableColumn.type === "geography"
3633
                            ) {
3634
                                const sql =
3635
                                    `SELECT * FROM (` +
52✔
3636
                                    `SELECT "f_table_schema" "table_schema", "f_table_name" "table_name", ` +
3637
                                    `"f_${tableColumn.type}_column" "column_name", "srid", "type" ` +
3638
                                    `FROM "${tableColumn.type}_columns"` +
3639
                                    `) AS _ ` +
3640
                                    `WHERE "column_name" = '${dbColumn["column_name"]}' AND ` +
3641
                                    `"table_schema" = '${dbColumn["table_schema"]}' AND ` +
3642
                                    `"table_name" = '${dbColumn["table_name"]}'`
3643

3644
                                const results: ObjectLiteral[] =
3645
                                    await this.query(sql)
52✔
3646

3647
                                if (results.length > 0) {
52✔
3648
                                    tableColumn.spatialFeatureType =
52✔
3649
                                        results[0].type
3650
                                    tableColumn.srid = results[0].srid
52✔
3651
                                }
3652
                            }
3653

3654
                            // check only columns that have length property
3655
                            if (
10,136✔
3656
                                this.driver.withLengthColumnTypes.indexOf(
3657
                                    tableColumn.type as ColumnType,
3658
                                ) !== -1
3659
                            ) {
3660
                                let length
3661
                                if (tableColumn.isArray) {
4,548✔
3662
                                    const match = /\((\d+)\)/.exec(
32✔
3663
                                        dbColumn["format_type"],
3664
                                    )
3665
                                    length = match ? match[1] : undefined
32✔
3666
                                } else if (
4,516✔
3667
                                    dbColumn["character_maximum_length"]
3668
                                ) {
3669
                                    length =
412✔
3670
                                        dbColumn[
3671
                                            "character_maximum_length"
3672
                                        ].toString()
3673
                                }
3674
                                if (length) {
4,548✔
3675
                                    tableColumn.length =
416✔
3676
                                        !this.isDefaultColumnLength(
416✔
3677
                                            table,
3678
                                            tableColumn,
3679
                                            length,
3680
                                        )
3681
                                            ? length
3682
                                            : ""
3683
                                }
3684
                            }
3685
                            tableColumn.isNullable =
10,136✔
3686
                                dbColumn["is_nullable"] === "YES"
3687

3688
                            const primaryConstraint = columnConstraints.find(
10,136✔
3689
                                (constraint) =>
3690
                                    constraint["constraint_type"] === "PRIMARY",
5,910✔
3691
                            )
3692
                            if (primaryConstraint) {
10,136✔
3693
                                tableColumn.isPrimary = true
2,896✔
3694
                                // find another columns involved in primary key constraint
3695
                                const anotherPrimaryConstraints =
3696
                                    dbConstraints.filter(
2,896✔
3697
                                        (constraint) =>
3698
                                            constraint["table_name"] ===
17,904✔
3699
                                                dbColumn["table_name"] &&
3700
                                            constraint["table_schema"] ===
3701
                                                dbColumn["table_schema"] &&
3702
                                            constraint["column_name"] !==
3703
                                                dbColumn["column_name"] &&
3704
                                            constraint["constraint_type"] ===
3705
                                                "PRIMARY",
3706
                                    )
3707

3708
                                // collect all column names
3709
                                const columnNames =
3710
                                    anotherPrimaryConstraints.map(
2,896✔
3711
                                        (constraint) =>
3712
                                            constraint["column_name"],
312✔
3713
                                    )
3714
                                columnNames.push(dbColumn["column_name"])
2,896✔
3715

3716
                                // build default primary key constraint name
3717
                                const pkName =
3718
                                    this.connection.namingStrategy.primaryKeyName(
2,896✔
3719
                                        table,
3720
                                        columnNames,
3721
                                    )
3722

3723
                                // if primary key has user-defined constraint name, write it in table column
3724
                                if (
2,896✔
3725
                                    primaryConstraint["constraint_name"] !==
3726
                                    pkName
3727
                                ) {
3728
                                    tableColumn.primaryKeyConstraintName =
108✔
3729
                                        primaryConstraint["constraint_name"]
3730
                                }
3731
                            }
3732

3733
                            const uniqueConstraints = columnConstraints.filter(
10,136✔
3734
                                (constraint) =>
3735
                                    constraint["constraint_type"] === "UNIQUE",
6,016✔
3736
                            )
3737
                            const isConstraintComposite =
3738
                                uniqueConstraints.every((uniqueConstraint) => {
10,136✔
3739
                                    return dbConstraints.some(
1,460✔
3740
                                        (dbConstraint) =>
3741
                                            dbConstraint["constraint_type"] ===
8,632✔
3742
                                                "UNIQUE" &&
3743
                                            dbConstraint["constraint_name"] ===
3744
                                                uniqueConstraint[
3745
                                                    "constraint_name"
3746
                                                ] &&
3747
                                            dbConstraint["column_name"] !==
3748
                                                dbColumn["column_name"],
3749
                                    )
3750
                                })
3751
                            tableColumn.isUnique =
10,136✔
3752
                                uniqueConstraints.length > 0 &&
11,596✔
3753
                                !isConstraintComposite
3754

3755
                            if (dbColumn.is_identity === "YES") {
10,136!
3756
                                // Postgres 10+ Identity column
3757
                                tableColumn.isGenerated = true
×
3758
                                tableColumn.generationStrategy = "identity"
×
3759
                                tableColumn.generatedIdentity =
×
3760
                                    dbColumn.identity_generation
3761
                            } else if (
10,136✔
3762
                                dbColumn["column_default"] !== null &&
12,748✔
3763
                                dbColumn["column_default"] !== undefined
3764
                            ) {
3765
                                const serialDefaultName = `nextval('${this.buildSequenceName(
2,612✔
3766
                                    table,
3767
                                    dbColumn["column_name"],
3768
                                )}'::regclass)`
3769
                                const serialDefaultPath = `nextval('${this.buildSequencePath(
2,612✔
3770
                                    table,
3771
                                    dbColumn["column_name"],
3772
                                )}'::regclass)`
3773

3774
                                const defaultWithoutQuotes = dbColumn[
2,612✔
3775
                                    "column_default"
3776
                                ].replace(/"/g, "")
3777

3778
                                if (
2,612✔
3779
                                    defaultWithoutQuotes ===
3,744✔
3780
                                        serialDefaultName ||
3781
                                    defaultWithoutQuotes === serialDefaultPath
3782
                                ) {
3783
                                    tableColumn.isGenerated = true
1,660✔
3784
                                    tableColumn.generationStrategy = "increment"
1,660✔
3785
                                } else if (
952✔
3786
                                    dbColumn["column_default"] ===
1,880✔
3787
                                        "gen_random_uuid()" ||
3788
                                    /^uuid_generate_v\d\(\)/.test(
3789
                                        dbColumn["column_default"],
3790
                                    )
3791
                                ) {
3792
                                    if (tableColumn.type === "uuid") {
120✔
3793
                                        tableColumn.isGenerated = true
112✔
3794
                                        tableColumn.generationStrategy = "uuid"
112✔
3795
                                    } else {
3796
                                        tableColumn.default =
8✔
3797
                                            dbColumn["column_default"]
3798
                                    }
3799
                                } else if (
832✔
3800
                                    dbColumn["column_default"] === "now()" ||
1,600✔
3801
                                    dbColumn["column_default"].indexOf(
3802
                                        "'now'::text",
3803
                                    ) !== -1
3804
                                ) {
3805
                                    tableColumn.default =
208✔
3806
                                        dbColumn["column_default"]
3807
                                } else {
3808
                                    tableColumn.default = dbColumn[
624✔
3809
                                        "column_default"
3810
                                    ].replace(/::[\w\s.[\]\-"]+/g, "")
3811
                                    tableColumn.default =
624✔
3812
                                        tableColumn.default.replace(
3813
                                            /^(-?\d+)$/,
3814
                                            "'$1'",
3815
                                        )
3816
                                }
3817
                            }
3818

3819
                            if (
10,136✔
3820
                                dbColumn["is_generated"] === "ALWAYS" &&
10,304✔
3821
                                dbColumn["generation_expression"]
3822
                            ) {
3823
                                // In postgres there is no VIRTUAL generated column type
3824
                                tableColumn.generatedType = "STORED"
168✔
3825
                                // We cannot relay on information_schema.columns.generation_expression, because it is formatted different.
3826
                                const asExpressionQuery =
3827
                                    this.selectTypeormMetadataSql({
168✔
3828
                                        database: currentDatabase,
3829
                                        schema: dbTable["table_schema"],
3830
                                        table: dbTable["table_name"],
3831
                                        type: MetadataTableType.GENERATED_COLUMN,
3832
                                        name: tableColumn.name,
3833
                                    })
3834

3835
                                const results = await this.query(
168✔
3836
                                    asExpressionQuery.query,
3837
                                    asExpressionQuery.parameters,
3838
                                )
3839
                                if (results[0] && results[0].value) {
168!
3840
                                    tableColumn.asExpression = results[0].value
168✔
3841
                                } else {
3842
                                    tableColumn.asExpression = ""
×
3843
                                }
3844
                            }
3845

3846
                            tableColumn.comment = dbColumn["description"]
10,136✔
3847
                                ? dbColumn["description"]
3848
                                : undefined
3849
                            if (dbColumn["character_set_name"])
10,136!
3850
                                tableColumn.charset =
×
3851
                                    dbColumn["character_set_name"]
3852
                            if (dbColumn["collation_name"])
10,136✔
3853
                                tableColumn.collation =
20✔
3854
                                    dbColumn["collation_name"]
3855
                            return tableColumn
10,136✔
3856
                        }),
3857
                )
3858

3859
                // find unique constraints of table, group them by constraint name and build TableUnique.
3860
                const tableUniqueConstraints = OrmUtils.uniq(
2,788✔
3861
                    dbConstraints.filter((dbConstraint) => {
3862
                        return (
17,356✔
3863
                            dbConstraint["table_name"] ===
29,396✔
3864
                                dbTable["table_name"] &&
3865
                            dbConstraint["table_schema"] ===
3866
                                dbTable["table_schema"] &&
3867
                            dbConstraint["constraint_type"] === "UNIQUE"
3868
                        )
3869
                    }),
3870
                    (dbConstraint) => dbConstraint["constraint_name"],
2,454✔
3871
                )
3872

3873
                table.uniques = tableUniqueConstraints.map((constraint) => {
2,788✔
3874
                    const uniques = dbConstraints.filter(
1,044✔
3875
                        (dbC) =>
3876
                            dbC["constraint_name"] ===
7,856✔
3877
                            constraint["constraint_name"],
3878
                    )
3879
                    return new TableUnique({
1,044✔
3880
                        name: constraint["constraint_name"],
3881
                        columnNames: uniques.map((u) => u["column_name"]),
1,472✔
3882
                        deferrable: constraint["deferrable"]
1,044!
3883
                            ? constraint["deferred"]
3884
                            : undefined,
3885
                    })
3886
                })
3887

3888
                // find check constraints of table, group them by constraint name and build TableCheck.
3889
                const tableCheckConstraints = OrmUtils.uniq(
2,788✔
3890
                    dbConstraints.filter((dbConstraint) => {
3891
                        return (
17,356✔
3892
                            dbConstraint["table_name"] ===
29,396✔
3893
                                dbTable["table_name"] &&
3894
                            dbConstraint["table_schema"] ===
3895
                                dbTable["table_schema"] &&
3896
                            dbConstraint["constraint_type"] === "CHECK"
3897
                        )
3898
                    }),
3899
                    (dbConstraint) => dbConstraint["constraint_name"],
476✔
3900
                )
3901

3902
                table.checks = tableCheckConstraints.map((constraint) => {
2,788✔
3903
                    const checks = dbConstraints.filter(
416✔
3904
                        (dbC) =>
3905
                            dbC["constraint_name"] ===
3,584✔
3906
                            constraint["constraint_name"],
3907
                    )
3908
                    return new TableCheck({
416✔
3909
                        name: constraint["constraint_name"],
3910
                        columnNames: checks.map((c) => c["column_name"]),
432✔
3911
                        expression: constraint["expression"].replace(
3912
                            /^\s*CHECK\s*\((.*)\)\s*$/i,
3913
                            "$1",
3914
                        ),
3915
                    })
3916
                })
3917

3918
                // find exclusion constraints of table, group them by constraint name and build TableExclusion.
3919
                const tableExclusionConstraints = OrmUtils.uniq(
2,788✔
3920
                    dbConstraints.filter((dbConstraint) => {
3921
                        return (
17,356✔
3922
                            dbConstraint["table_name"] ===
29,396✔
3923
                                dbTable["table_name"] &&
3924
                            dbConstraint["table_schema"] ===
3925
                                dbTable["table_schema"] &&
3926
                            dbConstraint["constraint_type"] === "EXCLUDE"
3927
                        )
3928
                    }),
3929
                    (dbConstraint) => dbConstraint["constraint_name"],
328✔
3930
                )
3931

3932
                table.exclusions = tableExclusionConstraints.map(
2,788✔
3933
                    (constraint) => {
3934
                        return new TableExclusion({
324✔
3935
                            name: constraint["constraint_name"],
3936
                            expression: constraint["expression"].substring(8), // trim EXCLUDE from start of expression
3937
                        })
3938
                    },
3939
                )
3940

3941
                // find foreign key constraints of table, group them by constraint name and build TableForeignKey.
3942
                const tableForeignKeyConstraints = OrmUtils.uniq(
2,788✔
3943
                    dbForeignKeys.filter((dbForeignKey) => {
3944
                        return (
3,144✔
3945
                            dbForeignKey["table_name"] ===
4,040✔
3946
                                dbTable["table_name"] &&
3947
                            dbForeignKey["table_schema"] ===
3948
                                dbTable["table_schema"]
3949
                        )
3950
                    }),
3951
                    (dbForeignKey) => dbForeignKey["constraint_name"],
1,356✔
3952
                )
3953

3954
                table.foreignKeys = tableForeignKeyConstraints.map(
2,788✔
3955
                    (dbForeignKey) => {
3956
                        const foreignKeys = dbForeignKeys.filter(
880✔
3957
                            (dbFk) =>
3958
                                dbFk["constraint_name"] ===
2,216✔
3959
                                dbForeignKey["constraint_name"],
3960
                        )
3961

3962
                        // if referenced table located in currently used schema, we don't need to concat schema name to table name.
3963
                        const schema = getSchemaFromKey(
880✔
3964
                            dbForeignKey,
3965
                            "referenced_table_schema",
3966
                        )
3967
                        const referencedTableName = this.driver.buildTableName(
880✔
3968
                            dbForeignKey["referenced_table_name"],
3969
                            schema,
3970
                        )
3971

3972
                        return new TableForeignKey({
880✔
3973
                            name: dbForeignKey["constraint_name"],
3974
                            columnNames: foreignKeys.map(
3975
                                (dbFk) => dbFk["column_name"],
896✔
3976
                            ),
3977
                            referencedSchema:
3978
                                dbForeignKey["referenced_table_schema"],
3979
                            referencedTableName: referencedTableName,
3980
                            referencedColumnNames: foreignKeys.map(
3981
                                (dbFk) => dbFk["referenced_column_name"],
896✔
3982
                            ),
3983
                            onDelete: dbForeignKey["on_delete"],
3984
                            onUpdate: dbForeignKey["on_update"],
3985
                            deferrable: dbForeignKey["deferrable"]
880!
3986
                                ? dbForeignKey["deferred"]
3987
                                : undefined,
3988
                        })
3989
                    },
3990
                )
3991

3992
                // find index constraints of table, group them by constraint name and build TableIndex.
3993
                const tableIndexConstraints = OrmUtils.uniq(
2,788✔
3994
                    dbIndices.filter((dbIndex) => {
3995
                        return (
1,544✔
3996
                            dbIndex["table_name"] === dbTable["table_name"] &&
2,248✔
3997
                            dbIndex["table_schema"] === dbTable["table_schema"]
3998
                        )
3999
                    }),
4000
                    (dbIndex) => dbIndex["constraint_name"],
1,076✔
4001
                )
4002

4003
                table.indices = tableIndexConstraints.map((constraint) => {
2,788✔
4004
                    const indices = dbIndices.filter((index) => {
636✔
4005
                        return (
1,336✔
4006
                            index["table_schema"] ===
3,992✔
4007
                                constraint["table_schema"] &&
4008
                            index["table_name"] === constraint["table_name"] &&
4009
                            index["constraint_name"] ===
4010
                                constraint["constraint_name"]
4011
                        )
4012
                    })
4013
                    return new TableIndex(<TableIndexOptions>{
636✔
4014
                        table: table,
4015
                        name: constraint["constraint_name"],
4016
                        columnNames: indices.map((i) => i["column_name"]),
704✔
4017
                        isUnique: constraint["is_unique"] === "TRUE",
4018
                        where: constraint["condition"],
4019
                        isSpatial: constraint["index_type"] === "gist",
4020
                        isFulltext: false,
4021
                    })
4022
                })
4023

4024
                return table
2,788✔
4025
            }),
4026
        )
4027
    }
4028

4029
    /**
4030
     * Builds create table sql.
4031
     */
4032
    protected createTableSql(table: Table, createForeignKeys?: boolean): Query {
4033
        const columnDefinitions = table.columns
21,848✔
4034
            .map((column) => this.buildCreateColumnSql(table, column))
71,068✔
4035
            .join(", ")
4036
        let sql = `CREATE TABLE ${this.escapePath(table)} (${columnDefinitions}`
21,848✔
4037

4038
        table.columns
21,848✔
4039
            .filter((column) => column.isUnique)
71,068✔
4040
            .forEach((column) => {
4041
                const isUniqueExist = table.uniques.some(
3,080✔
4042
                    (unique) =>
4043
                        unique.columnNames.length === 1 &&
4,104✔
4044
                        unique.columnNames[0] === column.name,
4045
                )
4046
                if (!isUniqueExist)
3,080✔
4047
                    table.uniques.push(
16✔
4048
                        new TableUnique({
4049
                            name: this.connection.namingStrategy.uniqueConstraintName(
4050
                                table,
4051
                                [column.name],
4052
                            ),
4053
                            columnNames: [column.name],
4054
                        }),
4055
                    )
4056
            })
4057

4058
        if (table.uniques.length > 0) {
21,848✔
4059
            const uniquesSql = table.uniques
2,668✔
4060
                .map((unique) => {
4061
                    const uniqueName = unique.name
3,908✔
4062
                        ? unique.name
4063
                        : this.connection.namingStrategy.uniqueConstraintName(
4064
                              table,
4065
                              unique.columnNames,
4066
                          )
4067
                    const columnNames = unique.columnNames
3,908✔
4068
                        .map((columnName) => `"${columnName}"`)
4,960✔
4069
                        .join(", ")
4070
                    let constraint = `CONSTRAINT "${uniqueName}" UNIQUE (${columnNames})`
3,908✔
4071
                    if (unique.deferrable)
3,908✔
4072
                        constraint += ` DEFERRABLE ${unique.deferrable}`
36✔
4073
                    return constraint
3,908✔
4074
                })
4075
                .join(", ")
4076

4077
            sql += `, ${uniquesSql}`
2,668✔
4078
        }
4079

4080
        if (table.checks.length > 0) {
21,848✔
4081
            const checksSql = table.checks
308✔
4082
                .map((check) => {
4083
                    const checkName = check.name
312✔
4084
                        ? check.name
4085
                        : this.connection.namingStrategy.checkConstraintName(
4086
                              table,
4087
                              check.expression!,
4088
                          )
4089
                    return `CONSTRAINT "${checkName}" CHECK (${check.expression})`
312✔
4090
                })
4091
                .join(", ")
4092

4093
            sql += `, ${checksSql}`
308✔
4094
        }
4095

4096
        if (table.exclusions.length > 0) {
21,848✔
4097
            const exclusionsSql = table.exclusions
304✔
4098
                .map((exclusion) => {
4099
                    const exclusionName = exclusion.name
304!
4100
                        ? exclusion.name
4101
                        : this.connection.namingStrategy.exclusionConstraintName(
4102
                              table,
4103
                              exclusion.expression!,
4104
                          )
4105
                    return `CONSTRAINT "${exclusionName}" EXCLUDE ${exclusion.expression}`
304✔
4106
                })
4107
                .join(", ")
4108

4109
            sql += `, ${exclusionsSql}`
304✔
4110
        }
4111

4112
        if (table.foreignKeys.length > 0 && createForeignKeys) {
21,848✔
4113
            const foreignKeysSql = table.foreignKeys
16✔
4114
                .map((fk) => {
4115
                    const columnNames = fk.columnNames
20✔
4116
                        .map((columnName) => `"${columnName}"`)
24✔
4117
                        .join(", ")
4118
                    if (!fk.name)
20✔
4119
                        fk.name = this.connection.namingStrategy.foreignKeyName(
12✔
4120
                            table,
4121
                            fk.columnNames,
4122
                            this.getTablePath(fk),
4123
                            fk.referencedColumnNames,
4124
                        )
4125

4126
                    const referencedColumnNames = fk.referencedColumnNames
20✔
4127
                        .map((columnName) => `"${columnName}"`)
24✔
4128
                        .join(", ")
4129

4130
                    let constraint = `CONSTRAINT "${
20✔
4131
                        fk.name
4132
                    }" FOREIGN KEY (${columnNames}) REFERENCES ${this.escapePath(
4133
                        this.getTablePath(fk),
4134
                    )} (${referencedColumnNames})`
4135
                    if (fk.onDelete) constraint += ` ON DELETE ${fk.onDelete}`
20✔
4136
                    if (fk.onUpdate) constraint += ` ON UPDATE ${fk.onUpdate}`
20✔
4137
                    if (fk.deferrable)
20!
4138
                        constraint += ` DEFERRABLE ${fk.deferrable}`
×
4139

4140
                    return constraint
20✔
4141
                })
4142
                .join(", ")
4143

4144
            sql += `, ${foreignKeysSql}`
16✔
4145
        }
4146

4147
        const primaryColumns = table.columns.filter(
21,848✔
4148
            (column) => column.isPrimary,
71,068✔
4149
        )
4150
        if (primaryColumns.length > 0) {
21,848✔
4151
            const primaryKeyName = primaryColumns[0].primaryKeyConstraintName
21,728✔
4152
                ? primaryColumns[0].primaryKeyConstraintName
4153
                : this.connection.namingStrategy.primaryKeyName(
4154
                      table,
4155
                      primaryColumns.map((column) => column.name),
27,104✔
4156
                  )
4157

4158
            const columnNames = primaryColumns
21,728✔
4159
                .map((column) => `"${column.name}"`)
27,200✔
4160
                .join(", ")
4161
            sql += `, CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNames})`
21,728✔
4162
        }
4163

4164
        sql += `)`
21,848✔
4165

4166
        table.columns
21,848✔
4167
            .filter((it) => it.comment)
71,068✔
4168
            .forEach(
4169
                (it) =>
4170
                    (sql += `; COMMENT ON COLUMN ${this.escapePath(table)}."${
248✔
4171
                        it.name
4172
                    }" IS ${this.escapeComment(it.comment)}`),
4173
            )
4174

4175
        return new Query(sql)
21,848✔
4176
    }
4177

4178
    /**
4179
     * Loads Postgres version.
4180
     */
4181
    async getVersion(): Promise<string> {
4182
        // we use `SELECT version()` instead of `SHOW server_version` or `SHOW server_version_num`
4183
        // to maintain compatability with Amazon Redshift.
4184
        //
4185
        // see:
4186
        //  - https://github.com/typeorm/typeorm/pull/9319
4187
        //  - https://docs.aws.amazon.com/redshift/latest/dg/c_unsupported-postgresql-functions.html
4188
        const result: [{ version: string }] = await this.query(
2,628✔
4189
            `SELECT version()`,
4190
        )
4191

4192
        // Examples:
4193
        // Postgres: "PostgreSQL 14.10 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit"
4194
        // Yugabyte: "PostgreSQL 11.2-YB-2.18.1.0-b0 on x86_64-pc-linux-gnu, compiled by clang version 15.0.3 (https://github.com/yugabyte/llvm-project.git 0b8d1183745fd3998d8beffeec8cbe99c1b20529), 64-bit"
4195
        return result[0].version.replace(/^PostgreSQL ([\d.]+).*$/, "$1")
2,628✔
4196
    }
4197

4198
    /**
4199
     * Builds drop table sql.
4200
     */
4201
    protected dropTableSql(tableOrPath: Table | string): Query {
4202
        return new Query(`DROP TABLE ${this.escapePath(tableOrPath)}`)
21,848✔
4203
    }
4204

4205
    protected createViewSql(view: View): Query {
4206
        const materializedClause = view.materialized ? "MATERIALIZED " : ""
200✔
4207
        const viewName = this.escapePath(view)
200✔
4208

4209
        if (typeof view.expression === "string") {
200✔
4210
            return new Query(
152✔
4211
                `CREATE ${materializedClause}VIEW ${viewName} AS ${view.expression}`,
4212
            )
4213
        } else {
4214
            return new Query(
48✔
4215
                `CREATE ${materializedClause}VIEW ${viewName} AS ${view
4216
                    .expression(this.connection)
4217
                    .getQuery()}`,
4218
            )
4219
        }
4220
    }
4221

4222
    protected async insertViewDefinitionSql(view: View): Promise<Query> {
4223
        const currentSchema = await this.getCurrentSchema()
196✔
4224

4225
        let { schema, tableName: name } = this.driver.parseTableName(view)
196✔
4226

4227
        if (!schema) {
196!
4228
            schema = currentSchema
×
4229
        }
4230

4231
        const type = view.materialized
196✔
4232
            ? MetadataTableType.MATERIALIZED_VIEW
4233
            : MetadataTableType.VIEW
4234
        const expression =
4235
            typeof view.expression === "string"
196✔
4236
                ? view.expression.trim()
4237
                : view.expression(this.connection).getQuery()
4238
        return this.insertTypeormMetadataSql({
196✔
4239
            type,
4240
            schema,
4241
            name,
4242
            value: expression,
4243
        })
4244
    }
4245

4246
    /**
4247
     * Builds drop view sql.
4248
     */
4249
    protected dropViewSql(view: View): Query {
4250
        const materializedClause = view.materialized ? "MATERIALIZED " : ""
200✔
4251
        return new Query(
200✔
4252
            `DROP ${materializedClause}VIEW ${this.escapePath(view)}`,
4253
        )
4254
    }
4255

4256
    /**
4257
     * Builds remove view sql.
4258
     */
4259
    protected async deleteViewDefinitionSql(view: View): Promise<Query> {
4260
        const currentSchema = await this.getCurrentSchema()
196✔
4261

4262
        let { schema, tableName: name } = this.driver.parseTableName(view)
196✔
4263

4264
        if (!schema) {
196!
4265
            schema = currentSchema
×
4266
        }
4267

4268
        const type = view.materialized
196✔
4269
            ? MetadataTableType.MATERIALIZED_VIEW
4270
            : MetadataTableType.VIEW
4271
        return this.deleteTypeormMetadataSql({ type, schema, name })
196✔
4272
    }
4273

4274
    /**
4275
     * Drops ENUM type from given schemas.
4276
     */
4277
    protected async dropEnumTypes(schemaNames: string): Promise<void> {
4278
        const selectDropsQuery =
4279
            `SELECT 'DROP TYPE IF EXISTS "' || n.nspname || '"."' || t.typname || '" CASCADE;' as "query" FROM "pg_type" "t" ` +
6,848✔
4280
            `INNER JOIN "pg_enum" "e" ON "e"."enumtypid" = "t"."oid" ` +
4281
            `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
4282
            `WHERE "n"."nspname" IN (${schemaNames}) GROUP BY "n"."nspname", "t"."typname"`
4283
        const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery)
6,848✔
4284
        await Promise.all(dropQueries.map((q) => this.query(q["query"])))
6,848✔
4285
    }
4286

4287
    /**
4288
     * Checks if enum with the given name exist in the database.
4289
     */
4290
    protected async hasEnumType(
4291
        table: Table,
4292
        column: TableColumn,
4293
    ): Promise<boolean> {
4294
        let { schema } = this.driver.parseTableName(table)
944✔
4295

4296
        if (!schema) {
944!
4297
            schema = await this.getCurrentSchema()
×
4298
        }
4299

4300
        const enumName = this.buildEnumName(table, column, false, true)
944✔
4301
        const sql =
4302
            `SELECT "n"."nspname", "t"."typname" FROM "pg_type" "t" ` +
944✔
4303
            `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
4304
            `WHERE "n"."nspname" = '${schema}' AND "t"."typname" = '${enumName}'`
4305
        const result = await this.query(sql)
944✔
4306
        return result.length ? true : false
944✔
4307
    }
4308

4309
    /**
4310
     * Builds create ENUM type sql.
4311
     */
4312
    protected createEnumTypeSql(
4313
        table: Table,
4314
        column: TableColumn,
4315
        enumName?: string,
4316
    ): Query {
4317
        if (!enumName) enumName = this.buildEnumName(table, column)
1,004✔
4318
        const enumValues = column
1,004✔
4319
            .enum!.map((value) => `'${value.replaceAll("'", "''")}'`)
3,368✔
4320
            .join(", ")
4321
        return new Query(`CREATE TYPE ${enumName} AS ENUM(${enumValues})`)
1,004✔
4322
    }
4323

4324
    /**
4325
     * Builds create ENUM type sql.
4326
     */
4327
    protected dropEnumTypeSql(
4328
        table: Table,
4329
        column: TableColumn,
4330
        enumName?: string,
4331
    ): Query {
4332
        if (!enumName) enumName = this.buildEnumName(table, column)
1,004✔
4333
        return new Query(`DROP TYPE ${enumName}`)
1,004✔
4334
    }
4335

4336
    /**
4337
     * Builds create index sql.
4338
     */
4339
    protected createIndexSql(table: Table, index: TableIndex): Query {
4340
        const columns = index.columnNames
7,996✔
4341
            .map((columnName) => `"${columnName}"`)
8,996✔
4342
            .join(", ")
4343
        return new Query(
7,996✔
4344
            `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX${
7,996✔
4345
                index.isConcurrent ? " CONCURRENTLY" : ""
7,996✔
4346
            } "${index.name}" ON ${this.escapePath(table)} ${
4347
                index.isSpatial ? "USING GiST " : ""
7,996✔
4348
            }(${columns}) ${index.where ? "WHERE " + index.where : ""}`,
7,996✔
4349
        )
4350
    }
4351

4352
    /**
4353
     * Builds create view index sql.
4354
     */
4355
    protected createViewIndexSql(view: View, index: TableIndex): Query {
4356
        const columns = index.columnNames
28✔
4357
            .map((columnName) => `"${columnName}"`)
28✔
4358
            .join(", ")
4359
        return new Query(
28✔
4360
            `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX "${
28✔
4361
                index.name
4362
            }" ON ${this.escapePath(view)} (${columns}) ${
4363
                index.where ? "WHERE " + index.where : ""
28!
4364
            }`,
4365
        )
4366
    }
4367

4368
    /**
4369
     * Builds drop index sql.
4370
     */
4371
    protected dropIndexSql(
4372
        table: Table | View,
4373
        indexOrName: TableIndex | string,
4374
    ): Query {
4375
        const indexName = InstanceChecker.isTableIndex(indexOrName)
8,024!
4376
            ? indexOrName.name
4377
            : indexOrName
4378
        const concurrent = InstanceChecker.isTableIndex(indexOrName)
8,024!
4379
            ? indexOrName.isConcurrent
4380
            : false
4381
        const { schema } = this.driver.parseTableName(table)
8,024✔
4382
        return schema
8,024!
4383
            ? new Query(
4384
                  `DROP INDEX ${
4385
                      concurrent ? "CONCURRENTLY " : ""
8,024✔
4386
                  }"${schema}"."${indexName}"`,
4387
              )
4388
            : new Query(
4389
                  `DROP INDEX ${
4390
                      concurrent ? "CONCURRENTLY " : ""
×
4391
                  }"${indexName}"`,
4392
              )
4393
    }
4394

4395
    /**
4396
     * Builds create primary key sql.
4397
     */
4398
    protected createPrimaryKeySql(
4399
        table: Table,
4400
        columnNames: string[],
4401
        constraintName?: string,
4402
    ): Query {
4403
        const primaryKeyName = constraintName
20!
4404
            ? constraintName
4405
            : this.connection.namingStrategy.primaryKeyName(table, columnNames)
4406

4407
        const columnNamesString = columnNames
20✔
4408
            .map((columnName) => `"${columnName}"`)
24✔
4409
            .join(", ")
4410

4411
        return new Query(
20✔
4412
            `ALTER TABLE ${this.escapePath(
4413
                table,
4414
            )} ADD CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNamesString})`,
4415
        )
4416
    }
4417

4418
    /**
4419
     * Builds drop primary key sql.
4420
     */
4421
    protected dropPrimaryKeySql(table: Table): Query {
4422
        if (!table.primaryColumns.length)
20!
4423
            throw new TypeORMError(`Table ${table} has no primary keys.`)
×
4424

4425
        const columnNames = table.primaryColumns.map((column) => column.name)
24✔
4426
        const constraintName = table.primaryColumns[0].primaryKeyConstraintName
20✔
4427
        const primaryKeyName = constraintName
20!
4428
            ? constraintName
4429
            : this.connection.namingStrategy.primaryKeyName(table, columnNames)
4430

4431
        return new Query(
20✔
4432
            `ALTER TABLE ${this.escapePath(
4433
                table,
4434
            )} DROP CONSTRAINT "${primaryKeyName}"`,
4435
        )
4436
    }
4437

4438
    /**
4439
     * Builds create unique constraint sql.
4440
     */
4441
    protected createUniqueConstraintSql(
4442
        table: Table,
4443
        uniqueConstraint: TableUnique,
4444
    ): Query {
4445
        const columnNames = uniqueConstraint.columnNames
60✔
4446
            .map((column) => `"` + column + `"`)
104✔
4447
            .join(", ")
4448
        let sql = `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
60✔
4449
            uniqueConstraint.name
4450
        }" UNIQUE (${columnNames})`
4451
        if (uniqueConstraint.deferrable)
60!
4452
            sql += ` DEFERRABLE ${uniqueConstraint.deferrable}`
×
4453
        return new Query(sql)
60✔
4454
    }
4455

4456
    /**
4457
     * Builds drop unique constraint sql.
4458
     */
4459
    protected dropUniqueConstraintSql(
4460
        table: Table,
4461
        uniqueOrName: TableUnique | string,
4462
    ): Query {
4463
        const uniqueName = InstanceChecker.isTableUnique(uniqueOrName)
60!
4464
            ? uniqueOrName.name
4465
            : uniqueOrName
4466
        return new Query(
60✔
4467
            `ALTER TABLE ${this.escapePath(
4468
                table,
4469
            )} DROP CONSTRAINT "${uniqueName}"`,
4470
        )
4471
    }
4472

4473
    /**
4474
     * Builds create check constraint sql.
4475
     */
4476
    protected createCheckConstraintSql(
4477
        table: Table,
4478
        checkConstraint: TableCheck,
4479
    ): Query {
4480
        return new Query(
40✔
4481
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
4482
                checkConstraint.name
4483
            }" CHECK (${checkConstraint.expression})`,
4484
        )
4485
    }
4486

4487
    /**
4488
     * Builds drop check constraint sql.
4489
     */
4490
    protected dropCheckConstraintSql(
4491
        table: Table,
4492
        checkOrName: TableCheck | string,
4493
    ): Query {
4494
        const checkName = InstanceChecker.isTableCheck(checkOrName)
40!
4495
            ? checkOrName.name
4496
            : checkOrName
4497
        return new Query(
40✔
4498
            `ALTER TABLE ${this.escapePath(
4499
                table,
4500
            )} DROP CONSTRAINT "${checkName}"`,
4501
        )
4502
    }
4503

4504
    /**
4505
     * Builds create exclusion constraint sql.
4506
     */
4507
    protected createExclusionConstraintSql(
4508
        table: Table,
4509
        exclusionConstraint: TableExclusion,
4510
    ): Query {
4511
        return new Query(
28✔
4512
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
4513
                exclusionConstraint.name
4514
            }" EXCLUDE ${exclusionConstraint.expression}`,
4515
        )
4516
    }
4517

4518
    /**
4519
     * Builds drop exclusion constraint sql.
4520
     */
4521
    protected dropExclusionConstraintSql(
4522
        table: Table,
4523
        exclusionOrName: TableExclusion | string,
4524
    ): Query {
4525
        const exclusionName = InstanceChecker.isTableExclusion(exclusionOrName)
28!
4526
            ? exclusionOrName.name
4527
            : exclusionOrName
4528
        return new Query(
28✔
4529
            `ALTER TABLE ${this.escapePath(
4530
                table,
4531
            )} DROP CONSTRAINT "${exclusionName}"`,
4532
        )
4533
    }
4534

4535
    /**
4536
     * Builds create foreign key sql.
4537
     */
4538
    protected createForeignKeySql(
4539
        table: Table,
4540
        foreignKey: TableForeignKey,
4541
    ): Query {
4542
        const columnNames = foreignKey.columnNames
14,588✔
4543
            .map((column) => `"` + column + `"`)
16,580✔
4544
            .join(", ")
4545
        const referencedColumnNames = foreignKey.referencedColumnNames
14,588✔
4546
            .map((column) => `"` + column + `"`)
16,580✔
4547
            .join(",")
4548
        let sql =
4549
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
14,588✔
4550
                foreignKey.name
4551
            }" FOREIGN KEY (${columnNames}) ` +
4552
            `REFERENCES ${this.escapePath(
4553
                this.getTablePath(foreignKey),
4554
            )}(${referencedColumnNames})`
4555
        if (foreignKey.onDelete) sql += ` ON DELETE ${foreignKey.onDelete}`
14,588✔
4556
        if (foreignKey.onUpdate) sql += ` ON UPDATE ${foreignKey.onUpdate}`
14,588✔
4557
        if (foreignKey.deferrable) sql += ` DEFERRABLE ${foreignKey.deferrable}`
14,588✔
4558

4559
        return new Query(sql)
14,588✔
4560
    }
4561

4562
    /**
4563
     * Builds drop foreign key sql.
4564
     */
4565
    protected dropForeignKeySql(
4566
        table: Table,
4567
        foreignKeyOrName: TableForeignKey | string,
4568
    ): Query {
4569
        const foreignKeyName = InstanceChecker.isTableForeignKey(
14,608!
4570
            foreignKeyOrName,
4571
        )
4572
            ? foreignKeyOrName.name
4573
            : foreignKeyOrName
4574
        return new Query(
14,608✔
4575
            `ALTER TABLE ${this.escapePath(
4576
                table,
4577
            )} DROP CONSTRAINT "${foreignKeyName}"`,
4578
        )
4579
    }
4580

4581
    /**
4582
     * Builds sequence name from given table and column.
4583
     */
4584
    protected buildSequenceName(
4585
        table: Table,
4586
        columnOrName: TableColumn | string,
4587
    ): string {
4588
        const { tableName } = this.driver.parseTableName(table)
5,528✔
4589

4590
        const columnName = InstanceChecker.isTableColumn(columnOrName)
5,528✔
4591
            ? columnOrName.name
4592
            : columnOrName
4593

4594
        let seqName = `${tableName}_${columnName}_seq`
5,528✔
4595

4596
        if (seqName.length > this.connection.driver.maxAliasLength!) {
5,528✔
4597
            // note doesn't yet handle corner cases where .length differs from number of UTF-8 bytes
4598
            seqName = `${tableName.substring(0, 29)}_${columnName.substring(
32✔
4599
                0,
4600
                Math.max(29, 63 - table.name.length - 5),
4601
            )}_seq`
4602
        }
4603

4604
        return seqName
5,528✔
4605
    }
4606

4607
    protected buildSequencePath(
4608
        table: Table,
4609
        columnOrName: TableColumn | string,
4610
    ): string {
4611
        const { schema } = this.driver.parseTableName(table)
2,812✔
4612

4613
        return schema
2,812!
4614
            ? `${schema}.${this.buildSequenceName(table, columnOrName)}`
4615
            : this.buildSequenceName(table, columnOrName)
4616
    }
4617

4618
    /**
4619
     * Builds ENUM type name from given table and column.
4620
     */
4621
    protected buildEnumName(
4622
        table: Table,
4623
        column: TableColumn,
4624
        withSchema: boolean = true,
2,036✔
4625
        disableEscape?: boolean,
4626
        toOld?: boolean,
4627
    ): string {
4628
        const { schema, tableName } = this.driver.parseTableName(table)
4,052✔
4629
        let enumName = column.enumName
4,052✔
4630
            ? column.enumName
4631
            : `${tableName}_${column.name.toLowerCase()}_enum`
4632
        if (schema && withSchema) enumName = `${schema}.${enumName}`
4,052✔
4633
        if (toOld) enumName = enumName + "_old"
4,052✔
4634
        return enumName
4,052✔
4635
            .split(".")
4636
            .map((i) => {
4637
                return disableEscape ? i : `"${i}"`
6,140✔
4638
            })
4639
            .join(".")
4640
    }
4641

4642
    protected async getUserDefinedTypeName(table: Table, column: TableColumn) {
4643
        let { schema, tableName: name } = this.driver.parseTableName(table)
936✔
4644

4645
        if (!schema) {
936!
4646
            schema = await this.getCurrentSchema()
×
4647
        }
4648

4649
        const result = await this.query(
936✔
4650
            `SELECT "udt_schema", "udt_name" ` +
4651
                `FROM "information_schema"."columns" WHERE "table_schema" = '${schema}' AND "table_name" = '${name}' AND "column_name"='${column.name}'`,
4652
        )
4653

4654
        // docs: https://www.postgresql.org/docs/current/xtypes.html
4655
        // When you define a new base type, PostgreSQL automatically provides support for arrays of that type.
4656
        // The array type typically has the same name as the base type with the underscore character (_) prepended.
4657
        // ----
4658
        // so, we must remove this underscore character from enum type name
4659
        let udtName = result[0]["udt_name"]
936✔
4660
        if (udtName.indexOf("_") === 0) {
936✔
4661
            udtName = udtName.substr(1, udtName.length)
288✔
4662
        }
4663
        return {
936✔
4664
            schema: result[0]["udt_schema"],
4665
            name: udtName,
4666
        }
4667
    }
4668

4669
    /**
4670
     * Escapes a given comment so it's safe to include in a query.
4671
     */
4672
    protected escapeComment(comment?: string) {
4673
        if (!comment || comment.length === 0) {
2,808✔
4674
            return "NULL"
2,528✔
4675
        }
4676

4677
        comment = comment.replace(/'/g, "''").replace(/\u0000/g, "") // Null bytes aren't allowed in comments
280✔
4678

4679
        return `'${comment}'`
280✔
4680
    }
4681

4682
    /**
4683
     * Escapes given table or view path.
4684
     */
4685
    protected escapePath(target: Table | View | string): string {
4686
        const { schema, tableName } = this.driver.parseTableName(target)
98,792✔
4687

4688
        if (schema && schema !== this.driver.searchSchema) {
98,792✔
4689
            return `"${schema}"."${tableName}"`
1,012✔
4690
        }
4691

4692
        return `"${tableName}"`
97,780✔
4693
    }
4694

4695
    /**
4696
     * Get the table name with table schema
4697
     * Note: Without ' or "
4698
     */
4699
    protected async getTableNameWithSchema(target: Table | string) {
4700
        const tableName = InstanceChecker.isTable(target) ? target.name : target
132!
4701
        if (tableName.indexOf(".") === -1) {
132!
4702
            const schemaResult = await this.query(`SELECT current_schema()`)
132✔
4703
            const schema = schemaResult[0]["current_schema"]
132✔
4704
            return `${schema}.${tableName}`
132✔
4705
        } else {
4706
            return `${tableName.split(".")[0]}.${tableName.split(".")[1]}`
×
4707
        }
4708
    }
4709

4710
    /**
4711
     * Builds a query for create column.
4712
     */
4713
    protected buildCreateColumnSql(table: Table, column: TableColumn) {
4714
        let c = '"' + column.name + '"'
71,400✔
4715
        if (
71,400✔
4716
            column.isGenerated === true &&
84,680✔
4717
            column.generationStrategy !== "uuid"
4718
        ) {
4719
            if (column.generationStrategy === "identity") {
12,556✔
4720
                // Postgres 10+ Identity generated column
4721
                const generatedIdentityOrDefault =
4722
                    column.generatedIdentity || "BY DEFAULT"
20✔
4723
                c += ` ${column.type} GENERATED ${generatedIdentityOrDefault} AS IDENTITY`
20✔
4724
            } else {
4725
                // classic SERIAL primary column
4726
                if (
12,536✔
4727
                    column.type === "integer" ||
12,648✔
4728
                    column.type === "int" ||
4729
                    column.type === "int4"
4730
                )
4731
                    c += " SERIAL"
12,496✔
4732
                if (column.type === "smallint" || column.type === "int2")
12,536✔
4733
                    c += " SMALLSERIAL"
4✔
4734
                if (column.type === "bigint" || column.type === "int8")
12,536✔
4735
                    c += " BIGSERIAL"
36✔
4736
            }
4737
        }
4738
        if (column.type === "enum" || column.type === "simple-enum") {
71,400✔
4739
            c += " " + this.buildEnumName(table, column)
964✔
4740
            if (column.isArray) c += " array"
964✔
4741
        } else if (!column.isGenerated || column.type === "uuid") {
70,436✔
4742
            c += " " + this.connection.driver.createFullType(column)
57,880✔
4743
        }
4744

4745
        // Postgres only supports the stored generated column type
4746
        if (column.generatedType === "STORED" && column.asExpression) {
71,400✔
4747
            c += ` GENERATED ALWAYS AS (${column.asExpression}) STORED`
132✔
4748
        }
4749

4750
        if (column.charset) c += ' CHARACTER SET "' + column.charset + '"'
71,400!
4751
        if (column.collation) c += ' COLLATE "' + column.collation + '"'
71,400✔
4752
        if (column.isNullable !== true) c += " NOT NULL"
71,400✔
4753
        if (column.default !== undefined && column.default !== null)
71,400✔
4754
            c += " DEFAULT " + column.default
5,012✔
4755
        if (
71,400✔
4756
            column.isGenerated &&
85,404✔
4757
            column.generationStrategy === "uuid" &&
4758
            !column.default
4759
        )
4760
            c += ` DEFAULT ${this.driver.uuidGenerator}`
724✔
4761

4762
        return c
71,400✔
4763
    }
4764

4765
    /**
4766
     * Checks if the PostgreSQL server has support for partitioned tables
4767
     */
4768
    protected async hasSupportForPartitionedTables() {
4769
        const result = await this.query(
1,968✔
4770
            `SELECT TRUE FROM information_schema.columns WHERE table_name = 'pg_class' and column_name = 'relispartition'`,
4771
        )
4772
        return result.length ? true : false
1,968!
4773
    }
4774

4775
    /**
4776
     * Change table comment.
4777
     */
4778
    async changeTableComment(
4779
        tableOrName: Table | string,
4780
        newComment?: string,
4781
    ): Promise<void> {
4782
        const upQueries: Query[] = []
1,260✔
4783
        const downQueries: Query[] = []
1,260✔
4784

4785
        const table = InstanceChecker.isTable(tableOrName)
1,260!
4786
            ? tableOrName
4787
            : await this.getCachedTable(tableOrName)
4788

4789
        newComment = this.escapeComment(newComment)
1,260✔
4790
        const comment = this.escapeComment(table.comment)
1,260✔
4791

4792
        if (newComment === comment) {
1,260✔
4793
            return
1,252✔
4794
        }
4795

4796
        const newTable = table.clone()
8✔
4797

4798
        upQueries.push(
8✔
4799
            new Query(
4800
                `COMMENT ON TABLE ${this.escapePath(
4801
                    newTable,
4802
                )} IS ${newComment}`,
4803
            ),
4804
        )
4805

4806
        downQueries.push(
8✔
4807
            new Query(
4808
                `COMMENT ON TABLE ${this.escapePath(table)} IS ${comment}`,
4809
            ),
4810
        )
4811

4812
        await this.executeQueries(upQueries, downQueries)
8✔
4813

4814
        table.comment = newTable.comment
8✔
4815
        this.replaceCachedTable(table, newTable)
8✔
4816
    }
4817
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc