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

typeorm / typeorm / 14081280213

26 Mar 2025 10:41AM UTC coverage: 72.384% (+0.004%) from 72.38%
14081280213

push

github

web-flow
refactor: database server version fetching & comparison (#11357)

8658 of 12643 branches covered (68.48%)

Branch coverage included in aggregate %.

37 of 59 new or added lines in 13 files covered. (62.71%)

3 existing lines in 3 files now uncovered.

17889 of 24032 relevant lines covered (74.44%)

159593.23 hits per line

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

88.79
/src/driver/postgres/PostgresQueryRunner.ts
1
import { ObjectLiteral } from "../../common/ObjectLiteral"
2
import { TypeORMError } from "../../error"
35✔
3
import { QueryFailedError } from "../../error/QueryFailedError"
35✔
4
import { QueryRunnerAlreadyReleasedError } from "../../error/QueryRunnerAlreadyReleasedError"
35✔
5
import { TransactionNotStartedError } from "../../error/TransactionNotStartedError"
35✔
6
import { ReadStream } from "../../platform/PlatformTools"
7
import { BaseQueryRunner } from "../../query-runner/BaseQueryRunner"
35✔
8
import { QueryResult } from "../../query-runner/QueryResult"
35✔
9
import { QueryRunner } from "../../query-runner/QueryRunner"
10
import { TableIndexOptions } from "../../schema-builder/options/TableIndexOptions"
11
import { Table } from "../../schema-builder/table/Table"
35✔
12
import { TableCheck } from "../../schema-builder/table/TableCheck"
35✔
13
import { TableColumn } from "../../schema-builder/table/TableColumn"
35✔
14
import { TableExclusion } from "../../schema-builder/table/TableExclusion"
35✔
15
import { TableForeignKey } from "../../schema-builder/table/TableForeignKey"
35✔
16
import { TableIndex } from "../../schema-builder/table/TableIndex"
35✔
17
import { TableUnique } from "../../schema-builder/table/TableUnique"
35✔
18
import { View } from "../../schema-builder/view/View"
35✔
19
import { Broadcaster } from "../../subscriber/Broadcaster"
35✔
20
import { BroadcasterResult } from "../../subscriber/BroadcasterResult"
35✔
21
import { InstanceChecker } from "../../util/InstanceChecker"
35✔
22
import { OrmUtils } from "../../util/OrmUtils"
35✔
23
import { DriverUtils } from "../DriverUtils"
35✔
24
import { Query } from "../Query"
35✔
25
import { ColumnType } from "../types/ColumnTypes"
26
import { IsolationLevel } from "../types/IsolationLevel"
27
import { MetadataTableType } from "../types/MetadataTableType"
35✔
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
35✔
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()
41,484✔
67
        this.driver = driver
41,484✔
68
        this.connection = driver.connection
41,484✔
69
        this.mode = mode
41,484✔
70
        this.broadcaster = new Broadcaster(this)
41,484✔
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)
221,331✔
83
            return Promise.resolve(this.databaseConnection)
181,593✔
84

85
        if (this.databaseConnectionPromise)
39,738✔
86
            return this.databaseConnectionPromise
186✔
87

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

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

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

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

127
                    return this.databaseConnection
39,540✔
128
                })
129
        }
130

131
        return this.databaseConnectionPromise
39,552✔
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) {
41,484!
140
            return
×
141
        }
142

143
        this.isReleased = true
41,484✔
144
        if (this.releaseCallback) {
41,484✔
145
            this.releaseCallback(err)
39,549✔
146
            this.releaseCallback = undefined
39,549✔
147
        }
148

149
        const index = this.driver.connectedQueryRunners.indexOf(this)
41,484✔
150

151
        if (index !== -1) {
41,484✔
152
            this.driver.connectedQueryRunners.splice(index, 1)
39,549✔
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()
41,484✔
162
    }
163

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

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

188
        await this.broadcaster.broadcast("AfterTransactionStart")
29,913✔
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()
29,805!
197

198
        await this.broadcaster.broadcast("BeforeTransactionCommit")
29,805✔
199

200
        if (this.transactionDepth > 1) {
29,805✔
201
            await this.query(
30✔
202
                `RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
203
            )
204
        } else {
205
            await this.query("COMMIT")
29,775✔
206
            this.isTransactionActive = false
29,772✔
207
        }
208
        this.transactionDepth -= 1
29,802✔
209

210
        await this.broadcaster.broadcast("AfterTransactionCommit")
29,802✔
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()
111!
219

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

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

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

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

245
        const databaseConnection = await this.connect()
221,319✔
246
        const broadcasterResult = new BroadcasterResult()
221,316✔
247

248
        this.driver.connection.logger.logQuery(query, parameters, this)
221,316✔
249
        this.broadcaster.broadcastBeforeQueryEvent(
221,316✔
250
            broadcasterResult,
251
            query,
252
            parameters,
253
        )
254

255
        try {
221,316✔
256
            const queryStartTime = +new Date()
221,316✔
257
            const raw = await databaseConnection.query(query, parameters)
221,316✔
258
            // log slow queries if maxQueryExecution time is set
259
            const maxQueryExecutionTime =
260
                this.driver.options.maxQueryExecutionTime
221,268✔
261
            const queryEndTime = +new Date()
221,268✔
262
            const queryExecutionTime = queryEndTime - queryStartTime
221,268✔
263

264
            this.broadcaster.broadcastAfterQueryEvent(
221,268✔
265
                broadcasterResult,
266
                query,
267
                parameters,
268
                true,
269
                queryExecutionTime,
270
                raw,
271
                undefined,
272
            )
273

274
            if (
221,268!
275
                maxQueryExecutionTime &&
221,268!
276
                queryExecutionTime > maxQueryExecutionTime
277
            )
278
                this.driver.connection.logger.logQuerySlow(
×
279
                    queryExecutionTime,
280
                    query,
281
                    parameters,
282
                    this,
283
                )
284

285
            const result = new QueryResult()
221,268✔
286
            if (raw) {
221,268✔
287
                if (raw.hasOwnProperty("rows")) {
221,268✔
288
                    result.records = raw.rows
221,157✔
289
                }
290

291
                if (raw.hasOwnProperty("rowCount")) {
221,268✔
292
                    result.affected = raw.rowCount
221,157✔
293
                }
294

295
                switch (raw.command) {
221,268✔
296
                    case "DELETE":
297
                    case "UPDATE":
298
                        // for UPDATE and DELETE query additionally return number of affected rows
299
                        result.raw = [raw.rows, raw.rowCount]
3,180✔
300
                        break
3,180✔
301
                    default:
302
                        result.raw = raw.rows
218,088✔
303
                }
304

305
                if (!useStructuredResult) {
221,268✔
306
                    return result.raw
172,806✔
307
                }
308
            }
309

310
            return result
48,462✔
311
        } catch (err) {
312
            this.driver.connection.logger.logQueryError(
48✔
313
                err,
314
                query,
315
                parameters,
316
                this,
317
            )
318
            this.broadcaster.broadcastAfterQueryEvent(
48✔
319
                broadcasterResult,
320
                query,
321
                parameters,
322
                false,
323
                undefined,
324
                undefined,
325
                err,
326
            )
327

328
            throw new QueryFailedError(query, parameters, err)
48✔
329
        } finally {
330
            await broadcasterResult.wait()
221,316✔
331
        }
332
    }
333

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

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

354
        return stream
6✔
355
    }
356

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

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

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

382
    /**
383
     * Loads currently using database
384
     */
385
    async getCurrentDatabase(): Promise<string> {
386
        const query = await this.query(`SELECT * FROM current_database()`)
6,633✔
387
        return query[0]["current_database"]
6,633✔
388
    }
389

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

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

408
    /**
409
     * Checks if table with the given name exist in the database.
410
     */
411
    async hasTable(tableOrName: Table | string): Promise<boolean> {
412
        const parsedTableName = this.driver.parseTableName(tableOrName)
5,721✔
413

414
        if (!parsedTableName.schema) {
5,721!
415
            parsedTableName.schema = await this.getCurrentSchema()
×
416
        }
417

418
        const sql = `SELECT * FROM "information_schema"."tables" WHERE "table_schema" = '${parsedTableName.schema}' AND "table_name" = '${parsedTableName.tableName}'`
5,721✔
419
        const result = await this.query(sql)
5,721✔
420
        return result.length ? true : false
5,721✔
421
    }
422

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

432
        if (!parsedTableName.schema) {
12!
433
            parsedTableName.schema = await this.getCurrentSchema()
×
434
        }
435

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

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

452
            if (databaseAlreadyExists) return Promise.resolve()
6!
453
        }
454

455
        const up = `CREATE DATABASE "${database}"`
6✔
456
        const down = `DROP DATABASE "${database}"`
6✔
457
        await this.executeQueries(new Query(up), new Query(down))
6✔
458
    }
459

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

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

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

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

504
        const up = ifExist
75✔
505
            ? `DROP SCHEMA IF EXISTS "${schema}" ${isCascade ? "CASCADE" : ""}`
72!
506
            : `DROP SCHEMA "${schema}" ${isCascade ? "CASCADE" : ""}`
3!
507
        const down = `CREATE SCHEMA "${schema}"`
75✔
508
        await this.executeQueries(new Query(up), new Query(down))
75✔
509
    }
510

511
    /**
512
     * Creates a new table.
513
     */
514
    async createTable(
515
        table: Table,
516
        ifNotExist: boolean = false,
138✔
517
        createForeignKeys: boolean = true,
279✔
518
        createIndices: boolean = true,
16,158✔
519
    ): Promise<void> {
520
        if (ifNotExist) {
16,158✔
521
            const isTableExist = await this.hasTable(table)
141✔
522
            if (isTableExist) return Promise.resolve()
141✔
523
        }
524
        const upQueries: Query[] = []
16,140✔
525
        const downQueries: Query[] = []
16,140✔
526

527
        // if table have column with ENUM type, we must create this type in postgres.
528
        const enumColumns = table.columns.filter(
16,140✔
529
            (column) => column.type === "enum" || column.type === "simple-enum",
52,404✔
530
        )
531
        const createdEnumTypes: string[] = []
16,140✔
532
        for (const column of enumColumns) {
16,140✔
533
            // TODO: Should also check if values of existing type matches expected ones
534
            const hasEnum = await this.hasEnumType(table, column)
672✔
535
            const enumName = this.buildEnumName(table, column)
672✔
536

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

545
        // if table have column with generated type, we must add the expression to the metadata table
546
        const generatedColumns = table.columns.filter(
16,140✔
547
            (column) =>
548
                column.generatedType === "STORED" && column.asExpression,
52,404✔
549
        )
550
        for (const column of generatedColumns) {
16,140✔
551
            const tableNameWithSchema = (
57✔
552
                await this.getTableNameWithSchema(table.name)
553
            ).split(".")
554
            const tableName = tableNameWithSchema[1]
57✔
555
            const schema = tableNameWithSchema[0]
57✔
556

557
            const insertQuery = this.insertTypeormMetadataSql({
57✔
558
                database: this.driver.database,
559
                schema,
560
                table: tableName,
561
                type: MetadataTableType.GENERATED_COLUMN,
562
                name: column.name,
563
                value: column.asExpression,
564
            })
565

566
            const deleteQuery = this.deleteTypeormMetadataSql({
57✔
567
                database: this.driver.database,
568
                schema,
569
                table: tableName,
570
                type: MetadataTableType.GENERATED_COLUMN,
571
                name: column.name,
572
            })
573

574
            upQueries.push(insertQuery)
57✔
575
            downQueries.push(deleteQuery)
57✔
576
        }
577

578
        upQueries.push(this.createTableSql(table, createForeignKeys))
16,140✔
579
        downQueries.push(this.dropTableSql(table))
16,140✔
580

581
        // if createForeignKeys is true, we must drop created foreign keys in down query.
582
        // createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
583
        if (createForeignKeys)
16,140✔
584
            table.foreignKeys.forEach((foreignKey) =>
261✔
585
                downQueries.push(this.dropForeignKeySql(table, foreignKey)),
9✔
586
            )
587

588
        if (createIndices) {
16,140✔
589
            table.indices.forEach((index) => {
16,140✔
590
                // new index may be passed without name. In this case we generate index name manually.
591
                if (!index.name)
5,889✔
592
                    index.name = this.connection.namingStrategy.indexName(
9✔
593
                        table,
594
                        index.columnNames,
595
                        index.where,
596
                    )
597
                upQueries.push(this.createIndexSql(table, index))
5,889✔
598
                downQueries.push(this.dropIndexSql(table, index))
5,889✔
599
            })
600
        }
601

602
        if (table.comment) {
16,140✔
603
            upQueries.push(
9✔
604
                new Query(
605
                    "COMMENT ON TABLE " +
606
                        this.escapePath(table) +
607
                        " IS '" +
608
                        table.comment +
609
                        "'",
610
                ),
611
            )
612
            downQueries.push(
9✔
613
                new Query(
614
                    "COMMENT ON TABLE " + this.escapePath(table) + " IS NULL",
615
                ),
616
            )
617
        }
618

619
        await this.executeQueries(upQueries, downQueries)
16,140✔
620
    }
621

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

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

645
        if (dropIndices) {
33✔
646
            table.indices.forEach((index) => {
33✔
647
                upQueries.push(this.dropIndexSql(table, index))
3✔
648
                downQueries.push(this.createIndexSql(table, index))
3✔
649
            })
650
        }
651

652
        if (dropForeignKeys)
33✔
653
            table.foreignKeys.forEach((foreignKey) =>
21✔
654
                upQueries.push(this.dropForeignKeySql(table, foreignKey)),
6✔
655
            )
656

657
        upQueries.push(this.dropTableSql(table))
33✔
658
        downQueries.push(this.createTableSql(table, createForeignKeys))
33✔
659

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

671
            const deleteQuery = this.deleteTypeormMetadataSql({
9✔
672
                database: this.driver.database,
673
                schema,
674
                table: tableName,
675
                type: MetadataTableType.GENERATED_COLUMN,
676
                name: column.name,
677
            })
678

679
            const insertQuery = this.insertTypeormMetadataSql({
9✔
680
                database: this.driver.database,
681
                schema,
682
                table: tableName,
683
                type: MetadataTableType.GENERATED_COLUMN,
684
                name: column.name,
685
                value: column.asExpression,
686
            })
687

688
            upQueries.push(deleteQuery)
9✔
689
            downQueries.push(insertQuery)
9✔
690
        }
691

692
        await this.executeQueries(upQueries, downQueries)
33✔
693
    }
694

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

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

720
        const upQueries: Query[] = []
24✔
721
        const downQueries: Query[] = []
24✔
722
        upQueries.push(await this.deleteViewDefinitionSql(view))
24✔
723
        upQueries.push(this.dropViewSql(view))
24✔
724
        downQueries.push(await this.insertViewDefinitionSql(view))
24✔
725
        downQueries.push(this.createViewSql(view))
24✔
726
        await this.executeQueries(upQueries, downQueries)
24✔
727
    }
728

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

743
        const { schema: schemaName, tableName: oldTableName } =
744
            this.driver.parseTableName(oldTable)
57✔
745

746
        newTable.name = schemaName
57!
747
            ? `${schemaName}.${newTableName}`
748
            : newTableName
749

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

765
        // rename column primary key constraint if it has default constraint name
766
        if (
57✔
767
            newTable.primaryColumns.length > 0 &&
114✔
768
            !newTable.primaryColumns[0].primaryKeyConstraintName
769
        ) {
770
            const columnNames = newTable.primaryColumns.map(
45✔
771
                (column) => column.name,
51✔
772
            )
773

774
            const oldPkName = this.connection.namingStrategy.primaryKeyName(
45✔
775
                oldTable,
776
                columnNames,
777
            )
778

779
            const newPkName = this.connection.namingStrategy.primaryKeyName(
45✔
780
                newTable,
781
                columnNames,
782
            )
783

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

800
        // rename sequences
801
        newTable.columns.map((col) => {
57✔
802
            if (col.isGenerated && col.generationStrategy === "increment") {
144✔
803
                const sequencePath = this.buildSequencePath(oldTable, col.name)
39✔
804
                const sequenceName = this.buildSequenceName(oldTable, col.name)
39✔
805

806
                const newSequencePath = this.buildSequencePath(
39✔
807
                    newTable,
808
                    col.name,
809
                )
810
                const newSequenceName = this.buildSequenceName(
39✔
811
                    newTable,
812
                    col.name,
813
                )
814

815
                const up = `ALTER SEQUENCE ${this.escapePath(
39✔
816
                    sequencePath,
817
                )} RENAME TO "${newSequenceName}"`
818
                const down = `ALTER SEQUENCE ${this.escapePath(
39✔
819
                    newSequencePath,
820
                )} RENAME TO "${sequenceName}"`
821

822
                upQueries.push(new Query(up))
39✔
823
                downQueries.push(new Query(down))
39✔
824
            }
825
        })
826

827
        // rename unique constraints
828
        newTable.uniques.forEach((unique) => {
57✔
829
            const oldUniqueName =
830
                this.connection.namingStrategy.uniqueConstraintName(
21✔
831
                    oldTable,
832
                    unique.columnNames,
833
                )
834

835
            // Skip renaming if Unique has user defined constraint name
836
            if (unique.name !== oldUniqueName) return
21✔
837

838
            // build new constraint name
839
            const newUniqueName =
840
                this.connection.namingStrategy.uniqueConstraintName(
9✔
841
                    newTable,
842
                    unique.columnNames,
843
                )
844

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

865
            // replace constraint name
866
            unique.name = newUniqueName
9✔
867
        })
868

869
        // rename index constraints
870
        newTable.indices.forEach((index) => {
57✔
871
            const oldIndexName = this.connection.namingStrategy.indexName(
27✔
872
                oldTable,
873
                index.columnNames,
874
                index.where,
875
            )
876

877
            // Skip renaming if Index has user defined constraint name
878
            if (index.name !== oldIndexName) return
27✔
879

880
            // build new constraint name
881
            const { schema } = this.driver.parseTableName(newTable)
15✔
882
            const newIndexName = this.connection.namingStrategy.indexName(
15✔
883
                newTable,
884
                index.columnNames,
885
                index.where,
886
            )
887

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

898
            // replace constraint name
899
            index.name = newIndexName
15✔
900
        })
901

902
        // rename foreign key constraints
903
        newTable.foreignKeys.forEach((foreignKey) => {
57✔
904
            const oldForeignKeyName =
905
                this.connection.namingStrategy.foreignKeyName(
27✔
906
                    oldTable,
907
                    foreignKey.columnNames,
908
                    this.getTablePath(foreignKey),
909
                    foreignKey.referencedColumnNames,
910
                )
911

912
            // Skip renaming if foreign key has user defined constraint name
913
            if (foreignKey.name !== oldForeignKeyName) return
27✔
914

915
            // build new constraint name
916
            const newForeignKeyName =
917
                this.connection.namingStrategy.foreignKeyName(
3✔
918
                    newTable,
919
                    foreignKey.columnNames,
920
                    this.getTablePath(foreignKey),
921
                    foreignKey.referencedColumnNames,
922
                )
923

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

944
            // replace constraint name
945
            foreignKey.name = newForeignKeyName
3✔
946
        })
947

948
        // rename ENUM types
949
        const enumColumns = newTable.columns.filter(
57✔
950
            (column) => column.type === "enum" || column.type === "simple-enum",
144✔
951
        )
952
        for (const column of enumColumns) {
57✔
953
            // skip renaming for user-defined enum name
954
            if (column.enumName) continue
15✔
955

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

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

997
        if (column.type === "enum" || column.type === "simple-enum") {
114✔
998
            const hasEnum = await this.hasEnumType(table, column)
15✔
999
            if (!hasEnum) {
15✔
1000
                upQueries.push(this.createEnumTypeSql(table, column))
15✔
1001
                downQueries.push(this.dropEnumTypeSql(table, column))
15✔
1002
            }
1003
        }
1004

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

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

1032
                const columnNames = primaryColumns
3✔
1033
                    .map((column) => `"${column.name}"`)
3✔
1034
                    .join(", ")
1035

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

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

1060
            const columnNames = primaryColumns
9✔
1061
                .map((column) => `"${column.name}"`)
12✔
1062
                .join(", ")
1063

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

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

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

1117
        if (column.generatedType === "STORED" && column.asExpression) {
114✔
1118
            const tableNameWithSchema = (
18✔
1119
                await this.getTableNameWithSchema(table.name)
1120
            ).split(".")
1121
            const tableName = tableNameWithSchema[1]
18✔
1122
            const schema = tableNameWithSchema[0]
18✔
1123

1124
            const insertQuery = this.insertTypeormMetadataSql({
18✔
1125
                database: this.driver.database,
1126
                schema,
1127
                table: tableName,
1128
                type: MetadataTableType.GENERATED_COLUMN,
1129
                name: column.name,
1130
                value: column.asExpression,
1131
            })
1132

1133
            const deleteQuery = this.deleteTypeormMetadataSql({
18✔
1134
                database: this.driver.database,
1135
                schema,
1136
                table: tableName,
1137
                type: MetadataTableType.GENERATED_COLUMN,
1138
                name: column.name,
1139
            })
1140

1141
            upQueries.push(insertQuery)
18✔
1142
            downQueries.push(deleteQuery)
18✔
1143
        }
1144

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

1163
        await this.executeQueries(upQueries, downQueries)
114✔
1164

1165
        clonedTable.addColumn(column)
111✔
1166
        this.replaceCachedTable(table, clonedTable)
111✔
1167
    }
1168

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

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

1200
        let newColumn
1201
        if (InstanceChecker.isTableColumn(newTableColumnOrName)) {
48✔
1202
            newColumn = newTableColumnOrName
33✔
1203
        } else {
1204
            newColumn = oldColumn.clone()
15✔
1205
            newColumn.name = newTableColumnOrName
15✔
1206
        }
1207

1208
        return this.changeColumn(table, oldColumn, newColumn)
48✔
1209
    }
1210

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

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

1237
        if (
360✔
1238
            oldColumn.type !== newColumn.type ||
1,866✔
1239
            oldColumn.length !== newColumn.length ||
1240
            newColumn.isArray !== oldColumn.isArray ||
1241
            (!oldColumn.generatedType &&
1242
                newColumn.generatedType === "STORED") ||
1243
            (oldColumn.asExpression !== newColumn.asExpression &&
1244
                newColumn.generatedType === "STORED")
1245
        ) {
1246
            // To avoid data conversion, we just recreate column
1247
            await this.dropColumn(table, oldColumn)
75✔
1248
            await this.addColumn(table, newColumn)
75✔
1249

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

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

1300
                // rename column primary key constraint
1301
                if (
75✔
1302
                    oldColumn.isPrimary === true &&
96✔
1303
                    !oldColumn.primaryKeyConstraintName
1304
                ) {
1305
                    const primaryColumns = clonedTable.primaryColumns
15✔
1306

1307
                    // build old primary constraint name
1308
                    const columnNames = primaryColumns.map(
15✔
1309
                        (column) => column.name,
27✔
1310
                    )
1311
                    const oldPkName =
1312
                        this.connection.namingStrategy.primaryKeyName(
15✔
1313
                            clonedTable,
1314
                            columnNames,
1315
                        )
1316

1317
                    // replace old column name with new column name
1318
                    columnNames.splice(columnNames.indexOf(oldColumn.name), 1)
15✔
1319
                    columnNames.push(newColumn.name)
15✔
1320

1321
                    // build new primary constraint name
1322
                    const newPkName =
1323
                        this.connection.namingStrategy.primaryKeyName(
15✔
1324
                            clonedTable,
1325
                            columnNames,
1326
                        )
1327

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

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

1358
                    const newSequencePath = this.buildSequencePath(
×
1359
                        table,
1360
                        newColumn.name,
1361
                    )
1362
                    const newSequenceName = this.buildSequenceName(
×
1363
                        table,
1364
                        newColumn.name,
1365
                    )
1366

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

1374
                    upQueries.push(new Query(up))
×
1375
                    downQueries.push(new Query(down))
×
1376
                }
1377

1378
                // rename unique constraints
1379
                clonedTable.findColumnUniques(oldColumn).forEach((unique) => {
75✔
1380
                    const oldUniqueName =
1381
                        this.connection.namingStrategy.uniqueConstraintName(
24✔
1382
                            clonedTable,
1383
                            unique.columnNames,
1384
                        )
1385

1386
                    // Skip renaming if Unique has user defined constraint name
1387
                    if (unique.name !== oldUniqueName) return
24✔
1388

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

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

1421
                    // replace constraint name
1422
                    unique.name = newUniqueName
12✔
1423
                })
1424

1425
                // rename index constraints
1426
                clonedTable.findColumnIndices(oldColumn).forEach((index) => {
75✔
1427
                    const oldIndexName =
1428
                        this.connection.namingStrategy.indexName(
21✔
1429
                            clonedTable,
1430
                            index.columnNames,
1431
                            index.where,
1432
                        )
1433

1434
                    // Skip renaming if Index has user defined constraint name
1435
                    if (index.name !== oldIndexName) return
21✔
1436

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

1451
                    // build queries
1452
                    const up = schema
15!
1453
                        ? `ALTER INDEX "${schema}"."${index.name}" RENAME TO "${newIndexName}"`
1454
                        : `ALTER INDEX "${index.name}" RENAME TO "${newIndexName}"`
1455
                    const down = schema
15!
1456
                        ? `ALTER INDEX "${schema}"."${newIndexName}" RENAME TO "${index.name}"`
1457
                        : `ALTER INDEX "${newIndexName}" RENAME TO "${index.name}"`
1458

1459
                    upQueries.push(new Query(up))
15✔
1460
                    downQueries.push(new Query(down))
15✔
1461

1462
                    // replace constraint name
1463
                    index.name = newIndexName
15✔
1464
                })
1465

1466
                // rename foreign key constraints
1467
                clonedTable
75✔
1468
                    .findColumnForeignKeys(oldColumn)
1469
                    .forEach((foreignKey) => {
1470
                        const foreignKeyName =
1471
                            this.connection.namingStrategy.foreignKeyName(
27✔
1472
                                clonedTable,
1473
                                foreignKey.columnNames,
1474
                                this.getTablePath(foreignKey),
1475
                                foreignKey.referencedColumnNames,
1476
                            )
1477

1478
                        // Skip renaming if foreign key has user defined constraint name
1479
                        if (foreignKey.name !== foreignKeyName) return
27✔
1480

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

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

1515
                        // replace constraint name
1516
                        foreignKey.name = newForeignKeyName
3✔
1517
                    })
1518

1519
                // rename old column in the Table object
1520
                const oldTableColumn = clonedTable.columns.find(
75✔
1521
                    (column) => column.name === oldColumn.name,
162✔
1522
                )
1523
                clonedTable.columns[
75✔
1524
                    clonedTable.columns.indexOf(oldTableColumn!)
1525
                ].name = newColumn.name
1526
                oldColumn.name = newColumn.name
75✔
1527
            }
1528

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

1549
            if (
285✔
1550
                (newColumn.type === "enum" ||
639!
1551
                    newColumn.type === "simple-enum") &&
1552
                (oldColumn.type === "enum" ||
1553
                    oldColumn.type === "simple-enum") &&
1554
                (!OrmUtils.isArraysEqual(newColumn.enum!, oldColumn.enum!) ||
1555
                    newColumn.enumName !== oldColumn.enumName)
1556
            ) {
1557
                const arraySuffix = newColumn.isArray ? "[]" : ""
39✔
1558

1559
                // "public"."new_enum"
1560
                const newEnumName = this.buildEnumName(table, newColumn)
39✔
1561

1562
                // "public"."old_enum"
1563
                const oldEnumName = this.buildEnumName(table, oldColumn)
39✔
1564

1565
                // "old_enum"
1566
                const oldEnumNameWithoutSchema = this.buildEnumName(
39✔
1567
                    table,
1568
                    oldColumn,
1569
                    false,
1570
                )
1571

1572
                //"public"."old_enum_old"
1573
                const oldEnumNameWithSchema_old = this.buildEnumName(
39✔
1574
                    table,
1575
                    oldColumn,
1576
                    true,
1577
                    false,
1578
                    true,
1579
                )
1580

1581
                //"old_enum_old"
1582
                const oldEnumNameWithoutSchema_old = this.buildEnumName(
39✔
1583
                    table,
1584
                    oldColumn,
1585
                    false,
1586
                    false,
1587
                    true,
1588
                )
1589

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

1602
                // create new ENUM
1603
                upQueries.push(
39✔
1604
                    this.createEnumTypeSql(table, newColumn, newEnumName),
1605
                )
1606
                downQueries.push(
39✔
1607
                    this.dropEnumTypeSql(table, newColumn, newEnumName),
1608
                )
1609

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

1635
                // build column types
1636
                const upType = `${newEnumName}${arraySuffix} USING "${newColumn.name}"::"text"::${newEnumName}${arraySuffix}`
39✔
1637
                const downType = `${oldEnumNameWithSchema_old}${arraySuffix} USING "${newColumn.name}"::"text"::${oldEnumNameWithSchema_old}${arraySuffix}`
39✔
1638

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

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

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

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

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

1746
            if (newColumn.isPrimary !== oldColumn.isPrimary) {
285✔
1747
                const primaryColumns = clonedTable.primaryColumns
12✔
1748

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

1758
                    const columnNames = primaryColumns
9✔
1759
                        .map((column) => `"${column.name}"`)
12✔
1760
                        .join(", ")
1761

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

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

1792
                    const columnNames = primaryColumns
6✔
1793
                        .map((column) => `"${column.name}"`)
9✔
1794
                        .join(", ")
1795

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

1819
                    // update column in table
1820
                    const column = clonedTable.columns.find(
6✔
1821
                        (column) => column.name === newColumn.name,
6✔
1822
                    )
1823
                    column!.isPrimary = false
6✔
1824

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

1835
                        const columnNames = primaryColumns
3✔
1836
                            .map((column) => `"${column.name}"`)
3✔
1837
                            .join(", ")
1838

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

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

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

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

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

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

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

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

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

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

2189
            if (newColumn.generatedType !== oldColumn.generatedType) {
285✔
2190
                // Convert generated column data to normal column
2191
                if (
6✔
2192
                    !newColumn.generatedType ||
6!
2193
                    newColumn.generatedType === "VIRTUAL"
2194
                ) {
2195
                    // We can copy the generated data to the new column
2196
                    const tableNameWithSchema = (
6✔
2197
                        await this.getTableNameWithSchema(table.name)
2198
                    ).split(".")
2199
                    const tableName = tableNameWithSchema[1]
6✔
2200
                    const schema = tableNameWithSchema[0]
6✔
2201

2202
                    upQueries.push(
6✔
2203
                        new Query(
2204
                            `ALTER TABLE ${this.escapePath(
2205
                                table,
2206
                            )} RENAME COLUMN "${oldColumn.name}" TO "TEMP_OLD_${
2207
                                oldColumn.name
2208
                            }"`,
2209
                        ),
2210
                    )
2211
                    upQueries.push(
6✔
2212
                        new Query(
2213
                            `ALTER TABLE ${this.escapePath(
2214
                                table,
2215
                            )} ADD ${this.buildCreateColumnSql(
2216
                                table,
2217
                                newColumn,
2218
                            )}`,
2219
                        ),
2220
                    )
2221
                    upQueries.push(
6✔
2222
                        new Query(
2223
                            `UPDATE ${this.escapePath(table)} SET "${
2224
                                newColumn.name
2225
                            }" = "TEMP_OLD_${oldColumn.name}"`,
2226
                        ),
2227
                    )
2228
                    upQueries.push(
6✔
2229
                        new Query(
2230
                            `ALTER TABLE ${this.escapePath(
2231
                                table,
2232
                            )} DROP COLUMN "TEMP_OLD_${oldColumn.name}"`,
2233
                        ),
2234
                    )
2235
                    upQueries.push(
6✔
2236
                        this.deleteTypeormMetadataSql({
2237
                            database: this.driver.database,
2238
                            schema,
2239
                            table: tableName,
2240
                            type: MetadataTableType.GENERATED_COLUMN,
2241
                            name: oldColumn.name,
2242
                        }),
2243
                    )
2244
                    // However, we can't copy it back on downgrade. It needs to regenerate.
2245
                    downQueries.push(
6✔
2246
                        this.insertTypeormMetadataSql({
2247
                            database: this.driver.database,
2248
                            schema,
2249
                            table: tableName,
2250
                            type: MetadataTableType.GENERATED_COLUMN,
2251
                            name: oldColumn.name,
2252
                            value: oldColumn.asExpression,
2253
                        }),
2254
                    )
2255
                    downQueries.push(
6✔
2256
                        new Query(
2257
                            `ALTER TABLE ${this.escapePath(
2258
                                table,
2259
                            )} ADD ${this.buildCreateColumnSql(
2260
                                table,
2261
                                oldColumn,
2262
                            )}`,
2263
                        ),
2264
                    )
2265
                    downQueries.push(
6✔
2266
                        new Query(
2267
                            `ALTER TABLE ${this.escapePath(
2268
                                table,
2269
                            )} DROP COLUMN "${newColumn.name}"`,
2270
                        ),
2271
                    )
2272
                    // downQueries.push(
2273
                    //     this.deleteTypeormMetadataSql({
2274
                    //         database: this.driver.database,
2275
                    //         schema,
2276
                    //         table: tableName,
2277
                    //         type: MetadataTableType.GENERATED_COLUMN,
2278
                    //         name: newColumn.name,
2279
                    //     }),
2280
                    // )
2281
                }
2282
            }
2283
        }
2284

2285
        await this.executeQueries(upQueries, downQueries)
360✔
2286
        this.replaceCachedTable(table, clonedTable)
360✔
2287
    }
2288

2289
    /**
2290
     * Changes a column in the table.
2291
     */
2292
    async changeColumns(
2293
        tableOrName: Table | string,
2294
        changedColumns: { newColumn: TableColumn; oldColumn: TableColumn }[],
2295
    ): Promise<void> {
2296
        for (const { oldColumn, newColumn } of changedColumns) {
168✔
2297
            await this.changeColumn(tableOrName, oldColumn, newColumn)
225✔
2298
        }
2299
    }
2300

2301
    /**
2302
     * Drops column in the table.
2303
     */
2304
    async dropColumn(
2305
        tableOrName: Table | string,
2306
        columnOrName: TableColumn | string,
2307
    ): Promise<void> {
2308
        const table = InstanceChecker.isTable(tableOrName)
126✔
2309
            ? tableOrName
2310
            : await this.getCachedTable(tableOrName)
2311
        const column = InstanceChecker.isTableColumn(columnOrName)
126✔
2312
            ? columnOrName
2313
            : table.findColumnByName(columnOrName)
2314
        if (!column)
126✔
2315
            throw new TypeORMError(
3✔
2316
                `Column "${columnOrName}" was not found in table "${table.name}"`,
2317
            )
2318

2319
        const clonedTable = table.clone()
123✔
2320
        const upQueries: Query[] = []
123✔
2321
        const downQueries: Query[] = []
123✔
2322

2323
        // drop primary key constraint
2324
        if (column.isPrimary) {
123✔
2325
            const pkName = column.primaryKeyConstraintName
12!
2326
                ? column.primaryKeyConstraintName
2327
                : this.connection.namingStrategy.primaryKeyName(
2328
                      clonedTable,
2329
                      clonedTable.primaryColumns.map((column) => column.name),
12✔
2330
                  )
2331

2332
            const columnNames = clonedTable.primaryColumns
12✔
2333
                .map((primaryColumn) => `"${primaryColumn.name}"`)
12✔
2334
                .join(", ")
2335

2336
            upQueries.push(
12✔
2337
                new Query(
2338
                    `ALTER TABLE ${this.escapePath(
2339
                        clonedTable,
2340
                    )} DROP CONSTRAINT "${pkName}"`,
2341
                ),
2342
            )
2343
            downQueries.push(
12✔
2344
                new Query(
2345
                    `ALTER TABLE ${this.escapePath(
2346
                        clonedTable,
2347
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
2348
                ),
2349
            )
2350

2351
            // update column in table
2352
            const tableColumn = clonedTable.findColumnByName(column.name)
12✔
2353
            tableColumn!.isPrimary = false
12✔
2354

2355
            // if primary key have multiple columns, we must recreate it without dropped column
2356
            if (clonedTable.primaryColumns.length > 0) {
12!
2357
                const pkName = clonedTable.primaryColumns[0]
×
2358
                    .primaryKeyConstraintName
2359
                    ? clonedTable.primaryColumns[0].primaryKeyConstraintName
2360
                    : this.connection.namingStrategy.primaryKeyName(
2361
                          clonedTable,
2362
                          clonedTable.primaryColumns.map(
2363
                              (column) => column.name,
×
2364
                          ),
2365
                      )
2366

2367
                const columnNames = clonedTable.primaryColumns
×
2368
                    .map((primaryColumn) => `"${primaryColumn.name}"`)
×
2369
                    .join(", ")
2370

2371
                upQueries.push(
×
2372
                    new Query(
2373
                        `ALTER TABLE ${this.escapePath(
2374
                            clonedTable,
2375
                        )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
2376
                    ),
2377
                )
2378
                downQueries.push(
×
2379
                    new Query(
2380
                        `ALTER TABLE ${this.escapePath(
2381
                            clonedTable,
2382
                        )} DROP CONSTRAINT "${pkName}"`,
2383
                    ),
2384
                )
2385
            }
2386
        }
2387

2388
        // drop column index
2389
        const columnIndex = clonedTable.indices.find(
123✔
2390
            (index) =>
2391
                index.columnNames.length === 1 &&
9✔
2392
                index.columnNames[0] === column.name,
2393
        )
2394
        if (columnIndex) {
123!
2395
            clonedTable.indices.splice(
×
2396
                clonedTable.indices.indexOf(columnIndex),
2397
                1,
2398
            )
2399
            upQueries.push(this.dropIndexSql(table, columnIndex))
×
2400
            downQueries.push(this.createIndexSql(table, columnIndex))
×
2401
        }
2402

2403
        // drop column check
2404
        const columnCheck = clonedTable.checks.find(
123✔
2405
            (check) =>
2406
                !!check.columnNames &&
42✔
2407
                check.columnNames.length === 1 &&
2408
                check.columnNames[0] === column.name,
2409
        )
2410
        if (columnCheck) {
123✔
2411
            clonedTable.checks.splice(
6✔
2412
                clonedTable.checks.indexOf(columnCheck),
2413
                1,
2414
            )
2415
            upQueries.push(this.dropCheckConstraintSql(table, columnCheck))
6✔
2416
            downQueries.push(this.createCheckConstraintSql(table, columnCheck))
6✔
2417
        }
2418

2419
        // drop column unique
2420
        const columnUnique = clonedTable.uniques.find(
123✔
2421
            (unique) =>
2422
                unique.columnNames.length === 1 &&
78✔
2423
                unique.columnNames[0] === column.name,
2424
        )
2425
        if (columnUnique) {
123✔
2426
            clonedTable.uniques.splice(
9✔
2427
                clonedTable.uniques.indexOf(columnUnique),
2428
                1,
2429
            )
2430
            upQueries.push(this.dropUniqueConstraintSql(table, columnUnique))
9✔
2431
            downQueries.push(
9✔
2432
                this.createUniqueConstraintSql(table, columnUnique),
2433
            )
2434
        }
2435

2436
        upQueries.push(
123✔
2437
            new Query(
2438
                `ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${
2439
                    column.name
2440
                }"`,
2441
            ),
2442
        )
2443
        downQueries.push(
123✔
2444
            new Query(
2445
                `ALTER TABLE ${this.escapePath(
2446
                    table,
2447
                )} ADD ${this.buildCreateColumnSql(table, column)}`,
2448
            ),
2449
        )
2450

2451
        // drop enum type
2452
        if (column.type === "enum" || column.type === "simple-enum") {
123✔
2453
            const hasEnum = await this.hasEnumType(table, column)
15✔
2454
            if (hasEnum) {
15✔
2455
                const enumType = await this.getUserDefinedTypeName(
15✔
2456
                    table,
2457
                    column,
2458
                )
2459
                const escapedEnumName = `"${enumType.schema}"."${enumType.name}"`
15✔
2460
                upQueries.push(
15✔
2461
                    this.dropEnumTypeSql(table, column, escapedEnumName),
2462
                )
2463
                downQueries.push(
15✔
2464
                    this.createEnumTypeSql(table, column, escapedEnumName),
2465
                )
2466
            }
2467
        }
2468

2469
        if (column.generatedType === "STORED") {
123✔
2470
            const tableNameWithSchema = (
9✔
2471
                await this.getTableNameWithSchema(table.name)
2472
            ).split(".")
2473
            const tableName = tableNameWithSchema[1]
9✔
2474
            const schema = tableNameWithSchema[0]
9✔
2475
            const deleteQuery = this.deleteTypeormMetadataSql({
9✔
2476
                database: this.driver.database,
2477
                schema,
2478
                table: tableName,
2479
                type: MetadataTableType.GENERATED_COLUMN,
2480
                name: column.name,
2481
            })
2482
            const insertQuery = this.insertTypeormMetadataSql({
9✔
2483
                database: this.driver.database,
2484
                schema,
2485
                table: tableName,
2486
                type: MetadataTableType.GENERATED_COLUMN,
2487
                name: column.name,
2488
                value: column.asExpression,
2489
            })
2490

2491
            upQueries.push(deleteQuery)
9✔
2492
            downQueries.push(insertQuery)
9✔
2493
        }
2494

2495
        await this.executeQueries(upQueries, downQueries)
123✔
2496

2497
        clonedTable.removeColumn(column)
123✔
2498
        this.replaceCachedTable(table, clonedTable)
123✔
2499
    }
2500

2501
    /**
2502
     * Drops the columns in the table.
2503
     */
2504
    async dropColumns(
2505
        tableOrName: Table | string,
2506
        columns: TableColumn[] | string[],
2507
    ): Promise<void> {
2508
        for (const column of columns) {
18✔
2509
            await this.dropColumn(tableOrName, column)
33✔
2510
        }
2511
    }
2512

2513
    /**
2514
     * Creates a new primary key.
2515
     */
2516
    async createPrimaryKey(
2517
        tableOrName: Table | string,
2518
        columnNames: string[],
2519
        constraintName?: string,
2520
    ): Promise<void> {
2521
        const table = InstanceChecker.isTable(tableOrName)
6!
2522
            ? tableOrName
2523
            : await this.getCachedTable(tableOrName)
2524
        const clonedTable = table.clone()
6✔
2525

2526
        const up = this.createPrimaryKeySql(table, columnNames, constraintName)
6✔
2527

2528
        // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names.
2529
        clonedTable.columns.forEach((column) => {
6✔
2530
            if (columnNames.find((columnName) => columnName === column.name))
21✔
2531
                column.isPrimary = true
9✔
2532
        })
2533
        const down = this.dropPrimaryKeySql(clonedTable)
6✔
2534

2535
        await this.executeQueries(up, down)
6✔
2536
        this.replaceCachedTable(table, clonedTable)
6✔
2537
    }
2538

2539
    /**
2540
     * Updates composite primary keys.
2541
     */
2542
    async updatePrimaryKeys(
2543
        tableOrName: Table | string,
2544
        columns: TableColumn[],
2545
    ): Promise<void> {
2546
        const table = InstanceChecker.isTable(tableOrName)
9!
2547
            ? tableOrName
2548
            : await this.getCachedTable(tableOrName)
2549
        const clonedTable = table.clone()
9✔
2550
        const columnNames = columns.map((column) => column.name)
18✔
2551
        const upQueries: Query[] = []
9✔
2552
        const downQueries: Query[] = []
9✔
2553

2554
        // if table already have primary columns, we must drop them.
2555
        const primaryColumns = clonedTable.primaryColumns
9✔
2556
        if (primaryColumns.length > 0) {
9✔
2557
            const pkName = primaryColumns[0].primaryKeyConstraintName
9!
2558
                ? primaryColumns[0].primaryKeyConstraintName
2559
                : this.connection.namingStrategy.primaryKeyName(
2560
                      clonedTable,
2561
                      primaryColumns.map((column) => column.name),
9✔
2562
                  )
2563

2564
            const columnNamesString = primaryColumns
9✔
2565
                .map((column) => `"${column.name}"`)
9✔
2566
                .join(", ")
2567

2568
            upQueries.push(
9✔
2569
                new Query(
2570
                    `ALTER TABLE ${this.escapePath(
2571
                        table,
2572
                    )} DROP CONSTRAINT "${pkName}"`,
2573
                ),
2574
            )
2575
            downQueries.push(
9✔
2576
                new Query(
2577
                    `ALTER TABLE ${this.escapePath(
2578
                        table,
2579
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
2580
                ),
2581
            )
2582
        }
2583

2584
        // update columns in table.
2585
        clonedTable.columns
9✔
2586
            .filter((column) => columnNames.indexOf(column.name) !== -1)
36✔
2587
            .forEach((column) => (column.isPrimary = true))
18✔
2588

2589
        const pkName = primaryColumns[0]?.primaryKeyConstraintName
9!
2590
            ? primaryColumns[0].primaryKeyConstraintName
2591
            : this.connection.namingStrategy.primaryKeyName(
2592
                  clonedTable,
2593
                  columnNames,
2594
              )
2595

2596
        const columnNamesString = columnNames
9✔
2597
            .map((columnName) => `"${columnName}"`)
18✔
2598
            .join(", ")
2599

2600
        upQueries.push(
9✔
2601
            new Query(
2602
                `ALTER TABLE ${this.escapePath(
2603
                    table,
2604
                )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
2605
            ),
2606
        )
2607
        downQueries.push(
9✔
2608
            new Query(
2609
                `ALTER TABLE ${this.escapePath(
2610
                    table,
2611
                )} DROP CONSTRAINT "${pkName}"`,
2612
            ),
2613
        )
2614

2615
        await this.executeQueries(upQueries, downQueries)
9✔
2616
        this.replaceCachedTable(table, clonedTable)
9✔
2617
    }
2618

2619
    /**
2620
     * Drops a primary key.
2621
     */
2622
    async dropPrimaryKey(
2623
        tableOrName: Table | string,
2624
        constraintName?: string,
2625
    ): Promise<void> {
2626
        const table = InstanceChecker.isTable(tableOrName)
9!
2627
            ? tableOrName
2628
            : await this.getCachedTable(tableOrName)
2629
        const up = this.dropPrimaryKeySql(table)
9✔
2630
        const down = this.createPrimaryKeySql(
9✔
2631
            table,
2632
            table.primaryColumns.map((column) => column.name),
9✔
2633
            constraintName,
2634
        )
2635
        await this.executeQueries(up, down)
9✔
2636
        table.primaryColumns.forEach((column) => {
9✔
2637
            column.isPrimary = false
9✔
2638
        })
2639
    }
2640

2641
    /**
2642
     * Creates new unique constraint.
2643
     */
2644
    async createUniqueConstraint(
2645
        tableOrName: Table | string,
2646
        uniqueConstraint: TableUnique,
2647
    ): Promise<void> {
2648
        const table = InstanceChecker.isTable(tableOrName)
18✔
2649
            ? tableOrName
2650
            : await this.getCachedTable(tableOrName)
2651

2652
        // new unique constraint may be passed without name. In this case we generate unique name manually.
2653
        if (!uniqueConstraint.name)
18✔
2654
            uniqueConstraint.name =
6✔
2655
                this.connection.namingStrategy.uniqueConstraintName(
2656
                    table,
2657
                    uniqueConstraint.columnNames,
2658
                )
2659

2660
        const up = this.createUniqueConstraintSql(table, uniqueConstraint)
18✔
2661
        const down = this.dropUniqueConstraintSql(table, uniqueConstraint)
18✔
2662
        await this.executeQueries(up, down)
18✔
2663
        table.addUniqueConstraint(uniqueConstraint)
18✔
2664
    }
2665

2666
    /**
2667
     * Creates new unique constraints.
2668
     */
2669
    async createUniqueConstraints(
2670
        tableOrName: Table | string,
2671
        uniqueConstraints: TableUnique[],
2672
    ): Promise<void> {
2673
        for (const uniqueConstraint of uniqueConstraints) {
12✔
2674
            await this.createUniqueConstraint(tableOrName, uniqueConstraint)
12✔
2675
        }
2676
    }
2677

2678
    /**
2679
     * Drops unique constraint.
2680
     */
2681
    async dropUniqueConstraint(
2682
        tableOrName: Table | string,
2683
        uniqueOrName: TableUnique | string,
2684
    ): Promise<void> {
2685
        const table = InstanceChecker.isTable(tableOrName)
18!
2686
            ? tableOrName
2687
            : await this.getCachedTable(tableOrName)
2688
        const uniqueConstraint = InstanceChecker.isTableUnique(uniqueOrName)
18!
2689
            ? uniqueOrName
2690
            : table.uniques.find((u) => u.name === uniqueOrName)
×
2691
        if (!uniqueConstraint)
18!
2692
            throw new TypeORMError(
×
2693
                `Supplied unique constraint was not found in table ${table.name}`,
2694
            )
2695

2696
        const up = this.dropUniqueConstraintSql(table, uniqueConstraint)
18✔
2697
        const down = this.createUniqueConstraintSql(table, uniqueConstraint)
18✔
2698
        await this.executeQueries(up, down)
18✔
2699
        table.removeUniqueConstraint(uniqueConstraint)
18✔
2700
    }
2701

2702
    /**
2703
     * Drops unique constraints.
2704
     */
2705
    async dropUniqueConstraints(
2706
        tableOrName: Table | string,
2707
        uniqueConstraints: TableUnique[],
2708
    ): Promise<void> {
2709
        for (const uniqueConstraint of uniqueConstraints) {
15✔
2710
            await this.dropUniqueConstraint(tableOrName, uniqueConstraint)
15✔
2711
        }
2712
    }
2713

2714
    /**
2715
     * Creates new check constraint.
2716
     */
2717
    async createCheckConstraint(
2718
        tableOrName: Table | string,
2719
        checkConstraint: TableCheck,
2720
    ): Promise<void> {
2721
        const table = InstanceChecker.isTable(tableOrName)
15✔
2722
            ? tableOrName
2723
            : await this.getCachedTable(tableOrName)
2724

2725
        // new unique constraint may be passed without name. In this case we generate unique name manually.
2726
        if (!checkConstraint.name)
15✔
2727
            checkConstraint.name =
9✔
2728
                this.connection.namingStrategy.checkConstraintName(
2729
                    table,
2730
                    checkConstraint.expression!,
2731
                )
2732

2733
        const up = this.createCheckConstraintSql(table, checkConstraint)
15✔
2734
        const down = this.dropCheckConstraintSql(table, checkConstraint)
15✔
2735
        await this.executeQueries(up, down)
15✔
2736
        table.addCheckConstraint(checkConstraint)
15✔
2737
    }
2738

2739
    /**
2740
     * Creates new check constraints.
2741
     */
2742
    async createCheckConstraints(
2743
        tableOrName: Table | string,
2744
        checkConstraints: TableCheck[],
2745
    ): Promise<void> {
2746
        const promises = checkConstraints.map((checkConstraint) =>
6✔
2747
            this.createCheckConstraint(tableOrName, checkConstraint),
6✔
2748
        )
2749
        await Promise.all(promises)
6✔
2750
    }
2751

2752
    /**
2753
     * Drops check constraint.
2754
     */
2755
    async dropCheckConstraint(
2756
        tableOrName: Table | string,
2757
        checkOrName: TableCheck | string,
2758
    ): Promise<void> {
2759
        const table = InstanceChecker.isTable(tableOrName)
9!
2760
            ? tableOrName
2761
            : await this.getCachedTable(tableOrName)
2762
        const checkConstraint = InstanceChecker.isTableCheck(checkOrName)
9!
2763
            ? checkOrName
2764
            : table.checks.find((c) => c.name === checkOrName)
×
2765
        if (!checkConstraint)
9!
2766
            throw new TypeORMError(
×
2767
                `Supplied check constraint was not found in table ${table.name}`,
2768
            )
2769

2770
        const up = this.dropCheckConstraintSql(table, checkConstraint)
9✔
2771
        const down = this.createCheckConstraintSql(table, checkConstraint)
9✔
2772
        await this.executeQueries(up, down)
9✔
2773
        table.removeCheckConstraint(checkConstraint)
9✔
2774
    }
2775

2776
    /**
2777
     * Drops check constraints.
2778
     */
2779
    async dropCheckConstraints(
2780
        tableOrName: Table | string,
2781
        checkConstraints: TableCheck[],
2782
    ): Promise<void> {
2783
        const promises = checkConstraints.map((checkConstraint) =>
6✔
2784
            this.dropCheckConstraint(tableOrName, checkConstraint),
6✔
2785
        )
2786
        await Promise.all(promises)
6✔
2787
    }
2788

2789
    /**
2790
     * Creates new exclusion constraint.
2791
     */
2792
    async createExclusionConstraint(
2793
        tableOrName: Table | string,
2794
        exclusionConstraint: TableExclusion,
2795
    ): Promise<void> {
2796
        const table = InstanceChecker.isTable(tableOrName)
12✔
2797
            ? tableOrName
2798
            : await this.getCachedTable(tableOrName)
2799

2800
        // new unique constraint may be passed without name. In this case we generate unique name manually.
2801
        if (!exclusionConstraint.name)
12✔
2802
            exclusionConstraint.name =
6✔
2803
                this.connection.namingStrategy.exclusionConstraintName(
2804
                    table,
2805
                    exclusionConstraint.expression!,
2806
                )
2807

2808
        const up = this.createExclusionConstraintSql(table, exclusionConstraint)
12✔
2809
        const down = this.dropExclusionConstraintSql(table, exclusionConstraint)
12✔
2810
        await this.executeQueries(up, down)
12✔
2811
        table.addExclusionConstraint(exclusionConstraint)
12✔
2812
    }
2813

2814
    /**
2815
     * Creates new exclusion constraints.
2816
     */
2817
    async createExclusionConstraints(
2818
        tableOrName: Table | string,
2819
        exclusionConstraints: TableExclusion[],
2820
    ): Promise<void> {
2821
        const promises = exclusionConstraints.map((exclusionConstraint) =>
9✔
2822
            this.createExclusionConstraint(tableOrName, exclusionConstraint),
12✔
2823
        )
2824
        await Promise.all(promises)
9✔
2825
    }
2826

2827
    /**
2828
     * Drops exclusion constraint.
2829
     */
2830
    async dropExclusionConstraint(
2831
        tableOrName: Table | string,
2832
        exclusionOrName: TableExclusion | string,
2833
    ): Promise<void> {
2834
        const table = InstanceChecker.isTable(tableOrName)
9!
2835
            ? tableOrName
2836
            : await this.getCachedTable(tableOrName)
2837
        const exclusionConstraint = InstanceChecker.isTableExclusion(
9!
2838
            exclusionOrName,
2839
        )
2840
            ? exclusionOrName
2841
            : table.exclusions.find((c) => c.name === exclusionOrName)
×
2842
        if (!exclusionConstraint)
9!
2843
            throw new TypeORMError(
×
2844
                `Supplied exclusion constraint was not found in table ${table.name}`,
2845
            )
2846

2847
        const up = this.dropExclusionConstraintSql(table, exclusionConstraint)
9✔
2848
        const down = this.createExclusionConstraintSql(
9✔
2849
            table,
2850
            exclusionConstraint,
2851
        )
2852
        await this.executeQueries(up, down)
9✔
2853
        table.removeExclusionConstraint(exclusionConstraint)
9✔
2854
    }
2855

2856
    /**
2857
     * Drops exclusion constraints.
2858
     */
2859
    async dropExclusionConstraints(
2860
        tableOrName: Table | string,
2861
        exclusionConstraints: TableExclusion[],
2862
    ): Promise<void> {
2863
        const promises = exclusionConstraints.map((exclusionConstraint) =>
6✔
2864
            this.dropExclusionConstraint(tableOrName, exclusionConstraint),
6✔
2865
        )
2866
        await Promise.all(promises)
6✔
2867
    }
2868

2869
    /**
2870
     * Creates a new foreign key.
2871
     */
2872
    async createForeignKey(
2873
        tableOrName: Table | string,
2874
        foreignKey: TableForeignKey,
2875
    ): Promise<void> {
2876
        const table = InstanceChecker.isTable(tableOrName)
10,743✔
2877
            ? tableOrName
2878
            : await this.getCachedTable(tableOrName)
2879

2880
        // new FK may be passed without name. In this case we generate FK name manually.
2881
        if (!foreignKey.name)
10,743✔
2882
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
3✔
2883
                table,
2884
                foreignKey.columnNames,
2885
                this.getTablePath(foreignKey),
2886
                foreignKey.referencedColumnNames,
2887
            )
2888

2889
        const up = this.createForeignKeySql(table, foreignKey)
10,743✔
2890
        const down = this.dropForeignKeySql(table, foreignKey)
10,743✔
2891
        await this.executeQueries(up, down)
10,743✔
2892
        table.addForeignKey(foreignKey)
10,743✔
2893
    }
2894

2895
    /**
2896
     * Creates a new foreign keys.
2897
     */
2898
    async createForeignKeys(
2899
        tableOrName: Table | string,
2900
        foreignKeys: TableForeignKey[],
2901
    ): Promise<void> {
2902
        for (const foreignKey of foreignKeys) {
6,348✔
2903
            await this.createForeignKey(tableOrName, foreignKey)
10,740✔
2904
        }
2905
    }
2906

2907
    /**
2908
     * Drops a foreign key from the table.
2909
     */
2910
    async dropForeignKey(
2911
        tableOrName: Table | string,
2912
        foreignKeyOrName: TableForeignKey | string,
2913
    ): Promise<void> {
2914
        const table = InstanceChecker.isTable(tableOrName)
42!
2915
            ? tableOrName
2916
            : await this.getCachedTable(tableOrName)
2917
        const foreignKey = InstanceChecker.isTableForeignKey(foreignKeyOrName)
42!
2918
            ? foreignKeyOrName
2919
            : table.foreignKeys.find((fk) => fk.name === foreignKeyOrName)
×
2920
        if (!foreignKey)
42!
2921
            throw new TypeORMError(
×
2922
                `Supplied foreign key was not found in table ${table.name}`,
2923
            )
2924

2925
        if (!foreignKey.name) {
42!
2926
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
×
2927
                table,
2928
                foreignKey.columnNames,
2929
                this.getTablePath(foreignKey),
2930
                foreignKey.referencedColumnNames,
2931
            )
2932
        }
2933

2934
        const up = this.dropForeignKeySql(table, foreignKey)
42✔
2935
        const down = this.createForeignKeySql(table, foreignKey)
42✔
2936
        await this.executeQueries(up, down)
42✔
2937
        table.removeForeignKey(foreignKey)
42✔
2938
    }
2939

2940
    /**
2941
     * Drops a foreign keys from the table.
2942
     */
2943
    async dropForeignKeys(
2944
        tableOrName: Table | string,
2945
        foreignKeys: TableForeignKey[],
2946
    ): Promise<void> {
2947
        for (const foreignKey of foreignKeys) {
39✔
2948
            await this.dropForeignKey(tableOrName, foreignKey)
39✔
2949
        }
2950
    }
2951

2952
    /**
2953
     * Creates a new index.
2954
     */
2955
    async createIndex(
2956
        tableOrName: Table | string,
2957
        index: TableIndex,
2958
    ): Promise<void> {
2959
        const table = InstanceChecker.isTable(tableOrName)
36✔
2960
            ? tableOrName
2961
            : await this.getCachedTable(tableOrName)
2962

2963
        // new index may be passed without name. In this case we generate index name manually.
2964
        if (!index.name) index.name = this.generateIndexName(table, index)
36✔
2965

2966
        const up = this.createIndexSql(table, index)
36✔
2967
        const down = this.dropIndexSql(table, index)
36✔
2968
        await this.executeQueries(up, down)
36✔
2969
        table.addIndex(index)
36✔
2970
    }
2971

2972
    /**
2973
     * Create a new view index.
2974
     */
2975
    async createViewIndex(
2976
        viewOrName: View | string,
2977
        index: TableIndex,
2978
    ): Promise<void> {
2979
        const view = InstanceChecker.isView(viewOrName)
12!
2980
            ? viewOrName
2981
            : await this.getCachedView(viewOrName)
2982

2983
        // new index may be passed without name. In this case we generate index name manually.
2984
        if (!index.name) index.name = this.generateIndexName(view, index)
12!
2985

2986
        const up = this.createViewIndexSql(view, index)
12✔
2987
        const down = this.dropIndexSql(view, index)
12✔
2988
        await this.executeQueries(up, down)
12✔
2989
        view.addIndex(index)
12✔
2990
    }
2991

2992
    /**
2993
     * Creates a new indices
2994
     */
2995
    async createIndices(
2996
        tableOrName: Table | string,
2997
        indices: TableIndex[],
2998
    ): Promise<void> {
2999
        for (const index of indices) {
24✔
3000
            await this.createIndex(tableOrName, index)
24✔
3001
        }
3002
    }
3003

3004
    /**
3005
     * Creates new view indices
3006
     */
3007
    async createViewIndices(
3008
        viewOrName: View | string,
3009
        indices: TableIndex[],
3010
    ): Promise<void> {
3011
        for (const index of indices) {
12✔
3012
            await this.createViewIndex(viewOrName, index)
12✔
3013
        }
3014
    }
3015

3016
    /**
3017
     * Drops an index from the table.
3018
     */
3019
    async dropIndex(
3020
        tableOrName: Table | string,
3021
        indexOrName: TableIndex | string,
3022
    ): Promise<void> {
3023
        const table = InstanceChecker.isTable(tableOrName)
45✔
3024
            ? tableOrName
3025
            : await this.getCachedTable(tableOrName)
3026
        const index = InstanceChecker.isTableIndex(indexOrName)
45!
3027
            ? indexOrName
3028
            : table.indices.find((i) => i.name === indexOrName)
×
3029
        if (!index)
45!
3030
            throw new TypeORMError(
×
3031
                `Supplied index ${indexOrName} was not found in table ${table.name}`,
3032
            )
3033
        // old index may be passed without name. In this case we generate index name manually.
3034
        if (!index.name) index.name = this.generateIndexName(table, index)
45✔
3035

3036
        const up = this.dropIndexSql(table, index)
45✔
3037
        const down = this.createIndexSql(table, index)
45✔
3038
        await this.executeQueries(up, down)
45✔
3039
        table.removeIndex(index)
45✔
3040
    }
3041

3042
    /**
3043
     * Drops an index from a view.
3044
     */
3045
    async dropViewIndex(
3046
        viewOrName: View | string,
3047
        indexOrName: TableIndex | string,
3048
    ): Promise<void> {
3049
        const view = InstanceChecker.isView(viewOrName)
9!
3050
            ? viewOrName
3051
            : await this.getCachedView(viewOrName)
3052
        const index = InstanceChecker.isTableIndex(indexOrName)
9!
3053
            ? indexOrName
3054
            : view.indices.find((i) => i.name === indexOrName)
×
3055
        if (!index)
9!
3056
            throw new TypeORMError(
×
3057
                `Supplied index ${indexOrName} was not found in view ${view.name}`,
3058
            )
3059
        // old index may be passed without name. In this case we generate index name manually.
3060
        if (!index.name) index.name = this.generateIndexName(view, index)
9!
3061

3062
        const up = this.dropIndexSql(view, index)
9✔
3063
        const down = this.createViewIndexSql(view, index)
9✔
3064
        await this.executeQueries(up, down)
9✔
3065
        view.removeIndex(index)
9✔
3066
    }
3067

3068
    /**
3069
     * Drops an indices from the table.
3070
     */
3071
    async dropIndices(
3072
        tableOrName: Table | string,
3073
        indices: TableIndex[],
3074
    ): Promise<void> {
3075
        for (const index of indices) {
3✔
3076
            await this.dropIndex(tableOrName, index)
6✔
3077
        }
3078
    }
3079

3080
    /**
3081
     * Clears all table contents.
3082
     * Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
3083
     */
3084
    async clearTable(tableName: string): Promise<void> {
3085
        await this.query(`TRUNCATE TABLE ${this.escapePath(tableName)}`)
6✔
3086
    }
3087

3088
    /**
3089
     * Removes all tables from the currently connected database.
3090
     */
3091
    async clearDatabase(): Promise<void> {
3092
        const schemas: string[] = []
5,028✔
3093
        this.connection.entityMetadatas
5,028✔
3094
            .filter((metadata) => metadata.schema)
16,593✔
3095
            .forEach((metadata) => {
3096
                const isSchemaExist = !!schemas.find(
135✔
3097
                    (schema) => schema === metadata.schema,
87✔
3098
                )
3099
                if (!isSchemaExist) schemas.push(metadata.schema!)
135✔
3100
            })
3101
        schemas.push(this.driver.options.schema || "current_schema()")
5,028✔
3102
        const schemaNamesString = schemas
5,028✔
3103
            .map((name) => {
3104
                return name === "current_schema()" ? name : "'" + name + "'"
5,133✔
3105
            })
3106
            .join(", ")
3107

3108
        const isAnotherTransactionActive = this.isTransactionActive
5,028✔
3109
        if (!isAnotherTransactionActive) await this.startTransaction()
5,028✔
3110
        try {
5,028✔
3111
            // drop views
3112
            const selectViewDropsQuery =
3113
                `SELECT 'DROP VIEW IF EXISTS "' || schemaname || '"."' || viewname || '" CASCADE;' as "query" ` +
5,028✔
3114
                `FROM "pg_views" WHERE "schemaname" IN (${schemaNamesString}) AND "viewname" NOT IN ('geography_columns', 'geometry_columns', 'raster_columns', 'raster_overviews')`
3115
            const dropViewQueries: ObjectLiteral[] = await this.query(
5,028✔
3116
                selectViewDropsQuery,
3117
            )
3118
            await Promise.all(
5,028✔
3119
                dropViewQueries.map((q) => this.query(q["query"])),
72✔
3120
            )
3121

3122
            // drop materialized views
3123
            // Note: materialized views introduced in Postgres 9.3
3124
            if (DriverUtils.isReleaseVersionOrGreater(this.driver, "9.3")) {
5,028✔
3125
                const selectMatViewDropsQuery =
3126
                    `SELECT 'DROP MATERIALIZED VIEW IF EXISTS "' || schemaname || '"."' || matviewname || '" CASCADE;' as "query" ` +
5,028✔
3127
                    `FROM "pg_matviews" WHERE "schemaname" IN (${schemaNamesString})`
3128
                const dropMatViewQueries: ObjectLiteral[] = await this.query(
5,028✔
3129
                    selectMatViewDropsQuery,
3130
                )
3131
                await Promise.all(
5,028✔
3132
                    dropMatViewQueries.map((q) => this.query(q["query"])),
30✔
3133
                )
3134
            }
3135

3136
            // ignore spatial_ref_sys; it's a special table supporting PostGIS
3137
            // TODO generalize this as this.driver.ignoreTables
3138

3139
            // drop tables
3140
            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')`
5,028✔
3141
            const dropTableQueries: ObjectLiteral[] = await this.query(
5,028✔
3142
                selectTableDropsQuery,
3143
            )
3144
            await Promise.all(
5,028✔
3145
                dropTableQueries.map((q) => this.query(q["query"])),
15,915✔
3146
            )
3147

3148
            // drop enum types
3149
            await this.dropEnumTypes(schemaNamesString)
5,028✔
3150

3151
            if (!isAnotherTransactionActive) {
5,028✔
3152
                await this.commitTransaction()
5,025✔
3153
            }
3154
        } catch (error) {
3155
            try {
×
3156
                // we throw original error even if rollback thrown an error
3157
                if (!isAnotherTransactionActive) {
×
3158
                    await this.rollbackTransaction()
×
3159
                }
3160
            } catch {
3161
                // no-op
3162
            }
UNCOV
3163
            throw error
×
3164
        }
3165
    }
3166

3167
    // -------------------------------------------------------------------------
3168
    // Protected Methods
3169
    // -------------------------------------------------------------------------
3170

3171
    protected async loadViews(viewNames?: string[]): Promise<View[]> {
3172
        const hasTable = await this.hasTable(this.getTypeormMetadataTableName())
5,445✔
3173

3174
        if (!hasTable) return []
5,445✔
3175

3176
        if (!viewNames) {
147!
3177
            viewNames = []
×
3178
        }
3179

3180
        const currentDatabase = await this.getCurrentDatabase()
147✔
3181
        const currentSchema = await this.getCurrentSchema()
147✔
3182
        const viewsCondition =
3183
            viewNames.length === 0
147✔
3184
                ? "1=1"
3185
                : viewNames
3186
                      .map((tableName) => this.driver.parseTableName(tableName))
186✔
3187
                      .map(({ schema, tableName }) => {
3188
                          if (!schema) {
186!
3189
                              schema =
×
3190
                                  this.driver.options.schema || currentSchema
×
3191
                          }
3192

3193
                          return `("t"."schema" = '${schema}' AND "t"."name" = '${tableName}')`
186✔
3194
                      })
3195
                      .join(" OR ")
3196

3197
        const constraintsCondition =
3198
            viewNames.length === 0
147✔
3199
                ? "1=1"
3200
                : viewNames
3201
                      .map((tableName) => this.driver.parseTableName(tableName))
186✔
3202
                      .map(({ schema, tableName }) => {
3203
                          if (!schema) {
186!
3204
                              schema =
×
3205
                                  this.driver.options.schema || currentSchema
×
3206
                          }
3207

3208
                          return `("ns"."nspname" = '${schema}' AND "t"."relname" = '${tableName}')`
186✔
3209
                      })
3210
                      .join(" OR ")
3211

3212
        const indicesSql =
3213
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", ` +
147✔
3214
            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", ` +
3215
            `"types"."typname" AS "type_name" ` +
3216
            `FROM "pg_class" "t" ` +
3217
            `INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" ` +
3218
            `INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid"  AND "a"."attnum" = ANY ("ix"."indkey") ` +
3219
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" ` +
3220
            `INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" ` +
3221
            `INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" ` +
3222
            `LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" ` +
3223
            `WHERE "t"."relkind" IN ('m') AND "cnst"."contype" IS NULL AND (${constraintsCondition})`
3224

3225
        const query =
3226
            `SELECT "t".* FROM ${this.escapePath(
147✔
3227
                this.getTypeormMetadataTableName(),
3228
            )} "t" ` +
3229
            `INNER JOIN "pg_catalog"."pg_class" "c" ON "c"."relname" = "t"."name" ` +
3230
            `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "c"."relnamespace" AND "n"."nspname" = "t"."schema" ` +
3231
            `WHERE "t"."type" IN ('${MetadataTableType.VIEW}', '${
3232
                MetadataTableType.MATERIALIZED_VIEW
3233
            }') ${viewsCondition ? `AND (${viewsCondition})` : ""}`
147!
3234

3235
        const dbViews = await this.query(query)
147✔
3236
        const dbIndices: ObjectLiteral[] = await this.query(indicesSql)
147✔
3237
        return dbViews.map((dbView: any) => {
147✔
3238
            // find index constraints of table, group them by constraint name and build TableIndex.
3239
            const tableIndexConstraints = OrmUtils.uniq(
75✔
3240
                dbIndices.filter((dbIndex) => {
3241
                    return (
21✔
3242
                        dbIndex["table_name"] === dbView["name"] &&
42✔
3243
                        dbIndex["table_schema"] === dbView["schema"]
3244
                    )
3245
                }),
3246
                (dbIndex) => dbIndex["constraint_name"],
21✔
3247
            )
3248
            const view = new View()
75✔
3249
            const schema =
3250
                dbView["schema"] === currentSchema &&
75!
3251
                !this.driver.options.schema
3252
                    ? undefined
3253
                    : dbView["schema"]
3254
            view.database = currentDatabase
75✔
3255
            view.schema = dbView["schema"]
75✔
3256
            view.name = this.driver.buildTableName(dbView["name"], schema)
75✔
3257
            view.expression = dbView["value"]
75✔
3258
            view.materialized =
75✔
3259
                dbView["type"] === MetadataTableType.MATERIALIZED_VIEW
3260
            view.indices = tableIndexConstraints.map((constraint) => {
75✔
3261
                const indices = dbIndices.filter((index) => {
21✔
3262
                    return (
21✔
3263
                        index["table_schema"] === constraint["table_schema"] &&
63✔
3264
                        index["table_name"] === constraint["table_name"] &&
3265
                        index["constraint_name"] ===
3266
                            constraint["constraint_name"]
3267
                    )
3268
                })
3269
                return new TableIndex(<TableIndexOptions>{
21✔
3270
                    view: view,
3271
                    name: constraint["constraint_name"],
3272
                    columnNames: indices.map((i) => i["column_name"]),
21✔
3273
                    isUnique: constraint["is_unique"] === "TRUE",
3274
                    where: constraint["condition"],
3275
                    isFulltext: false,
3276
                })
3277
            })
3278
            return view
75✔
3279
        })
3280
    }
3281

3282
    /**
3283
     * Loads all tables (with given names) from the database and creates a Table from them.
3284
     */
3285
    protected async loadTables(tableNames?: string[]): Promise<Table[]> {
3286
        // if no tables given then no need to proceed
3287
        if (tableNames && tableNames.length === 0) {
6,576✔
3288
            return []
90✔
3289
        }
3290

3291
        const currentSchema = await this.getCurrentSchema()
6,486✔
3292
        const currentDatabase = await this.getCurrentDatabase()
6,486✔
3293

3294
        const dbTables: {
3295
            table_schema: string
3296
            table_name: string
3297
            table_comment: string
3298
        }[] = []
6,486✔
3299

3300
        if (!tableNames) {
6,486!
3301
            const tablesSql = `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables"`
×
3302
            dbTables.push(...(await this.query(tablesSql)))
×
3303
        } else {
3304
            const tablesCondition = tableNames
6,486✔
3305
                .map((tableName) => this.driver.parseTableName(tableName))
18,027✔
3306
                .map(({ schema, tableName }) => {
3307
                    return `("table_schema" = '${
18,027✔
3308
                        schema || currentSchema
18,027!
3309
                    }' AND "table_name" = '${tableName}')`
3310
                })
3311
                .join(" OR ")
3312

3313
            const tablesSql =
3314
                `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE ` +
6,486✔
3315
                tablesCondition
3316
            dbTables.push(...(await this.query(tablesSql)))
6,486✔
3317
        }
3318

3319
        // if tables were not found in the db, no need to proceed
3320
        if (dbTables.length === 0) {
6,486✔
3321
            return []
5,037✔
3322
        }
3323

3324
        /**
3325
         * Uses standard SQL information_schema.columns table and postgres-specific
3326
         * pg_catalog.pg_attribute table to get column information.
3327
         * @see https://stackoverflow.com/a/19541865
3328
         */
3329
        const columnsCondition = dbTables
1,449✔
3330
            .map(({ table_schema, table_name }) => {
3331
                return `("table_schema" = '${table_schema}' AND "table_name" = '${table_name}')`
2,061✔
3332
            })
3333
            .join(" OR ")
3334
        const columnsSql =
3335
            `SELECT columns.*, pg_catalog.col_description(('"' || table_catalog || '"."' || table_schema || '"."' || table_name || '"')::regclass::oid, ordinal_position) AS description, ` +
1,449✔
3336
            `('"' || "udt_schema" || '"."' || "udt_name" || '"')::"regtype" AS "regtype", pg_catalog.format_type("col_attr"."atttypid", "col_attr"."atttypmod") AS "format_type" ` +
3337
            `FROM "information_schema"."columns" ` +
3338
            `LEFT JOIN "pg_catalog"."pg_attribute" AS "col_attr" ON "col_attr"."attname" = "columns"."column_name" ` +
3339
            `AND "col_attr"."attrelid" = ( ` +
3340
            `SELECT "cls"."oid" FROM "pg_catalog"."pg_class" AS "cls" ` +
3341
            `LEFT JOIN "pg_catalog"."pg_namespace" AS "ns" ON "ns"."oid" = "cls"."relnamespace" ` +
3342
            `WHERE "cls"."relname" = "columns"."table_name" ` +
3343
            `AND "ns"."nspname" = "columns"."table_schema" ` +
3344
            `) ` +
3345
            `WHERE ` +
3346
            columnsCondition
3347

3348
        const constraintsCondition = dbTables
1,449✔
3349
            .map(({ table_schema, table_name }) => {
3350
                return `("ns"."nspname" = '${table_schema}' AND "t"."relname" = '${table_name}')`
2,061✔
3351
            })
3352
            .join(" OR ")
3353

3354
        const constraintsSql =
3355
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "cnst"."conname" AS "constraint_name", ` +
1,449✔
3356
            `pg_get_constraintdef("cnst"."oid") AS "expression", ` +
3357
            `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" ` +
3358
            `FROM "pg_constraint" "cnst" ` +
3359
            `INNER JOIN "pg_class" "t" ON "t"."oid" = "cnst"."conrelid" ` +
3360
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "cnst"."connamespace" ` +
3361
            `LEFT JOIN "pg_attribute" "a" ON "a"."attrelid" = "cnst"."conrelid" AND "a"."attnum" = ANY ("cnst"."conkey") ` +
3362
            `WHERE "t"."relkind" IN ('r', 'p') AND (${constraintsCondition})`
3363

3364
        const indicesSql =
3365
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", ` +
1,449✔
3366
            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", ` +
3367
            `"types"."typname" AS "type_name", "am"."amname" AS "index_type" ` +
3368
            `FROM "pg_class" "t" ` +
3369
            `INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" ` +
3370
            `INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid"  AND "a"."attnum" = ANY ("ix"."indkey") ` +
3371
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" ` +
3372
            `INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" ` +
3373
            `INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" ` +
3374
            `INNER JOIN "pg_am" "am" ON "i"."relam" = "am"."oid" ` +
3375
            `LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" ` +
3376
            `WHERE "t"."relkind" IN ('r', 'p') AND "cnst"."contype" IS NULL AND (${constraintsCondition})`
3377

3378
        const foreignKeysCondition = dbTables
1,449✔
3379
            .map(({ table_schema, table_name }) => {
3380
                return `("ns"."nspname" = '${table_schema}' AND "cl"."relname" = '${table_name}')`
2,061✔
3381
            })
3382
            .join(" OR ")
3383

3384
        const hasRelispartitionColumn =
3385
            await this.hasSupportForPartitionedTables()
1,449✔
3386
        const isPartitionCondition = hasRelispartitionColumn
1,449!
3387
            ? ` AND "cl"."relispartition" = 'f'`
3388
            : ""
3389

3390
        const foreignKeysSql =
3391
            `SELECT "con"."conname" AS "constraint_name", "con"."nspname" AS "table_schema", "con"."relname" AS "table_name", "att2"."attname" AS "column_name", ` +
1,449✔
3392
            `"ns"."nspname" AS "referenced_table_schema", "cl"."relname" AS "referenced_table_name", "att"."attname" AS "referenced_column_name", "con"."confdeltype" AS "on_delete", ` +
3393
            `"con"."confupdtype" AS "on_update", "con"."condeferrable" AS "deferrable", "con"."condeferred" AS "deferred" ` +
3394
            `FROM ( ` +
3395
            `SELECT UNNEST ("con1"."conkey") AS "parent", UNNEST ("con1"."confkey") AS "child", "con1"."confrelid", "con1"."conrelid", "con1"."conname", "con1"."contype", "ns"."nspname", ` +
3396
            `"cl"."relname", "con1"."condeferrable", ` +
3397
            `CASE WHEN "con1"."condeferred" THEN 'INITIALLY DEFERRED' ELSE 'INITIALLY IMMEDIATE' END as condeferred, ` +
3398
            `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", ` +
3399
            `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" ` +
3400
            `FROM "pg_class" "cl" ` +
3401
            `INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" ` +
3402
            `INNER JOIN "pg_constraint" "con1" ON "con1"."conrelid" = "cl"."oid" ` +
3403
            `WHERE "con1"."contype" = 'f' AND (${foreignKeysCondition}) ` +
3404
            `) "con" ` +
3405
            `INNER JOIN "pg_attribute" "att" ON "att"."attrelid" = "con"."confrelid" AND "att"."attnum" = "con"."child" ` +
3406
            `INNER JOIN "pg_class" "cl" ON "cl"."oid" = "con"."confrelid" ${isPartitionCondition}` +
3407
            `INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" ` +
3408
            `INNER JOIN "pg_attribute" "att2" ON "att2"."attrelid" = "con"."conrelid" AND "att2"."attnum" = "con"."parent"`
3409

3410
        const [
3411
            dbColumns,
3412
            dbConstraints,
3413
            dbIndices,
3414
            dbForeignKeys,
3415
        ]: ObjectLiteral[][] = await Promise.all([
1,449✔
3416
            this.query(columnsSql),
3417
            this.query(constraintsSql),
3418
            this.query(indicesSql),
3419
            this.query(foreignKeysSql),
3420
        ])
3421

3422
        // create tables for loaded tables
3423
        return Promise.all(
1,449✔
3424
            dbTables.map(async (dbTable) => {
3425
                const table = new Table()
2,061✔
3426

3427
                const getSchemaFromKey = (dbObject: any, key: string) => {
2,061✔
3428
                    return dbObject[key] === currentSchema &&
2,679✔
3429
                        (!this.driver.options.schema ||
3430
                            this.driver.options.schema === currentSchema)
3431
                        ? undefined
3432
                        : dbObject[key]
3433
                }
3434
                // We do not need to join schema name, when database is by default.
3435
                const schema = getSchemaFromKey(dbTable, "table_schema")
2,061✔
3436
                table.database = currentDatabase
2,061✔
3437
                table.schema = dbTable["table_schema"]
2,061✔
3438
                table.comment = dbTable["table_comment"]
2,061✔
3439
                table.name = this.driver.buildTableName(
2,061✔
3440
                    dbTable["table_name"],
3441
                    schema,
3442
                )
3443

3444
                // create columns from the loaded columns
3445
                table.columns = await Promise.all(
2,061✔
3446
                    dbColumns
3447
                        .filter(
3448
                            (dbColumn) =>
3449
                                dbColumn["table_name"] ===
20,256✔
3450
                                    dbTable["table_name"] &&
3451
                                dbColumn["table_schema"] ===
3452
                                    dbTable["table_schema"],
3453
                        )
3454
                        .map(async (dbColumn) => {
3455
                            const columnConstraints = dbConstraints.filter(
7,512✔
3456
                                (dbConstraint) => {
3457
                                    return (
44,541✔
3458
                                        dbConstraint["table_name"] ===
81,303✔
3459
                                            dbColumn["table_name"] &&
3460
                                        dbConstraint["table_schema"] ===
3461
                                            dbColumn["table_schema"] &&
3462
                                        dbConstraint["column_name"] ===
3463
                                            dbColumn["column_name"]
3464
                                    )
3465
                                },
3466
                            )
3467

3468
                            const tableColumn = new TableColumn()
7,512✔
3469
                            tableColumn.name = dbColumn["column_name"]
7,512✔
3470
                            tableColumn.type = dbColumn["regtype"].toLowerCase()
7,512✔
3471

3472
                            if (
7,512✔
3473
                                tableColumn.type === "numeric" ||
29,952✔
3474
                                tableColumn.type === "numeric[]" ||
3475
                                tableColumn.type === "decimal" ||
3476
                                tableColumn.type === "float"
3477
                            ) {
3478
                                let numericPrecision =
3479
                                    dbColumn["numeric_precision"]
33✔
3480
                                let numericScale = dbColumn["numeric_scale"]
33✔
3481
                                if (dbColumn["data_type"] === "ARRAY") {
33✔
3482
                                    const numericSize = dbColumn[
3✔
3483
                                        "format_type"
3484
                                    ].match(
3485
                                        /^numeric\(([0-9]+),([0-9]+)\)\[\]$/,
3486
                                    )
3487
                                    if (numericSize) {
3✔
3488
                                        numericPrecision = +numericSize[1]
3✔
3489
                                        numericScale = +numericSize[2]
3✔
3490
                                    }
3491
                                }
3492
                                // If one of these properties was set, and another was not, Postgres sets '0' in to unspecified property
3493
                                // we set 'undefined' in to unspecified property to avoid changing column on sync
3494
                                if (
33✔
3495
                                    numericPrecision !== null &&
54✔
3496
                                    !this.isDefaultColumnPrecision(
3497
                                        table,
3498
                                        tableColumn,
3499
                                        numericPrecision,
3500
                                    )
3501
                                ) {
3502
                                    tableColumn.precision = numericPrecision
21✔
3503
                                } else if (
12!
3504
                                    numericScale !== null &&
12!
3505
                                    !this.isDefaultColumnScale(
3506
                                        table,
3507
                                        tableColumn,
3508
                                        numericScale,
3509
                                    )
3510
                                ) {
3511
                                    tableColumn.precision = undefined
×
3512
                                }
3513
                                if (
33✔
3514
                                    numericScale !== null &&
54✔
3515
                                    !this.isDefaultColumnScale(
3516
                                        table,
3517
                                        tableColumn,
3518
                                        numericScale,
3519
                                    )
3520
                                ) {
3521
                                    tableColumn.scale = numericScale
21✔
3522
                                } else if (
12!
3523
                                    numericPrecision !== null &&
12!
3524
                                    !this.isDefaultColumnPrecision(
3525
                                        table,
3526
                                        tableColumn,
3527
                                        numericPrecision,
3528
                                    )
3529
                                ) {
3530
                                    tableColumn.scale = undefined
×
3531
                                }
3532
                            }
3533

3534
                            if (
7,512✔
3535
                                tableColumn.type === "interval" ||
37,251✔
3536
                                tableColumn.type === "time without time zone" ||
3537
                                tableColumn.type === "time with time zone" ||
3538
                                tableColumn.type ===
3539
                                    "timestamp without time zone" ||
3540
                                tableColumn.type === "timestamp with time zone"
3541
                            ) {
3542
                                tableColumn.precision =
186✔
3543
                                    !this.isDefaultColumnPrecision(
186✔
3544
                                        table,
3545
                                        tableColumn,
3546
                                        dbColumn["datetime_precision"],
3547
                                    )
3548
                                        ? dbColumn["datetime_precision"]
3549
                                        : undefined
3550
                            }
3551

3552
                            // check if column has user-defined data type.
3553
                            // NOTE: if ENUM type defined with "array:true" it comes with ARRAY type instead of USER-DEFINED
3554
                            if (
7,512✔
3555
                                dbColumn["data_type"] === "USER-DEFINED" ||
14,565✔
3556
                                dbColumn["data_type"] === "ARRAY"
3557
                            ) {
3558
                                const { name } =
3559
                                    await this.getUserDefinedTypeName(
669✔
3560
                                        table,
3561
                                        tableColumn,
3562
                                    )
3563

3564
                                // check if `enumName` is specified by user
3565
                                const builtEnumName = this.buildEnumName(
669✔
3566
                                    table,
3567
                                    tableColumn,
3568
                                    false,
3569
                                    true,
3570
                                )
3571
                                const enumName =
3572
                                    builtEnumName !== name ? name : undefined
669✔
3573

3574
                                // check if type is ENUM
3575
                                const sql =
3576
                                    `SELECT "e"."enumlabel" AS "value" FROM "pg_enum" "e" ` +
669✔
3577
                                    `INNER JOIN "pg_type" "t" ON "t"."oid" = "e"."enumtypid" ` +
3578
                                    `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
3579
                                    `WHERE "n"."nspname" = '${
3580
                                        dbTable["table_schema"]
3581
                                    }' AND "t"."typname" = '${
3582
                                        enumName || name
1,113✔
3583
                                    }'`
3584
                                const results: ObjectLiteral[] =
3585
                                    await this.query(sql)
669✔
3586

3587
                                if (results.length) {
669✔
3588
                                    tableColumn.type = "enum"
579✔
3589
                                    tableColumn.enum = results.map(
579✔
3590
                                        (result) => result["value"],
1,863✔
3591
                                    )
3592
                                    tableColumn.enumName = enumName
579✔
3593
                                }
3594

3595
                                if (dbColumn["data_type"] === "ARRAY") {
669✔
3596
                                    tableColumn.isArray = true
210✔
3597
                                    const type = tableColumn.type.replace(
210✔
3598
                                        "[]",
3599
                                        "",
3600
                                    )
3601
                                    tableColumn.type =
210✔
3602
                                        this.connection.driver.normalizeType({
3603
                                            type: type,
3604
                                        })
3605
                                }
3606
                            }
3607

3608
                            if (
7,512✔
3609
                                tableColumn.type === "geometry" ||
14,994✔
3610
                                tableColumn.type === "geography"
3611
                            ) {
3612
                                const sql =
3613
                                    `SELECT * FROM (` +
39✔
3614
                                    `SELECT "f_table_schema" "table_schema", "f_table_name" "table_name", ` +
3615
                                    `"f_${tableColumn.type}_column" "column_name", "srid", "type" ` +
3616
                                    `FROM "${tableColumn.type}_columns"` +
3617
                                    `) AS _ ` +
3618
                                    `WHERE "column_name" = '${dbColumn["column_name"]}' AND ` +
3619
                                    `"table_schema" = '${dbColumn["table_schema"]}' AND ` +
3620
                                    `"table_name" = '${dbColumn["table_name"]}'`
3621

3622
                                const results: ObjectLiteral[] =
3623
                                    await this.query(sql)
39✔
3624

3625
                                if (results.length > 0) {
39✔
3626
                                    tableColumn.spatialFeatureType =
39✔
3627
                                        results[0].type
3628
                                    tableColumn.srid = results[0].srid
39✔
3629
                                }
3630
                            }
3631

3632
                            // check only columns that have length property
3633
                            if (
7,512✔
3634
                                this.driver.withLengthColumnTypes.indexOf(
3635
                                    tableColumn.type as ColumnType,
3636
                                ) !== -1
3637
                            ) {
3638
                                let length
3639
                                if (tableColumn.isArray) {
3,369✔
3640
                                    const match = /\((\d+)\)/.exec(
24✔
3641
                                        dbColumn["format_type"],
3642
                                    )
3643
                                    length = match ? match[1] : undefined
24✔
3644
                                } else if (
3,345✔
3645
                                    dbColumn["character_maximum_length"]
3646
                                ) {
3647
                                    length =
282✔
3648
                                        dbColumn[
3649
                                            "character_maximum_length"
3650
                                        ].toString()
3651
                                }
3652
                                if (length) {
3,369✔
3653
                                    tableColumn.length =
285✔
3654
                                        !this.isDefaultColumnLength(
285✔
3655
                                            table,
3656
                                            tableColumn,
3657
                                            length,
3658
                                        )
3659
                                            ? length
3660
                                            : ""
3661
                                }
3662
                            }
3663
                            tableColumn.isNullable =
7,512✔
3664
                                dbColumn["is_nullable"] === "YES"
3665

3666
                            const primaryConstraint = columnConstraints.find(
7,512✔
3667
                                (constraint) =>
3668
                                    constraint["constraint_type"] === "PRIMARY",
4,344✔
3669
                            )
3670
                            if (primaryConstraint) {
7,512✔
3671
                                tableColumn.isPrimary = true
2,142✔
3672
                                // find another columns involved in primary key constraint
3673
                                const anotherPrimaryConstraints =
3674
                                    dbConstraints.filter(
2,142✔
3675
                                        (constraint) =>
3676
                                            constraint["table_name"] ===
13,320✔
3677
                                                dbColumn["table_name"] &&
3678
                                            constraint["table_schema"] ===
3679
                                                dbColumn["table_schema"] &&
3680
                                            constraint["column_name"] !==
3681
                                                dbColumn["column_name"] &&
3682
                                            constraint["constraint_type"] ===
3683
                                                "PRIMARY",
3684
                                    )
3685

3686
                                // collect all column names
3687
                                const columnNames =
3688
                                    anotherPrimaryConstraints.map(
2,142✔
3689
                                        (constraint) =>
3690
                                            constraint["column_name"],
234✔
3691
                                    )
3692
                                columnNames.push(dbColumn["column_name"])
2,142✔
3693

3694
                                // build default primary key constraint name
3695
                                const pkName =
3696
                                    this.connection.namingStrategy.primaryKeyName(
2,142✔
3697
                                        table,
3698
                                        columnNames,
3699
                                    )
3700

3701
                                // if primary key has user-defined constraint name, write it in table column
3702
                                if (
2,142✔
3703
                                    primaryConstraint["constraint_name"] !==
3704
                                    pkName
3705
                                ) {
3706
                                    tableColumn.primaryKeyConstraintName =
81✔
3707
                                        primaryConstraint["constraint_name"]
3708
                                }
3709
                            }
3710

3711
                            const uniqueConstraints = columnConstraints.filter(
7,512✔
3712
                                (constraint) =>
3713
                                    constraint["constraint_type"] === "UNIQUE",
4,416✔
3714
                            )
3715
                            const isConstraintComposite =
3716
                                uniqueConstraints.every((uniqueConstraint) => {
7,512✔
3717
                                    return dbConstraints.some(
1,077✔
3718
                                        (dbConstraint) =>
3719
                                            dbConstraint["constraint_type"] ===
6,675✔
3720
                                                "UNIQUE" &&
3721
                                            dbConstraint["constraint_name"] ===
3722
                                                uniqueConstraint[
3723
                                                    "constraint_name"
3724
                                                ] &&
3725
                                            dbConstraint["column_name"] !==
3726
                                                dbColumn["column_name"],
3727
                                    )
3728
                                })
3729
                            tableColumn.isUnique =
7,512✔
3730
                                uniqueConstraints.length > 0 &&
8,589✔
3731
                                !isConstraintComposite
3732

3733
                            if (dbColumn.is_identity === "YES") {
7,512!
3734
                                // Postgres 10+ Identity column
3735
                                tableColumn.isGenerated = true
×
3736
                                tableColumn.generationStrategy = "identity"
×
3737
                                tableColumn.generatedIdentity =
×
3738
                                    dbColumn.identity_generation
3739
                            } else if (
7,512✔
3740
                                dbColumn["column_default"] !== null &&
9,453✔
3741
                                dbColumn["column_default"] !== undefined
3742
                            ) {
3743
                                const serialDefaultName = `nextval('${this.buildSequenceName(
1,941✔
3744
                                    table,
3745
                                    dbColumn["column_name"],
3746
                                )}'::regclass)`
3747
                                const serialDefaultPath = `nextval('${this.buildSequencePath(
1,941✔
3748
                                    table,
3749
                                    dbColumn["column_name"],
3750
                                )}'::regclass)`
3751

3752
                                const defaultWithoutQuotes = dbColumn[
1,941✔
3753
                                    "column_default"
3754
                                ].replace(/"/g, "")
3755

3756
                                if (
1,941✔
3757
                                    defaultWithoutQuotes ===
2,790✔
3758
                                        serialDefaultName ||
3759
                                    defaultWithoutQuotes === serialDefaultPath
3760
                                ) {
3761
                                    tableColumn.isGenerated = true
1,227✔
3762
                                    tableColumn.generationStrategy = "increment"
1,227✔
3763
                                } else if (
714✔
3764
                                    dbColumn["column_default"] ===
1,410✔
3765
                                        "gen_random_uuid()" ||
3766
                                    /^uuid_generate_v\d\(\)/.test(
3767
                                        dbColumn["column_default"],
3768
                                    )
3769
                                ) {
3770
                                    if (tableColumn.type === "uuid") {
90✔
3771
                                        tableColumn.isGenerated = true
84✔
3772
                                        tableColumn.generationStrategy = "uuid"
84✔
3773
                                    } else {
3774
                                        tableColumn.default =
6✔
3775
                                            dbColumn["column_default"]
3776
                                    }
3777
                                } else if (
624✔
3778
                                    dbColumn["column_default"] === "now()" ||
1,200✔
3779
                                    dbColumn["column_default"].indexOf(
3780
                                        "'now'::text",
3781
                                    ) !== -1
3782
                                ) {
3783
                                    tableColumn.default =
156✔
3784
                                        dbColumn["column_default"]
3785
                                } else {
3786
                                    tableColumn.default = dbColumn[
468✔
3787
                                        "column_default"
3788
                                    ].replace(/::[\w\s.[\]\-"]+/g, "")
3789
                                    tableColumn.default =
468✔
3790
                                        tableColumn.default.replace(
3791
                                            /^(-?\d+)$/,
3792
                                            "'$1'",
3793
                                        )
3794
                                }
3795
                            }
3796

3797
                            if (
7,512✔
3798
                                dbColumn["is_generated"] === "ALWAYS" &&
7,638✔
3799
                                dbColumn["generation_expression"]
3800
                            ) {
3801
                                // In postgres there is no VIRTUAL generated column type
3802
                                tableColumn.generatedType = "STORED"
126✔
3803
                                // We cannot relay on information_schema.columns.generation_expression, because it is formatted different.
3804
                                const asExpressionQuery =
3805
                                    this.selectTypeormMetadataSql({
126✔
3806
                                        database: currentDatabase,
3807
                                        schema: dbTable["table_schema"],
3808
                                        table: dbTable["table_name"],
3809
                                        type: MetadataTableType.GENERATED_COLUMN,
3810
                                        name: tableColumn.name,
3811
                                    })
3812

3813
                                const results = await this.query(
126✔
3814
                                    asExpressionQuery.query,
3815
                                    asExpressionQuery.parameters,
3816
                                )
3817
                                if (results[0] && results[0].value) {
126!
3818
                                    tableColumn.asExpression = results[0].value
126✔
3819
                                } else {
3820
                                    tableColumn.asExpression = ""
×
3821
                                }
3822
                            }
3823

3824
                            tableColumn.comment = dbColumn["description"]
7,512✔
3825
                                ? dbColumn["description"]
3826
                                : undefined
3827
                            if (dbColumn["character_set_name"])
7,512!
3828
                                tableColumn.charset =
×
3829
                                    dbColumn["character_set_name"]
3830
                            if (dbColumn["collation_name"])
7,512✔
3831
                                tableColumn.collation =
3✔
3832
                                    dbColumn["collation_name"]
3833
                            return tableColumn
7,512✔
3834
                        }),
3835
                )
3836

3837
                // find unique constraints of table, group them by constraint name and build TableUnique.
3838
                const tableUniqueConstraints = OrmUtils.uniq(
2,061✔
3839
                    dbConstraints.filter((dbConstraint) => {
3840
                        return (
12,909✔
3841
                            dbConstraint["table_name"] ===
21,747✔
3842
                                dbTable["table_name"] &&
3843
                            dbConstraint["table_schema"] ===
3844
                                dbTable["table_schema"] &&
3845
                            dbConstraint["constraint_type"] === "UNIQUE"
3846
                        )
3847
                    }),
3848
                    (dbConstraint) => dbConstraint["constraint_name"],
1,794✔
3849
                )
3850

3851
                table.uniques = tableUniqueConstraints.map((constraint) => {
2,061✔
3852
                    const uniques = dbConstraints.filter(
771✔
3853
                        (dbC) =>
3854
                            dbC["constraint_name"] ===
5,844✔
3855
                            constraint["constraint_name"],
3856
                    )
3857
                    return new TableUnique({
771✔
3858
                        name: constraint["constraint_name"],
3859
                        columnNames: uniques.map((u) => u["column_name"]),
1,086✔
3860
                        deferrable: constraint["deferrable"]
771!
3861
                            ? constraint["deferred"]
3862
                            : undefined,
3863
                    })
3864
                })
3865

3866
                // find check constraints of table, group them by constraint name and build TableCheck.
3867
                const tableCheckConstraints = OrmUtils.uniq(
2,061✔
3868
                    dbConstraints.filter((dbConstraint) => {
3869
                        return (
12,909✔
3870
                            dbConstraint["table_name"] ===
21,747✔
3871
                                dbTable["table_name"] &&
3872
                            dbConstraint["table_schema"] ===
3873
                                dbTable["table_schema"] &&
3874
                            dbConstraint["constraint_type"] === "CHECK"
3875
                        )
3876
                    }),
3877
                    (dbConstraint) => dbConstraint["constraint_name"],
357✔
3878
                )
3879

3880
                table.checks = tableCheckConstraints.map((constraint) => {
2,061✔
3881
                    const checks = dbConstraints.filter(
312✔
3882
                        (dbC) =>
3883
                            dbC["constraint_name"] ===
2,688✔
3884
                            constraint["constraint_name"],
3885
                    )
3886
                    return new TableCheck({
312✔
3887
                        name: constraint["constraint_name"],
3888
                        columnNames: checks.map((c) => c["column_name"]),
324✔
3889
                        expression: constraint["expression"].replace(
3890
                            /^\s*CHECK\s*\((.*)\)\s*$/i,
3891
                            "$1",
3892
                        ),
3893
                    })
3894
                })
3895

3896
                // find exclusion constraints of table, group them by constraint name and build TableExclusion.
3897
                const tableExclusionConstraints = OrmUtils.uniq(
2,061✔
3898
                    dbConstraints.filter((dbConstraint) => {
3899
                        return (
12,909✔
3900
                            dbConstraint["table_name"] ===
21,747✔
3901
                                dbTable["table_name"] &&
3902
                            dbConstraint["table_schema"] ===
3903
                                dbTable["table_schema"] &&
3904
                            dbConstraint["constraint_type"] === "EXCLUDE"
3905
                        )
3906
                    }),
3907
                    (dbConstraint) => dbConstraint["constraint_name"],
246✔
3908
                )
3909

3910
                table.exclusions = tableExclusionConstraints.map(
2,061✔
3911
                    (constraint) => {
3912
                        return new TableExclusion({
243✔
3913
                            name: constraint["constraint_name"],
3914
                            expression: constraint["expression"].substring(8), // trim EXCLUDE from start of expression
3915
                        })
3916
                    },
3917
                )
3918

3919
                // find foreign key constraints of table, group them by constraint name and build TableForeignKey.
3920
                const tableForeignKeyConstraints = OrmUtils.uniq(
2,061✔
3921
                    dbForeignKeys.filter((dbForeignKey) => {
3922
                        return (
2,310✔
3923
                            dbForeignKey["table_name"] ===
2,934✔
3924
                                dbTable["table_name"] &&
3925
                            dbForeignKey["table_schema"] ===
3926
                                dbTable["table_schema"]
3927
                        )
3928
                    }),
3929
                    (dbForeignKey) => dbForeignKey["constraint_name"],
843✔
3930
                )
3931

3932
                table.foreignKeys = tableForeignKeyConstraints.map(
2,061✔
3933
                    (dbForeignKey) => {
3934
                        const foreignKeys = dbForeignKeys.filter(
618✔
3935
                            (dbFk) =>
3936
                                dbFk["constraint_name"] ===
1,404✔
3937
                                dbForeignKey["constraint_name"],
3938
                        )
3939

3940
                        // if referenced table located in currently used schema, we don't need to concat schema name to table name.
3941
                        const schema = getSchemaFromKey(
618✔
3942
                            dbForeignKey,
3943
                            "referenced_table_schema",
3944
                        )
3945
                        const referencedTableName = this.driver.buildTableName(
618✔
3946
                            dbForeignKey["referenced_table_name"],
3947
                            schema,
3948
                        )
3949

3950
                        return new TableForeignKey({
618✔
3951
                            name: dbForeignKey["constraint_name"],
3952
                            columnNames: foreignKeys.map(
3953
                                (dbFk) => dbFk["column_name"],
624✔
3954
                            ),
3955
                            referencedSchema:
3956
                                dbForeignKey["referenced_table_schema"],
3957
                            referencedTableName: referencedTableName,
3958
                            referencedColumnNames: foreignKeys.map(
3959
                                (dbFk) => dbFk["referenced_column_name"],
624✔
3960
                            ),
3961
                            onDelete: dbForeignKey["on_delete"],
3962
                            onUpdate: dbForeignKey["on_update"],
3963
                            deferrable: dbForeignKey["deferrable"]
618!
3964
                                ? dbForeignKey["deferred"]
3965
                                : undefined,
3966
                        })
3967
                    },
3968
                )
3969

3970
                // find index constraints of table, group them by constraint name and build TableIndex.
3971
                const tableIndexConstraints = OrmUtils.uniq(
2,061✔
3972
                    dbIndices.filter((dbIndex) => {
3973
                        return (
1,158✔
3974
                            dbIndex["table_name"] === dbTable["table_name"] &&
1,686✔
3975
                            dbIndex["table_schema"] === dbTable["table_schema"]
3976
                        )
3977
                    }),
3978
                    (dbIndex) => dbIndex["constraint_name"],
807✔
3979
                )
3980

3981
                table.indices = tableIndexConstraints.map((constraint) => {
2,061✔
3982
                    const indices = dbIndices.filter((index) => {
477✔
3983
                        return (
1,002✔
3984
                            index["table_schema"] ===
2,994✔
3985
                                constraint["table_schema"] &&
3986
                            index["table_name"] === constraint["table_name"] &&
3987
                            index["constraint_name"] ===
3988
                                constraint["constraint_name"]
3989
                        )
3990
                    })
3991
                    return new TableIndex(<TableIndexOptions>{
477✔
3992
                        table: table,
3993
                        name: constraint["constraint_name"],
3994
                        columnNames: indices.map((i) => i["column_name"]),
528✔
3995
                        isUnique: constraint["is_unique"] === "TRUE",
3996
                        where: constraint["condition"],
3997
                        isSpatial: constraint["index_type"] === "gist",
3998
                        isFulltext: false,
3999
                    })
4000
                })
4001

4002
                return table
2,061✔
4003
            }),
4004
        )
4005
    }
4006

4007
    /**
4008
     * Builds create table sql.
4009
     */
4010
    protected createTableSql(table: Table, createForeignKeys?: boolean): Query {
4011
        const columnDefinitions = table.columns
16,173✔
4012
            .map((column) => this.buildCreateColumnSql(table, column))
52,524✔
4013
            .join(", ")
4014
        let sql = `CREATE TABLE ${this.escapePath(table)} (${columnDefinitions}`
16,173✔
4015

4016
        table.columns
16,173✔
4017
            .filter((column) => column.isUnique)
52,524✔
4018
            .forEach((column) => {
4019
                const isUniqueExist = table.uniques.some(
2,286✔
4020
                    (unique) =>
4021
                        unique.columnNames.length === 1 &&
3,054✔
4022
                        unique.columnNames[0] === column.name,
4023
                )
4024
                if (!isUniqueExist)
2,286✔
4025
                    table.uniques.push(
12✔
4026
                        new TableUnique({
4027
                            name: this.connection.namingStrategy.uniqueConstraintName(
4028
                                table,
4029
                                [column.name],
4030
                            ),
4031
                            columnNames: [column.name],
4032
                        }),
4033
                    )
4034
            })
4035

4036
        if (table.uniques.length > 0) {
16,173✔
4037
            const uniquesSql = table.uniques
1,959✔
4038
                .map((unique) => {
4039
                    const uniqueName = unique.name
2,889✔
4040
                        ? unique.name
4041
                        : this.connection.namingStrategy.uniqueConstraintName(
4042
                              table,
4043
                              unique.columnNames,
4044
                          )
4045
                    const columnNames = unique.columnNames
2,889✔
4046
                        .map((columnName) => `"${columnName}"`)
3,660✔
4047
                        .join(", ")
4048
                    let constraint = `CONSTRAINT "${uniqueName}" UNIQUE (${columnNames})`
2,889✔
4049
                    if (unique.deferrable)
2,889✔
4050
                        constraint += ` DEFERRABLE ${unique.deferrable}`
27✔
4051
                    return constraint
2,889✔
4052
                })
4053
                .join(", ")
4054

4055
            sql += `, ${uniquesSql}`
1,959✔
4056
        }
4057

4058
        if (table.checks.length > 0) {
16,173✔
4059
            const checksSql = table.checks
231✔
4060
                .map((check) => {
4061
                    const checkName = check.name
234✔
4062
                        ? check.name
4063
                        : this.connection.namingStrategy.checkConstraintName(
4064
                              table,
4065
                              check.expression!,
4066
                          )
4067
                    return `CONSTRAINT "${checkName}" CHECK (${check.expression})`
234✔
4068
                })
4069
                .join(", ")
4070

4071
            sql += `, ${checksSql}`
231✔
4072
        }
4073

4074
        if (table.exclusions.length > 0) {
16,173✔
4075
            const exclusionsSql = table.exclusions
228✔
4076
                .map((exclusion) => {
4077
                    const exclusionName = exclusion.name
228!
4078
                        ? exclusion.name
4079
                        : this.connection.namingStrategy.exclusionConstraintName(
4080
                              table,
4081
                              exclusion.expression!,
4082
                          )
4083
                    return `CONSTRAINT "${exclusionName}" EXCLUDE ${exclusion.expression}`
228✔
4084
                })
4085
                .join(", ")
4086

4087
            sql += `, ${exclusionsSql}`
228✔
4088
        }
4089

4090
        if (table.foreignKeys.length > 0 && createForeignKeys) {
16,173✔
4091
            const foreignKeysSql = table.foreignKeys
12✔
4092
                .map((fk) => {
4093
                    const columnNames = fk.columnNames
15✔
4094
                        .map((columnName) => `"${columnName}"`)
18✔
4095
                        .join(", ")
4096
                    if (!fk.name)
15✔
4097
                        fk.name = this.connection.namingStrategy.foreignKeyName(
9✔
4098
                            table,
4099
                            fk.columnNames,
4100
                            this.getTablePath(fk),
4101
                            fk.referencedColumnNames,
4102
                        )
4103

4104
                    const referencedColumnNames = fk.referencedColumnNames
15✔
4105
                        .map((columnName) => `"${columnName}"`)
18✔
4106
                        .join(", ")
4107

4108
                    let constraint = `CONSTRAINT "${
15✔
4109
                        fk.name
4110
                    }" FOREIGN KEY (${columnNames}) REFERENCES ${this.escapePath(
4111
                        this.getTablePath(fk),
4112
                    )} (${referencedColumnNames})`
4113
                    if (fk.onDelete) constraint += ` ON DELETE ${fk.onDelete}`
15✔
4114
                    if (fk.onUpdate) constraint += ` ON UPDATE ${fk.onUpdate}`
15✔
4115
                    if (fk.deferrable)
15!
4116
                        constraint += ` DEFERRABLE ${fk.deferrable}`
×
4117

4118
                    return constraint
15✔
4119
                })
4120
                .join(", ")
4121

4122
            sql += `, ${foreignKeysSql}`
12✔
4123
        }
4124

4125
        const primaryColumns = table.columns.filter(
16,173✔
4126
            (column) => column.isPrimary,
52,524✔
4127
        )
4128
        if (primaryColumns.length > 0) {
16,173✔
4129
            const primaryKeyName = primaryColumns[0].primaryKeyConstraintName
16,083✔
4130
                ? primaryColumns[0].primaryKeyConstraintName
4131
                : this.connection.namingStrategy.primaryKeyName(
4132
                      table,
4133
                      primaryColumns.map((column) => column.name),
20,100✔
4134
                  )
4135

4136
            const columnNames = primaryColumns
16,083✔
4137
                .map((column) => `"${column.name}"`)
20,172✔
4138
                .join(", ")
4139
            sql += `, CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNames})`
16,083✔
4140
        }
4141

4142
        sql += `)`
16,173✔
4143

4144
        table.columns
16,173✔
4145
            .filter((it) => it.comment)
52,524✔
4146
            .forEach(
4147
                (it) =>
4148
                    (sql += `; COMMENT ON COLUMN ${this.escapePath(table)}."${
186✔
4149
                        it.name
4150
                    }" IS ${this.escapeComment(it.comment)}`),
4151
            )
4152

4153
        return new Query(sql)
16,173✔
4154
    }
4155

4156
    /**
4157
     * Loads Postgres version.
4158
     */
4159
    async getVersion(): Promise<string> {
4160
        const result: [{ version: string }] = await this.query(
1,932✔
4161
            `SELECT version()`,
4162
        )
4163
        return result[0].version.replace(/^PostgreSQL ([\d.]+) .*$/, "$1")
1,932✔
4164
    }
4165

4166
    /**
4167
     * Builds drop table sql.
4168
     */
4169
    protected dropTableSql(tableOrPath: Table | string): Query {
4170
        return new Query(`DROP TABLE ${this.escapePath(tableOrPath)}`)
16,173✔
4171
    }
4172

4173
    protected createViewSql(view: View): Query {
4174
        const materializedClause = view.materialized ? "MATERIALIZED " : ""
150✔
4175
        const viewName = this.escapePath(view)
150✔
4176

4177
        if (typeof view.expression === "string") {
150✔
4178
            return new Query(
114✔
4179
                `CREATE ${materializedClause}VIEW ${viewName} AS ${view.expression}`,
4180
            )
4181
        } else {
4182
            return new Query(
36✔
4183
                `CREATE ${materializedClause}VIEW ${viewName} AS ${view
4184
                    .expression(this.connection)
4185
                    .getQuery()}`,
4186
            )
4187
        }
4188
    }
4189

4190
    protected async insertViewDefinitionSql(view: View): Promise<Query> {
4191
        const currentSchema = await this.getCurrentSchema()
147✔
4192

4193
        let { schema, tableName: name } = this.driver.parseTableName(view)
147✔
4194

4195
        if (!schema) {
147!
4196
            schema = currentSchema
×
4197
        }
4198

4199
        const type = view.materialized
147✔
4200
            ? MetadataTableType.MATERIALIZED_VIEW
4201
            : MetadataTableType.VIEW
4202
        const expression =
4203
            typeof view.expression === "string"
147✔
4204
                ? view.expression.trim()
4205
                : view.expression(this.connection).getQuery()
4206
        return this.insertTypeormMetadataSql({
147✔
4207
            type,
4208
            schema,
4209
            name,
4210
            value: expression,
4211
        })
4212
    }
4213

4214
    /**
4215
     * Builds drop view sql.
4216
     */
4217
    protected dropViewSql(view: View): Query {
4218
        const materializedClause = view.materialized ? "MATERIALIZED " : ""
150✔
4219
        return new Query(
150✔
4220
            `DROP ${materializedClause}VIEW ${this.escapePath(view)}`,
4221
        )
4222
    }
4223

4224
    /**
4225
     * Builds remove view sql.
4226
     */
4227
    protected async deleteViewDefinitionSql(view: View): Promise<Query> {
4228
        const currentSchema = await this.getCurrentSchema()
147✔
4229

4230
        let { schema, tableName: name } = this.driver.parseTableName(view)
147✔
4231

4232
        if (!schema) {
147!
4233
            schema = currentSchema
×
4234
        }
4235

4236
        const type = view.materialized
147✔
4237
            ? MetadataTableType.MATERIALIZED_VIEW
4238
            : MetadataTableType.VIEW
4239
        return this.deleteTypeormMetadataSql({ type, schema, name })
147✔
4240
    }
4241

4242
    /**
4243
     * Drops ENUM type from given schemas.
4244
     */
4245
    protected async dropEnumTypes(schemaNames: string): Promise<void> {
4246
        const selectDropsQuery =
4247
            `SELECT 'DROP TYPE IF EXISTS "' || n.nspname || '"."' || t.typname || '" CASCADE;' as "query" FROM "pg_type" "t" ` +
5,028✔
4248
            `INNER JOIN "pg_enum" "e" ON "e"."enumtypid" = "t"."oid" ` +
4249
            `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
4250
            `WHERE "n"."nspname" IN (${schemaNames}) GROUP BY "n"."nspname", "t"."typname"`
4251
        const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery)
5,028✔
4252
        await Promise.all(dropQueries.map((q) => this.query(q["query"])))
5,028✔
4253
    }
4254

4255
    /**
4256
     * Checks if enum with the given name exist in the database.
4257
     */
4258
    protected async hasEnumType(
4259
        table: Table,
4260
        column: TableColumn,
4261
    ): Promise<boolean> {
4262
        let { schema } = this.driver.parseTableName(table)
702✔
4263

4264
        if (!schema) {
702!
4265
            schema = await this.getCurrentSchema()
×
4266
        }
4267

4268
        const enumName = this.buildEnumName(table, column, false, true)
702✔
4269
        const sql =
4270
            `SELECT "n"."nspname", "t"."typname" FROM "pg_type" "t" ` +
702✔
4271
            `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
4272
            `WHERE "n"."nspname" = '${schema}' AND "t"."typname" = '${enumName}'`
4273
        const result = await this.query(sql)
702✔
4274
        return result.length ? true : false
702✔
4275
    }
4276

4277
    /**
4278
     * Builds create ENUM type sql.
4279
     */
4280
    protected createEnumTypeSql(
4281
        table: Table,
4282
        column: TableColumn,
4283
        enumName?: string,
4284
    ): Query {
4285
        if (!enumName) enumName = this.buildEnumName(table, column)
747✔
4286
        const enumValues = column
747✔
4287
            .enum!.map((value) => `'${value.replace("'", "''")}'`)
2,508✔
4288
            .join(", ")
4289
        return new Query(`CREATE TYPE ${enumName} AS ENUM(${enumValues})`)
747✔
4290
    }
4291

4292
    /**
4293
     * Builds create ENUM type sql.
4294
     */
4295
    protected dropEnumTypeSql(
4296
        table: Table,
4297
        column: TableColumn,
4298
        enumName?: string,
4299
    ): Query {
4300
        if (!enumName) enumName = this.buildEnumName(table, column)
747✔
4301
        return new Query(`DROP TYPE ${enumName}`)
747✔
4302
    }
4303

4304
    /**
4305
     * Builds create index sql.
4306
     */
4307
    protected createIndexSql(table: Table, index: TableIndex): Query {
4308
        const columns = index.columnNames
5,973✔
4309
            .map((columnName) => `"${columnName}"`)
6,723✔
4310
            .join(", ")
4311
        return new Query(
5,973✔
4312
            `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX${
5,973✔
4313
                index.isConcurrent ? " CONCURRENTLY" : ""
5,973✔
4314
            } "${index.name}" ON ${this.escapePath(table)} ${
4315
                index.isSpatial ? "USING GiST " : ""
5,973✔
4316
            }(${columns}) ${index.where ? "WHERE " + index.where : ""}`,
5,973✔
4317
        )
4318
    }
4319

4320
    /**
4321
     * Builds create view index sql.
4322
     */
4323
    protected createViewIndexSql(view: View, index: TableIndex): Query {
4324
        const columns = index.columnNames
21✔
4325
            .map((columnName) => `"${columnName}"`)
21✔
4326
            .join(", ")
4327
        return new Query(
21✔
4328
            `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX "${
21✔
4329
                index.name
4330
            }" ON ${this.escapePath(view)} (${columns}) ${
4331
                index.where ? "WHERE " + index.where : ""
21!
4332
            }`,
4333
        )
4334
    }
4335

4336
    /**
4337
     * Builds drop index sql.
4338
     */
4339
    protected dropIndexSql(
4340
        table: Table | View,
4341
        indexOrName: TableIndex | string,
4342
    ): Query {
4343
        const indexName = InstanceChecker.isTableIndex(indexOrName)
5,994!
4344
            ? indexOrName.name
4345
            : indexOrName
4346
        const concurrent = InstanceChecker.isTableIndex(indexOrName)
5,994!
4347
            ? indexOrName.isConcurrent
4348
            : false
4349
        const { schema } = this.driver.parseTableName(table)
5,994✔
4350
        return schema
5,994!
4351
            ? new Query(
4352
                  `DROP INDEX ${
4353
                      concurrent ? "CONCURRENTLY " : ""
5,994✔
4354
                  }"${schema}"."${indexName}"`,
4355
              )
4356
            : new Query(
4357
                  `DROP INDEX ${
4358
                      concurrent ? "CONCURRENTLY " : ""
×
4359
                  }"${indexName}"`,
4360
              )
4361
    }
4362

4363
    /**
4364
     * Builds create primary key sql.
4365
     */
4366
    protected createPrimaryKeySql(
4367
        table: Table,
4368
        columnNames: string[],
4369
        constraintName?: string,
4370
    ): Query {
4371
        const primaryKeyName = constraintName
15!
4372
            ? constraintName
4373
            : this.connection.namingStrategy.primaryKeyName(table, columnNames)
4374

4375
        const columnNamesString = columnNames
15✔
4376
            .map((columnName) => `"${columnName}"`)
18✔
4377
            .join(", ")
4378

4379
        return new Query(
15✔
4380
            `ALTER TABLE ${this.escapePath(
4381
                table,
4382
            )} ADD CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNamesString})`,
4383
        )
4384
    }
4385

4386
    /**
4387
     * Builds drop primary key sql.
4388
     */
4389
    protected dropPrimaryKeySql(table: Table): Query {
4390
        if (!table.primaryColumns.length)
15!
4391
            throw new TypeORMError(`Table ${table} has no primary keys.`)
×
4392

4393
        const columnNames = table.primaryColumns.map((column) => column.name)
18✔
4394
        const constraintName = table.primaryColumns[0].primaryKeyConstraintName
15✔
4395
        const primaryKeyName = constraintName
15!
4396
            ? constraintName
4397
            : this.connection.namingStrategy.primaryKeyName(table, columnNames)
4398

4399
        return new Query(
15✔
4400
            `ALTER TABLE ${this.escapePath(
4401
                table,
4402
            )} DROP CONSTRAINT "${primaryKeyName}"`,
4403
        )
4404
    }
4405

4406
    /**
4407
     * Builds create unique constraint sql.
4408
     */
4409
    protected createUniqueConstraintSql(
4410
        table: Table,
4411
        uniqueConstraint: TableUnique,
4412
    ): Query {
4413
        const columnNames = uniqueConstraint.columnNames
45✔
4414
            .map((column) => `"` + column + `"`)
78✔
4415
            .join(", ")
4416
        let sql = `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
45✔
4417
            uniqueConstraint.name
4418
        }" UNIQUE (${columnNames})`
4419
        if (uniqueConstraint.deferrable)
45!
4420
            sql += ` DEFERRABLE ${uniqueConstraint.deferrable}`
×
4421
        return new Query(sql)
45✔
4422
    }
4423

4424
    /**
4425
     * Builds drop unique constraint sql.
4426
     */
4427
    protected dropUniqueConstraintSql(
4428
        table: Table,
4429
        uniqueOrName: TableUnique | string,
4430
    ): Query {
4431
        const uniqueName = InstanceChecker.isTableUnique(uniqueOrName)
45!
4432
            ? uniqueOrName.name
4433
            : uniqueOrName
4434
        return new Query(
45✔
4435
            `ALTER TABLE ${this.escapePath(
4436
                table,
4437
            )} DROP CONSTRAINT "${uniqueName}"`,
4438
        )
4439
    }
4440

4441
    /**
4442
     * Builds create check constraint sql.
4443
     */
4444
    protected createCheckConstraintSql(
4445
        table: Table,
4446
        checkConstraint: TableCheck,
4447
    ): Query {
4448
        return new Query(
30✔
4449
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
4450
                checkConstraint.name
4451
            }" CHECK (${checkConstraint.expression})`,
4452
        )
4453
    }
4454

4455
    /**
4456
     * Builds drop check constraint sql.
4457
     */
4458
    protected dropCheckConstraintSql(
4459
        table: Table,
4460
        checkOrName: TableCheck | string,
4461
    ): Query {
4462
        const checkName = InstanceChecker.isTableCheck(checkOrName)
30!
4463
            ? checkOrName.name
4464
            : checkOrName
4465
        return new Query(
30✔
4466
            `ALTER TABLE ${this.escapePath(
4467
                table,
4468
            )} DROP CONSTRAINT "${checkName}"`,
4469
        )
4470
    }
4471

4472
    /**
4473
     * Builds create exclusion constraint sql.
4474
     */
4475
    protected createExclusionConstraintSql(
4476
        table: Table,
4477
        exclusionConstraint: TableExclusion,
4478
    ): Query {
4479
        return new Query(
21✔
4480
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
4481
                exclusionConstraint.name
4482
            }" EXCLUDE ${exclusionConstraint.expression}`,
4483
        )
4484
    }
4485

4486
    /**
4487
     * Builds drop exclusion constraint sql.
4488
     */
4489
    protected dropExclusionConstraintSql(
4490
        table: Table,
4491
        exclusionOrName: TableExclusion | string,
4492
    ): Query {
4493
        const exclusionName = InstanceChecker.isTableExclusion(exclusionOrName)
21!
4494
            ? exclusionOrName.name
4495
            : exclusionOrName
4496
        return new Query(
21✔
4497
            `ALTER TABLE ${this.escapePath(
4498
                table,
4499
            )} DROP CONSTRAINT "${exclusionName}"`,
4500
        )
4501
    }
4502

4503
    /**
4504
     * Builds create foreign key sql.
4505
     */
4506
    protected createForeignKeySql(
4507
        table: Table,
4508
        foreignKey: TableForeignKey,
4509
    ): Query {
4510
        const columnNames = foreignKey.columnNames
10,785✔
4511
            .map((column) => `"` + column + `"`)
12,258✔
4512
            .join(", ")
4513
        const referencedColumnNames = foreignKey.referencedColumnNames
10,785✔
4514
            .map((column) => `"` + column + `"`)
12,258✔
4515
            .join(",")
4516
        let sql =
4517
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
10,785✔
4518
                foreignKey.name
4519
            }" FOREIGN KEY (${columnNames}) ` +
4520
            `REFERENCES ${this.escapePath(
4521
                this.getTablePath(foreignKey),
4522
            )}(${referencedColumnNames})`
4523
        if (foreignKey.onDelete) sql += ` ON DELETE ${foreignKey.onDelete}`
10,785✔
4524
        if (foreignKey.onUpdate) sql += ` ON UPDATE ${foreignKey.onUpdate}`
10,785✔
4525
        if (foreignKey.deferrable) sql += ` DEFERRABLE ${foreignKey.deferrable}`
10,785✔
4526

4527
        return new Query(sql)
10,785✔
4528
    }
4529

4530
    /**
4531
     * Builds drop foreign key sql.
4532
     */
4533
    protected dropForeignKeySql(
4534
        table: Table,
4535
        foreignKeyOrName: TableForeignKey | string,
4536
    ): Query {
4537
        const foreignKeyName = InstanceChecker.isTableForeignKey(
10,800!
4538
            foreignKeyOrName,
4539
        )
4540
            ? foreignKeyOrName.name
4541
            : foreignKeyOrName
4542
        return new Query(
10,800✔
4543
            `ALTER TABLE ${this.escapePath(
4544
                table,
4545
            )} DROP CONSTRAINT "${foreignKeyName}"`,
4546
        )
4547
    }
4548

4549
    /**
4550
     * Builds sequence name from given table and column.
4551
     */
4552
    protected buildSequenceName(
4553
        table: Table,
4554
        columnOrName: TableColumn | string,
4555
    ): string {
4556
        const { tableName } = this.driver.parseTableName(table)
4,110✔
4557

4558
        const columnName = InstanceChecker.isTableColumn(columnOrName)
4,110✔
4559
            ? columnOrName.name
4560
            : columnOrName
4561

4562
        let seqName = `${tableName}_${columnName}_seq`
4,110✔
4563

4564
        if (seqName.length > this.connection.driver.maxAliasLength!) {
4,110✔
4565
            // note doesn't yet handle corner cases where .length differs from number of UTF-8 bytes
4566
            seqName = `${tableName.substring(0, 29)}_${columnName.substring(
24✔
4567
                0,
4568
                Math.max(29, 63 - table.name.length - 5),
4569
            )}_seq`
4570
        }
4571

4572
        return seqName
4,110✔
4573
    }
4574

4575
    protected buildSequencePath(
4576
        table: Table,
4577
        columnOrName: TableColumn | string,
4578
    ): string {
4579
        const { schema } = this.driver.parseTableName(table)
2,091✔
4580

4581
        return schema
2,091!
4582
            ? `${schema}.${this.buildSequenceName(table, columnOrName)}`
4583
            : this.buildSequenceName(table, columnOrName)
4584
    }
4585

4586
    /**
4587
     * Builds ENUM type name from given table and column.
4588
     */
4589
    protected buildEnumName(
4590
        table: Table,
4591
        column: TableColumn,
4592
        withSchema: boolean = true,
1,515✔
4593
        disableEscape?: boolean,
4594
        toOld?: boolean,
4595
    ): string {
4596
        const { schema, tableName } = this.driver.parseTableName(table)
3,021✔
4597
        let enumName = column.enumName
3,021✔
4598
            ? column.enumName
4599
            : `${tableName}_${column.name.toLowerCase()}_enum`
4600
        if (schema && withSchema) enumName = `${schema}.${enumName}`
3,021✔
4601
        if (toOld) enumName = enumName + "_old"
3,021✔
4602
        return enumName
3,021✔
4603
            .split(".")
4604
            .map((i) => {
4605
                return disableEscape ? i : `"${i}"`
4,575✔
4606
            })
4607
            .join(".")
4608
    }
4609

4610
    protected async getUserDefinedTypeName(table: Table, column: TableColumn) {
4611
        let { schema, tableName: name } = this.driver.parseTableName(table)
702✔
4612

4613
        if (!schema) {
702!
4614
            schema = await this.getCurrentSchema()
×
4615
        }
4616

4617
        const result = await this.query(
702✔
4618
            `SELECT "udt_schema", "udt_name" ` +
4619
                `FROM "information_schema"."columns" WHERE "table_schema" = '${schema}' AND "table_name" = '${name}' AND "column_name"='${column.name}'`,
4620
        )
4621

4622
        // docs: https://www.postgresql.org/docs/current/xtypes.html
4623
        // When you define a new base type, PostgreSQL automatically provides support for arrays of that type.
4624
        // The array type typically has the same name as the base type with the underscore character (_) prepended.
4625
        // ----
4626
        // so, we must remove this underscore character from enum type name
4627
        let udtName = result[0]["udt_name"]
702✔
4628
        if (udtName.indexOf("_") === 0) {
702✔
4629
            udtName = udtName.substr(1, udtName.length)
216✔
4630
        }
4631
        return {
702✔
4632
            schema: result[0]["udt_schema"],
4633
            name: udtName,
4634
        }
4635
    }
4636

4637
    /**
4638
     * Escapes a given comment so it's safe to include in a query.
4639
     */
4640
    protected escapeComment(comment?: string) {
4641
        if (!comment || comment.length === 0) {
2,082✔
4642
            return "NULL"
1,872✔
4643
        }
4644

4645
        comment = comment.replace(/'/g, "''").replace(/\u0000/g, "") // Null bytes aren't allowed in comments
210✔
4646

4647
        return `'${comment}'`
210✔
4648
    }
4649

4650
    /**
4651
     * Escapes given table or view path.
4652
     */
4653
    protected escapePath(target: Table | View | string): string {
4654
        const { schema, tableName } = this.driver.parseTableName(target)
73,164✔
4655

4656
        if (schema && schema !== this.driver.searchSchema) {
73,164✔
4657
            return `"${schema}"."${tableName}"`
753✔
4658
        }
4659

4660
        return `"${tableName}"`
72,411✔
4661
    }
4662

4663
    /**
4664
     * Get the table name with table schema
4665
     * Note: Without ' or "
4666
     */
4667
    protected async getTableNameWithSchema(target: Table | string) {
4668
        const tableName = InstanceChecker.isTable(target) ? target.name : target
99!
4669
        if (tableName.indexOf(".") === -1) {
99!
4670
            const schemaResult = await this.query(`SELECT current_schema()`)
99✔
4671
            const schema = schemaResult[0]["current_schema"]
99✔
4672
            return `${schema}.${tableName}`
99✔
4673
        } else {
4674
            return `${tableName.split(".")[0]}.${tableName.split(".")[1]}`
×
4675
        }
4676
    }
4677

4678
    /**
4679
     * Builds a query for create column.
4680
     */
4681
    protected buildCreateColumnSql(table: Table, column: TableColumn) {
4682
        let c = '"' + column.name + '"'
52,773✔
4683
        if (
52,773✔
4684
            column.isGenerated === true &&
62,676✔
4685
            column.generationStrategy !== "uuid"
4686
        ) {
4687
            if (column.generationStrategy === "identity") {
9,363✔
4688
                // Postgres 10+ Identity generated column
4689
                const generatedIdentityOrDefault =
4690
                    column.generatedIdentity || "BY DEFAULT"
15✔
4691
                c += ` ${column.type} GENERATED ${generatedIdentityOrDefault} AS IDENTITY`
15✔
4692
            } else {
4693
                // classic SERIAL primary column
4694
                if (
9,348✔
4695
                    column.type === "integer" ||
9,432✔
4696
                    column.type === "int" ||
4697
                    column.type === "int4"
4698
                )
4699
                    c += " SERIAL"
9,318✔
4700
                if (column.type === "smallint" || column.type === "int2")
9,348✔
4701
                    c += " SMALLSERIAL"
3✔
4702
                if (column.type === "bigint" || column.type === "int8")
9,348✔
4703
                    c += " BIGSERIAL"
27✔
4704
            }
4705
        }
4706
        if (column.type === "enum" || column.type === "simple-enum") {
52,773✔
4707
            c += " " + this.buildEnumName(table, column)
717✔
4708
            if (column.isArray) c += " array"
717✔
4709
        } else if (!column.isGenerated || column.type === "uuid") {
52,056✔
4710
            c += " " + this.connection.driver.createFullType(column)
42,693✔
4711
        }
4712

4713
        // Postgres only supports the stored generated column type
4714
        if (column.generatedType === "STORED" && column.asExpression) {
52,773✔
4715
            c += ` GENERATED ALWAYS AS (${column.asExpression}) STORED`
99✔
4716
        }
4717

4718
        if (column.charset) c += ' CHARACTER SET "' + column.charset + '"'
52,773!
4719
        if (column.collation) c += ' COLLATE "' + column.collation + '"'
52,773✔
4720
        if (column.isNullable !== true) c += " NOT NULL"
52,773✔
4721
        if (column.default !== undefined && column.default !== null)
52,773✔
4722
            c += " DEFAULT " + column.default
3,687✔
4723
        if (
52,773✔
4724
            column.isGenerated &&
63,216✔
4725
            column.generationStrategy === "uuid" &&
4726
            !column.default
4727
        )
4728
            c += ` DEFAULT ${this.driver.uuidGenerator}`
540✔
4729

4730
        return c
52,773✔
4731
    }
4732

4733
    /**
4734
     * Checks if the PostgreSQL server has support for partitioned tables
4735
     */
4736
    protected async hasSupportForPartitionedTables() {
4737
        const result = await this.query(
1,449✔
4738
            `SELECT TRUE FROM information_schema.columns WHERE table_name = 'pg_class' and column_name = 'relispartition'`,
4739
        )
4740
        return result.length ? true : false
1,449!
4741
    }
4742

4743
    /**
4744
     * Change table comment.
4745
     */
4746
    async changeTableComment(
4747
        tableOrName: Table | string,
4748
        newComment?: string,
4749
    ): Promise<void> {
4750
        const upQueries: Query[] = []
933✔
4751
        const downQueries: Query[] = []
933✔
4752

4753
        const table = InstanceChecker.isTable(tableOrName)
933!
4754
            ? tableOrName
4755
            : await this.getCachedTable(tableOrName)
4756

4757
        newComment = this.escapeComment(newComment)
933✔
4758
        const comment = this.escapeComment(table.comment)
933✔
4759

4760
        if (newComment === comment) {
933✔
4761
            return
927✔
4762
        }
4763

4764
        const newTable = table.clone()
6✔
4765

4766
        upQueries.push(
6✔
4767
            new Query(
4768
                `COMMENT ON TABLE ${this.escapePath(
4769
                    newTable,
4770
                )} IS ${newComment}`,
4771
            ),
4772
        )
4773

4774
        downQueries.push(
6✔
4775
            new Query(
4776
                `COMMENT ON TABLE ${this.escapePath(table)} IS ${comment}`,
4777
            ),
4778
        )
4779

4780
        await this.executeQueries(upQueries, downQueries)
6✔
4781

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

© 2026 Coveralls, Inc