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

typeorm / typeorm / 23390157208

21 Mar 2026 10:26PM UTC coverage: 56.678% (-16.6%) from 73.277%
23390157208

Pull #12252

github

web-flow
Merge 5b60ba41c into 7038fa166
Pull Request #12252: fix: unskip cascade soft remove test

17767 of 26580 branches covered (66.84%)

Branch coverage included in aggregate %.

64033 of 117744 relevant lines covered (54.38%)

1514.83 hits per line

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

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

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

28✔
43
    /**
28✔
44
     * Database driver used by connection.
28✔
45
     */
28✔
46
    driver: PostgresDriver
28✔
47

28✔
48
    // -------------------------------------------------------------------------
28✔
49
    // Protected Properties
28✔
50
    // -------------------------------------------------------------------------
28✔
51

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

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

28✔
62
    // -------------------------------------------------------------------------
28✔
63
    // Constructor
28✔
64
    // -------------------------------------------------------------------------
28✔
65

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

28✔
74
    // -------------------------------------------------------------------------
28✔
75
    // Public Methods
28✔
76
    // -------------------------------------------------------------------------
28✔
77

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

76✔
86
        if (this.databaseConnectionPromise)
76✔
87
            return this.databaseConnectionPromise
368✔
88

64✔
89
        if (this.mode === "slave" && this.driver.isReplicated) {
368!
90
            this.databaseConnectionPromise = this.driver
×
91
                .obtainSlaveConnection()
×
92
                .then(([connection, release]: any[]) => {
×
93
                    this.driver.connectedQueryRunners.push(this)
×
94
                    this.databaseConnection = connection
×
95

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

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

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

64✔
128
                    return this.databaseConnection
64✔
129
                })
64✔
130
        }
64✔
131

64✔
132
        return this.databaseConnectionPromise
64✔
133
    }
64✔
134

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

68✔
145
        this.isReleased = true
68✔
146
        if (this.releaseCallback) {
68✔
147
            this.releaseCallback(err)
64✔
148
            this.releaseCallback = undefined
64✔
149
        }
64✔
150

68✔
151
        const index = this.driver.connectedQueryRunners.indexOf(this)
68✔
152

68✔
153
        if (index !== -1) {
68✔
154
            this.driver.connectedQueryRunners.splice(index, 1)
64✔
155
        }
64✔
156
    }
68✔
157

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

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

40✔
179
        if (this.transactionDepth === 0) {
40✔
180
            await this.query("START TRANSACTION")
40✔
181
            if (isolationLevel) {
40!
182
                await this.query(
×
183
                    "SET TRANSACTION ISOLATION LEVEL " + isolationLevel,
×
184
                )
×
185
            }
×
186
        } else {
40!
187
            await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
×
188
        }
×
189
        this.transactionDepth += 1
40✔
190

40✔
191
        await this.broadcaster.broadcast("AfterTransactionStart")
40✔
192
    }
40✔
193

28✔
194
    /**
28✔
195
     * Commits transaction.
28✔
196
     * Error will be thrown if transaction was not started.
28✔
197
     */
28✔
198
    async commitTransaction(): Promise<void> {
28✔
199
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
40!
200

40✔
201
        await this.broadcaster.broadcast("BeforeTransactionCommit")
40✔
202

40✔
203
        if (this.transactionDepth > 1) {
40!
204
            await this.query(
×
205
                `RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
×
206
            )
×
207
        } else {
40✔
208
            await this.query("COMMIT")
40✔
209
            this.isTransactionActive = false
40✔
210
        }
40✔
211
        this.transactionDepth -= 1
40✔
212

40✔
213
        await this.broadcaster.broadcast("AfterTransactionCommit")
40✔
214
    }
40✔
215

28✔
216
    /**
28✔
217
     * Rollbacks transaction.
28✔
218
     * Error will be thrown if transaction was not started.
28✔
219
     */
28✔
220
    async rollbackTransaction(): Promise<void> {
28✔
221
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
×
222

×
223
        await this.broadcaster.broadcast("BeforeTransactionRollback")
×
224

×
225
        if (this.transactionDepth > 1) {
×
226
            await this.query(
×
227
                `ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
×
228
            )
×
229
        } else {
×
230
            await this.query("ROLLBACK")
×
231
            this.isTransactionActive = false
×
232
        }
×
233
        this.transactionDepth -= 1
×
234

×
235
        await this.broadcaster.broadcast("AfterTransactionRollback")
×
236
    }
×
237

28✔
238
    /**
28✔
239
     * Executes a given SQL query.
28✔
240
     * @param query
28✔
241
     * @param parameters
28✔
242
     * @param useStructuredResult
28✔
243
     */
28✔
244
    async query(
28✔
245
        query: string,
368✔
246
        parameters?: any[],
368✔
247
        useStructuredResult: boolean = false,
368✔
248
    ): Promise<any> {
368✔
249
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
368!
250

368✔
251
        const databaseConnection = await this.connect()
368✔
252

368✔
253
        this.driver.connection.logger.logQuery(query, parameters, this)
368✔
254
        await this.broadcaster.broadcast("BeforeQuery", query, parameters)
368✔
255

368✔
256
        const broadcasterResult = new BroadcasterResult()
368✔
257

368✔
258
        try {
368✔
259
            const queryStartTime = Date.now()
368✔
260
            const raw = await databaseConnection.query(query, parameters)
368✔
261
            // log slow queries if maxQueryExecution time is set
368✔
262
            const maxQueryExecutionTime =
368✔
263
                this.driver.options.maxQueryExecutionTime
368✔
264
            const queryEndTime = Date.now()
368✔
265
            const queryExecutionTime = queryEndTime - queryStartTime
368✔
266

368✔
267
            this.broadcaster.broadcastAfterQueryEvent(
368✔
268
                broadcasterResult,
368✔
269
                query,
368✔
270
                parameters,
368✔
271
                true,
368✔
272
                queryExecutionTime,
368✔
273
                raw,
368✔
274
                undefined,
368✔
275
            )
368✔
276

368✔
277
            if (
368✔
278
                maxQueryExecutionTime &&
368!
279
                queryExecutionTime > maxQueryExecutionTime
×
280
            )
368✔
281
                this.driver.connection.logger.logQuerySlow(
368!
282
                    queryExecutionTime,
×
283
                    query,
×
284
                    parameters,
×
285
                    this,
×
286
                )
×
287

368✔
288
            const result = new QueryResult()
368✔
289
            if (raw) {
368✔
290
                if (raw.hasOwnProperty("rows")) {
368✔
291
                    result.records = raw.rows
368✔
292
                }
368✔
293

368✔
294
                if (raw.hasOwnProperty("rowCount")) {
368✔
295
                    result.affected = raw.rowCount
368✔
296
                }
368✔
297

368✔
298
                switch (raw.command) {
368✔
299
                    case "DELETE":
368!
300
                    case "UPDATE":
368✔
301
                        // for UPDATE and DELETE query additionally return number of affected rows
48✔
302
                        result.raw = [raw.rows, raw.rowCount]
48✔
303
                        break
48✔
304
                    default:
368✔
305
                        result.raw = raw.rows
320✔
306
                }
368✔
307

368✔
308
                if (!useStructuredResult) {
368✔
309
                    return result.raw
228✔
310
                }
228✔
311
            }
368✔
312

140✔
313
            return result
140✔
314
        } catch (err) {
368!
315
            this.driver.connection.logger.logQueryError(
×
316
                err,
×
317
                query,
×
318
                parameters,
×
319
                this,
×
320
            )
×
321
            this.broadcaster.broadcastAfterQueryEvent(
×
322
                broadcasterResult,
×
323
                query,
×
324
                parameters,
×
325
                false,
×
326
                undefined,
×
327
                undefined,
×
328
                err,
×
329
            )
×
330

×
331
            throw new QueryFailedError(query, parameters, err)
×
332
        } finally {
×
333
            await broadcasterResult.wait()
368✔
334
        }
368✔
335
    }
368✔
336

28✔
337
    /**
28✔
338
     * Returns raw data stream.
28✔
339
     * @param query
28✔
340
     * @param parameters
28✔
341
     * @param onEnd
28✔
342
     * @param onError
28✔
343
     */
28✔
344
    async stream(
28✔
345
        query: string,
×
346
        parameters?: any[],
×
347
        onEnd?: Function,
×
348
        onError?: Function,
×
349
    ): Promise<ReadStream> {
×
350
        const QueryStream = this.driver.loadStreamDependency()
×
351
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
×
352

×
353
        const databaseConnection = await this.connect()
×
354
        this.driver.connection.logger.logQuery(query, parameters, this)
×
355
        const stream = databaseConnection.query(
×
356
            new QueryStream(query, parameters),
×
357
        )
×
358
        if (onEnd) stream.on("end", onEnd)
×
359
        if (onError) stream.on("error", onError)
×
360

×
361
        return stream
×
362
    }
×
363

28✔
364
    /**
28✔
365
     * Returns all available database names including system databases.
28✔
366
     */
28✔
367
    async getDatabases(): Promise<string[]> {
28✔
368
        return Promise.resolve([])
×
369
    }
×
370

28✔
371
    /**
28✔
372
     * Returns all available schema names including system schemas.
28✔
373
     * If database parameter specified, returns schemas of that database.
28✔
374
     * @param database
28✔
375
     */
28✔
376
    async getSchemas(database?: string): Promise<string[]> {
28✔
377
        return Promise.resolve([])
×
378
    }
×
379

28✔
380
    /**
28✔
381
     * Checks if database with the given name exist.
28✔
382
     * @param database
28✔
383
     */
28✔
384
    async hasDatabase(database: string): Promise<boolean> {
28✔
385
        const result = await this.query(
×
386
            `SELECT * FROM pg_database WHERE datname=$1;`,
×
387
            [database],
×
388
        )
×
389
        return result.length ? true : false
×
390
    }
×
391

28✔
392
    /**
28✔
393
     * Loads currently using database
28✔
394
     */
28✔
395
    async getCurrentDatabase(): Promise<string> {
28✔
396
        const query = await this.query(`SELECT * FROM current_database()`)
8✔
397
        return query[0]["current_database"]
8✔
398
    }
8✔
399

28✔
400
    /**
28✔
401
     * Checks if schema with the given name exist.
28✔
402
     * @param schema
28✔
403
     */
28✔
404
    async hasSchema(schema: string): Promise<boolean> {
28✔
405
        const result = await this.query(
×
406
            `SELECT * FROM "information_schema"."schemata" WHERE "schema_name" = $1`,
×
407
            [schema],
×
408
        )
×
409
        return result.length ? true : false
×
410
    }
×
411

28✔
412
    /**
28✔
413
     * Loads currently using database schema
28✔
414
     */
28✔
415
    async getCurrentSchema(): Promise<string> {
28✔
416
        const query = await this.query(`SELECT * FROM current_schema()`)
12✔
417
        return query[0]["current_schema"]
12✔
418
    }
12✔
419

28✔
420
    /**
28✔
421
     * Checks if table with the given name exist in the database.
28✔
422
     * @param tableOrName
28✔
423
     */
28✔
424
    async hasTable(tableOrName: Table | string): Promise<boolean> {
28✔
425
        const parsedTableName = this.driver.parseTableName(tableOrName)
8✔
426

8✔
427
        if (!parsedTableName.schema) {
8!
428
            parsedTableName.schema = await this.getCurrentSchema()
×
429
        }
×
430

8✔
431
        const sql = `SELECT * FROM "information_schema"."tables" WHERE "table_schema" = $1 AND "table_name" = $2`
8✔
432
        const result = await this.query(sql, [
8✔
433
            parsedTableName.schema,
8✔
434
            parsedTableName.tableName,
8✔
435
        ])
8✔
436
        return result.length ? true : false
8!
437
    }
8✔
438

28✔
439
    /**
28✔
440
     * Checks if column with the given name exist in the given table.
28✔
441
     * @param tableOrName
28✔
442
     * @param columnName
28✔
443
     */
28✔
444
    async hasColumn(
28✔
445
        tableOrName: Table | string,
×
446
        columnName: string,
×
447
    ): Promise<boolean> {
×
448
        const parsedTableName = this.driver.parseTableName(tableOrName)
×
449

×
450
        if (!parsedTableName.schema) {
×
451
            parsedTableName.schema = await this.getCurrentSchema()
×
452
        }
×
453

×
454
        const sql = `SELECT * FROM "information_schema"."columns" WHERE "table_schema" = $1 AND "table_name" = $2 AND "column_name" = $3`
×
455
        const result = await this.query(sql, [
×
456
            parsedTableName.schema,
×
457
            parsedTableName.tableName,
×
458
            columnName,
×
459
        ])
×
460
        return result.length ? true : false
×
461
    }
×
462

28✔
463
    /**
28✔
464
     * Creates a new database.
28✔
465
     * Note: Postgres does not support database creation inside a transaction block.
28✔
466
     * @param database
28✔
467
     * @param ifNotExists
28✔
468
     */
28✔
469
    async createDatabase(
28✔
470
        database: string,
×
471
        ifNotExists?: boolean,
×
472
    ): Promise<void> {
×
473
        if (ifNotExists) {
×
474
            const databaseAlreadyExists = await this.hasDatabase(database)
×
475

×
476
            if (databaseAlreadyExists) return Promise.resolve()
×
477
        }
×
478

×
479
        const up = `CREATE DATABASE ${this.driver.escape(database)}`
×
480
        const down = `DROP DATABASE ${this.driver.escape(database)}`
×
481
        await this.executeQueries(new Query(up), new Query(down))
×
482
    }
×
483

28✔
484
    /**
28✔
485
     * Drops database.
28✔
486
     * Note: Postgres does not support database dropping inside a transaction block.
28✔
487
     * @param database
28✔
488
     * @param ifExists
28✔
489
     */
28✔
490
    async dropDatabase(database: string, ifExists?: boolean): Promise<void> {
28✔
491
        const up = ifExists
×
492
            ? `DROP DATABASE IF EXISTS ${this.driver.escape(database)}`
×
493
            : `DROP DATABASE ${this.driver.escape(database)}`
×
494
        const down = `CREATE DATABASE ${this.driver.escape(database)}`
×
495
        await this.executeQueries(new Query(up), new Query(down))
×
496
    }
×
497

28✔
498
    /**
28✔
499
     * Creates a new table schema.
28✔
500
     * @param schemaPath
28✔
501
     * @param ifNotExists
28✔
502
     */
28✔
503
    async createSchema(
28✔
504
        schemaPath: string,
×
505
        ifNotExists?: boolean,
×
506
    ): Promise<void> {
×
507
        const schema =
×
508
            schemaPath.indexOf(".") === -1
×
509
                ? schemaPath
×
510
                : schemaPath.split(".")[1]
×
511
        const escapedSchema = this.driver.escape(schema)
×
512

×
513
        const up = ifNotExists
×
514
            ? `CREATE SCHEMA IF NOT EXISTS ${escapedSchema}`
×
515
            : `CREATE SCHEMA ${escapedSchema}`
×
516
        const down = `DROP SCHEMA ${escapedSchema} CASCADE`
×
517
        await this.executeQueries(new Query(up), new Query(down))
×
518
    }
×
519

28✔
520
    /**
28✔
521
     * Drops table schema.
28✔
522
     * @param schemaPath
28✔
523
     * @param ifExists
28✔
524
     * @param isCascade
28✔
525
     */
28✔
526
    async dropSchema(
28✔
527
        schemaPath: string,
×
528
        ifExists?: boolean,
×
529
        isCascade?: boolean,
×
530
    ): Promise<void> {
×
531
        const schema =
×
532
            schemaPath.indexOf(".") === -1
×
533
                ? schemaPath
×
534
                : schemaPath.split(".")[1]
×
535
        const escapedSchema = this.driver.escape(schema)
×
536

×
537
        const up = ifExists
×
538
            ? `DROP SCHEMA IF EXISTS ${escapedSchema} ${isCascade ? "CASCADE" : ""}`
×
539
            : `DROP SCHEMA ${escapedSchema} ${isCascade ? "CASCADE" : ""}`
×
540
        const down = `CREATE SCHEMA ${escapedSchema}`
×
541
        await this.executeQueries(new Query(up), new Query(down))
×
542
    }
×
543

28✔
544
    /**
28✔
545
     * Creates a new table.
28✔
546
     * @param table
28✔
547
     * @param ifNotExists
28✔
548
     * @param createForeignKeys
28✔
549
     * @param createIndices
28✔
550
     */
28✔
551
    async createTable(
28✔
552
        table: Table,
24✔
553
        ifNotExists: boolean = false,
24✔
554
        createForeignKeys: boolean = true,
24✔
555
        createIndices: boolean = true,
24✔
556
    ): Promise<void> {
24✔
557
        if (ifNotExists) {
24!
558
            const isTableExist = await this.hasTable(table)
×
559
            if (isTableExist) return Promise.resolve()
×
560
        }
×
561
        const upQueries: Query[] = []
24✔
562
        const downQueries: Query[] = []
24✔
563

24✔
564
        // if table have column with ENUM type, we must create this type in postgres.
24✔
565
        const enumColumns = table.columns.filter(
24✔
566
            (column) => column.type === "enum" || column.type === "simple-enum",
24✔
567
        )
24✔
568
        const createdEnumTypes: string[] = []
24✔
569
        for (const column of enumColumns) {
24!
570
            // TODO: Should also check if values of existing type matches expected ones
×
571
            const hasEnum = await this.hasEnumType(table, column)
×
572
            const enumName = this.buildEnumName(table, column)
×
573

×
574
            // if enum with the same "enumName" is defined more then once, me must prevent double creation
×
575
            if (!hasEnum && createdEnumTypes.indexOf(enumName) === -1) {
×
576
                createdEnumTypes.push(enumName)
×
577
                upQueries.push(this.createEnumTypeSql(table, column, enumName))
×
578
                downQueries.push(this.dropEnumTypeSql(table, column, enumName))
×
579
            }
×
580
        }
×
581

24✔
582
        // if table have column with generated type, we must add the expression to the metadata table
24✔
583
        const generatedColumns = table.columns.filter(
24✔
584
            (column) =>
24✔
585
                column.generatedType === "STORED" && column.asExpression,
24!
586
        )
24✔
587
        for (const column of generatedColumns) {
24!
588
            const tableNameWithSchema = (
×
589
                await this.getTableNameWithSchema(table.name)
×
590
            ).split(".")
×
591
            const tableName = tableNameWithSchema[1]
×
592
            const schema = tableNameWithSchema[0]
×
593

×
594
            const insertQuery = this.insertTypeormMetadataSql({
×
595
                database: this.driver.database,
×
596
                schema,
×
597
                table: tableName,
×
598
                type: MetadataTableType.GENERATED_COLUMN,
×
599
                name: column.name,
×
600
                value: column.asExpression,
×
601
            })
×
602

×
603
            const deleteQuery = this.deleteTypeormMetadataSql({
×
604
                database: this.driver.database,
×
605
                schema,
×
606
                table: tableName,
×
607
                type: MetadataTableType.GENERATED_COLUMN,
×
608
                name: column.name,
×
609
            })
×
610

×
611
            upQueries.push(insertQuery)
×
612
            downQueries.push(deleteQuery)
×
613
        }
×
614

24✔
615
        upQueries.push(this.createTableSql(table, createForeignKeys))
24✔
616
        downQueries.push(this.dropTableSql(table))
24✔
617

24✔
618
        // if createForeignKeys is true, we must drop created foreign keys in down query.
24✔
619
        // createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
24✔
620
        if (createForeignKeys)
24✔
621
            table.foreignKeys.forEach((foreignKey) =>
24!
622
                downQueries.push(this.dropForeignKeySql(table, foreignKey)),
×
623
            )
×
624

24✔
625
        if (createIndices) {
24✔
626
            table.indices.forEach((index) => {
24✔
627
                // new index may be passed without name. In this case we generate index name manually.
16✔
628
                if (!index.name)
16✔
629
                    index.name = this.connection.namingStrategy.indexName(
16!
630
                        table,
×
631
                        index.columnNames,
×
632
                        index.where,
×
633
                    )
×
634
                upQueries.push(this.createIndexSql(table, index))
16✔
635
                downQueries.push(this.dropIndexSql(table, index))
16✔
636
            })
24✔
637
        }
24✔
638

24✔
639
        if (table.comment) {
24!
640
            upQueries.push(
×
641
                new Query(
×
642
                    `COMMENT ON TABLE ${this.escapePath(table)}` +
×
643
                        ` IS ${this.escapeComment(table.comment)}`,
×
644
                ),
×
645
            )
×
646
            downQueries.push(
×
647
                new Query(
×
648
                    `COMMENT ON TABLE ${this.escapePath(table)}` + ` IS NULL`,
×
649
                ),
×
650
            )
×
651
        }
×
652

24✔
653
        await this.executeQueries(upQueries, downQueries)
24✔
654
    }
24✔
655

28✔
656
    /**
28✔
657
     * Drops the table.
28✔
658
     * @param target
28✔
659
     * @param ifExists
28✔
660
     * @param dropForeignKeys
28✔
661
     * @param dropIndices
28✔
662
     */
28✔
663
    async dropTable(
28✔
664
        target: Table | string,
×
665
        ifExists?: boolean,
×
666
        dropForeignKeys: boolean = true,
×
667
        dropIndices: boolean = true,
×
668
    ): Promise<void> {
×
669
        // It needs because if table does not exist and dropForeignKeys or dropIndices is true, we don't need
×
670
        // to perform drop queries for foreign keys and indices.
×
671
        if (ifExists) {
×
672
            const isTableExist = await this.hasTable(target)
×
673
            if (!isTableExist) return Promise.resolve()
×
674
        }
×
675

×
676
        // if dropTable called with dropForeignKeys = true, we must create foreign keys in down query.
×
677
        const createForeignKeys: boolean = dropForeignKeys
×
678
        const tablePath = this.getTablePath(target)
×
679
        const table = await this.getCachedTable(tablePath)
×
680
        const upQueries: Query[] = []
×
681
        const downQueries: Query[] = []
×
682

×
683
        if (dropIndices) {
×
684
            table.indices.forEach((index) => {
×
685
                upQueries.push(this.dropIndexSql(table, index))
×
686
                downQueries.push(this.createIndexSql(table, index))
×
687
            })
×
688
        }
×
689

×
690
        if (dropForeignKeys)
×
691
            table.foreignKeys.forEach((foreignKey) =>
×
692
                upQueries.push(this.dropForeignKeySql(table, foreignKey)),
×
693
            )
×
694

×
695
        upQueries.push(this.dropTableSql(table))
×
696
        downQueries.push(this.createTableSql(table, createForeignKeys))
×
697

×
698
        // if table had columns with generated type, we must remove the expression from the metadata table
×
699
        const generatedColumns = table.columns.filter(
×
700
            (column) => column.generatedType && column.asExpression,
×
701
        )
×
702
        for (const column of generatedColumns) {
×
703
            const tableNameWithSchema = (
×
704
                await this.getTableNameWithSchema(table.name)
×
705
            ).split(".")
×
706
            const tableName = tableNameWithSchema[1]
×
707
            const schema = tableNameWithSchema[0]
×
708

×
709
            const deleteQuery = this.deleteTypeormMetadataSql({
×
710
                database: this.driver.database,
×
711
                schema,
×
712
                table: tableName,
×
713
                type: MetadataTableType.GENERATED_COLUMN,
×
714
                name: column.name,
×
715
            })
×
716

×
717
            const insertQuery = this.insertTypeormMetadataSql({
×
718
                database: this.driver.database,
×
719
                schema,
×
720
                table: tableName,
×
721
                type: MetadataTableType.GENERATED_COLUMN,
×
722
                name: column.name,
×
723
                value: column.asExpression,
×
724
            })
×
725

×
726
            upQueries.push(deleteQuery)
×
727
            downQueries.push(insertQuery)
×
728
        }
×
729

×
730
        await this.executeQueries(upQueries, downQueries)
×
731
    }
×
732

28✔
733
    /**
28✔
734
     * Creates a new view.
28✔
735
     * @param view
28✔
736
     * @param syncWithMetadata
28✔
737
     */
28✔
738
    async createView(
28✔
739
        view: View,
×
740
        syncWithMetadata: boolean = false,
×
741
    ): Promise<void> {
×
742
        const upQueries: Query[] = []
×
743
        const downQueries: Query[] = []
×
744
        upQueries.push(this.createViewSql(view))
×
745
        if (syncWithMetadata)
×
746
            upQueries.push(await this.insertViewDefinitionSql(view))
×
747
        downQueries.push(this.dropViewSql(view))
×
748
        if (syncWithMetadata)
×
749
            downQueries.push(await this.deleteViewDefinitionSql(view))
×
750
        await this.executeQueries(upQueries, downQueries)
×
751
    }
×
752

28✔
753
    /**
28✔
754
     * Drops the view.
28✔
755
     * @param target
28✔
756
     * @param ifExists
28✔
757
     */
28✔
758
    async dropView(target: View | string, ifExists?: boolean): Promise<void> {
28✔
759
        const viewName = InstanceChecker.isView(target) ? target.name : target
×
760

×
761
        let view: View
×
762
        try {
×
763
            view = await this.getCachedView(viewName)
×
764
        } catch {
×
765
            if (ifExists) return
×
766
            throw new TypeORMError(`View "${viewName}" does not exist.`)
×
767
        }
×
768

×
769
        await this.executeQueries(
×
770
            [
×
771
                await this.deleteViewDefinitionSql(view),
×
772
                this.dropViewSql(view, ifExists),
×
773
            ],
×
774
            [
×
775
                await this.insertViewDefinitionSql(view),
×
776
                this.createViewSql(view),
×
777
            ],
×
778
        )
×
779
    }
×
780

28✔
781
    /**
28✔
782
     * Renames the given table.
28✔
783
     * @param oldTableOrName
28✔
784
     * @param newTableName
28✔
785
     */
28✔
786
    async renameTable(
28✔
787
        oldTableOrName: Table | string,
×
788
        newTableName: string,
×
789
    ): Promise<void> {
×
790
        const upQueries: Query[] = []
×
791
        const downQueries: Query[] = []
×
792
        const oldTable = InstanceChecker.isTable(oldTableOrName)
×
793
            ? oldTableOrName
×
794
            : await this.getCachedTable(oldTableOrName)
×
795
        const newTable = oldTable.clone()
×
796

×
797
        const { schema: schemaName, tableName: oldTableName } =
×
798
            this.driver.parseTableName(oldTable)
×
799

×
800
        newTable.name = schemaName
×
801
            ? `${schemaName}.${newTableName}`
×
802
            : newTableName
×
803

×
804
        upQueries.push(
×
805
            new Query(
×
806
                `ALTER TABLE ${this.escapePath(
×
807
                    oldTable,
×
808
                )} RENAME TO "${newTableName}"`,
×
809
            ),
×
810
        )
×
811
        downQueries.push(
×
812
            new Query(
×
813
                `ALTER TABLE ${this.escapePath(
×
814
                    newTable,
×
815
                )} RENAME TO "${oldTableName}"`,
×
816
            ),
×
817
        )
×
818

×
819
        // rename column primary key constraint if it has default constraint name
×
820
        if (
×
821
            newTable.primaryColumns.length > 0 &&
×
822
            !newTable.primaryColumns[0].primaryKeyConstraintName
×
823
        ) {
×
824
            const columnNames = newTable.primaryColumns.map(
×
825
                (column) => column.name,
×
826
            )
×
827

×
828
            const oldPkName = this.connection.namingStrategy.primaryKeyName(
×
829
                oldTable,
×
830
                columnNames,
×
831
            )
×
832

×
833
            const newPkName = this.connection.namingStrategy.primaryKeyName(
×
834
                newTable,
×
835
                columnNames,
×
836
            )
×
837

×
838
            upQueries.push(
×
839
                new Query(
×
840
                    `ALTER TABLE ${this.escapePath(
×
841
                        newTable,
×
842
                    )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
×
843
                ),
×
844
            )
×
845
            downQueries.push(
×
846
                new Query(
×
847
                    `ALTER TABLE ${this.escapePath(
×
848
                        newTable,
×
849
                    )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
×
850
                ),
×
851
            )
×
852
        }
×
853

×
854
        // rename sequences
×
855
        newTable.columns.map((col) => {
×
856
            if (col.isGenerated && col.generationStrategy === "increment") {
×
857
                const sequencePath = this.buildSequencePath(oldTable, col.name)
×
858
                const sequenceName = this.buildSequenceName(oldTable, col.name)
×
859

×
860
                const newSequencePath = this.buildSequencePath(
×
861
                    newTable,
×
862
                    col.name,
×
863
                )
×
864
                const newSequenceName = this.buildSequenceName(
×
865
                    newTable,
×
866
                    col.name,
×
867
                )
×
868

×
869
                const up = `ALTER SEQUENCE ${this.escapePath(
×
870
                    sequencePath,
×
871
                )} RENAME TO "${newSequenceName}"`
×
872
                const down = `ALTER SEQUENCE ${this.escapePath(
×
873
                    newSequencePath,
×
874
                )} RENAME TO "${sequenceName}"`
×
875

×
876
                upQueries.push(new Query(up))
×
877
                downQueries.push(new Query(down))
×
878
            }
×
879
        })
×
880

×
881
        // rename unique constraints
×
882
        newTable.uniques.forEach((unique) => {
×
883
            const oldUniqueName =
×
884
                this.connection.namingStrategy.uniqueConstraintName(
×
885
                    oldTable,
×
886
                    unique.columnNames,
×
887
                )
×
888

×
889
            // Skip renaming if Unique has user defined constraint name
×
890
            if (unique.name !== oldUniqueName) return
×
891

×
892
            // build new constraint name
×
893
            const newUniqueName =
×
894
                this.connection.namingStrategy.uniqueConstraintName(
×
895
                    newTable,
×
896
                    unique.columnNames,
×
897
                )
×
898

×
899
            // build queries
×
900
            upQueries.push(
×
901
                new Query(
×
902
                    `ALTER TABLE ${this.escapePath(
×
903
                        newTable,
×
904
                    )} RENAME CONSTRAINT "${
×
905
                        unique.name
×
906
                    }" TO "${newUniqueName}"`,
×
907
                ),
×
908
            )
×
909
            downQueries.push(
×
910
                new Query(
×
911
                    `ALTER TABLE ${this.escapePath(
×
912
                        newTable,
×
913
                    )} RENAME CONSTRAINT "${newUniqueName}" TO "${
×
914
                        unique.name
×
915
                    }"`,
×
916
                ),
×
917
            )
×
918

×
919
            // replace constraint name
×
920
            unique.name = newUniqueName
×
921
        })
×
922

×
923
        // rename index constraints
×
924
        newTable.indices.forEach((index) => {
×
925
            const oldIndexName = this.connection.namingStrategy.indexName(
×
926
                oldTable,
×
927
                index.columnNames,
×
928
                index.where,
×
929
            )
×
930

×
931
            // Skip renaming if Index has user defined constraint name
×
932
            if (index.name !== oldIndexName) return
×
933

×
934
            // build new constraint name
×
935
            const { schema } = this.driver.parseTableName(newTable)
×
936
            const newIndexName = this.connection.namingStrategy.indexName(
×
937
                newTable,
×
938
                index.columnNames,
×
939
                index.where,
×
940
            )
×
941

×
942
            // build queries
×
943
            const up = schema
×
944
                ? `ALTER INDEX "${schema}"."${index.name}" RENAME TO "${newIndexName}"`
×
945
                : `ALTER INDEX "${index.name}" RENAME TO "${newIndexName}"`
×
946
            const down = schema
×
947
                ? `ALTER INDEX "${schema}"."${newIndexName}" RENAME TO "${index.name}"`
×
948
                : `ALTER INDEX "${newIndexName}" RENAME TO "${index.name}"`
×
949
            upQueries.push(new Query(up))
×
950
            downQueries.push(new Query(down))
×
951

×
952
            // replace constraint name
×
953
            index.name = newIndexName
×
954
        })
×
955

×
956
        // rename foreign key constraints
×
957
        newTable.foreignKeys.forEach((foreignKey) => {
×
958
            const oldForeignKeyName =
×
959
                this.connection.namingStrategy.foreignKeyName(
×
960
                    oldTable,
×
961
                    foreignKey.columnNames,
×
962
                    this.getTablePath(foreignKey),
×
963
                    foreignKey.referencedColumnNames,
×
964
                )
×
965

×
966
            // Skip renaming if foreign key has user defined constraint name
×
967
            if (foreignKey.name !== oldForeignKeyName) return
×
968

×
969
            // build new constraint name
×
970
            const newForeignKeyName =
×
971
                this.connection.namingStrategy.foreignKeyName(
×
972
                    newTable,
×
973
                    foreignKey.columnNames,
×
974
                    this.getTablePath(foreignKey),
×
975
                    foreignKey.referencedColumnNames,
×
976
                )
×
977

×
978
            // build queries
×
979
            upQueries.push(
×
980
                new Query(
×
981
                    `ALTER TABLE ${this.escapePath(
×
982
                        newTable,
×
983
                    )} RENAME CONSTRAINT "${
×
984
                        foreignKey.name
×
985
                    }" TO "${newForeignKeyName}"`,
×
986
                ),
×
987
            )
×
988
            downQueries.push(
×
989
                new Query(
×
990
                    `ALTER TABLE ${this.escapePath(
×
991
                        newTable,
×
992
                    )} RENAME CONSTRAINT "${newForeignKeyName}" TO "${
×
993
                        foreignKey.name
×
994
                    }"`,
×
995
                ),
×
996
            )
×
997

×
998
            // replace constraint name
×
999
            foreignKey.name = newForeignKeyName
×
1000
        })
×
1001

×
1002
        // rename ENUM types
×
1003
        const enumColumns = newTable.columns.filter(
×
1004
            (column) => column.type === "enum" || column.type === "simple-enum",
×
1005
        )
×
1006
        for (const column of enumColumns) {
×
1007
            // skip renaming for user-defined enum name
×
1008
            if (column.enumName) continue
×
1009

×
1010
            const oldEnumType = await this.getUserDefinedTypeName(
×
1011
                oldTable,
×
1012
                column,
×
1013
            )
×
1014
            upQueries.push(
×
1015
                new Query(
×
1016
                    `ALTER TYPE "${oldEnumType.schema}"."${
×
1017
                        oldEnumType.name
×
1018
                    }" RENAME TO ${this.buildEnumName(
×
1019
                        newTable,
×
1020
                        column,
×
1021
                        false,
×
1022
                    )}`,
×
1023
                ),
×
1024
            )
×
1025
            downQueries.push(
×
1026
                new Query(
×
1027
                    `ALTER TYPE ${this.buildEnumName(
×
1028
                        newTable,
×
1029
                        column,
×
1030
                    )} RENAME TO "${oldEnumType.name}"`,
×
1031
                ),
×
1032
            )
×
1033
        }
×
1034
        await this.executeQueries(upQueries, downQueries)
×
1035
    }
×
1036

28✔
1037
    /**
28✔
1038
     * Creates a new column from the column in the table.
28✔
1039
     * @param tableOrName
28✔
1040
     * @param column
28✔
1041
     */
28✔
1042
    async addColumn(
28✔
1043
        tableOrName: Table | string,
×
1044
        column: TableColumn,
×
1045
    ): Promise<void> {
×
1046
        const table = InstanceChecker.isTable(tableOrName)
×
1047
            ? tableOrName
×
1048
            : await this.getCachedTable(tableOrName)
×
1049
        const clonedTable = table.clone()
×
1050
        const upQueries: Query[] = []
×
1051
        const downQueries: Query[] = []
×
1052

×
1053
        if (column.type === "enum" || column.type === "simple-enum") {
×
1054
            const hasEnum = await this.hasEnumType(table, column)
×
1055
            if (!hasEnum) {
×
1056
                upQueries.push(this.createEnumTypeSql(table, column))
×
1057
                downQueries.push(this.dropEnumTypeSql(table, column))
×
1058
            }
×
1059
        }
×
1060

×
1061
        upQueries.push(
×
1062
            new Query(
×
1063
                `ALTER TABLE ${this.escapePath(
×
1064
                    table,
×
1065
                )} ADD ${this.buildCreateColumnSql(table, column)}`,
×
1066
            ),
×
1067
        )
×
1068
        downQueries.push(
×
1069
            new Query(
×
1070
                `ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${
×
1071
                    column.name
×
1072
                }"`,
×
1073
            ),
×
1074
        )
×
1075

×
1076
        // create or update primary key constraint
×
1077
        if (column.isPrimary) {
×
1078
            const primaryColumns = clonedTable.primaryColumns
×
1079
            // if table already have primary key, me must drop it and recreate again
×
1080
            if (primaryColumns.length > 0) {
×
1081
                const pkName = primaryColumns[0].primaryKeyConstraintName
×
1082
                    ? primaryColumns[0].primaryKeyConstraintName
×
1083
                    : this.connection.namingStrategy.primaryKeyName(
×
1084
                          clonedTable,
×
1085
                          primaryColumns.map((column) => column.name),
×
1086
                      )
×
1087

×
1088
                const columnNames = primaryColumns
×
1089
                    .map((column) => `"${column.name}"`)
×
1090
                    .join(", ")
×
1091

×
1092
                upQueries.push(
×
1093
                    new Query(
×
1094
                        `ALTER TABLE ${this.escapePath(
×
1095
                            table,
×
1096
                        )} DROP CONSTRAINT "${pkName}"`,
×
1097
                    ),
×
1098
                )
×
1099
                downQueries.push(
×
1100
                    new Query(
×
1101
                        `ALTER TABLE ${this.escapePath(
×
1102
                            table,
×
1103
                        )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
×
1104
                    ),
×
1105
                )
×
1106
            }
×
1107

×
1108
            primaryColumns.push(column)
×
1109
            const pkName = primaryColumns[0].primaryKeyConstraintName
×
1110
                ? primaryColumns[0].primaryKeyConstraintName
×
1111
                : this.connection.namingStrategy.primaryKeyName(
×
1112
                      clonedTable,
×
1113
                      primaryColumns.map((column) => column.name),
×
1114
                  )
×
1115

×
1116
            const columnNames = primaryColumns
×
1117
                .map((column) => `"${column.name}"`)
×
1118
                .join(", ")
×
1119

×
1120
            upQueries.push(
×
1121
                new Query(
×
1122
                    `ALTER TABLE ${this.escapePath(
×
1123
                        table,
×
1124
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
×
1125
                ),
×
1126
            )
×
1127
            downQueries.push(
×
1128
                new Query(
×
1129
                    `ALTER TABLE ${this.escapePath(
×
1130
                        table,
×
1131
                    )} DROP CONSTRAINT "${pkName}"`,
×
1132
                ),
×
1133
            )
×
1134
        }
×
1135

×
1136
        // create column index
×
1137
        const columnIndex = clonedTable.indices.find(
×
1138
            (index) =>
×
1139
                index.columnNames.length === 1 &&
×
1140
                index.columnNames[0] === column.name,
×
1141
        )
×
1142
        if (columnIndex) {
×
1143
            upQueries.push(this.createIndexSql(table, columnIndex))
×
1144
            downQueries.push(this.dropIndexSql(table, columnIndex))
×
1145
        }
×
1146

×
1147
        // create unique constraint
×
1148
        if (column.isUnique) {
×
1149
            const uniqueConstraint = new TableUnique({
×
1150
                name: this.connection.namingStrategy.uniqueConstraintName(
×
1151
                    table,
×
1152
                    [column.name],
×
1153
                ),
×
1154
                columnNames: [column.name],
×
1155
            })
×
1156
            clonedTable.uniques.push(uniqueConstraint)
×
1157
            upQueries.push(
×
1158
                new Query(
×
1159
                    `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
×
1160
                        uniqueConstraint.name
×
1161
                    }" UNIQUE ("${column.name}")`,
×
1162
                ),
×
1163
            )
×
1164
            downQueries.push(
×
1165
                new Query(
×
1166
                    `ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${
×
1167
                        uniqueConstraint.name
×
1168
                    }"`,
×
1169
                ),
×
1170
            )
×
1171
        }
×
1172

×
1173
        if (column.generatedType === "STORED" && column.asExpression) {
×
1174
            const tableNameWithSchema = (
×
1175
                await this.getTableNameWithSchema(table.name)
×
1176
            ).split(".")
×
1177
            const tableName = tableNameWithSchema[1]
×
1178
            const schema = tableNameWithSchema[0]
×
1179

×
1180
            const insertQuery = this.insertTypeormMetadataSql({
×
1181
                database: this.driver.database,
×
1182
                schema,
×
1183
                table: tableName,
×
1184
                type: MetadataTableType.GENERATED_COLUMN,
×
1185
                name: column.name,
×
1186
                value: column.asExpression,
×
1187
            })
×
1188

×
1189
            const deleteQuery = this.deleteTypeormMetadataSql({
×
1190
                database: this.driver.database,
×
1191
                schema,
×
1192
                table: tableName,
×
1193
                type: MetadataTableType.GENERATED_COLUMN,
×
1194
                name: column.name,
×
1195
            })
×
1196

×
1197
            upQueries.push(insertQuery)
×
1198
            downQueries.push(deleteQuery)
×
1199
        }
×
1200

×
1201
        // create column's comment
×
1202
        if (column.comment) {
×
1203
            upQueries.push(
×
1204
                new Query(
×
1205
                    `COMMENT ON COLUMN ${this.escapePath(table)}."${
×
1206
                        column.name
×
1207
                    }" IS ${this.escapeComment(column.comment)}`,
×
1208
                ),
×
1209
            )
×
1210
            downQueries.push(
×
1211
                new Query(
×
1212
                    `COMMENT ON COLUMN ${this.escapePath(table)}."${
×
1213
                        column.name
×
1214
                    }" IS ${this.escapeComment(column.comment)}`,
×
1215
                ),
×
1216
            )
×
1217
        }
×
1218

×
1219
        await this.executeQueries(upQueries, downQueries)
×
1220

×
1221
        clonedTable.addColumn(column)
×
1222
        this.replaceCachedTable(table, clonedTable)
×
1223
    }
×
1224

28✔
1225
    /**
28✔
1226
     * Creates a new columns from the column in the table.
28✔
1227
     * @param tableOrName
28✔
1228
     * @param columns
28✔
1229
     */
28✔
1230
    async addColumns(
28✔
1231
        tableOrName: Table | string,
×
1232
        columns: TableColumn[],
×
1233
    ): Promise<void> {
×
1234
        for (const column of columns) {
×
1235
            await this.addColumn(tableOrName, column)
×
1236
        }
×
1237
    }
×
1238

28✔
1239
    /**
28✔
1240
     * Renames column in the given table.
28✔
1241
     * @param tableOrName
28✔
1242
     * @param oldTableColumnOrName
28✔
1243
     * @param newTableColumnOrName
28✔
1244
     */
28✔
1245
    async renameColumn(
28✔
1246
        tableOrName: Table | string,
×
1247
        oldTableColumnOrName: TableColumn | string,
×
1248
        newTableColumnOrName: TableColumn | string,
×
1249
    ): Promise<void> {
×
1250
        const table = InstanceChecker.isTable(tableOrName)
×
1251
            ? tableOrName
×
1252
            : await this.getCachedTable(tableOrName)
×
1253
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
×
1254
            ? oldTableColumnOrName
×
1255
            : table.columns.find((c) => c.name === oldTableColumnOrName)
×
1256
        if (!oldColumn)
×
1257
            throw new TypeORMError(
×
1258
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
×
1259
            )
×
1260

×
1261
        let newColumn
×
1262
        if (InstanceChecker.isTableColumn(newTableColumnOrName)) {
×
1263
            newColumn = newTableColumnOrName
×
1264
        } else {
×
1265
            newColumn = oldColumn.clone()
×
1266
            newColumn.name = newTableColumnOrName
×
1267
        }
×
1268

×
1269
        return this.changeColumn(table, oldColumn, newColumn)
×
1270
    }
×
1271

28✔
1272
    /**
28✔
1273
     * Changes a column in the table.
28✔
1274
     * @param tableOrName
28✔
1275
     * @param oldTableColumnOrName
28✔
1276
     * @param newColumn
28✔
1277
     */
28✔
1278
    async changeColumn(
28✔
1279
        tableOrName: Table | string,
×
1280
        oldTableColumnOrName: TableColumn | string,
×
1281
        newColumn: TableColumn,
×
1282
    ): Promise<void> {
×
1283
        const table = InstanceChecker.isTable(tableOrName)
×
1284
            ? tableOrName
×
1285
            : await this.getCachedTable(tableOrName)
×
1286
        let clonedTable = table.clone()
×
1287
        const upQueries: Query[] = []
×
1288
        const downQueries: Query[] = []
×
1289
        let defaultValueChanged = false
×
1290

×
1291
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
×
1292
            ? oldTableColumnOrName
×
1293
            : table.columns.find(
×
1294
                  (column) => column.name === oldTableColumnOrName,
×
1295
              )
×
1296
        if (!oldColumn)
×
1297
            throw new TypeORMError(
×
1298
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
×
1299
            )
×
1300

×
1301
        if (
×
1302
            oldColumn.type !== newColumn.type ||
×
1303
            oldColumn.length !== newColumn.length ||
×
1304
            newColumn.isArray !== oldColumn.isArray ||
×
1305
            (!oldColumn.generatedType &&
×
1306
                newColumn.generatedType === "STORED") ||
×
1307
            (oldColumn.asExpression !== newColumn.asExpression &&
×
1308
                newColumn.generatedType === "STORED")
×
1309
        ) {
×
1310
            // To avoid data conversion, we just recreate column
×
1311
            await this.dropColumn(table, oldColumn)
×
1312
            await this.addColumn(table, newColumn)
×
1313

×
1314
            // update cloned table
×
1315
            clonedTable = table.clone()
×
1316
        } else {
×
1317
            if (oldColumn.name !== newColumn.name) {
×
1318
                // rename column
×
1319
                upQueries.push(
×
1320
                    new Query(
×
1321
                        `ALTER TABLE ${this.escapePath(table)} RENAME COLUMN "${
×
1322
                            oldColumn.name
×
1323
                        }" TO "${newColumn.name}"`,
×
1324
                    ),
×
1325
                )
×
1326
                downQueries.push(
×
1327
                    new Query(
×
1328
                        `ALTER TABLE ${this.escapePath(table)} RENAME COLUMN "${
×
1329
                            newColumn.name
×
1330
                        }" TO "${oldColumn.name}"`,
×
1331
                    ),
×
1332
                )
×
1333

×
1334
                // rename ENUM type
×
1335
                if (
×
1336
                    oldColumn.type === "enum" ||
×
1337
                    oldColumn.type === "simple-enum"
×
1338
                ) {
×
1339
                    const oldEnumType = await this.getUserDefinedTypeName(
×
1340
                        table,
×
1341
                        oldColumn,
×
1342
                    )
×
1343
                    upQueries.push(
×
1344
                        new Query(
×
1345
                            `ALTER TYPE "${oldEnumType.schema}"."${
×
1346
                                oldEnumType.name
×
1347
                            }" RENAME TO ${this.buildEnumName(
×
1348
                                table,
×
1349
                                newColumn,
×
1350
                                false,
×
1351
                            )}`,
×
1352
                        ),
×
1353
                    )
×
1354
                    downQueries.push(
×
1355
                        new Query(
×
1356
                            `ALTER TYPE ${this.buildEnumName(
×
1357
                                table,
×
1358
                                newColumn,
×
1359
                            )} RENAME TO "${oldEnumType.name}"`,
×
1360
                        ),
×
1361
                    )
×
1362
                }
×
1363

×
1364
                // rename column primary key constraint
×
1365
                if (
×
1366
                    oldColumn.isPrimary === true &&
×
1367
                    !oldColumn.primaryKeyConstraintName
×
1368
                ) {
×
1369
                    const primaryColumns = clonedTable.primaryColumns
×
1370

×
1371
                    // build old primary constraint name
×
1372
                    const columnNames = primaryColumns.map(
×
1373
                        (column) => column.name,
×
1374
                    )
×
1375
                    const oldPkName =
×
1376
                        this.connection.namingStrategy.primaryKeyName(
×
1377
                            clonedTable,
×
1378
                            columnNames,
×
1379
                        )
×
1380

×
1381
                    // replace old column name with new column name
×
1382
                    columnNames.splice(columnNames.indexOf(oldColumn.name), 1)
×
1383
                    columnNames.push(newColumn.name)
×
1384

×
1385
                    // build new primary constraint name
×
1386
                    const newPkName =
×
1387
                        this.connection.namingStrategy.primaryKeyName(
×
1388
                            clonedTable,
×
1389
                            columnNames,
×
1390
                        )
×
1391

×
1392
                    upQueries.push(
×
1393
                        new Query(
×
1394
                            `ALTER TABLE ${this.escapePath(
×
1395
                                table,
×
1396
                            )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
×
1397
                        ),
×
1398
                    )
×
1399
                    downQueries.push(
×
1400
                        new Query(
×
1401
                            `ALTER TABLE ${this.escapePath(
×
1402
                                table,
×
1403
                            )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
×
1404
                        ),
×
1405
                    )
×
1406
                }
×
1407

×
1408
                // rename column sequence
×
1409
                if (
×
1410
                    oldColumn.isGenerated === true &&
×
1411
                    newColumn.generationStrategy === "increment"
×
1412
                ) {
×
1413
                    const sequencePath = this.buildSequencePath(
×
1414
                        table,
×
1415
                        oldColumn.name,
×
1416
                    )
×
1417
                    const sequenceName = this.buildSequenceName(
×
1418
                        table,
×
1419
                        oldColumn.name,
×
1420
                    )
×
1421

×
1422
                    const newSequencePath = this.buildSequencePath(
×
1423
                        table,
×
1424
                        newColumn.name,
×
1425
                    )
×
1426
                    const newSequenceName = this.buildSequenceName(
×
1427
                        table,
×
1428
                        newColumn.name,
×
1429
                    )
×
1430

×
1431
                    const up = `ALTER SEQUENCE ${this.escapePath(
×
1432
                        sequencePath,
×
1433
                    )} RENAME TO "${newSequenceName}"`
×
1434
                    const down = `ALTER SEQUENCE ${this.escapePath(
×
1435
                        newSequencePath,
×
1436
                    )} RENAME TO "${sequenceName}"`
×
1437

×
1438
                    upQueries.push(new Query(up))
×
1439
                    downQueries.push(new Query(down))
×
1440
                }
×
1441

×
1442
                // rename unique constraints
×
1443
                clonedTable.findColumnUniques(oldColumn).forEach((unique) => {
×
1444
                    const oldUniqueName =
×
1445
                        this.connection.namingStrategy.uniqueConstraintName(
×
1446
                            clonedTable,
×
1447
                            unique.columnNames,
×
1448
                        )
×
1449

×
1450
                    // Skip renaming if Unique has user defined constraint name
×
1451
                    if (unique.name !== oldUniqueName) return
×
1452

×
1453
                    // build new constraint name
×
1454
                    unique.columnNames.splice(
×
1455
                        unique.columnNames.indexOf(oldColumn.name),
×
1456
                        1,
×
1457
                    )
×
1458
                    unique.columnNames.push(newColumn.name)
×
1459
                    const newUniqueName =
×
1460
                        this.connection.namingStrategy.uniqueConstraintName(
×
1461
                            clonedTable,
×
1462
                            unique.columnNames,
×
1463
                        )
×
1464

×
1465
                    // build queries
×
1466
                    upQueries.push(
×
1467
                        new Query(
×
1468
                            `ALTER TABLE ${this.escapePath(
×
1469
                                table,
×
1470
                            )} RENAME CONSTRAINT "${
×
1471
                                unique.name
×
1472
                            }" TO "${newUniqueName}"`,
×
1473
                        ),
×
1474
                    )
×
1475
                    downQueries.push(
×
1476
                        new Query(
×
1477
                            `ALTER TABLE ${this.escapePath(
×
1478
                                table,
×
1479
                            )} RENAME CONSTRAINT "${newUniqueName}" TO "${
×
1480
                                unique.name
×
1481
                            }"`,
×
1482
                        ),
×
1483
                    )
×
1484

×
1485
                    // replace constraint name
×
1486
                    unique.name = newUniqueName
×
1487
                })
×
1488

×
1489
                // rename index constraints
×
1490
                clonedTable.findColumnIndices(oldColumn).forEach((index) => {
×
1491
                    const oldIndexName =
×
1492
                        this.connection.namingStrategy.indexName(
×
1493
                            clonedTable,
×
1494
                            index.columnNames,
×
1495
                            index.where,
×
1496
                        )
×
1497

×
1498
                    // Skip renaming if Index has user defined constraint name
×
1499
                    if (index.name !== oldIndexName) return
×
1500

×
1501
                    // build new constraint name
×
1502
                    index.columnNames.splice(
×
1503
                        index.columnNames.indexOf(oldColumn.name),
×
1504
                        1,
×
1505
                    )
×
1506
                    index.columnNames.push(newColumn.name)
×
1507
                    const { schema } = this.driver.parseTableName(table)
×
1508
                    const newIndexName =
×
1509
                        this.connection.namingStrategy.indexName(
×
1510
                            clonedTable,
×
1511
                            index.columnNames,
×
1512
                            index.where,
×
1513
                        )
×
1514

×
1515
                    // build queries
×
1516
                    const up = schema
×
1517
                        ? `ALTER INDEX "${schema}"."${index.name}" RENAME TO "${newIndexName}"`
×
1518
                        : `ALTER INDEX "${index.name}" RENAME TO "${newIndexName}"`
×
1519
                    const down = schema
×
1520
                        ? `ALTER INDEX "${schema}"."${newIndexName}" RENAME TO "${index.name}"`
×
1521
                        : `ALTER INDEX "${newIndexName}" RENAME TO "${index.name}"`
×
1522

×
1523
                    upQueries.push(new Query(up))
×
1524
                    downQueries.push(new Query(down))
×
1525

×
1526
                    // replace constraint name
×
1527
                    index.name = newIndexName
×
1528
                })
×
1529

×
1530
                // rename foreign key constraints
×
1531
                clonedTable
×
1532
                    .findColumnForeignKeys(oldColumn)
×
1533
                    .forEach((foreignKey) => {
×
1534
                        const foreignKeyName =
×
1535
                            this.connection.namingStrategy.foreignKeyName(
×
1536
                                clonedTable,
×
1537
                                foreignKey.columnNames,
×
1538
                                this.getTablePath(foreignKey),
×
1539
                                foreignKey.referencedColumnNames,
×
1540
                            )
×
1541

×
1542
                        // Skip renaming if foreign key has user defined constraint name
×
1543
                        if (foreignKey.name !== foreignKeyName) return
×
1544

×
1545
                        // build new constraint name
×
1546
                        foreignKey.columnNames.splice(
×
1547
                            foreignKey.columnNames.indexOf(oldColumn.name),
×
1548
                            1,
×
1549
                        )
×
1550
                        foreignKey.columnNames.push(newColumn.name)
×
1551
                        const newForeignKeyName =
×
1552
                            this.connection.namingStrategy.foreignKeyName(
×
1553
                                clonedTable,
×
1554
                                foreignKey.columnNames,
×
1555
                                this.getTablePath(foreignKey),
×
1556
                                foreignKey.referencedColumnNames,
×
1557
                            )
×
1558

×
1559
                        // build queries
×
1560
                        upQueries.push(
×
1561
                            new Query(
×
1562
                                `ALTER TABLE ${this.escapePath(
×
1563
                                    table,
×
1564
                                )} RENAME CONSTRAINT "${
×
1565
                                    foreignKey.name
×
1566
                                }" TO "${newForeignKeyName}"`,
×
1567
                            ),
×
1568
                        )
×
1569
                        downQueries.push(
×
1570
                            new Query(
×
1571
                                `ALTER TABLE ${this.escapePath(
×
1572
                                    table,
×
1573
                                )} RENAME CONSTRAINT "${newForeignKeyName}" TO "${
×
1574
                                    foreignKey.name
×
1575
                                }"`,
×
1576
                            ),
×
1577
                        )
×
1578

×
1579
                        // replace constraint name
×
1580
                        foreignKey.name = newForeignKeyName
×
1581
                    })
×
1582

×
1583
                // rename old column in the Table object
×
1584
                const oldTableColumn = clonedTable.columns.find(
×
1585
                    (column) => column.name === oldColumn.name,
×
1586
                )
×
1587
                clonedTable.columns[
×
1588
                    clonedTable.columns.indexOf(oldTableColumn!)
×
1589
                ].name = newColumn.name
×
1590
                oldColumn.name = newColumn.name
×
1591
            }
×
1592

×
1593
            if (
×
1594
                newColumn.precision !== oldColumn.precision ||
×
1595
                newColumn.scale !== oldColumn.scale
×
1596
            ) {
×
1597
                upQueries.push(
×
1598
                    new Query(
×
1599
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
×
1600
                            newColumn.name
×
1601
                        }" TYPE ${this.driver.createFullType(newColumn)}`,
×
1602
                    ),
×
1603
                )
×
1604
                downQueries.push(
×
1605
                    new Query(
×
1606
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
×
1607
                            newColumn.name
×
1608
                        }" TYPE ${this.driver.createFullType(oldColumn)}`,
×
1609
                    ),
×
1610
                )
×
1611
            }
×
1612

×
1613
            if (
×
1614
                (newColumn.type === "enum" ||
×
1615
                    newColumn.type === "simple-enum") &&
×
1616
                (oldColumn.type === "enum" ||
×
1617
                    oldColumn.type === "simple-enum") &&
×
1618
                (!OrmUtils.isArraysEqual(newColumn.enum!, oldColumn.enum!) ||
×
1619
                    newColumn.enumName !== oldColumn.enumName)
×
1620
            ) {
×
1621
                const arraySuffix = newColumn.isArray ? "[]" : ""
×
1622

×
1623
                const { extraItems, missingItems } = OrmUtils.getArraysDiff(
×
1624
                    newColumn.enum!,
×
1625
                    oldColumn.enum!,
×
1626
                )
×
1627

×
1628
                const version = this.driver.version
×
1629

×
1630
                // when the only change is new enum value(s) we can use ADD VALUE syntax
×
1631
                const useAddValueForUp =
×
1632
                    VersionUtils.isGreaterOrEqual(version, "12.0") &&
×
1633
                    missingItems.length === 0 &&
×
1634
                    extraItems.length > 0
×
1635

×
1636
                // "public"."new_enum"
×
1637
                const newEnumName = this.buildEnumName(table, newColumn)
×
1638

×
1639
                // "public"."old_enum"
×
1640
                const oldEnumName = this.buildEnumName(table, oldColumn)
×
1641

×
1642
                // "old_enum"
×
1643
                const oldEnumNameWithoutSchema = this.buildEnumName(
×
1644
                    table,
×
1645
                    oldColumn,
×
1646
                    false,
×
1647
                )
×
1648

×
1649
                //"public"."old_enum_old"
×
1650
                const oldEnumNameWithSchema_old = this.buildEnumName(
×
1651
                    table,
×
1652
                    oldColumn,
×
1653
                    true,
×
1654
                    false,
×
1655
                    true,
×
1656
                )
×
1657

×
1658
                //"old_enum_old"
×
1659
                const oldEnumNameWithoutSchema_old = this.buildEnumName(
×
1660
                    table,
×
1661
                    oldColumn,
×
1662
                    false,
×
1663
                    false,
×
1664
                    true,
×
1665
                )
×
1666

×
1667
                // ADD VALUE allows us to just add new enum values without any type changes, but can of course only be used when only new values were added
×
1668
                if (useAddValueForUp) {
×
1669
                    // Add values for up - that's all we need
×
1670
                    for (const item of extraItems) {
×
1671
                        const escapedValue = item.replaceAll("'", "''")
×
1672

×
1673
                        upQueries.push(
×
1674
                            new Query(
×
1675
                                `ALTER TYPE ${oldEnumName} ADD VALUE '${escapedValue}'`,
×
1676
                            ),
×
1677
                        )
×
1678
                    }
×
1679

×
1680
                    // For down query, we're be doing the doing the same as normal enum change (create new type, alter column, drop old type)
×
1681

×
1682
                    downQueries.push(
×
1683
                        new Query(
×
1684
                            `ALTER TYPE ${oldEnumNameWithSchema_old} RENAME TO ${oldEnumNameWithoutSchema}`,
×
1685
                        ),
×
1686
                    )
×
1687

×
1688
                    downQueries.push(
×
1689
                        this.dropEnumTypeSql(table, newColumn, newEnumName),
×
1690
                    )
×
1691

×
1692
                    const downType = `${oldEnumNameWithSchema_old}${arraySuffix} USING "${newColumn.name}"::"text"::${oldEnumNameWithSchema_old}${arraySuffix}`
×
1693

×
1694
                    downQueries.push(
×
1695
                        new Query(
×
1696
                            `ALTER TABLE ${this.escapePath(
×
1697
                                table,
×
1698
                            )} ALTER COLUMN "${
×
1699
                                newColumn.name
×
1700
                            }" TYPE ${downType}`,
×
1701
                        ),
×
1702
                    )
×
1703

×
1704
                    downQueries.push(
×
1705
                        this.createEnumTypeSql(
×
1706
                            table,
×
1707
                            oldColumn,
×
1708
                            oldEnumNameWithSchema_old,
×
1709
                        ),
×
1710
                    )
×
1711
                } else {
×
1712
                    // rename old ENUM
×
1713
                    upQueries.push(
×
1714
                        new Query(
×
1715
                            `ALTER TYPE ${oldEnumName} RENAME TO ${oldEnumNameWithoutSchema_old}`,
×
1716
                        ),
×
1717
                    )
×
1718
                    downQueries.push(
×
1719
                        new Query(
×
1720
                            `ALTER TYPE ${oldEnumNameWithSchema_old} RENAME TO ${oldEnumNameWithoutSchema}`,
×
1721
                        ),
×
1722
                    )
×
1723

×
1724
                    // create new ENUM
×
1725
                    upQueries.push(
×
1726
                        this.createEnumTypeSql(table, newColumn, newEnumName),
×
1727
                    )
×
1728
                    downQueries.push(
×
1729
                        this.dropEnumTypeSql(table, newColumn, newEnumName),
×
1730
                    )
×
1731

×
1732
                    // if column have default value, we must drop it to avoid issues with type casting
×
1733
                    if (
×
1734
                        oldColumn.default !== null &&
×
1735
                        oldColumn.default !== undefined
×
1736
                    ) {
×
1737
                        // mark default as changed to prevent double update
×
1738
                        defaultValueChanged = true
×
1739
                        upQueries.push(
×
1740
                            new Query(
×
1741
                                `ALTER TABLE ${this.escapePath(
×
1742
                                    table,
×
1743
                                )} ALTER COLUMN "${
×
1744
                                    oldColumn.name
×
1745
                                }" DROP DEFAULT`,
×
1746
                            ),
×
1747
                        )
×
1748
                        downQueries.push(
×
1749
                            new Query(
×
1750
                                `ALTER TABLE ${this.escapePath(
×
1751
                                    table,
×
1752
                                )} ALTER COLUMN "${
×
1753
                                    oldColumn.name
×
1754
                                }" SET DEFAULT ${oldColumn.default}`,
×
1755
                            ),
×
1756
                        )
×
1757
                    }
×
1758

×
1759
                    // build column types
×
1760
                    const upType = `${newEnumName}${arraySuffix} USING "${newColumn.name}"::"text"::${newEnumName}${arraySuffix}`
×
1761
                    const downType = `${oldEnumNameWithSchema_old}${arraySuffix} USING "${newColumn.name}"::"text"::${oldEnumNameWithSchema_old}${arraySuffix}`
×
1762

×
1763
                    // update column to use new type
×
1764
                    upQueries.push(
×
1765
                        new Query(
×
1766
                            `ALTER TABLE ${this.escapePath(
×
1767
                                table,
×
1768
                            )} ALTER COLUMN "${newColumn.name}" TYPE ${upType}`,
×
1769
                        ),
×
1770
                    )
×
1771
                    downQueries.push(
×
1772
                        new Query(
×
1773
                            `ALTER TABLE ${this.escapePath(
×
1774
                                table,
×
1775
                            )} ALTER COLUMN "${
×
1776
                                newColumn.name
×
1777
                            }" TYPE ${downType}`,
×
1778
                        ),
×
1779
                    )
×
1780

×
1781
                    // restore column default or create new one
×
1782
                    if (
×
1783
                        newColumn.default !== null &&
×
1784
                        newColumn.default !== undefined
×
1785
                    ) {
×
1786
                        upQueries.push(
×
1787
                            new Query(
×
1788
                                `ALTER TABLE ${this.escapePath(
×
1789
                                    table,
×
1790
                                )} ALTER COLUMN "${
×
1791
                                    newColumn.name
×
1792
                                }" SET DEFAULT ${newColumn.default}`,
×
1793
                            ),
×
1794
                        )
×
1795
                        downQueries.push(
×
1796
                            new Query(
×
1797
                                `ALTER TABLE ${this.escapePath(
×
1798
                                    table,
×
1799
                                )} ALTER COLUMN "${
×
1800
                                    newColumn.name
×
1801
                                }" DROP DEFAULT`,
×
1802
                            ),
×
1803
                        )
×
1804
                    }
×
1805

×
1806
                    // remove old ENUM
×
1807
                    upQueries.push(
×
1808
                        this.dropEnumTypeSql(
×
1809
                            table,
×
1810
                            oldColumn,
×
1811
                            oldEnumNameWithSchema_old,
×
1812
                        ),
×
1813
                    )
×
1814
                    downQueries.push(
×
1815
                        this.createEnumTypeSql(
×
1816
                            table,
×
1817
                            oldColumn,
×
1818
                            oldEnumNameWithSchema_old,
×
1819
                        ),
×
1820
                    )
×
1821
                }
×
1822
            }
×
1823

×
1824
            if (oldColumn.isNullable !== newColumn.isNullable) {
×
1825
                if (newColumn.isNullable) {
×
1826
                    upQueries.push(
×
1827
                        new Query(
×
1828
                            `ALTER TABLE ${this.escapePath(
×
1829
                                table,
×
1830
                            )} ALTER COLUMN "${oldColumn.name}" DROP NOT NULL`,
×
1831
                        ),
×
1832
                    )
×
1833
                    downQueries.push(
×
1834
                        new Query(
×
1835
                            `ALTER TABLE ${this.escapePath(
×
1836
                                table,
×
1837
                            )} ALTER COLUMN "${oldColumn.name}" SET NOT NULL`,
×
1838
                        ),
×
1839
                    )
×
1840
                } else {
×
1841
                    upQueries.push(
×
1842
                        new Query(
×
1843
                            `ALTER TABLE ${this.escapePath(
×
1844
                                table,
×
1845
                            )} ALTER COLUMN "${oldColumn.name}" SET NOT NULL`,
×
1846
                        ),
×
1847
                    )
×
1848
                    downQueries.push(
×
1849
                        new Query(
×
1850
                            `ALTER TABLE ${this.escapePath(
×
1851
                                table,
×
1852
                            )} ALTER COLUMN "${oldColumn.name}" DROP NOT NULL`,
×
1853
                        ),
×
1854
                    )
×
1855
                }
×
1856
            }
×
1857

×
1858
            if (oldColumn.comment !== newColumn.comment) {
×
1859
                upQueries.push(
×
1860
                    new Query(
×
1861
                        `COMMENT ON COLUMN ${this.escapePath(table)}."${
×
1862
                            oldColumn.name
×
1863
                        }" IS ${this.escapeComment(newColumn.comment)}`,
×
1864
                    ),
×
1865
                )
×
1866
                downQueries.push(
×
1867
                    new Query(
×
1868
                        `COMMENT ON COLUMN ${this.escapePath(table)}."${
×
1869
                            newColumn.name
×
1870
                        }" IS ${this.escapeComment(oldColumn.comment)}`,
×
1871
                    ),
×
1872
                )
×
1873
            }
×
1874

×
1875
            if (newColumn.isPrimary !== oldColumn.isPrimary) {
×
1876
                const primaryColumns = clonedTable.primaryColumns
×
1877

×
1878
                // if primary column state changed, we must always drop existed constraint.
×
1879
                if (primaryColumns.length > 0) {
×
1880
                    const pkName = primaryColumns[0].primaryKeyConstraintName
×
1881
                        ? primaryColumns[0].primaryKeyConstraintName
×
1882
                        : this.connection.namingStrategy.primaryKeyName(
×
1883
                              clonedTable,
×
1884
                              primaryColumns.map((column) => column.name),
×
1885
                          )
×
1886

×
1887
                    const columnNames = primaryColumns
×
1888
                        .map((column) => `"${column.name}"`)
×
1889
                        .join(", ")
×
1890

×
1891
                    upQueries.push(
×
1892
                        new Query(
×
1893
                            `ALTER TABLE ${this.escapePath(
×
1894
                                table,
×
1895
                            )} DROP CONSTRAINT "${pkName}"`,
×
1896
                        ),
×
1897
                    )
×
1898
                    downQueries.push(
×
1899
                        new Query(
×
1900
                            `ALTER TABLE ${this.escapePath(
×
1901
                                table,
×
1902
                            )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
×
1903
                        ),
×
1904
                    )
×
1905
                }
×
1906

×
1907
                if (newColumn.isPrimary === true) {
×
1908
                    primaryColumns.push(newColumn)
×
1909
                    // update column in table
×
1910
                    const column = clonedTable.columns.find(
×
1911
                        (column) => column.name === newColumn.name,
×
1912
                    )
×
1913
                    column!.isPrimary = true
×
1914
                    const pkName = primaryColumns[0].primaryKeyConstraintName
×
1915
                        ? primaryColumns[0].primaryKeyConstraintName
×
1916
                        : this.connection.namingStrategy.primaryKeyName(
×
1917
                              clonedTable,
×
1918
                              primaryColumns.map((column) => column.name),
×
1919
                          )
×
1920

×
1921
                    const columnNames = primaryColumns
×
1922
                        .map((column) => `"${column.name}"`)
×
1923
                        .join(", ")
×
1924

×
1925
                    upQueries.push(
×
1926
                        new Query(
×
1927
                            `ALTER TABLE ${this.escapePath(
×
1928
                                table,
×
1929
                            )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
×
1930
                        ),
×
1931
                    )
×
1932
                    downQueries.push(
×
1933
                        new Query(
×
1934
                            `ALTER TABLE ${this.escapePath(
×
1935
                                table,
×
1936
                            )} DROP CONSTRAINT "${pkName}"`,
×
1937
                        ),
×
1938
                    )
×
1939
                } else {
×
1940
                    const primaryColumn = primaryColumns.find(
×
1941
                        (c) => c.name === newColumn.name,
×
1942
                    )
×
1943
                    primaryColumns.splice(
×
1944
                        primaryColumns.indexOf(primaryColumn!),
×
1945
                        1,
×
1946
                    )
×
1947

×
1948
                    // update column in table
×
1949
                    const column = clonedTable.columns.find(
×
1950
                        (column) => column.name === newColumn.name,
×
1951
                    )
×
1952
                    column!.isPrimary = false
×
1953

×
1954
                    // if we have another primary keys, we must recreate constraint.
×
1955
                    if (primaryColumns.length > 0) {
×
1956
                        const pkName = primaryColumns[0]
×
1957
                            .primaryKeyConstraintName
×
1958
                            ? primaryColumns[0].primaryKeyConstraintName
×
1959
                            : this.connection.namingStrategy.primaryKeyName(
×
1960
                                  clonedTable,
×
1961
                                  primaryColumns.map((column) => column.name),
×
1962
                              )
×
1963

×
1964
                        const columnNames = primaryColumns
×
1965
                            .map((column) => `"${column.name}"`)
×
1966
                            .join(", ")
×
1967

×
1968
                        upQueries.push(
×
1969
                            new Query(
×
1970
                                `ALTER TABLE ${this.escapePath(
×
1971
                                    table,
×
1972
                                )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
×
1973
                            ),
×
1974
                        )
×
1975
                        downQueries.push(
×
1976
                            new Query(
×
1977
                                `ALTER TABLE ${this.escapePath(
×
1978
                                    table,
×
1979
                                )} DROP CONSTRAINT "${pkName}"`,
×
1980
                            ),
×
1981
                        )
×
1982
                    }
×
1983
                }
×
1984
            }
×
1985

×
1986
            if (newColumn.isUnique !== oldColumn.isUnique) {
×
1987
                if (newColumn.isUnique === true) {
×
1988
                    const uniqueConstraint = new TableUnique({
×
1989
                        name: this.connection.namingStrategy.uniqueConstraintName(
×
1990
                            table,
×
1991
                            [newColumn.name],
×
1992
                        ),
×
1993
                        columnNames: [newColumn.name],
×
1994
                    })
×
1995
                    clonedTable.uniques.push(uniqueConstraint)
×
1996
                    upQueries.push(
×
1997
                        new Query(
×
1998
                            `ALTER TABLE ${this.escapePath(
×
1999
                                table,
×
2000
                            )} ADD CONSTRAINT "${
×
2001
                                uniqueConstraint.name
×
2002
                            }" UNIQUE ("${newColumn.name}")`,
×
2003
                        ),
×
2004
                    )
×
2005
                    downQueries.push(
×
2006
                        new Query(
×
2007
                            `ALTER TABLE ${this.escapePath(
×
2008
                                table,
×
2009
                            )} DROP CONSTRAINT "${uniqueConstraint.name}"`,
×
2010
                        ),
×
2011
                    )
×
2012
                } else {
×
2013
                    const uniqueConstraint = clonedTable.uniques.find(
×
2014
                        (unique) => {
×
2015
                            return (
×
2016
                                unique.columnNames.length === 1 &&
×
2017
                                !!unique.columnNames.find(
×
2018
                                    (columnName) =>
×
2019
                                        columnName === newColumn.name,
×
2020
                                )
×
2021
                            )
×
2022
                        },
×
2023
                    )
×
2024
                    clonedTable.uniques.splice(
×
2025
                        clonedTable.uniques.indexOf(uniqueConstraint!),
×
2026
                        1,
×
2027
                    )
×
2028
                    upQueries.push(
×
2029
                        new Query(
×
2030
                            `ALTER TABLE ${this.escapePath(
×
2031
                                table,
×
2032
                            )} DROP CONSTRAINT "${uniqueConstraint!.name}"`,
×
2033
                        ),
×
2034
                    )
×
2035
                    downQueries.push(
×
2036
                        new Query(
×
2037
                            `ALTER TABLE ${this.escapePath(
×
2038
                                table,
×
2039
                            )} ADD CONSTRAINT "${
×
2040
                                uniqueConstraint!.name
×
2041
                            }" UNIQUE ("${newColumn.name}")`,
×
2042
                        ),
×
2043
                    )
×
2044
                }
×
2045
            }
×
2046

×
2047
            if (oldColumn.isGenerated !== newColumn.isGenerated) {
×
2048
                // if old column was "generated", we should clear defaults
×
2049
                if (oldColumn.isGenerated) {
×
2050
                    if (oldColumn.generationStrategy === "uuid") {
×
2051
                        upQueries.push(
×
2052
                            new Query(
×
2053
                                `ALTER TABLE ${this.escapePath(
×
2054
                                    table,
×
2055
                                )} ALTER COLUMN "${
×
2056
                                    oldColumn.name
×
2057
                                }" DROP DEFAULT`,
×
2058
                            ),
×
2059
                        )
×
2060
                        downQueries.push(
×
2061
                            new Query(
×
2062
                                `ALTER TABLE ${this.escapePath(
×
2063
                                    table,
×
2064
                                )} ALTER COLUMN "${
×
2065
                                    oldColumn.name
×
2066
                                }" SET DEFAULT ${this.driver.uuidGenerator}`,
×
2067
                            ),
×
2068
                        )
×
2069
                    } else if (oldColumn.generationStrategy === "increment") {
×
2070
                        upQueries.push(
×
2071
                            new Query(
×
2072
                                `ALTER TABLE ${this.escapePath(
×
2073
                                    table,
×
2074
                                )} ALTER COLUMN "${
×
2075
                                    newColumn.name
×
2076
                                }" DROP DEFAULT`,
×
2077
                            ),
×
2078
                        )
×
2079
                        downQueries.push(
×
2080
                            new Query(
×
2081
                                `ALTER TABLE ${this.escapePath(
×
2082
                                    table,
×
2083
                                )} ALTER COLUMN "${
×
2084
                                    newColumn.name
×
2085
                                }" SET DEFAULT nextval('${this.escapePath(
×
2086
                                    this.buildSequencePath(table, newColumn),
×
2087
                                )}')`,
×
2088
                            ),
×
2089
                        )
×
2090

×
2091
                        upQueries.push(
×
2092
                            new Query(
×
2093
                                `DROP SEQUENCE ${this.escapePath(
×
2094
                                    this.buildSequencePath(table, newColumn),
×
2095
                                )}`,
×
2096
                            ),
×
2097
                        )
×
2098
                        downQueries.push(
×
2099
                            new Query(
×
2100
                                `CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(
×
2101
                                    this.buildSequencePath(table, newColumn),
×
2102
                                )} OWNED BY ${this.escapePath(table)}."${
×
2103
                                    newColumn.name
×
2104
                                }"`,
×
2105
                            ),
×
2106
                        )
×
2107
                    }
×
2108
                }
×
2109

×
2110
                if (newColumn.generationStrategy === "uuid") {
×
2111
                    if (newColumn.isGenerated === true) {
×
2112
                        upQueries.push(
×
2113
                            new Query(
×
2114
                                `ALTER TABLE ${this.escapePath(
×
2115
                                    table,
×
2116
                                )} ALTER COLUMN "${
×
2117
                                    newColumn.name
×
2118
                                }" SET DEFAULT ${this.driver.uuidGenerator}`,
×
2119
                            ),
×
2120
                        )
×
2121
                        downQueries.push(
×
2122
                            new Query(
×
2123
                                `ALTER TABLE ${this.escapePath(
×
2124
                                    table,
×
2125
                                )} ALTER COLUMN "${
×
2126
                                    newColumn.name
×
2127
                                }" DROP DEFAULT`,
×
2128
                            ),
×
2129
                        )
×
2130
                    } else {
×
2131
                        upQueries.push(
×
2132
                            new Query(
×
2133
                                `ALTER TABLE ${this.escapePath(
×
2134
                                    table,
×
2135
                                )} ALTER COLUMN "${
×
2136
                                    newColumn.name
×
2137
                                }" DROP DEFAULT`,
×
2138
                            ),
×
2139
                        )
×
2140
                        downQueries.push(
×
2141
                            new Query(
×
2142
                                `ALTER TABLE ${this.escapePath(
×
2143
                                    table,
×
2144
                                )} ALTER COLUMN "${
×
2145
                                    newColumn.name
×
2146
                                }" SET DEFAULT ${this.driver.uuidGenerator}`,
×
2147
                            ),
×
2148
                        )
×
2149
                    }
×
2150
                } else if (newColumn.generationStrategy === "increment") {
×
2151
                    if (newColumn.isGenerated === true) {
×
2152
                        upQueries.push(
×
2153
                            new Query(
×
2154
                                `CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(
×
2155
                                    this.buildSequencePath(table, newColumn),
×
2156
                                )} OWNED BY ${this.escapePath(table)}."${
×
2157
                                    newColumn.name
×
2158
                                }"`,
×
2159
                            ),
×
2160
                        )
×
2161
                        downQueries.push(
×
2162
                            new Query(
×
2163
                                `DROP SEQUENCE ${this.escapePath(
×
2164
                                    this.buildSequencePath(table, newColumn),
×
2165
                                )}`,
×
2166
                            ),
×
2167
                        )
×
2168

×
2169
                        upQueries.push(
×
2170
                            new Query(
×
2171
                                `ALTER TABLE ${this.escapePath(
×
2172
                                    table,
×
2173
                                )} ALTER COLUMN "${
×
2174
                                    newColumn.name
×
2175
                                }" SET DEFAULT nextval('${this.escapePath(
×
2176
                                    this.buildSequencePath(table, newColumn),
×
2177
                                )}')`,
×
2178
                            ),
×
2179
                        )
×
2180
                        downQueries.push(
×
2181
                            new Query(
×
2182
                                `ALTER TABLE ${this.escapePath(
×
2183
                                    table,
×
2184
                                )} ALTER COLUMN "${
×
2185
                                    newColumn.name
×
2186
                                }" DROP DEFAULT`,
×
2187
                            ),
×
2188
                        )
×
2189
                    } else {
×
2190
                        upQueries.push(
×
2191
                            new Query(
×
2192
                                `ALTER TABLE ${this.escapePath(
×
2193
                                    table,
×
2194
                                )} ALTER COLUMN "${
×
2195
                                    newColumn.name
×
2196
                                }" DROP DEFAULT`,
×
2197
                            ),
×
2198
                        )
×
2199
                        downQueries.push(
×
2200
                            new Query(
×
2201
                                `ALTER TABLE ${this.escapePath(
×
2202
                                    table,
×
2203
                                )} ALTER COLUMN "${
×
2204
                                    newColumn.name
×
2205
                                }" SET DEFAULT nextval('${this.escapePath(
×
2206
                                    this.buildSequencePath(table, newColumn),
×
2207
                                )}')`,
×
2208
                            ),
×
2209
                        )
×
2210

×
2211
                        upQueries.push(
×
2212
                            new Query(
×
2213
                                `DROP SEQUENCE ${this.escapePath(
×
2214
                                    this.buildSequencePath(table, newColumn),
×
2215
                                )}`,
×
2216
                            ),
×
2217
                        )
×
2218
                        downQueries.push(
×
2219
                            new Query(
×
2220
                                `CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(
×
2221
                                    this.buildSequencePath(table, newColumn),
×
2222
                                )} OWNED BY ${this.escapePath(table)}."${
×
2223
                                    newColumn.name
×
2224
                                }"`,
×
2225
                            ),
×
2226
                        )
×
2227
                    }
×
2228
                }
×
2229
            }
×
2230

×
2231
            // the default might have changed when the enum changed
×
2232
            if (
×
2233
                newColumn.default !== oldColumn.default &&
×
2234
                !defaultValueChanged
×
2235
            ) {
×
2236
                if (
×
2237
                    newColumn.default !== null &&
×
2238
                    newColumn.default !== undefined
×
2239
                ) {
×
2240
                    upQueries.push(
×
2241
                        new Query(
×
2242
                            `ALTER TABLE ${this.escapePath(
×
2243
                                table,
×
2244
                            )} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${
×
2245
                                newColumn.default
×
2246
                            }`,
×
2247
                        ),
×
2248
                    )
×
2249

×
2250
                    if (
×
2251
                        oldColumn.default !== null &&
×
2252
                        oldColumn.default !== undefined
×
2253
                    ) {
×
2254
                        downQueries.push(
×
2255
                            new Query(
×
2256
                                `ALTER TABLE ${this.escapePath(
×
2257
                                    table,
×
2258
                                )} ALTER COLUMN "${
×
2259
                                    newColumn.name
×
2260
                                }" SET DEFAULT ${oldColumn.default}`,
×
2261
                            ),
×
2262
                        )
×
2263
                    } else {
×
2264
                        downQueries.push(
×
2265
                            new Query(
×
2266
                                `ALTER TABLE ${this.escapePath(
×
2267
                                    table,
×
2268
                                )} ALTER COLUMN "${
×
2269
                                    newColumn.name
×
2270
                                }" DROP DEFAULT`,
×
2271
                            ),
×
2272
                        )
×
2273
                    }
×
2274
                } else if (
×
2275
                    oldColumn.default !== null &&
×
2276
                    oldColumn.default !== undefined
×
2277
                ) {
×
2278
                    upQueries.push(
×
2279
                        new Query(
×
2280
                            `ALTER TABLE ${this.escapePath(
×
2281
                                table,
×
2282
                            )} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`,
×
2283
                        ),
×
2284
                    )
×
2285
                    downQueries.push(
×
2286
                        new Query(
×
2287
                            `ALTER TABLE ${this.escapePath(
×
2288
                                table,
×
2289
                            )} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${
×
2290
                                oldColumn.default
×
2291
                            }`,
×
2292
                        ),
×
2293
                    )
×
2294
                }
×
2295
            }
×
2296

×
2297
            if (
×
2298
                (newColumn.spatialFeatureType || "").toLowerCase() !==
×
2299
                    (oldColumn.spatialFeatureType || "").toLowerCase() ||
×
2300
                newColumn.srid !== oldColumn.srid
×
2301
            ) {
×
2302
                upQueries.push(
×
2303
                    new Query(
×
2304
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
×
2305
                            newColumn.name
×
2306
                        }" TYPE ${this.driver.createFullType(newColumn)}`,
×
2307
                    ),
×
2308
                )
×
2309
                downQueries.push(
×
2310
                    new Query(
×
2311
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
×
2312
                            newColumn.name
×
2313
                        }" TYPE ${this.driver.createFullType(oldColumn)}`,
×
2314
                    ),
×
2315
                )
×
2316
            }
×
2317

×
2318
            // update column collation
×
2319
            if (newColumn.collation !== oldColumn.collation) {
×
2320
                upQueries.push(
×
2321
                    new Query(
×
2322
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
×
2323
                            newColumn.name
×
2324
                        }" TYPE ${newColumn.type} COLLATE "${
×
2325
                            newColumn.collation
×
2326
                        }"`,
×
2327
                    ),
×
2328
                )
×
2329

×
2330
                const oldCollation = oldColumn.collation
×
2331
                    ? `"${oldColumn.collation}"`
×
2332
                    : `pg_catalog."default"` // if there's no old collation, use default
×
2333

×
2334
                downQueries.push(
×
2335
                    new Query(
×
2336
                        `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
×
2337
                            newColumn.name
×
2338
                        }" TYPE ${newColumn.type} COLLATE ${oldCollation}`,
×
2339
                    ),
×
2340
                )
×
2341
            }
×
2342

×
2343
            if (newColumn.generatedType !== oldColumn.generatedType) {
×
2344
                // Convert generated column data to normal column
×
2345
                if (
×
2346
                    !newColumn.generatedType ||
×
2347
                    newColumn.generatedType === "VIRTUAL"
×
2348
                ) {
×
2349
                    // We can copy the generated data to the new column
×
2350
                    const tableNameWithSchema = (
×
2351
                        await this.getTableNameWithSchema(table.name)
×
2352
                    ).split(".")
×
2353
                    const tableName = tableNameWithSchema[1]
×
2354
                    const schema = tableNameWithSchema[0]
×
2355

×
2356
                    upQueries.push(
×
2357
                        new Query(
×
2358
                            `ALTER TABLE ${this.escapePath(
×
2359
                                table,
×
2360
                            )} RENAME COLUMN "${oldColumn.name}" TO "TEMP_OLD_${
×
2361
                                oldColumn.name
×
2362
                            }"`,
×
2363
                        ),
×
2364
                    )
×
2365
                    upQueries.push(
×
2366
                        new Query(
×
2367
                            `ALTER TABLE ${this.escapePath(
×
2368
                                table,
×
2369
                            )} ADD ${this.buildCreateColumnSql(
×
2370
                                table,
×
2371
                                newColumn,
×
2372
                            )}`,
×
2373
                        ),
×
2374
                    )
×
2375
                    upQueries.push(
×
2376
                        new Query(
×
2377
                            `UPDATE ${this.escapePath(table)} SET "${
×
2378
                                newColumn.name
×
2379
                            }" = "TEMP_OLD_${oldColumn.name}"`,
×
2380
                        ),
×
2381
                    )
×
2382
                    upQueries.push(
×
2383
                        new Query(
×
2384
                            `ALTER TABLE ${this.escapePath(
×
2385
                                table,
×
2386
                            )} DROP COLUMN "TEMP_OLD_${oldColumn.name}"`,
×
2387
                        ),
×
2388
                    )
×
2389
                    upQueries.push(
×
2390
                        this.deleteTypeormMetadataSql({
×
2391
                            database: this.driver.database,
×
2392
                            schema,
×
2393
                            table: tableName,
×
2394
                            type: MetadataTableType.GENERATED_COLUMN,
×
2395
                            name: oldColumn.name,
×
2396
                        }),
×
2397
                    )
×
2398
                    // However, we can't copy it back on downgrade. It needs to regenerate.
×
2399
                    downQueries.push(
×
2400
                        this.insertTypeormMetadataSql({
×
2401
                            database: this.driver.database,
×
2402
                            schema,
×
2403
                            table: tableName,
×
2404
                            type: MetadataTableType.GENERATED_COLUMN,
×
2405
                            name: oldColumn.name,
×
2406
                            value: oldColumn.asExpression,
×
2407
                        }),
×
2408
                    )
×
2409
                    downQueries.push(
×
2410
                        new Query(
×
2411
                            `ALTER TABLE ${this.escapePath(
×
2412
                                table,
×
2413
                            )} ADD ${this.buildCreateColumnSql(
×
2414
                                table,
×
2415
                                oldColumn,
×
2416
                            )}`,
×
2417
                        ),
×
2418
                    )
×
2419
                    downQueries.push(
×
2420
                        new Query(
×
2421
                            `ALTER TABLE ${this.escapePath(
×
2422
                                table,
×
2423
                            )} DROP COLUMN "${newColumn.name}"`,
×
2424
                        ),
×
2425
                    )
×
2426
                    // downQueries.push(
×
2427
                    //     this.deleteTypeormMetadataSql({
×
2428
                    //         database: this.driver.database,
×
2429
                    //         schema,
×
2430
                    //         table: tableName,
×
2431
                    //         type: MetadataTableType.GENERATED_COLUMN,
×
2432
                    //         name: newColumn.name,
×
2433
                    //     }),
×
2434
                    // )
×
2435
                }
×
2436
            }
×
2437
        }
×
2438

×
2439
        await this.executeQueries(upQueries, downQueries)
×
2440
        this.replaceCachedTable(table, clonedTable)
×
2441
    }
×
2442

28✔
2443
    /**
28✔
2444
     * Changes a column in the table.
28✔
2445
     * @param tableOrName
28✔
2446
     * @param changedColumns
28✔
2447
     */
28✔
2448
    async changeColumns(
28✔
2449
        tableOrName: Table | string,
×
2450
        changedColumns: { newColumn: TableColumn; oldColumn: TableColumn }[],
×
2451
    ): Promise<void> {
×
2452
        for (const { oldColumn, newColumn } of changedColumns) {
×
2453
            await this.changeColumn(tableOrName, oldColumn, newColumn)
×
2454
        }
×
2455
    }
×
2456

28✔
2457
    /**
28✔
2458
     * Drops column in the table.
28✔
2459
     * @param tableOrName
28✔
2460
     * @param columnOrName
28✔
2461
     * @param ifExists
28✔
2462
     */
28✔
2463
    async dropColumn(
28✔
2464
        tableOrName: Table | string,
×
2465
        columnOrName: TableColumn | string,
×
2466
        ifExists?: boolean,
×
2467
    ): Promise<void> {
×
2468
        const table = InstanceChecker.isTable(tableOrName)
×
2469
            ? tableOrName
×
2470
            : await this.getCachedTable(tableOrName)
×
2471
        const column = InstanceChecker.isTableColumn(columnOrName)
×
2472
            ? columnOrName
×
2473
            : table.findColumnByName(columnOrName)
×
2474
        if (!column) {
×
2475
            if (ifExists) return
×
2476
            throw new TypeORMError(
×
2477
                `Column "${columnOrName}" was not found in table "${table.name}"`,
×
2478
            )
×
2479
        }
×
2480

×
2481
        const clonedTable = table.clone()
×
2482
        const upQueries: Query[] = []
×
2483
        const downQueries: Query[] = []
×
2484

×
2485
        // drop primary key constraint
×
2486
        if (column.isPrimary) {
×
2487
            const pkName = column.primaryKeyConstraintName
×
2488
                ? column.primaryKeyConstraintName
×
2489
                : this.connection.namingStrategy.primaryKeyName(
×
2490
                      clonedTable,
×
2491
                      clonedTable.primaryColumns.map((column) => column.name),
×
2492
                  )
×
2493

×
2494
            const columnNames = clonedTable.primaryColumns
×
2495
                .map((primaryColumn) => `"${primaryColumn.name}"`)
×
2496
                .join(", ")
×
2497

×
2498
            upQueries.push(
×
2499
                new Query(
×
2500
                    `ALTER TABLE ${this.escapePath(
×
2501
                        clonedTable,
×
2502
                    )} DROP CONSTRAINT "${pkName}"`,
×
2503
                ),
×
2504
            )
×
2505
            downQueries.push(
×
2506
                new Query(
×
2507
                    `ALTER TABLE ${this.escapePath(
×
2508
                        clonedTable,
×
2509
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
×
2510
                ),
×
2511
            )
×
2512

×
2513
            // update column in table
×
2514
            const tableColumn = clonedTable.findColumnByName(column.name)
×
2515
            tableColumn!.isPrimary = false
×
2516

×
2517
            // if primary key have multiple columns, we must recreate it without dropped column
×
2518
            if (clonedTable.primaryColumns.length > 0) {
×
2519
                const pkName = clonedTable.primaryColumns[0]
×
2520
                    .primaryKeyConstraintName
×
2521
                    ? clonedTable.primaryColumns[0].primaryKeyConstraintName
×
2522
                    : this.connection.namingStrategy.primaryKeyName(
×
2523
                          clonedTable,
×
2524
                          clonedTable.primaryColumns.map(
×
2525
                              (column) => column.name,
×
2526
                          ),
×
2527
                      )
×
2528

×
2529
                const columnNames = clonedTable.primaryColumns
×
2530
                    .map((primaryColumn) => `"${primaryColumn.name}"`)
×
2531
                    .join(", ")
×
2532

×
2533
                upQueries.push(
×
2534
                    new Query(
×
2535
                        `ALTER TABLE ${this.escapePath(
×
2536
                            clonedTable,
×
2537
                        )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
×
2538
                    ),
×
2539
                )
×
2540
                downQueries.push(
×
2541
                    new Query(
×
2542
                        `ALTER TABLE ${this.escapePath(
×
2543
                            clonedTable,
×
2544
                        )} DROP CONSTRAINT "${pkName}"`,
×
2545
                    ),
×
2546
                )
×
2547
            }
×
2548
        }
×
2549

×
2550
        // drop column index
×
2551
        const columnIndex = clonedTable.indices.find(
×
2552
            (index) =>
×
2553
                index.columnNames.length === 1 &&
×
2554
                index.columnNames[0] === column.name,
×
2555
        )
×
2556
        if (columnIndex) {
×
2557
            clonedTable.indices.splice(
×
2558
                clonedTable.indices.indexOf(columnIndex),
×
2559
                1,
×
2560
            )
×
2561
            upQueries.push(this.dropIndexSql(table, columnIndex))
×
2562
            downQueries.push(this.createIndexSql(table, columnIndex))
×
2563
        }
×
2564

×
2565
        // drop column check
×
2566
        const columnCheck = clonedTable.checks.find(
×
2567
            (check) =>
×
2568
                !!check.columnNames &&
×
2569
                check.columnNames.length === 1 &&
×
2570
                check.columnNames[0] === column.name,
×
2571
        )
×
2572
        if (columnCheck) {
×
2573
            clonedTable.checks.splice(
×
2574
                clonedTable.checks.indexOf(columnCheck),
×
2575
                1,
×
2576
            )
×
2577
            upQueries.push(this.dropCheckConstraintSql(table, columnCheck))
×
2578
            downQueries.push(this.createCheckConstraintSql(table, columnCheck))
×
2579
        }
×
2580

×
2581
        // drop column unique
×
2582
        const columnUnique = clonedTable.uniques.find(
×
2583
            (unique) =>
×
2584
                unique.columnNames.length === 1 &&
×
2585
                unique.columnNames[0] === column.name,
×
2586
        )
×
2587
        if (columnUnique) {
×
2588
            clonedTable.uniques.splice(
×
2589
                clonedTable.uniques.indexOf(columnUnique),
×
2590
                1,
×
2591
            )
×
2592
            upQueries.push(this.dropUniqueConstraintSql(table, columnUnique))
×
2593
            downQueries.push(
×
2594
                this.createUniqueConstraintSql(table, columnUnique),
×
2595
            )
×
2596
        }
×
2597

×
2598
        const ifExistsClause = ifExists ? "IF EXISTS " : ""
×
2599
        upQueries.push(
×
2600
            new Query(
×
2601
                `ALTER TABLE ${this.escapePath(table)} DROP COLUMN ${ifExistsClause}"${
×
2602
                    column.name
×
2603
                }"`,
×
2604
            ),
×
2605
        )
×
2606
        downQueries.push(
×
2607
            new Query(
×
2608
                `ALTER TABLE ${this.escapePath(
×
2609
                    table,
×
2610
                )} ADD ${this.buildCreateColumnSql(table, column)}`,
×
2611
            ),
×
2612
        )
×
2613

×
2614
        // drop enum type
×
2615
        if (column.type === "enum" || column.type === "simple-enum") {
×
2616
            const hasEnum = await this.hasEnumType(table, column)
×
2617
            if (hasEnum) {
×
2618
                const enumType = await this.getUserDefinedTypeName(
×
2619
                    table,
×
2620
                    column,
×
2621
                )
×
2622
                const escapedEnumName = `"${enumType.schema}"."${enumType.name}"`
×
2623
                upQueries.push(
×
2624
                    this.dropEnumTypeSql(table, column, escapedEnumName),
×
2625
                )
×
2626
                downQueries.push(
×
2627
                    this.createEnumTypeSql(table, column, escapedEnumName),
×
2628
                )
×
2629
            }
×
2630
        }
×
2631

×
2632
        if (column.generatedType === "STORED") {
×
2633
            const tableNameWithSchema = (
×
2634
                await this.getTableNameWithSchema(table.name)
×
2635
            ).split(".")
×
2636
            const tableName = tableNameWithSchema[1]
×
2637
            const schema = tableNameWithSchema[0]
×
2638
            const deleteQuery = this.deleteTypeormMetadataSql({
×
2639
                database: this.driver.database,
×
2640
                schema,
×
2641
                table: tableName,
×
2642
                type: MetadataTableType.GENERATED_COLUMN,
×
2643
                name: column.name,
×
2644
            })
×
2645
            const insertQuery = this.insertTypeormMetadataSql({
×
2646
                database: this.driver.database,
×
2647
                schema,
×
2648
                table: tableName,
×
2649
                type: MetadataTableType.GENERATED_COLUMN,
×
2650
                name: column.name,
×
2651
                value: column.asExpression,
×
2652
            })
×
2653

×
2654
            upQueries.push(deleteQuery)
×
2655
            downQueries.push(insertQuery)
×
2656
        }
×
2657

×
2658
        await this.executeQueries(upQueries, downQueries)
×
2659

×
2660
        clonedTable.removeColumn(column)
×
2661
        this.replaceCachedTable(table, clonedTable)
×
2662
    }
×
2663

28✔
2664
    /**
28✔
2665
     * Drops the columns in the table.
28✔
2666
     * @param tableOrName
28✔
2667
     * @param columns
28✔
2668
     * @param ifExists
28✔
2669
     */
28✔
2670
    async dropColumns(
28✔
2671
        tableOrName: Table | string,
×
2672
        columns: TableColumn[] | string[],
×
2673
        ifExists?: boolean,
×
2674
    ): Promise<void> {
×
2675
        for (const column of [...columns]) {
×
2676
            await this.dropColumn(tableOrName, column, ifExists)
×
2677
        }
×
2678
    }
×
2679

28✔
2680
    /**
28✔
2681
     * Creates a new primary key.
28✔
2682
     * @param tableOrName
28✔
2683
     * @param columnNames
28✔
2684
     * @param constraintName
28✔
2685
     */
28✔
2686
    async createPrimaryKey(
28✔
2687
        tableOrName: Table | string,
×
2688
        columnNames: string[],
×
2689
        constraintName?: string,
×
2690
    ): Promise<void> {
×
2691
        const table = InstanceChecker.isTable(tableOrName)
×
2692
            ? tableOrName
×
2693
            : await this.getCachedTable(tableOrName)
×
2694
        const clonedTable = table.clone()
×
2695

×
2696
        const up = this.createPrimaryKeySql(table, columnNames, constraintName)
×
2697

×
2698
        // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names.
×
2699
        clonedTable.columns.forEach((column) => {
×
2700
            if (columnNames.find((columnName) => columnName === column.name))
×
2701
                column.isPrimary = true
×
2702
        })
×
2703
        const down = this.dropPrimaryKeySql(clonedTable)
×
2704

×
2705
        await this.executeQueries(up, down)
×
2706
        this.replaceCachedTable(table, clonedTable)
×
2707
    }
×
2708

28✔
2709
    /**
28✔
2710
     * Updates composite primary keys.
28✔
2711
     * @param tableOrName
28✔
2712
     * @param columns
28✔
2713
     */
28✔
2714
    async updatePrimaryKeys(
28✔
2715
        tableOrName: Table | string,
×
2716
        columns: TableColumn[],
×
2717
    ): Promise<void> {
×
2718
        const table = InstanceChecker.isTable(tableOrName)
×
2719
            ? tableOrName
×
2720
            : await this.getCachedTable(tableOrName)
×
2721
        const clonedTable = table.clone()
×
2722
        const columnNames = columns.map((column) => column.name)
×
2723
        const upQueries: Query[] = []
×
2724
        const downQueries: Query[] = []
×
2725

×
2726
        // if table already have primary columns, we must drop them.
×
2727
        const primaryColumns = clonedTable.primaryColumns
×
2728
        if (primaryColumns.length > 0) {
×
2729
            const pkName = primaryColumns[0].primaryKeyConstraintName
×
2730
                ? primaryColumns[0].primaryKeyConstraintName
×
2731
                : this.connection.namingStrategy.primaryKeyName(
×
2732
                      clonedTable,
×
2733
                      primaryColumns.map((column) => column.name),
×
2734
                  )
×
2735

×
2736
            const columnNamesString = primaryColumns
×
2737
                .map((column) => `"${column.name}"`)
×
2738
                .join(", ")
×
2739

×
2740
            upQueries.push(
×
2741
                new Query(
×
2742
                    `ALTER TABLE ${this.escapePath(
×
2743
                        table,
×
2744
                    )} DROP CONSTRAINT "${pkName}"`,
×
2745
                ),
×
2746
            )
×
2747
            downQueries.push(
×
2748
                new Query(
×
2749
                    `ALTER TABLE ${this.escapePath(
×
2750
                        table,
×
2751
                    )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
×
2752
                ),
×
2753
            )
×
2754
        }
×
2755

×
2756
        // update columns in table.
×
2757
        clonedTable.columns
×
2758
            .filter((column) => columnNames.indexOf(column.name) !== -1)
×
2759
            .forEach((column) => {
×
2760
                column.isPrimary = true
×
2761
            })
×
2762

×
2763
        const pkName = primaryColumns[0]?.primaryKeyConstraintName
×
2764
            ? primaryColumns[0].primaryKeyConstraintName
×
2765
            : this.connection.namingStrategy.primaryKeyName(
×
2766
                  clonedTable,
×
2767
                  columnNames,
×
2768
              )
×
2769

×
2770
        const columnNamesString = columnNames
×
2771
            .map((columnName) => `"${columnName}"`)
×
2772
            .join(", ")
×
2773

×
2774
        upQueries.push(
×
2775
            new Query(
×
2776
                `ALTER TABLE ${this.escapePath(
×
2777
                    table,
×
2778
                )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
×
2779
            ),
×
2780
        )
×
2781
        downQueries.push(
×
2782
            new Query(
×
2783
                `ALTER TABLE ${this.escapePath(
×
2784
                    table,
×
2785
                )} DROP CONSTRAINT "${pkName}"`,
×
2786
            ),
×
2787
        )
×
2788

×
2789
        await this.executeQueries(upQueries, downQueries)
×
2790
        this.replaceCachedTable(table, clonedTable)
×
2791
    }
×
2792

28✔
2793
    /**
28✔
2794
     * Drops a primary key.
28✔
2795
     * @param tableOrName
28✔
2796
     * @param constraintName
28✔
2797
     * @param ifExists
28✔
2798
     */
28✔
2799
    async dropPrimaryKey(
28✔
2800
        tableOrName: Table | string,
×
2801
        constraintName?: string,
×
2802
        ifExists?: boolean,
×
2803
    ): Promise<void> {
×
2804
        const table = InstanceChecker.isTable(tableOrName)
×
2805
            ? tableOrName
×
2806
            : await this.getCachedTable(tableOrName)
×
2807
        const up = this.dropPrimaryKeySql(table, ifExists)
×
2808
        const down = this.createPrimaryKeySql(
×
2809
            table,
×
2810
            table.primaryColumns.map((column) => column.name),
×
2811
            constraintName,
×
2812
        )
×
2813
        await this.executeQueries(up, down)
×
2814
        table.primaryColumns.forEach((column) => {
×
2815
            column.isPrimary = false
×
2816
        })
×
2817
    }
×
2818

28✔
2819
    /**
28✔
2820
     * Creates new unique constraint.
28✔
2821
     * @param tableOrName
28✔
2822
     * @param uniqueConstraint
28✔
2823
     */
28✔
2824
    async createUniqueConstraint(
28✔
2825
        tableOrName: Table | string,
×
2826
        uniqueConstraint: TableUnique,
×
2827
    ): Promise<void> {
×
2828
        const table = InstanceChecker.isTable(tableOrName)
×
2829
            ? tableOrName
×
2830
            : await this.getCachedTable(tableOrName)
×
2831

×
2832
        // new unique constraint may be passed without name. In this case we generate unique name manually.
×
2833
        if (!uniqueConstraint.name)
×
2834
            uniqueConstraint.name =
×
2835
                this.connection.namingStrategy.uniqueConstraintName(
×
2836
                    table,
×
2837
                    uniqueConstraint.columnNames,
×
2838
                )
×
2839

×
2840
        const up = this.createUniqueConstraintSql(table, uniqueConstraint)
×
2841
        const down = this.dropUniqueConstraintSql(table, uniqueConstraint)
×
2842
        await this.executeQueries(up, down)
×
2843
        table.addUniqueConstraint(uniqueConstraint)
×
2844
    }
×
2845

28✔
2846
    /**
28✔
2847
     * Creates new unique constraints.
28✔
2848
     * @param tableOrName
28✔
2849
     * @param uniqueConstraints
28✔
2850
     */
28✔
2851
    async createUniqueConstraints(
28✔
2852
        tableOrName: Table | string,
×
2853
        uniqueConstraints: TableUnique[],
×
2854
    ): Promise<void> {
×
2855
        for (const uniqueConstraint of uniqueConstraints) {
×
2856
            await this.createUniqueConstraint(tableOrName, uniqueConstraint)
×
2857
        }
×
2858
    }
×
2859

28✔
2860
    /**
28✔
2861
     * Drops unique constraint.
28✔
2862
     * @param tableOrName
28✔
2863
     * @param uniqueOrName
28✔
2864
     * @param ifExists
28✔
2865
     */
28✔
2866
    async dropUniqueConstraint(
28✔
2867
        tableOrName: Table | string,
×
2868
        uniqueOrName: TableUnique | string,
×
2869
        ifExists?: boolean,
×
2870
    ): Promise<void> {
×
2871
        const table = InstanceChecker.isTable(tableOrName)
×
2872
            ? tableOrName
×
2873
            : await this.getCachedTable(tableOrName)
×
2874
        const uniqueConstraint = InstanceChecker.isTableUnique(uniqueOrName)
×
2875
            ? uniqueOrName
×
2876
            : table.uniques.find((u) => u.name === uniqueOrName)
×
2877
        if (!uniqueConstraint) {
×
2878
            if (ifExists) return
×
2879
            throw new TypeORMError(
×
2880
                `Supplied unique constraint was not found in table ${table.name}`,
×
2881
            )
×
2882
        }
×
2883

×
2884
        const up = this.dropUniqueConstraintSql(
×
2885
            table,
×
2886
            uniqueConstraint,
×
2887
            ifExists,
×
2888
        )
×
2889
        const down = this.createUniqueConstraintSql(table, uniqueConstraint)
×
2890
        await this.executeQueries(up, down)
×
2891
        table.removeUniqueConstraint(uniqueConstraint)
×
2892
    }
×
2893

28✔
2894
    /**
28✔
2895
     * Drops unique constraints.
28✔
2896
     * @param tableOrName
28✔
2897
     * @param uniqueConstraints
28✔
2898
     * @param ifExists
28✔
2899
     */
28✔
2900
    async dropUniqueConstraints(
28✔
2901
        tableOrName: Table | string,
×
2902
        uniqueConstraints: TableUnique[],
×
2903
        ifExists?: boolean,
×
2904
    ): Promise<void> {
×
2905
        for (const uniqueConstraint of [...uniqueConstraints]) {
×
2906
            await this.dropUniqueConstraint(
×
2907
                tableOrName,
×
2908
                uniqueConstraint,
×
2909
                ifExists,
×
2910
            )
×
2911
        }
×
2912
    }
×
2913

28✔
2914
    /**
28✔
2915
     * Creates new check constraint.
28✔
2916
     * @param tableOrName
28✔
2917
     * @param checkConstraint
28✔
2918
     */
28✔
2919
    async createCheckConstraint(
28✔
2920
        tableOrName: Table | string,
×
2921
        checkConstraint: TableCheck,
×
2922
    ): Promise<void> {
×
2923
        const table = InstanceChecker.isTable(tableOrName)
×
2924
            ? tableOrName
×
2925
            : await this.getCachedTable(tableOrName)
×
2926

×
2927
        // new unique constraint may be passed without name. In this case we generate unique name manually.
×
2928
        if (!checkConstraint.name)
×
2929
            checkConstraint.name =
×
2930
                this.connection.namingStrategy.checkConstraintName(
×
2931
                    table,
×
2932
                    checkConstraint.expression!,
×
2933
                )
×
2934

×
2935
        const up = this.createCheckConstraintSql(table, checkConstraint)
×
2936
        const down = this.dropCheckConstraintSql(table, checkConstraint)
×
2937
        await this.executeQueries(up, down)
×
2938
        table.addCheckConstraint(checkConstraint)
×
2939
    }
×
2940

28✔
2941
    /**
28✔
2942
     * Creates new check constraints.
28✔
2943
     * @param tableOrName
28✔
2944
     * @param checkConstraints
28✔
2945
     */
28✔
2946
    async createCheckConstraints(
28✔
2947
        tableOrName: Table | string,
×
2948
        checkConstraints: TableCheck[],
×
2949
    ): Promise<void> {
×
2950
        const promises = checkConstraints.map((checkConstraint) =>
×
2951
            this.createCheckConstraint(tableOrName, checkConstraint),
×
2952
        )
×
2953
        await Promise.all(promises)
×
2954
    }
×
2955

28✔
2956
    /**
28✔
2957
     * Drops check constraint.
28✔
2958
     * @param tableOrName
28✔
2959
     * @param checkOrName
28✔
2960
     * @param ifExists
28✔
2961
     */
28✔
2962
    async dropCheckConstraint(
28✔
2963
        tableOrName: Table | string,
×
2964
        checkOrName: TableCheck | string,
×
2965
        ifExists?: boolean,
×
2966
    ): Promise<void> {
×
2967
        const table = InstanceChecker.isTable(tableOrName)
×
2968
            ? tableOrName
×
2969
            : await this.getCachedTable(tableOrName)
×
2970
        const checkConstraint = InstanceChecker.isTableCheck(checkOrName)
×
2971
            ? checkOrName
×
2972
            : table.checks.find((c) => c.name === checkOrName)
×
2973
        if (!checkConstraint) {
×
2974
            if (ifExists) return
×
2975
            throw new TypeORMError(
×
2976
                `Supplied check constraint was not found in table ${table.name}`,
×
2977
            )
×
2978
        }
×
2979

×
2980
        const up = this.dropCheckConstraintSql(table, checkConstraint, ifExists)
×
2981
        const down = this.createCheckConstraintSql(table, checkConstraint)
×
2982
        await this.executeQueries(up, down)
×
2983
        table.removeCheckConstraint(checkConstraint)
×
2984
    }
×
2985

28✔
2986
    /**
28✔
2987
     * Drops check constraints.
28✔
2988
     * @param tableOrName
28✔
2989
     * @param checkConstraints
28✔
2990
     * @param ifExists
28✔
2991
     */
28✔
2992
    async dropCheckConstraints(
28✔
2993
        tableOrName: Table | string,
×
2994
        checkConstraints: TableCheck[],
×
2995
        ifExists?: boolean,
×
2996
    ): Promise<void> {
×
2997
        const promises = checkConstraints.map((checkConstraint) =>
×
2998
            this.dropCheckConstraint(tableOrName, checkConstraint, ifExists),
×
2999
        )
×
3000
        await Promise.all(promises)
×
3001
    }
×
3002

28✔
3003
    /**
28✔
3004
     * Creates new exclusion constraint.
28✔
3005
     * @param tableOrName
28✔
3006
     * @param exclusionConstraint
28✔
3007
     */
28✔
3008
    async createExclusionConstraint(
28✔
3009
        tableOrName: Table | string,
×
3010
        exclusionConstraint: TableExclusion,
×
3011
    ): Promise<void> {
×
3012
        const table = InstanceChecker.isTable(tableOrName)
×
3013
            ? tableOrName
×
3014
            : await this.getCachedTable(tableOrName)
×
3015

×
3016
        // new unique constraint may be passed without name. In this case we generate unique name manually.
×
3017
        if (!exclusionConstraint.name)
×
3018
            exclusionConstraint.name =
×
3019
                this.connection.namingStrategy.exclusionConstraintName(
×
3020
                    table,
×
3021
                    exclusionConstraint.expression!,
×
3022
                )
×
3023

×
3024
        const up = this.createExclusionConstraintSql(table, exclusionConstraint)
×
3025
        const down = this.dropExclusionConstraintSql(table, exclusionConstraint)
×
3026
        await this.executeQueries(up, down)
×
3027
        table.addExclusionConstraint(exclusionConstraint)
×
3028
    }
×
3029

28✔
3030
    /**
28✔
3031
     * Creates new exclusion constraints.
28✔
3032
     * @param tableOrName
28✔
3033
     * @param exclusionConstraints
28✔
3034
     */
28✔
3035
    async createExclusionConstraints(
28✔
3036
        tableOrName: Table | string,
×
3037
        exclusionConstraints: TableExclusion[],
×
3038
    ): Promise<void> {
×
3039
        const promises = exclusionConstraints.map((exclusionConstraint) =>
×
3040
            this.createExclusionConstraint(tableOrName, exclusionConstraint),
×
3041
        )
×
3042
        await Promise.all(promises)
×
3043
    }
×
3044

28✔
3045
    /**
28✔
3046
     * Drops exclusion constraint.
28✔
3047
     * @param tableOrName
28✔
3048
     * @param exclusionOrName
28✔
3049
     * @param ifExists
28✔
3050
     */
28✔
3051
    async dropExclusionConstraint(
28✔
3052
        tableOrName: Table | string,
×
3053
        exclusionOrName: TableExclusion | string,
×
3054
        ifExists?: boolean,
×
3055
    ): Promise<void> {
×
3056
        const table = InstanceChecker.isTable(tableOrName)
×
3057
            ? tableOrName
×
3058
            : await this.getCachedTable(tableOrName)
×
3059
        const exclusionConstraint = InstanceChecker.isTableExclusion(
×
3060
            exclusionOrName,
×
3061
        )
×
3062
            ? exclusionOrName
×
3063
            : table.exclusions.find((c) => c.name === exclusionOrName)
×
3064
        if (!exclusionConstraint) {
×
3065
            if (ifExists) return
×
3066
            throw new TypeORMError(
×
3067
                `Supplied exclusion constraint was not found in table ${table.name}`,
×
3068
            )
×
3069
        }
×
3070

×
3071
        const up = this.dropExclusionConstraintSql(
×
3072
            table,
×
3073
            exclusionConstraint,
×
3074
            ifExists,
×
3075
        )
×
3076
        const down = this.createExclusionConstraintSql(
×
3077
            table,
×
3078
            exclusionConstraint,
×
3079
        )
×
3080
        await this.executeQueries(up, down)
×
3081
        table.removeExclusionConstraint(exclusionConstraint)
×
3082
    }
×
3083

28✔
3084
    /**
28✔
3085
     * Drops exclusion constraints.
28✔
3086
     * @param tableOrName
28✔
3087
     * @param exclusionConstraints
28✔
3088
     * @param ifExists
28✔
3089
     */
28✔
3090
    async dropExclusionConstraints(
28✔
3091
        tableOrName: Table | string,
×
3092
        exclusionConstraints: TableExclusion[],
×
3093
        ifExists?: boolean,
×
3094
    ): Promise<void> {
×
3095
        const promises = exclusionConstraints.map((exclusionConstraint) =>
×
3096
            this.dropExclusionConstraint(
×
3097
                tableOrName,
×
3098
                exclusionConstraint,
×
3099
                ifExists,
×
3100
            ),
×
3101
        )
×
3102
        await Promise.all(promises)
×
3103
    }
×
3104

28✔
3105
    /**
28✔
3106
     * Creates a new foreign key.
28✔
3107
     * @param tableOrName
28✔
3108
     * @param foreignKey
28✔
3109
     */
28✔
3110
    async createForeignKey(
28✔
3111
        tableOrName: Table | string,
24✔
3112
        foreignKey: TableForeignKey,
24✔
3113
    ): Promise<void> {
24✔
3114
        const table = InstanceChecker.isTable(tableOrName)
24✔
3115
            ? tableOrName
24✔
3116
            : await this.getCachedTable(tableOrName)
24!
3117

×
3118
        // new FK may be passed without name. In this case we generate FK name manually.
×
3119
        if (!foreignKey.name)
×
3120
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
×
3121
                table,
×
3122
                foreignKey.columnNames,
×
3123
                this.getTablePath(foreignKey),
×
3124
                foreignKey.referencedColumnNames,
×
3125
            )
×
3126

24✔
3127
        const up = this.createForeignKeySql(table, foreignKey)
24✔
3128
        const down = this.dropForeignKeySql(table, foreignKey)
24✔
3129
        await this.executeQueries(up, down)
24✔
3130
        table.addForeignKey(foreignKey)
24✔
3131
    }
24✔
3132

28✔
3133
    /**
28✔
3134
     * Creates a new foreign keys.
28✔
3135
     * @param tableOrName
28✔
3136
     * @param foreignKeys
28✔
3137
     */
28✔
3138
    async createForeignKeys(
28✔
3139
        tableOrName: Table | string,
16✔
3140
        foreignKeys: TableForeignKey[],
16✔
3141
    ): Promise<void> {
16✔
3142
        for (const foreignKey of foreignKeys) {
16✔
3143
            await this.createForeignKey(tableOrName, foreignKey)
24✔
3144
        }
24✔
3145
    }
16✔
3146

28✔
3147
    /**
28✔
3148
     * Drops a foreign key from the table.
28✔
3149
     * @param tableOrName
28✔
3150
     * @param foreignKeyOrName
28✔
3151
     * @param ifExists
28✔
3152
     */
28✔
3153
    async dropForeignKey(
28✔
3154
        tableOrName: Table | string,
×
3155
        foreignKeyOrName: TableForeignKey | string,
×
3156
        ifExists?: boolean,
×
3157
    ): Promise<void> {
×
3158
        const table = InstanceChecker.isTable(tableOrName)
×
3159
            ? tableOrName
×
3160
            : await this.getCachedTable(tableOrName)
×
3161
        const foreignKey = InstanceChecker.isTableForeignKey(foreignKeyOrName)
×
3162
            ? foreignKeyOrName
×
3163
            : table.foreignKeys.find((fk) => fk.name === foreignKeyOrName)
×
3164
        if (!foreignKey) {
×
3165
            if (ifExists) return
×
3166
            throw new TypeORMError(
×
3167
                `Supplied foreign key was not found in table ${table.name}`,
×
3168
            )
×
3169
        }
×
3170

×
3171
        if (!foreignKey.name) {
×
3172
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
×
3173
                table,
×
3174
                foreignKey.columnNames,
×
3175
                this.getTablePath(foreignKey),
×
3176
                foreignKey.referencedColumnNames,
×
3177
            )
×
3178
        }
×
3179

×
3180
        const up = this.dropForeignKeySql(table, foreignKey, ifExists)
×
3181
        const down = this.createForeignKeySql(table, foreignKey)
×
3182
        await this.executeQueries(up, down)
×
3183
        table.removeForeignKey(foreignKey)
×
3184
    }
×
3185

28✔
3186
    /**
28✔
3187
     * Drops a foreign keys from the table.
28✔
3188
     * @param tableOrName
28✔
3189
     * @param foreignKeys
28✔
3190
     * @param ifExists
28✔
3191
     */
28✔
3192
    async dropForeignKeys(
28✔
3193
        tableOrName: Table | string,
×
3194
        foreignKeys: TableForeignKey[],
×
3195
        ifExists?: boolean,
×
3196
    ): Promise<void> {
×
3197
        for (const foreignKey of [...foreignKeys]) {
×
3198
            await this.dropForeignKey(tableOrName, foreignKey, ifExists)
×
3199
        }
×
3200
    }
×
3201

28✔
3202
    /**
28✔
3203
     * Creates a new index.
28✔
3204
     * @param tableOrName
28✔
3205
     * @param index
28✔
3206
     */
28✔
3207
    async createIndex(
28✔
3208
        tableOrName: Table | string,
×
3209
        index: TableIndex,
×
3210
    ): Promise<void> {
×
3211
        const table = InstanceChecker.isTable(tableOrName)
×
3212
            ? tableOrName
×
3213
            : await this.getCachedTable(tableOrName)
×
3214

×
3215
        // new index may be passed without name. In this case we generate index name manually.
×
3216
        if (!index.name) index.name = this.generateIndexName(table, index)
×
3217

×
3218
        const up = this.createIndexSql(table, index)
×
3219
        const down = this.dropIndexSql(table, index)
×
3220
        await this.executeQueries(up, down)
×
3221
        table.addIndex(index)
×
3222
    }
×
3223

28✔
3224
    /**
28✔
3225
     * Create a new view index.
28✔
3226
     * @param viewOrName
28✔
3227
     * @param index
28✔
3228
     */
28✔
3229
    async createViewIndex(
28✔
3230
        viewOrName: View | string,
×
3231
        index: TableIndex,
×
3232
    ): Promise<void> {
×
3233
        const view = InstanceChecker.isView(viewOrName)
×
3234
            ? viewOrName
×
3235
            : await this.getCachedView(viewOrName)
×
3236

×
3237
        // new index may be passed without name. In this case we generate index name manually.
×
3238
        if (!index.name) index.name = this.generateIndexName(view, index)
×
3239

×
3240
        const up = this.createViewIndexSql(view, index)
×
3241
        const down = this.dropIndexSql(view, index)
×
3242
        await this.executeQueries(up, down)
×
3243
        view.addIndex(index)
×
3244
    }
×
3245

28✔
3246
    /**
28✔
3247
     * Creates a new indices
28✔
3248
     * @param tableOrName
28✔
3249
     * @param indices
28✔
3250
     */
28✔
3251
    async createIndices(
28✔
3252
        tableOrName: Table | string,
×
3253
        indices: TableIndex[],
×
3254
    ): Promise<void> {
×
3255
        for (const index of indices) {
×
3256
            await this.createIndex(tableOrName, index)
×
3257
        }
×
3258
    }
×
3259

28✔
3260
    /**
28✔
3261
     * Creates new view indices
28✔
3262
     * @param viewOrName
28✔
3263
     * @param indices
28✔
3264
     */
28✔
3265
    async createViewIndices(
28✔
3266
        viewOrName: View | string,
×
3267
        indices: TableIndex[],
×
3268
    ): Promise<void> {
×
3269
        for (const index of indices) {
×
3270
            await this.createViewIndex(viewOrName, index)
×
3271
        }
×
3272
    }
×
3273

28✔
3274
    /**
28✔
3275
     * Drops an index from the table.
28✔
3276
     * @param tableOrName
28✔
3277
     * @param indexOrName
28✔
3278
     * @param ifExists
28✔
3279
     */
28✔
3280
    async dropIndex(
28✔
3281
        tableOrName: Table | string,
×
3282
        indexOrName: TableIndex | string,
×
3283
        ifExists?: boolean,
×
3284
    ): Promise<void> {
×
3285
        const table = InstanceChecker.isTable(tableOrName)
×
3286
            ? tableOrName
×
3287
            : await this.getCachedTable(tableOrName)
×
3288
        const index = InstanceChecker.isTableIndex(indexOrName)
×
3289
            ? indexOrName
×
3290
            : table.indices.find((i) => i.name === indexOrName)
×
3291
        if (!index) {
×
3292
            if (ifExists) return
×
3293
            throw new TypeORMError(
×
3294
                `Supplied index ${indexOrName} was not found in table ${table.name}`,
×
3295
            )
×
3296
        }
×
3297
        // old index may be passed without name. In this case we generate index name manually.
×
3298
        if (!index.name) index.name = this.generateIndexName(table, index)
×
3299

×
3300
        const up = this.dropIndexSql(table, index, ifExists)
×
3301
        const down = this.createIndexSql(table, index)
×
3302
        await this.executeQueries(up, down)
×
3303
        table.removeIndex(index)
×
3304
    }
×
3305

28✔
3306
    /**
28✔
3307
     * Drops an index from a view.
28✔
3308
     * @param viewOrName
28✔
3309
     * @param indexOrName
28✔
3310
     */
28✔
3311
    async dropViewIndex(
28✔
3312
        viewOrName: View | string,
×
3313
        indexOrName: TableIndex | string,
×
3314
    ): Promise<void> {
×
3315
        const view = InstanceChecker.isView(viewOrName)
×
3316
            ? viewOrName
×
3317
            : await this.getCachedView(viewOrName)
×
3318
        const index = InstanceChecker.isTableIndex(indexOrName)
×
3319
            ? indexOrName
×
3320
            : view.indices.find((i) => i.name === indexOrName)
×
3321
        if (!index)
×
3322
            throw new TypeORMError(
×
3323
                `Supplied index ${indexOrName} was not found in view ${view.name}`,
×
3324
            )
×
3325
        // old index may be passed without name. In this case we generate index name manually.
×
3326
        if (!index.name) index.name = this.generateIndexName(view, index)
×
3327

×
3328
        const up = this.dropIndexSql(view, index)
×
3329
        const down = this.createViewIndexSql(view, index)
×
3330
        await this.executeQueries(up, down)
×
3331
        view.removeIndex(index)
×
3332
    }
×
3333

28✔
3334
    /**
28✔
3335
     * Drops an indices from the table.
28✔
3336
     * @param tableOrName
28✔
3337
     * @param indices
28✔
3338
     * @param ifExists
28✔
3339
     */
28✔
3340
    async dropIndices(
28✔
3341
        tableOrName: Table | string,
×
3342
        indices: TableIndex[],
×
3343
        ifExists?: boolean,
×
3344
    ): Promise<void> {
×
3345
        for (const index of [...indices]) {
×
3346
            await this.dropIndex(tableOrName, index, ifExists)
×
3347
        }
×
3348
    }
×
3349

28✔
3350
    /**
28✔
3351
     * Clears all table contents.
28✔
3352
     * Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
28✔
3353
     * @param tableName
28✔
3354
     * @param options
28✔
3355
     * @param options.cascade
28✔
3356
     */
28✔
3357
    async clearTable(
28✔
3358
        tableName: string,
×
3359
        options?: { cascade?: boolean },
×
3360
    ): Promise<void> {
×
3361
        const cascade = options?.cascade ? " CASCADE" : ""
×
3362
        await this.query(
×
3363
            `TRUNCATE TABLE ${this.escapePath(tableName)}${cascade}`,
×
3364
        )
×
3365
    }
×
3366

28✔
3367
    /**
28✔
3368
     * Removes all tables from the currently connected database.
28✔
3369
     */
28✔
3370
    async clearDatabase(): Promise<void> {
28✔
3371
        const schemas: string[] = []
8✔
3372
        this.connection.entityMetadatas
8✔
3373
            .filter((metadata) => metadata.schema)
8✔
3374
            .forEach((metadata) => {
8✔
3375
                const isSchemaExist = !!schemas.find(
×
3376
                    (schema) => schema === metadata.schema,
×
3377
                )
×
3378
                if (!isSchemaExist) schemas.push(metadata.schema!)
×
3379
            })
8✔
3380
        if (this.driver.options.schema) {
8!
3381
            schemas.push(this.driver.options.schema)
×
3382
        } else {
8✔
3383
            const [{ currentSchema }] = await this.query(
8✔
3384
                `SELECT current_schema() AS "currentSchema"`,
8✔
3385
            )
8✔
3386
            schemas.push(currentSchema)
8✔
3387
        }
8✔
3388

8✔
3389
        const isAnotherTransactionActive = this.isTransactionActive
8✔
3390
        if (!isAnotherTransactionActive) await this.startTransaction()
8✔
3391
        try {
8✔
3392
            // drop views
8✔
3393
            const views: ObjectLiteral[] = await this.query(
8✔
3394
                `SELECT quote_ident(schemaname) || '.' || quote_ident(viewname) as "name" ` +
8✔
3395
                    `FROM "pg_views" WHERE "schemaname" = ANY($1) AND "viewname" NOT IN ('geography_columns', 'geometry_columns', 'raster_columns', 'raster_overviews')`,
8✔
3396
                [schemas],
8✔
3397
            )
8✔
3398
            if (views.length > 0) {
8!
3399
                await this.query(
×
3400
                    `DROP VIEW IF EXISTS ${views.map(({ name }) => name).join(", ")} CASCADE`,
×
3401
                )
×
3402
            }
×
3403

8✔
3404
            // drop materialized views
8✔
3405
            // Note: materialized views introduced in Postgres 9.3
8✔
3406
            if (DriverUtils.isReleaseVersionOrGreater(this.driver, "9.3")) {
8✔
3407
                const matViews: ObjectLiteral[] = await this.query(
8✔
3408
                    `SELECT quote_ident(schemaname) || '.' || quote_ident(matviewname) as "name" ` +
8✔
3409
                        `FROM "pg_matviews" WHERE "schemaname" = ANY($1)`,
8✔
3410
                    [schemas],
8✔
3411
                )
8✔
3412
                if (matViews.length > 0) {
8!
3413
                    await this.query(
×
3414
                        `DROP MATERIALIZED VIEW IF EXISTS ${matViews.map(({ name }) => name).join(", ")} CASCADE`,
×
3415
                    )
×
3416
                }
×
3417
            }
8✔
3418

8✔
3419
            // ignore spatial_ref_sys; it's a special table supporting PostGIS
8✔
3420
            // TODO generalize this as this.driver.ignoreTables
8✔
3421

8✔
3422
            // drop tables
8✔
3423
            const tables: ObjectLiteral[] = await this.query(
8✔
3424
                `SELECT quote_ident(schemaname) || '.' || quote_ident(tablename) as "name" ` +
8✔
3425
                    `FROM "pg_tables" WHERE "schemaname" = ANY($1) AND "tablename" NOT IN ('spatial_ref_sys')`,
8✔
3426
                [schemas],
8✔
3427
            )
8✔
3428
            if (tables.length > 0) {
8✔
3429
                await this.query(
4✔
3430
                    `DROP TABLE IF EXISTS ${tables.map(({ name }) => name).join(", ")} CASCADE`,
4✔
3431
                )
4✔
3432
            }
4✔
3433

8✔
3434
            // drop enum types
8✔
3435
            await this.dropEnumTypes(schemas)
8✔
3436

8✔
3437
            if (!isAnotherTransactionActive) {
8✔
3438
                await this.commitTransaction()
8✔
3439
            }
8✔
3440
        } catch (error) {
8!
3441
            try {
×
3442
                // we throw original error even if rollback thrown an error
×
3443
                if (!isAnotherTransactionActive) {
×
3444
                    await this.rollbackTransaction()
×
3445
                }
×
3446
            } catch {
×
3447
                // no-op
×
3448
            }
×
3449
            throw error
×
3450
        }
×
3451
    }
8✔
3452

28✔
3453
    // -------------------------------------------------------------------------
28✔
3454
    // Protected Methods
28✔
3455
    // -------------------------------------------------------------------------
28✔
3456

28✔
3457
    protected async loadViews(viewNames?: string[]): Promise<View[]> {
28✔
3458
        const hasTable = await this.hasTable(this.getTypeormMetadataTableName())
8✔
3459

8✔
3460
        if (!hasTable) return []
8✔
3461

×
3462
        if (!viewNames) {
×
3463
            viewNames = []
×
3464
        }
×
3465

×
3466
        const currentDatabase = await this.getCurrentDatabase()
×
3467
        const currentSchema = await this.getCurrentSchema()
×
3468
        const viewsCondition =
×
3469
            viewNames.length === 0
×
3470
                ? "1=1"
×
3471
                : viewNames
8!
3472
                      .map((tableName) => this.driver.parseTableName(tableName))
×
3473
                      .map(({ schema, tableName }) => {
×
3474
                          if (!schema) {
×
3475
                              schema =
×
3476
                                  this.driver.options.schema || currentSchema
×
3477
                          }
×
3478

×
3479
                          return `("t"."schema" = '${schema}' AND "t"."name" = '${tableName}')`
×
3480
                      })
×
3481
                      .join(" OR ")
×
3482

8✔
3483
        const constraintsCondition =
8✔
3484
            viewNames.length === 0
8✔
3485
                ? "1=1"
8!
3486
                : viewNames
8!
3487
                      .map((tableName) => this.driver.parseTableName(tableName))
×
3488
                      .map(({ schema, tableName }) => {
×
3489
                          if (!schema) {
×
3490
                              schema =
×
3491
                                  this.driver.options.schema || currentSchema
×
3492
                          }
×
3493

×
3494
                          return `("ns"."nspname" = '${schema}' AND "t"."relname" = '${tableName}')`
×
3495
                      })
×
3496
                      .join(" OR ")
×
3497

8✔
3498
        const indicesSql =
8✔
3499
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", ` +
8✔
3500
            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", ` +
8✔
3501
            `"types"."typname" AS "type_name", "am"."amname" AS "index_type" ` +
8✔
3502
            `FROM "pg_class" "t" ` +
8✔
3503
            `INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" ` +
8✔
3504
            `INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid"  AND "a"."attnum" = ANY ("ix"."indkey") ` +
8✔
3505
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" ` +
8✔
3506
            `INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" ` +
8✔
3507
            `INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" ` +
8✔
3508
            `INNER JOIN "pg_am" "am" ON "i"."relam" = "am"."oid" ` +
8✔
3509
            `LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" ` +
8✔
3510
            `WHERE "t"."relkind" IN ('m') AND "cnst"."contype" IS NULL AND (${constraintsCondition})`
8✔
3511

8✔
3512
        const query =
8✔
3513
            `SELECT "t".* FROM ${this.escapePath(
8✔
3514
                this.getTypeormMetadataTableName(),
8✔
3515
            )} "t" ` +
8✔
3516
            `INNER JOIN "pg_catalog"."pg_class" "c" ON "c"."relname" = "t"."name" ` +
8✔
3517
            `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "c"."relnamespace" AND "n"."nspname" = "t"."schema" ` +
8✔
3518
            `WHERE "t"."type" IN ('${MetadataTableType.VIEW}', '${
8✔
3519
                MetadataTableType.MATERIALIZED_VIEW
8✔
3520
            }') ${viewsCondition ? `AND (${viewsCondition})` : ""}`
8!
3521

8✔
3522
        const dbViews = await this.query(query)
8✔
3523
        const dbIndices: ObjectLiteral[] = await this.query(indicesSql)
×
3524
        return dbViews.map((dbView: any) => {
×
3525
            // find index constraints of table, group them by constraint name and build TableIndex.
×
3526
            const tableIndexConstraints = OrmUtils.uniq(
×
3527
                dbIndices.filter((dbIndex) => {
×
3528
                    return (
×
3529
                        dbIndex["table_name"] === dbView["name"] &&
×
3530
                        dbIndex["table_schema"] === dbView["schema"]
×
3531
                    )
×
3532
                }),
×
3533
                (dbIndex) => dbIndex["constraint_name"],
×
3534
            )
×
3535
            const view = new View()
×
3536
            const schema =
×
3537
                dbView["schema"] === currentSchema &&
×
3538
                !this.driver.options.schema
×
3539
                    ? undefined
×
3540
                    : dbView["schema"]
×
3541
            view.database = currentDatabase
×
3542
            view.schema = dbView["schema"]
×
3543
            view.name = this.driver.buildTableName(dbView["name"], schema)
×
3544
            view.expression = dbView["value"]
×
3545
            view.materialized =
×
3546
                dbView["type"] === MetadataTableType.MATERIALIZED_VIEW
×
3547
            view.indices = tableIndexConstraints.map((constraint) => {
×
3548
                const indices = dbIndices.filter((index) => {
×
3549
                    return (
×
3550
                        index["table_schema"] === constraint["table_schema"] &&
×
3551
                        index["table_name"] === constraint["table_name"] &&
×
3552
                        index["constraint_name"] ===
×
3553
                            constraint["constraint_name"]
×
3554
                    )
×
3555
                })
×
3556

×
3557
                return new TableIndex(<TableIndexOptions>{
×
3558
                    view: view,
×
3559
                    name: constraint["constraint_name"],
×
3560
                    columnNames: indices.map((i) => i["column_name"]),
×
3561
                    isUnique: constraint["is_unique"] === "TRUE",
×
3562
                    where: constraint["condition"],
×
3563
                    isSpatial: constraint["index_type"] === "gist",
×
3564
                    isFulltext: false,
×
3565
                    type: constraint["index_type"],
×
3566
                })
×
3567
            })
×
3568
            return view
×
3569
        })
×
3570
    }
×
3571

28✔
3572
    /**
28✔
3573
     * Loads all tables (with given names) from the database and creates a Table from them.
28✔
3574
     * @param tableNames
28✔
3575
     */
28✔
3576
    protected async loadTables(tableNames?: string[]): Promise<Table[]> {
28✔
3577
        // if no tables given then no need to proceed
8✔
3578
        if (tableNames && tableNames.length === 0) {
8!
3579
            return []
×
3580
        }
×
3581

8✔
3582
        const currentSchema = await this.getCurrentSchema()
8✔
3583
        const currentDatabase = await this.getCurrentDatabase()
8✔
3584

8✔
3585
        const dbTables: {
8✔
3586
            table_schema: string
8✔
3587
            table_name: string
8✔
3588
            table_comment: string
8✔
3589
        }[] = []
8✔
3590

8✔
3591
        if (!tableNames) {
8!
3592
            const tablesSql = `SELECT "table_schema", "table_name", obj_description((quote_ident("table_schema") || '.' || quote_ident("table_name"))::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables"`
×
3593
            dbTables.push(...(await this.query(tablesSql)))
×
3594
        } else {
8✔
3595
            const tablesCondition = tableNames
8✔
3596
                .map((tableName) => this.driver.parseTableName(tableName))
8✔
3597
                .map(({ schema, tableName }) => {
8✔
3598
                    return `("table_schema" = '${
24✔
3599
                        schema || currentSchema
24!
3600
                    }' AND "table_name" = '${tableName}')`
24✔
3601
                })
8✔
3602
                .join(" OR ")
8✔
3603

8✔
3604
            const tablesSql =
8✔
3605
                `SELECT "table_schema", "table_name", obj_description((quote_ident("table_schema") || '.' || quote_ident("table_name"))::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE ` +
8✔
3606
                tablesCondition
8✔
3607
            dbTables.push(...(await this.query(tablesSql)))
8✔
3608
        }
8✔
3609

8✔
3610
        // if tables were not found in the db, no need to proceed
8✔
3611
        if (dbTables.length === 0) {
8✔
3612
            return []
8✔
3613
        }
8✔
3614

×
3615
        /**
×
3616
         * Uses standard SQL information_schema.columns table and postgres-specific
×
3617
         * pg_catalog.pg_attribute table to get column information.
×
3618
         * @see https://stackoverflow.com/a/19541865
×
3619
         */
×
3620
        const columnsCondition = dbTables
×
3621
            .map(({ table_schema, table_name }) => {
×
3622
                return `("table_schema" = '${table_schema}' AND "table_name" = '${table_name}')`
×
3623
            })
×
3624
            .join(" OR ")
×
3625
        const columnsSql =
×
3626
            `SELECT columns.*, pg_catalog.col_description((quote_ident(table_catalog) || '.' || quote_ident(table_schema) || '.' || quote_ident(table_name))::regclass::oid, ordinal_position) AS description, ` +
×
3627
            `('"' || "udt_schema" || '"."' || "udt_name" || '"')::"regtype"::text AS "regtype", pg_catalog.format_type("col_attr"."atttypid", "col_attr"."atttypmod") AS "format_type" ` +
×
3628
            `FROM "information_schema"."columns" ` +
×
3629
            `LEFT JOIN "pg_catalog"."pg_attribute" AS "col_attr" ON "col_attr"."attname" = "columns"."column_name" ` +
×
3630
            `AND "col_attr"."attrelid" = ( ` +
×
3631
            `SELECT "cls"."oid" FROM "pg_catalog"."pg_class" AS "cls" ` +
×
3632
            `LEFT JOIN "pg_catalog"."pg_namespace" AS "ns" ON "ns"."oid" = "cls"."relnamespace" ` +
×
3633
            `WHERE "cls"."relname" = "columns"."table_name" ` +
×
3634
            `AND "ns"."nspname" = "columns"."table_schema" ` +
×
3635
            `) ` +
×
3636
            `WHERE ` +
×
3637
            columnsCondition
×
3638

×
3639
        const constraintsCondition = dbTables
×
3640
            .map(({ table_schema, table_name }) => {
×
3641
                return `("ns"."nspname" = '${table_schema}' AND "t"."relname" = '${table_name}')`
×
3642
            })
×
3643
            .join(" OR ")
×
3644

×
3645
        const constraintsSql =
×
3646
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "cnst"."conname" AS "constraint_name", ` +
×
3647
            `pg_get_constraintdef("cnst"."oid") AS "expression", ` +
×
3648
            `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" ` +
×
3649
            `FROM "pg_constraint" "cnst" ` +
×
3650
            `INNER JOIN "pg_class" "t" ON "t"."oid" = "cnst"."conrelid" ` +
×
3651
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "cnst"."connamespace" ` +
×
3652
            `LEFT JOIN "pg_attribute" "a" ON "a"."attrelid" = "cnst"."conrelid" AND "a"."attnum" = ANY ("cnst"."conkey") ` +
×
3653
            `WHERE "t"."relkind" IN ('r', 'p') AND (${constraintsCondition})`
×
3654

×
3655
        const indicesSql =
×
3656
            `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", ` +
×
3657
            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", ` +
×
3658
            `"types"."typname" AS "type_name", "am"."amname" AS "index_type" ` +
×
3659
            `FROM "pg_class" "t" ` +
×
3660
            `INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" ` +
×
3661
            `INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid"  AND "a"."attnum" = ANY ("ix"."indkey") ` +
×
3662
            `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" ` +
×
3663
            `INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" ` +
×
3664
            `INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" ` +
×
3665
            `INNER JOIN "pg_am" "am" ON "i"."relam" = "am"."oid" ` +
×
3666
            `LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" ` +
×
3667
            `WHERE "t"."relkind" IN ('r', 'p') AND "cnst"."contype" IS NULL AND (${constraintsCondition})`
×
3668

×
3669
        const foreignKeysCondition = dbTables
×
3670
            .map(({ table_schema, table_name }) => {
×
3671
                return `("ns"."nspname" = '${table_schema}' AND "cl"."relname" = '${table_name}')`
×
3672
            })
×
3673
            .join(" OR ")
×
3674

×
3675
        const hasRelispartitionColumn =
×
3676
            await this.hasSupportForPartitionedTables()
×
3677
        const isPartitionCondition = hasRelispartitionColumn
×
3678
            ? ` AND "cl"."relispartition" = 'f'`
×
3679
            : ""
8!
3680

8✔
3681
        const foreignKeysSql =
8✔
3682
            `SELECT "con"."conname" AS "constraint_name", "con"."nspname" AS "table_schema", "con"."relname" AS "table_name", "att2"."attname" AS "column_name", ` +
8✔
3683
            `"ns"."nspname" AS "referenced_table_schema", "cl"."relname" AS "referenced_table_name", "att"."attname" AS "referenced_column_name", "con"."confdeltype" AS "on_delete", ` +
8✔
3684
            `"con"."confupdtype" AS "on_update", "con"."condeferrable" AS "deferrable", "con"."condeferred" AS "deferred" ` +
8✔
3685
            `FROM ( ` +
8✔
3686
            `SELECT UNNEST ("con1"."conkey") AS "parent", UNNEST ("con1"."confkey") AS "child", "con1"."confrelid", "con1"."conrelid", "con1"."conname", "con1"."contype", "ns"."nspname", ` +
8✔
3687
            `"cl"."relname", "con1"."condeferrable", ` +
8✔
3688
            `CASE WHEN "con1"."condeferred" THEN 'INITIALLY DEFERRED' ELSE 'INITIALLY IMMEDIATE' END as condeferred, ` +
8✔
3689
            `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", ` +
8✔
3690
            `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" ` +
8✔
3691
            `FROM "pg_class" "cl" ` +
8✔
3692
            `INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" ` +
8✔
3693
            `INNER JOIN "pg_constraint" "con1" ON "con1"."conrelid" = "cl"."oid" ` +
8✔
3694
            `WHERE "con1"."contype" = 'f' AND (${foreignKeysCondition}) ` +
8✔
3695
            `) "con" ` +
8✔
3696
            `INNER JOIN "pg_attribute" "att" ON "att"."attrelid" = "con"."confrelid" AND "att"."attnum" = "con"."child" ` +
8✔
3697
            `INNER JOIN "pg_class" "cl" ON "cl"."oid" = "con"."confrelid" ${isPartitionCondition}` +
8✔
3698
            `INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" ` +
8✔
3699
            `INNER JOIN "pg_attribute" "att2" ON "att2"."attrelid" = "con"."conrelid" AND "att2"."attnum" = "con"."parent"`
8✔
3700

8✔
3701
        const dbColumns: ObjectLiteral[] = await this.query(columnsSql)
8✔
3702
        const dbConstraints: ObjectLiteral[] = await this.query(constraintsSql)
×
3703
        const dbIndices: ObjectLiteral[] = await this.query(indicesSql)
×
3704
        const dbForeignKeys: ObjectLiteral[] = await this.query(foreignKeysSql)
×
3705

×
3706
        // create tables for loaded tables
×
3707
        return Promise.all(
×
3708
            dbTables.map(async (dbTable) => {
×
3709
                const table = new Table()
×
3710

×
3711
                const getSchemaFromKey = (dbObject: any, key: string) => {
×
3712
                    return dbObject[key] === currentSchema &&
×
3713
                        (!this.driver.options.schema ||
×
3714
                            this.driver.options.schema === currentSchema)
×
3715
                        ? undefined
×
3716
                        : dbObject[key]
×
3717
                }
×
3718
                // We do not need to join schema name, when database is by default.
×
3719
                const schema = getSchemaFromKey(dbTable, "table_schema")
×
3720
                table.database = currentDatabase
×
3721
                table.schema = dbTable["table_schema"]
×
3722
                table.comment = dbTable["table_comment"]
×
3723
                table.name = this.driver.buildTableName(
×
3724
                    dbTable["table_name"],
×
3725
                    schema,
×
3726
                )
×
3727

×
3728
                // create columns from the loaded columns
×
3729
                table.columns = await Promise.all(
×
3730
                    dbColumns
×
3731
                        .filter(
×
3732
                            (dbColumn) =>
×
3733
                                dbColumn["table_name"] ===
×
3734
                                    dbTable["table_name"] &&
×
3735
                                dbColumn["table_schema"] ===
×
3736
                                    dbTable["table_schema"],
×
3737
                        )
×
3738
                        .map(async (dbColumn) => {
×
3739
                            const columnConstraints = dbConstraints.filter(
×
3740
                                (dbConstraint) => {
×
3741
                                    return (
×
3742
                                        dbConstraint["table_name"] ===
×
3743
                                            dbColumn["table_name"] &&
×
3744
                                        dbConstraint["table_schema"] ===
×
3745
                                            dbColumn["table_schema"] &&
×
3746
                                        dbConstraint["column_name"] ===
×
3747
                                            dbColumn["column_name"]
×
3748
                                    )
×
3749
                                },
×
3750
                            )
×
3751

×
3752
                            const tableColumn = new TableColumn()
×
3753
                            tableColumn.name = dbColumn["column_name"]
×
3754
                            tableColumn.type = dbColumn["regtype"].toLowerCase()
×
3755

×
3756
                            if (
×
3757
                                tableColumn.type === "vector" ||
×
3758
                                tableColumn.type === "halfvec"
×
3759
                            ) {
×
3760
                                const lengthMatch = dbColumn[
×
3761
                                    "format_type"
×
3762
                                ].match(/^(?:vector|halfvec)\((\d+)\)$/)
×
3763
                                if (lengthMatch && lengthMatch[1]) {
×
3764
                                    tableColumn.length = lengthMatch[1]
×
3765
                                }
×
3766
                            }
×
3767

×
3768
                            if (
×
3769
                                tableColumn.type === "numeric" ||
×
3770
                                tableColumn.type === "numeric[]" ||
×
3771
                                tableColumn.type === "decimal" ||
×
3772
                                tableColumn.type === "float"
×
3773
                            ) {
×
3774
                                let numericPrecision =
×
3775
                                    dbColumn["numeric_precision"]
×
3776
                                let numericScale = dbColumn["numeric_scale"]
×
3777
                                if (dbColumn["data_type"] === "ARRAY") {
×
3778
                                    const numericSize = dbColumn[
×
3779
                                        "format_type"
×
3780
                                    ].match(
×
3781
                                        /^numeric\(([0-9]+),([0-9]+)\)\[\]$/,
×
3782
                                    )
×
3783
                                    if (numericSize) {
×
3784
                                        numericPrecision = +numericSize[1]
×
3785
                                        numericScale = +numericSize[2]
×
3786
                                    }
×
3787
                                }
×
3788
                                // If one of these properties was set, and another was not, Postgres sets '0' in to unspecified property
×
3789
                                // we set 'undefined' in to unspecified property to avoid changing column on sync
×
3790
                                if (
×
3791
                                    numericPrecision !== null &&
×
3792
                                    !this.isDefaultColumnPrecision(
×
3793
                                        table,
×
3794
                                        tableColumn,
×
3795
                                        numericPrecision,
×
3796
                                    )
×
3797
                                ) {
×
3798
                                    tableColumn.precision = numericPrecision
×
3799
                                } else if (
×
3800
                                    numericScale !== null &&
×
3801
                                    !this.isDefaultColumnScale(
×
3802
                                        table,
×
3803
                                        tableColumn,
×
3804
                                        numericScale,
×
3805
                                    )
×
3806
                                ) {
×
3807
                                    tableColumn.precision = undefined
×
3808
                                }
×
3809
                                if (
×
3810
                                    numericScale !== null &&
×
3811
                                    !this.isDefaultColumnScale(
×
3812
                                        table,
×
3813
                                        tableColumn,
×
3814
                                        numericScale,
×
3815
                                    )
×
3816
                                ) {
×
3817
                                    tableColumn.scale = numericScale
×
3818
                                } else if (
×
3819
                                    numericPrecision !== null &&
×
3820
                                    !this.isDefaultColumnPrecision(
×
3821
                                        table,
×
3822
                                        tableColumn,
×
3823
                                        numericPrecision,
×
3824
                                    )
×
3825
                                ) {
×
3826
                                    tableColumn.scale = undefined
×
3827
                                }
×
3828
                            }
×
3829

×
3830
                            if (
×
3831
                                tableColumn.type === "interval" ||
×
3832
                                tableColumn.type === "time without time zone" ||
×
3833
                                tableColumn.type === "time with time zone" ||
×
3834
                                tableColumn.type ===
×
3835
                                    "timestamp without time zone" ||
×
3836
                                tableColumn.type === "timestamp with time zone"
×
3837
                            ) {
×
3838
                                tableColumn.precision =
×
3839
                                    !this.isDefaultColumnPrecision(
×
3840
                                        table,
×
3841
                                        tableColumn,
×
3842
                                        dbColumn["datetime_precision"],
×
3843
                                    )
×
3844
                                        ? dbColumn["datetime_precision"]
×
3845
                                        : undefined
×
3846
                            }
×
3847

×
3848
                            // check if column has user-defined data type.
×
3849
                            // NOTE: if ENUM type defined with "array:true" it comes with ARRAY type instead of USER-DEFINED
×
3850
                            if (
×
3851
                                dbColumn["data_type"] === "USER-DEFINED" ||
×
3852
                                dbColumn["data_type"] === "ARRAY"
×
3853
                            ) {
×
3854
                                const { name } =
×
3855
                                    await this.getUserDefinedTypeName(
×
3856
                                        table,
×
3857
                                        tableColumn,
×
3858
                                    )
×
3859

×
3860
                                // check if `enumName` is specified by user
×
3861
                                const builtEnumName = this.buildEnumName(
×
3862
                                    table,
×
3863
                                    tableColumn,
×
3864
                                    false,
×
3865
                                    true,
×
3866
                                )
×
3867
                                const enumName =
×
3868
                                    builtEnumName !== name ? name : undefined
×
3869

×
3870
                                // check if type is ENUM
×
3871
                                const sql =
×
3872
                                    `SELECT "e"."enumlabel" AS "value" FROM "pg_enum" "e" ` +
×
3873
                                    `INNER JOIN "pg_type" "t" ON "t"."oid" = "e"."enumtypid" ` +
×
3874
                                    `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
×
3875
                                    `WHERE "n"."nspname" = '${
×
3876
                                        dbTable["table_schema"]
×
3877
                                    }' AND "t"."typname" = '${
×
3878
                                        enumName || name
×
3879
                                    }'`
×
3880
                                const results: ObjectLiteral[] =
×
3881
                                    await this.query(sql)
×
3882

×
3883
                                if (results.length) {
×
3884
                                    tableColumn.type = "enum"
×
3885
                                    tableColumn.enum = results.map(
×
3886
                                        (result) => result["value"],
×
3887
                                    )
×
3888
                                    tableColumn.enumName = enumName
×
3889
                                }
×
3890

×
3891
                                if (dbColumn["data_type"] === "ARRAY") {
×
3892
                                    tableColumn.isArray = true
×
3893
                                    const type = tableColumn.type.replace(
×
3894
                                        "[]",
×
3895
                                        "",
×
3896
                                    )
×
3897
                                    tableColumn.type =
×
3898
                                        this.connection.driver.normalizeType({
×
3899
                                            type: type,
×
3900
                                        })
×
3901
                                }
×
3902
                            }
×
3903

×
3904
                            if (
×
3905
                                tableColumn.type === "geometry" ||
×
3906
                                tableColumn.type === "geography"
×
3907
                            ) {
×
3908
                                const sql =
×
3909
                                    `SELECT * FROM (` +
×
3910
                                    `SELECT "f_table_schema" "table_schema", "f_table_name" "table_name", ` +
×
3911
                                    `"f_${tableColumn.type}_column" "column_name", "srid", "type" ` +
×
3912
                                    `FROM "${tableColumn.type}_columns"` +
×
3913
                                    `) AS _ ` +
×
3914
                                    `WHERE "column_name" = '${dbColumn["column_name"]}' AND ` +
×
3915
                                    `"table_schema" = '${dbColumn["table_schema"]}' AND ` +
×
3916
                                    `"table_name" = '${dbColumn["table_name"]}'`
×
3917

×
3918
                                const results: ObjectLiteral[] =
×
3919
                                    await this.query(sql)
×
3920

×
3921
                                if (results.length > 0) {
×
3922
                                    tableColumn.spatialFeatureType =
×
3923
                                        results[0].type
×
3924
                                    tableColumn.srid = results[0].srid
×
3925
                                }
×
3926
                            }
×
3927

×
3928
                            // check only columns that have length property
×
3929
                            if (
×
3930
                                this.driver.withLengthColumnTypes.indexOf(
×
3931
                                    tableColumn.type as ColumnType,
×
3932
                                ) !== -1
×
3933
                            ) {
×
3934
                                let length
×
3935
                                if (tableColumn.isArray) {
×
3936
                                    const match = /\((\d+)\)/.exec(
×
3937
                                        dbColumn["format_type"],
×
3938
                                    )
×
3939
                                    length = match ? match[1] : undefined
×
3940
                                } else if (
×
3941
                                    dbColumn["character_maximum_length"]
×
3942
                                ) {
×
3943
                                    length =
×
3944
                                        dbColumn[
×
3945
                                            "character_maximum_length"
×
3946
                                        ].toString()
×
3947
                                }
×
3948
                                if (length) {
×
3949
                                    tableColumn.length =
×
3950
                                        !this.isDefaultColumnLength(
×
3951
                                            table,
×
3952
                                            tableColumn,
×
3953
                                            length,
×
3954
                                        )
×
3955
                                            ? length
×
3956
                                            : ""
×
3957
                                }
×
3958
                            }
×
3959
                            tableColumn.isNullable =
×
3960
                                dbColumn["is_nullable"] === "YES"
×
3961

×
3962
                            const primaryConstraint = columnConstraints.find(
×
3963
                                (constraint) =>
×
3964
                                    constraint["constraint_type"] === "PRIMARY",
×
3965
                            )
×
3966
                            if (primaryConstraint) {
×
3967
                                tableColumn.isPrimary = true
×
3968
                                // find another columns involved in primary key constraint
×
3969
                                const anotherPrimaryConstraints =
×
3970
                                    dbConstraints.filter(
×
3971
                                        (constraint) =>
×
3972
                                            constraint["table_name"] ===
×
3973
                                                dbColumn["table_name"] &&
×
3974
                                            constraint["table_schema"] ===
×
3975
                                                dbColumn["table_schema"] &&
×
3976
                                            constraint["column_name"] !==
×
3977
                                                dbColumn["column_name"] &&
×
3978
                                            constraint["constraint_type"] ===
×
3979
                                                "PRIMARY",
×
3980
                                    )
×
3981

×
3982
                                // collect all column names
×
3983
                                const columnNames =
×
3984
                                    anotherPrimaryConstraints.map(
×
3985
                                        (constraint) =>
×
3986
                                            constraint["column_name"],
×
3987
                                    )
×
3988
                                columnNames.push(dbColumn["column_name"])
×
3989

×
3990
                                // build default primary key constraint name
×
3991
                                const pkName =
×
3992
                                    this.connection.namingStrategy.primaryKeyName(
×
3993
                                        table,
×
3994
                                        columnNames,
×
3995
                                    )
×
3996

×
3997
                                // if primary key has user-defined constraint name, write it in table column
×
3998
                                if (
×
3999
                                    primaryConstraint["constraint_name"] !==
×
4000
                                    pkName
×
4001
                                ) {
×
4002
                                    tableColumn.primaryKeyConstraintName =
×
4003
                                        primaryConstraint["constraint_name"]
×
4004
                                }
×
4005
                            }
×
4006

×
4007
                            const uniqueConstraints = columnConstraints.filter(
×
4008
                                (constraint) =>
×
4009
                                    constraint["constraint_type"] === "UNIQUE",
×
4010
                            )
×
4011
                            const isConstraintComposite =
×
4012
                                uniqueConstraints.every((uniqueConstraint) => {
×
4013
                                    return dbConstraints.some(
×
4014
                                        (dbConstraint) =>
×
4015
                                            dbConstraint["constraint_type"] ===
×
4016
                                                "UNIQUE" &&
×
4017
                                            dbConstraint["constraint_name"] ===
×
4018
                                                uniqueConstraint[
×
4019
                                                    "constraint_name"
×
4020
                                                ] &&
×
4021
                                            dbConstraint["column_name"] !==
×
4022
                                                dbColumn["column_name"],
×
4023
                                    )
×
4024
                                })
×
4025
                            tableColumn.isUnique =
×
4026
                                uniqueConstraints.length > 0 &&
×
4027
                                !isConstraintComposite
×
4028

×
4029
                            if (dbColumn.is_identity === "YES") {
×
4030
                                // Postgres 10+ Identity column
×
4031
                                tableColumn.isGenerated = true
×
4032
                                tableColumn.generationStrategy = "identity"
×
4033
                                tableColumn.generatedIdentity =
×
4034
                                    dbColumn.identity_generation
×
4035
                            } else if (
×
4036
                                dbColumn["column_default"] !== null &&
×
4037
                                dbColumn["column_default"] !== undefined
×
4038
                            ) {
×
4039
                                const serialDefaultName = `nextval('${this.buildSequenceName(
×
4040
                                    table,
×
4041
                                    dbColumn["column_name"],
×
4042
                                )}'::regclass)`
×
4043
                                const serialDefaultPath = `nextval('${this.buildSequencePath(
×
4044
                                    table,
×
4045
                                    dbColumn["column_name"],
×
4046
                                )}'::regclass)`
×
4047

×
4048
                                const defaultWithoutQuotes = dbColumn[
×
4049
                                    "column_default"
×
4050
                                ].replace(/"/g, "")
×
4051

×
4052
                                if (
×
4053
                                    defaultWithoutQuotes ===
×
4054
                                        serialDefaultName ||
×
4055
                                    defaultWithoutQuotes === serialDefaultPath
×
4056
                                ) {
×
4057
                                    tableColumn.isGenerated = true
×
4058
                                    tableColumn.generationStrategy = "increment"
×
4059
                                } else if (
×
4060
                                    dbColumn["column_default"] ===
×
4061
                                        "gen_random_uuid()" ||
×
4062
                                    /^uuid_generate_v\d\(\)/.test(
×
4063
                                        dbColumn["column_default"],
×
4064
                                    )
×
4065
                                ) {
×
4066
                                    if (tableColumn.type === "uuid") {
×
4067
                                        tableColumn.isGenerated = true
×
4068
                                        tableColumn.generationStrategy = "uuid"
×
4069
                                    } else {
×
4070
                                        tableColumn.default =
×
4071
                                            dbColumn["column_default"]
×
4072
                                    }
×
4073
                                } else if (
×
4074
                                    dbColumn["column_default"] === "now()" ||
×
4075
                                    dbColumn["column_default"].indexOf(
×
4076
                                        "'now'::text",
×
4077
                                    ) !== -1
×
4078
                                ) {
×
4079
                                    tableColumn.default =
×
4080
                                        dbColumn["column_default"]
×
4081
                                } else {
×
4082
                                    tableColumn.default = dbColumn[
×
4083
                                        "column_default"
×
4084
                                    ].replace(/::[\w\s.[\]\-"]+/g, "")
×
4085
                                    tableColumn.default =
×
4086
                                        tableColumn.default.replace(
×
4087
                                            /^(-?\d+)$/,
×
4088
                                            "'$1'",
×
4089
                                        )
×
4090
                                }
×
4091
                            }
×
4092

×
4093
                            if (
×
4094
                                dbColumn["is_generated"] === "ALWAYS" &&
×
4095
                                dbColumn["generation_expression"]
×
4096
                            ) {
×
4097
                                // In postgres there is no VIRTUAL generated column type
×
4098
                                tableColumn.generatedType = "STORED"
×
4099
                                // We cannot relay on information_schema.columns.generation_expression, because it is formatted different.
×
4100
                                const asExpressionQuery =
×
4101
                                    this.selectTypeormMetadataSql({
×
4102
                                        database: currentDatabase,
×
4103
                                        schema: dbTable["table_schema"],
×
4104
                                        table: dbTable["table_name"],
×
4105
                                        type: MetadataTableType.GENERATED_COLUMN,
×
4106
                                        name: tableColumn.name,
×
4107
                                    })
×
4108

×
4109
                                const results = await this.query(
×
4110
                                    asExpressionQuery.query,
×
4111
                                    asExpressionQuery.parameters,
×
4112
                                )
×
4113
                                if (results[0] && results[0].value) {
×
4114
                                    tableColumn.asExpression = results[0].value
×
4115
                                } else {
×
4116
                                    tableColumn.asExpression = ""
×
4117
                                }
×
4118
                            }
×
4119

×
4120
                            tableColumn.comment = dbColumn["description"]
×
4121
                                ? dbColumn["description"]
×
4122
                                : undefined
×
4123
                            if (dbColumn["character_set_name"])
×
4124
                                tableColumn.charset =
×
4125
                                    dbColumn["character_set_name"]
×
4126
                            if (dbColumn["collation_name"])
×
4127
                                tableColumn.collation =
×
4128
                                    dbColumn["collation_name"]
×
4129
                            return tableColumn
×
4130
                        }),
×
4131
                )
×
4132

×
4133
                // find unique constraints of table, group them by constraint name and build TableUnique.
×
4134
                const tableUniqueConstraints = OrmUtils.uniq(
×
4135
                    dbConstraints.filter((dbConstraint) => {
×
4136
                        return (
×
4137
                            dbConstraint["table_name"] ===
×
4138
                                dbTable["table_name"] &&
×
4139
                            dbConstraint["table_schema"] ===
×
4140
                                dbTable["table_schema"] &&
×
4141
                            dbConstraint["constraint_type"] === "UNIQUE"
×
4142
                        )
×
4143
                    }),
×
4144
                    (dbConstraint) => dbConstraint["constraint_name"],
×
4145
                )
×
4146

×
4147
                table.uniques = tableUniqueConstraints.map((constraint) => {
×
4148
                    const uniques = dbConstraints.filter(
×
4149
                        (dbC) =>
×
4150
                            dbC["constraint_name"] ===
×
4151
                            constraint["constraint_name"],
×
4152
                    )
×
4153
                    return new TableUnique({
×
4154
                        name: constraint["constraint_name"],
×
4155
                        columnNames: uniques.map((u) => u["column_name"]),
×
4156
                        deferrable: constraint["deferrable"]
×
4157
                            ? constraint["deferred"]
×
4158
                            : undefined,
×
4159
                    })
×
4160
                })
×
4161

×
4162
                // find check constraints of table, group them by constraint name and build TableCheck.
×
4163
                const tableCheckConstraints = OrmUtils.uniq(
×
4164
                    dbConstraints.filter((dbConstraint) => {
×
4165
                        return (
×
4166
                            dbConstraint["table_name"] ===
×
4167
                                dbTable["table_name"] &&
×
4168
                            dbConstraint["table_schema"] ===
×
4169
                                dbTable["table_schema"] &&
×
4170
                            dbConstraint["constraint_type"] === "CHECK"
×
4171
                        )
×
4172
                    }),
×
4173
                    (dbConstraint) => dbConstraint["constraint_name"],
×
4174
                )
×
4175

×
4176
                table.checks = tableCheckConstraints.map((constraint) => {
×
4177
                    const checks = dbConstraints.filter(
×
4178
                        (dbC) =>
×
4179
                            dbC["constraint_name"] ===
×
4180
                            constraint["constraint_name"],
×
4181
                    )
×
4182
                    return new TableCheck({
×
4183
                        name: constraint["constraint_name"],
×
4184
                        columnNames: checks.map((c) => c["column_name"]),
×
4185
                        expression: constraint["expression"].replace(
×
4186
                            /^\s*CHECK\s*\((.*)\)\s*$/i,
×
4187
                            "$1",
×
4188
                        ),
×
4189
                    })
×
4190
                })
×
4191

×
4192
                // find exclusion constraints of table, group them by constraint name and build TableExclusion.
×
4193
                const tableExclusionConstraints = OrmUtils.uniq(
×
4194
                    dbConstraints.filter((dbConstraint) => {
×
4195
                        return (
×
4196
                            dbConstraint["table_name"] ===
×
4197
                                dbTable["table_name"] &&
×
4198
                            dbConstraint["table_schema"] ===
×
4199
                                dbTable["table_schema"] &&
×
4200
                            dbConstraint["constraint_type"] === "EXCLUDE"
×
4201
                        )
×
4202
                    }),
×
4203
                    (dbConstraint) => dbConstraint["constraint_name"],
×
4204
                )
×
4205

×
4206
                table.exclusions = tableExclusionConstraints.map(
×
4207
                    (constraint) => {
×
4208
                        return new TableExclusion({
×
4209
                            name: constraint["constraint_name"],
×
4210
                            expression: constraint["expression"].substring(8), // trim EXCLUDE from start of expression
×
4211
                        })
×
4212
                    },
×
4213
                )
×
4214

×
4215
                // find foreign key constraints of table, group them by constraint name and build TableForeignKey.
×
4216
                const tableForeignKeyConstraints = OrmUtils.uniq(
×
4217
                    dbForeignKeys.filter((dbForeignKey) => {
×
4218
                        return (
×
4219
                            dbForeignKey["table_name"] ===
×
4220
                                dbTable["table_name"] &&
×
4221
                            dbForeignKey["table_schema"] ===
×
4222
                                dbTable["table_schema"]
×
4223
                        )
×
4224
                    }),
×
4225
                    (dbForeignKey) => dbForeignKey["constraint_name"],
×
4226
                )
×
4227

×
4228
                table.foreignKeys = tableForeignKeyConstraints.map(
×
4229
                    (dbForeignKey) => {
×
4230
                        const foreignKeys = dbForeignKeys.filter(
×
4231
                            (dbFk) =>
×
4232
                                dbFk["constraint_name"] ===
×
4233
                                dbForeignKey["constraint_name"],
×
4234
                        )
×
4235

×
4236
                        // if referenced table located in currently used schema, we don't need to concat schema name to table name.
×
4237
                        const schema = getSchemaFromKey(
×
4238
                            dbForeignKey,
×
4239
                            "referenced_table_schema",
×
4240
                        )
×
4241
                        const referencedTableName = this.driver.buildTableName(
×
4242
                            dbForeignKey["referenced_table_name"],
×
4243
                            schema,
×
4244
                        )
×
4245

×
4246
                        return new TableForeignKey({
×
4247
                            name: dbForeignKey["constraint_name"],
×
4248
                            columnNames: foreignKeys.map(
×
4249
                                (dbFk) => dbFk["column_name"],
×
4250
                            ),
×
4251
                            referencedSchema:
×
4252
                                dbForeignKey["referenced_table_schema"],
×
4253
                            referencedTableName: referencedTableName,
×
4254
                            referencedColumnNames: foreignKeys.map(
×
4255
                                (dbFk) => dbFk["referenced_column_name"],
×
4256
                            ),
×
4257
                            onDelete: dbForeignKey["on_delete"],
×
4258
                            onUpdate: dbForeignKey["on_update"],
×
4259
                            deferrable: dbForeignKey["deferrable"]
×
4260
                                ? dbForeignKey["deferred"]
×
4261
                                : undefined,
×
4262
                        })
×
4263
                    },
×
4264
                )
×
4265

×
4266
                // find index constraints of table, group them by constraint name and build TableIndex.
×
4267
                const tableIndexConstraints = OrmUtils.uniq(
×
4268
                    dbIndices.filter((dbIndex) => {
×
4269
                        return (
×
4270
                            dbIndex["table_name"] === dbTable["table_name"] &&
×
4271
                            dbIndex["table_schema"] === dbTable["table_schema"]
×
4272
                        )
×
4273
                    }),
×
4274
                    (dbIndex) => dbIndex["constraint_name"],
×
4275
                )
×
4276

×
4277
                table.indices = tableIndexConstraints.map((constraint) => {
×
4278
                    const indices = dbIndices.filter((index) => {
×
4279
                        return (
×
4280
                            index["table_schema"] ===
×
4281
                                constraint["table_schema"] &&
×
4282
                            index["table_name"] === constraint["table_name"] &&
×
4283
                            index["constraint_name"] ===
×
4284
                                constraint["constraint_name"]
×
4285
                        )
×
4286
                    })
×
4287
                    return new TableIndex(<TableIndexOptions>{
×
4288
                        table: table,
×
4289
                        name: constraint["constraint_name"],
×
4290
                        columnNames: indices.map((i) => i["column_name"]),
×
4291
                        isUnique: constraint["is_unique"] === "TRUE",
×
4292
                        where: constraint["condition"],
×
4293
                        isSpatial: constraint["index_type"] === "gist",
×
4294
                        type: constraint["index_type"],
×
4295
                        isFulltext: false,
×
4296
                    })
×
4297
                })
×
4298

×
4299
                return table
×
4300
            }),
×
4301
        )
×
4302
    }
×
4303

28✔
4304
    /**
28✔
4305
     * Builds create table sql.
28✔
4306
     * @param table
28✔
4307
     * @param createForeignKeys
28✔
4308
     */
28✔
4309
    protected createTableSql(table: Table, createForeignKeys?: boolean): Query {
28✔
4310
        const columnDefinitions = table.columns
24✔
4311
            .map((column) => this.buildCreateColumnSql(table, column))
24✔
4312
            .join(", ")
24✔
4313
        let sql = `CREATE TABLE ${this.escapePath(table)} (${columnDefinitions}`
24✔
4314

24✔
4315
        table.columns
24✔
4316
            .filter((column) => column.isUnique)
24✔
4317
            .forEach((column) => {
24✔
4318
                const isUniqueExist = table.uniques.some(
×
4319
                    (unique) =>
×
4320
                        unique.columnNames.length === 1 &&
×
4321
                        unique.columnNames[0] === column.name,
×
4322
                )
×
4323
                if (!isUniqueExist)
×
4324
                    table.uniques.push(
×
4325
                        new TableUnique({
×
4326
                            name: this.connection.namingStrategy.uniqueConstraintName(
×
4327
                                table,
×
4328
                                [column.name],
×
4329
                            ),
×
4330
                            columnNames: [column.name],
×
4331
                        }),
×
4332
                    )
×
4333
            })
24✔
4334

24✔
4335
        if (table.uniques.length > 0) {
24!
4336
            const uniquesSql = table.uniques
×
4337
                .map((unique) => {
×
4338
                    const uniqueName = unique.name
×
4339
                        ? unique.name
×
4340
                        : this.connection.namingStrategy.uniqueConstraintName(
×
4341
                              table,
×
4342
                              unique.columnNames,
×
4343
                          )
×
4344
                    const columnNames = unique.columnNames
×
4345
                        .map((columnName) => `"${columnName}"`)
×
4346
                        .join(", ")
×
4347
                    let constraint = `CONSTRAINT "${uniqueName}" UNIQUE (${columnNames})`
×
4348
                    if (unique.deferrable)
×
4349
                        constraint += ` DEFERRABLE ${unique.deferrable}`
×
4350
                    return constraint
×
4351
                })
×
4352
                .join(", ")
×
4353

×
4354
            sql += `, ${uniquesSql}`
×
4355
        }
×
4356

24✔
4357
        if (table.checks.length > 0) {
24!
4358
            const checksSql = table.checks
×
4359
                .map((check) => {
×
4360
                    const checkName = check.name
×
4361
                        ? check.name
×
4362
                        : this.connection.namingStrategy.checkConstraintName(
×
4363
                              table,
×
4364
                              check.expression!,
×
4365
                          )
×
4366
                    return `CONSTRAINT "${checkName}" CHECK (${check.expression})`
×
4367
                })
×
4368
                .join(", ")
×
4369

×
4370
            sql += `, ${checksSql}`
×
4371
        }
×
4372

24✔
4373
        if (table.exclusions.length > 0) {
24!
4374
            const exclusionsSql = table.exclusions
×
4375
                .map((exclusion) => {
×
4376
                    const exclusionName = exclusion.name
×
4377
                        ? exclusion.name
×
4378
                        : this.connection.namingStrategy.exclusionConstraintName(
×
4379
                              table,
×
4380
                              exclusion.expression!,
×
4381
                          )
×
4382
                    let constraint = `CONSTRAINT "${exclusionName}" EXCLUDE ${exclusion.expression}`
×
4383
                    if (exclusion.deferrable)
×
4384
                        constraint += ` DEFERRABLE ${exclusion.deferrable}`
×
4385
                    return constraint
×
4386
                })
×
4387
                .join(", ")
×
4388

×
4389
            sql += `, ${exclusionsSql}`
×
4390
        }
×
4391

24✔
4392
        if (table.foreignKeys.length > 0 && createForeignKeys) {
24!
4393
            const foreignKeysSql = table.foreignKeys
×
4394
                .map((fk) => {
×
4395
                    const columnNames = fk.columnNames
×
4396
                        .map((columnName) => `"${columnName}"`)
×
4397
                        .join(", ")
×
4398
                    if (!fk.name)
×
4399
                        fk.name = this.connection.namingStrategy.foreignKeyName(
×
4400
                            table,
×
4401
                            fk.columnNames,
×
4402
                            this.getTablePath(fk),
×
4403
                            fk.referencedColumnNames,
×
4404
                        )
×
4405

×
4406
                    const referencedColumnNames = fk.referencedColumnNames
×
4407
                        .map((columnName) => `"${columnName}"`)
×
4408
                        .join(", ")
×
4409

×
4410
                    let constraint = `CONSTRAINT "${
×
4411
                        fk.name
×
4412
                    }" FOREIGN KEY (${columnNames}) REFERENCES ${this.escapePath(
×
4413
                        this.getTablePath(fk),
×
4414
                    )} (${referencedColumnNames})`
×
4415
                    if (fk.onDelete) constraint += ` ON DELETE ${fk.onDelete}`
×
4416
                    if (fk.onUpdate) constraint += ` ON UPDATE ${fk.onUpdate}`
×
4417
                    if (fk.deferrable)
×
4418
                        constraint += ` DEFERRABLE ${fk.deferrable}`
×
4419

×
4420
                    return constraint
×
4421
                })
×
4422
                .join(", ")
×
4423

×
4424
            sql += `, ${foreignKeysSql}`
×
4425
        }
×
4426

24✔
4427
        const primaryColumns = table.columns.filter(
24✔
4428
            (column) => column.isPrimary,
24✔
4429
        )
24✔
4430
        if (primaryColumns.length > 0) {
24✔
4431
            const primaryKeyName = primaryColumns[0].primaryKeyConstraintName
24✔
4432
                ? primaryColumns[0].primaryKeyConstraintName
24!
4433
                : this.connection.namingStrategy.primaryKeyName(
24✔
4434
                      table,
24✔
4435
                      primaryColumns.map((column) => column.name),
24✔
4436
                  )
24✔
4437

24✔
4438
            const columnNames = primaryColumns
24✔
4439
                .map((column) => `"${column.name}"`)
24✔
4440
                .join(", ")
24✔
4441
            sql += `, CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNames})`
24✔
4442
        }
24✔
4443

24✔
4444
        sql += `)`
24✔
4445

24✔
4446
        table.columns
24✔
4447
            .filter((it) => it.comment)
24✔
4448
            .forEach((it) => {
24✔
4449
                sql += `; COMMENT ON COLUMN ${this.escapePath(table)}."${
×
4450
                    it.name
×
4451
                }" IS ${this.escapeComment(it.comment)}`
×
4452
            })
24✔
4453

24✔
4454
        return new Query(sql)
24✔
4455
    }
24✔
4456

28✔
4457
    /**
28✔
4458
     * Loads Postgres version.
28✔
4459
     */
28✔
4460
    async getVersion(): Promise<string> {
28✔
4461
        // we use `SELECT version()` instead of `SHOW server_version` or `SHOW server_version_num`
4✔
4462
        // to maintain compatability with Amazon Redshift.
4✔
4463
        //
4✔
4464
        // see:
4✔
4465
        //  - https://github.com/typeorm/typeorm/pull/9319
4✔
4466
        //  - https://docs.aws.amazon.com/redshift/latest/dg/c_unsupported-postgresql-functions.html
4✔
4467
        const result: [{ version: string }] =
4✔
4468
            await this.query(`SELECT version()`)
4✔
4469

4✔
4470
        // Examples:
4✔
4471
        // Postgres: "PostgreSQL 14.10 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-20), 64-bit"
4✔
4472
        // Yugabyte: "PostgreSQL 11.2-YB-2.18.1.0-b0 on x86_64-pc-linux-gnu, compiled by clang version 15.0.3 (https://github.com/yugabyte/llvm-project.git 0b8d1183745fd3998d8beffeec8cbe99c1b20529), 64-bit"
4✔
4473
        return result[0].version.replace(/^PostgreSQL ([\d.]+).*$/, "$1")
4✔
4474
    }
4✔
4475

28✔
4476
    /**
28✔
4477
     * Builds drop table sql.
28✔
4478
     * @param tableOrPath
28✔
4479
     */
28✔
4480
    protected dropTableSql(tableOrPath: Table | string): Query {
28✔
4481
        return new Query(`DROP TABLE ${this.escapePath(tableOrPath)}`)
24✔
4482
    }
24✔
4483

28✔
4484
    protected createViewSql(view: View): Query {
28✔
4485
        const materializedClause = view.materialized ? "MATERIALIZED " : ""
×
4486
        const viewName = this.escapePath(view)
×
4487

×
4488
        if (typeof view.expression === "string") {
×
4489
            return new Query(
×
4490
                `CREATE ${materializedClause}VIEW ${viewName} AS ${view.expression}`,
×
4491
            )
×
4492
        } else {
×
4493
            return new Query(
×
4494
                `CREATE ${materializedClause}VIEW ${viewName} AS ${view
×
4495
                    .expression(this.connection)
×
4496
                    .getQuery()}`,
×
4497
            )
×
4498
        }
×
4499
    }
×
4500

28✔
4501
    protected async insertViewDefinitionSql(view: View): Promise<Query> {
28✔
4502
        const currentSchema = await this.getCurrentSchema()
×
4503

×
4504
        let { schema, tableName: name } = this.driver.parseTableName(view)
×
4505

×
4506
        if (!schema) {
×
4507
            schema = currentSchema
×
4508
        }
×
4509

×
4510
        const type = view.materialized
×
4511
            ? MetadataTableType.MATERIALIZED_VIEW
×
4512
            : MetadataTableType.VIEW
×
4513
        const expression =
×
4514
            typeof view.expression === "string"
×
4515
                ? view.expression.trim()
×
4516
                : view.expression(this.connection).getQuery()
×
4517
        return this.insertTypeormMetadataSql({
×
4518
            type,
×
4519
            schema,
×
4520
            name,
×
4521
            value: expression,
×
4522
        })
×
4523
    }
×
4524

28✔
4525
    /**
28✔
4526
     * Builds drop view sql.
28✔
4527
     * @param view
28✔
4528
     * @param ifExists
28✔
4529
     */
28✔
4530
    protected dropViewSql(view: View, ifExists?: boolean): Query {
28✔
4531
        const materializedClause = view.materialized ? "MATERIALIZED " : ""
×
4532
        const ifExistsClause = ifExists ? "IF EXISTS " : ""
×
4533
        return new Query(
×
4534
            `DROP ${materializedClause}VIEW ${ifExistsClause}${this.escapePath(view)}`,
×
4535
        )
×
4536
    }
×
4537

28✔
4538
    /**
28✔
4539
     * Builds remove view sql.
28✔
4540
     * @param view
28✔
4541
     */
28✔
4542
    protected async deleteViewDefinitionSql(view: View): Promise<Query> {
28✔
4543
        const currentSchema = await this.getCurrentSchema()
×
4544

×
4545
        let { schema, tableName: name } = this.driver.parseTableName(view)
×
4546

×
4547
        if (!schema) {
×
4548
            schema = currentSchema
×
4549
        }
×
4550

×
4551
        const type = view.materialized
×
4552
            ? MetadataTableType.MATERIALIZED_VIEW
×
4553
            : MetadataTableType.VIEW
×
4554
        return this.deleteTypeormMetadataSql({ type, schema, name })
×
4555
    }
×
4556

28✔
4557
    /**
28✔
4558
     * Drops ENUM type from given schemas.
28✔
4559
     * @param schemaNames
28✔
4560
     */
28✔
4561
    protected async dropEnumTypes(schemaNames: string[]): Promise<void> {
28✔
4562
        const enums: ObjectLiteral[] = await this.query(
8✔
4563
            `SELECT quote_ident(n.nspname) || '.' || quote_ident(t.typname) as "name" FROM "pg_type" "t" ` +
8✔
4564
                `INNER JOIN "pg_enum" "e" ON "e"."enumtypid" = "t"."oid" ` +
8✔
4565
                `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
8✔
4566
                `WHERE "n"."nspname" = ANY($1) GROUP BY "n"."nspname", "t"."typname"`,
8✔
4567
            [schemaNames],
8✔
4568
        )
8✔
4569
        if (enums.length > 0) {
8!
4570
            await this.query(
×
4571
                `DROP TYPE IF EXISTS ${enums.map(({ name }) => name).join(", ")} CASCADE`,
×
4572
            )
×
4573
        }
×
4574
    }
8✔
4575

28✔
4576
    /**
28✔
4577
     * Checks if enum with the given name exist in the database.
28✔
4578
     * @param table
28✔
4579
     * @param column
28✔
4580
     */
28✔
4581
    protected async hasEnumType(
28✔
4582
        table: Table,
×
4583
        column: TableColumn,
×
4584
    ): Promise<boolean> {
×
4585
        let { schema } = this.driver.parseTableName(table)
×
4586

×
4587
        if (!schema) {
×
4588
            schema = await this.getCurrentSchema()
×
4589
        }
×
4590

×
4591
        const enumName = this.buildEnumName(table, column, false, true)
×
4592
        const sql =
×
4593
            `SELECT "n"."nspname", "t"."typname" FROM "pg_type" "t" ` +
×
4594
            `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
×
4595
            `WHERE "n"."nspname" = '${schema}' AND "t"."typname" = '${enumName}'`
×
4596
        const result = await this.query(sql)
×
4597
        return result.length ? true : false
×
4598
    }
×
4599

28✔
4600
    /**
28✔
4601
     * Builds create ENUM type sql.
28✔
4602
     * @param table
28✔
4603
     * @param column
28✔
4604
     * @param enumName
28✔
4605
     */
28✔
4606
    protected createEnumTypeSql(
28✔
4607
        table: Table,
×
4608
        column: TableColumn,
×
4609
        enumName?: string,
×
4610
    ): Query {
×
4611
        if (!enumName) enumName = this.buildEnumName(table, column)
×
4612
        const enumValues = column
×
4613
            .enum!.map((value) => `'${value.replaceAll("'", "''")}'`)
×
4614
            .join(", ")
×
4615
        return new Query(`CREATE TYPE ${enumName} AS ENUM(${enumValues})`)
×
4616
    }
×
4617

28✔
4618
    /**
28✔
4619
     * Builds create ENUM type sql.
28✔
4620
     * @param table
28✔
4621
     * @param column
28✔
4622
     * @param enumName
28✔
4623
     */
28✔
4624
    protected dropEnumTypeSql(
28✔
4625
        table: Table,
×
4626
        column: TableColumn,
×
4627
        enumName?: string,
×
4628
    ): Query {
×
4629
        if (!enumName) enumName = this.buildEnumName(table, column)
×
4630
        return new Query(`DROP TYPE ${enumName}`)
×
4631
    }
×
4632

28✔
4633
    /**
28✔
4634
     * Builds the SQL `USING <index_type>` clause based on the index type, prioritizing `isSpatial` as `GiST`.
28✔
4635
     */
28✔
4636

28✔
4637
    private buildIndexTypeClause(index: TableIndex) {
28✔
4638
        const type = index.isSpatial ? "gist" : index.type
16!
4639

16✔
4640
        if (typeof type !== "string") return null
16✔
4641

×
4642
        return `USING ${type}`
×
4643
    }
×
4644

28✔
4645
    /**
28✔
4646
     * Builds create index sql.
28✔
4647
     * @param table
28✔
4648
     * @param index
28✔
4649
     */
28✔
4650
    protected createIndexSql(table: Table, index: TableIndex): Query {
28✔
4651
        const indexTypeClause = this.buildIndexTypeClause(index)
16✔
4652

16✔
4653
        const columns = index.columnNames
16✔
4654
            .map((columnName) => `"${columnName}"`)
16✔
4655
            .join(", ")
16✔
4656
        return new Query(
16✔
4657
            `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX${
16!
4658
                index.isConcurrent ? " CONCURRENTLY" : ""
16!
4659
            } "${index.name}" ON ${this.escapePath(table)} ${
16✔
4660
                indexTypeClause ?? ""
16✔
4661
            } (${columns}) ${index.where ? "WHERE " + index.where : ""}`,
16!
4662
        )
16✔
4663
    }
16✔
4664

28✔
4665
    /**
28✔
4666
     * Builds create view index sql.
28✔
4667
     * @param view
28✔
4668
     * @param index
28✔
4669
     */
28✔
4670
    protected createViewIndexSql(view: View, index: TableIndex): Query {
28✔
4671
        const indexTypeClause = this.buildIndexTypeClause(index)
×
4672

×
4673
        const columns = index.columnNames
×
4674
            .map((columnName) => `"${columnName}"`)
×
4675
            .join(", ")
×
4676
        return new Query(
×
4677
            `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX "${
×
4678
                index.name
×
4679
            }" ON ${this.escapePath(view)} ${
×
4680
                indexTypeClause ?? ""
×
4681
            } (${columns}) ${index.where ? "WHERE " + index.where : ""}`,
×
4682
        )
×
4683
    }
×
4684

28✔
4685
    /**
28✔
4686
     * Builds drop index sql.
28✔
4687
     * @param table
28✔
4688
     * @param indexOrName
28✔
4689
     * @param ifExists
28✔
4690
     */
28✔
4691
    protected dropIndexSql(
28✔
4692
        table: Table | View,
16✔
4693
        indexOrName: TableIndex | string,
16✔
4694
        ifExists?: boolean,
16✔
4695
    ): Query {
16✔
4696
        const indexName = InstanceChecker.isTableIndex(indexOrName)
16✔
4697
            ? indexOrName.name
16✔
4698
            : indexOrName
16!
4699
        const concurrent = InstanceChecker.isTableIndex(indexOrName)
16✔
4700
            ? indexOrName.isConcurrent
16✔
4701
            : false
16!
4702
        const ifExistsClause = ifExists ? "IF EXISTS " : ""
16!
4703
        const { schema } = this.driver.parseTableName(table)
16✔
4704
        return schema
16✔
4705
            ? new Query(
16✔
4706
                  `DROP INDEX ${
16✔
4707
                      concurrent ? "CONCURRENTLY " : ""
16!
4708
                  }${ifExistsClause}"${schema}"."${indexName}"`,
16✔
4709
              )
16✔
4710
            : new Query(
16!
4711
                  `DROP INDEX ${
×
4712
                      concurrent ? "CONCURRENTLY " : ""
×
4713
                  }${ifExistsClause}"${indexName}"`,
×
4714
              )
16✔
4715
    }
16✔
4716

28✔
4717
    /**
28✔
4718
     * Builds create primary key sql.
28✔
4719
     * @param table
28✔
4720
     * @param columnNames
28✔
4721
     * @param constraintName
28✔
4722
     */
28✔
4723
    protected createPrimaryKeySql(
28✔
4724
        table: Table,
×
4725
        columnNames: string[],
×
4726
        constraintName?: string,
×
4727
    ): Query {
×
4728
        const primaryKeyName = constraintName
×
4729
            ? constraintName
×
4730
            : this.connection.namingStrategy.primaryKeyName(table, columnNames)
×
4731

×
4732
        const columnNamesString = columnNames
×
4733
            .map((columnName) => `"${columnName}"`)
×
4734
            .join(", ")
×
4735

×
4736
        return new Query(
×
4737
            `ALTER TABLE ${this.escapePath(
×
4738
                table,
×
4739
            )} ADD CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNamesString})`,
×
4740
        )
×
4741
    }
×
4742

28✔
4743
    /**
28✔
4744
     * Builds drop primary key sql.
28✔
4745
     * @param table
28✔
4746
     * @param ifExists
28✔
4747
     */
28✔
4748
    protected dropPrimaryKeySql(table: Table, ifExists?: boolean): Query {
28✔
4749
        if (!table.primaryColumns.length)
×
4750
            throw new TypeORMError(`Table ${table} has no primary keys.`)
×
4751

×
4752
        const columnNames = table.primaryColumns.map((column) => column.name)
×
4753
        const constraintName = table.primaryColumns[0].primaryKeyConstraintName
×
4754
        const primaryKeyName = constraintName
×
4755
            ? constraintName
×
4756
            : this.connection.namingStrategy.primaryKeyName(table, columnNames)
×
4757

×
4758
        const ifExistsClause = ifExists ? "IF EXISTS " : ""
×
4759
        return new Query(
×
4760
            `ALTER TABLE ${this.escapePath(
×
4761
                table,
×
4762
            )} DROP CONSTRAINT ${ifExistsClause}"${primaryKeyName}"`,
×
4763
        )
×
4764
    }
×
4765

28✔
4766
    /**
28✔
4767
     * Builds create unique constraint sql.
28✔
4768
     * @param table
28✔
4769
     * @param uniqueConstraint
28✔
4770
     */
28✔
4771
    protected createUniqueConstraintSql(
28✔
4772
        table: Table,
×
4773
        uniqueConstraint: TableUnique,
×
4774
    ): Query {
×
4775
        const columnNames = uniqueConstraint.columnNames
×
4776
            .map((column) => `"` + column + `"`)
×
4777
            .join(", ")
×
4778
        let sql = `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
×
4779
            uniqueConstraint.name
×
4780
        }" UNIQUE (${columnNames})`
×
4781
        if (uniqueConstraint.deferrable)
×
4782
            sql += ` DEFERRABLE ${uniqueConstraint.deferrable}`
×
4783
        return new Query(sql)
×
4784
    }
×
4785

28✔
4786
    /**
28✔
4787
     * Builds drop unique constraint sql.
28✔
4788
     * @param table
28✔
4789
     * @param uniqueOrName
28✔
4790
     * @param ifExists
28✔
4791
     */
28✔
4792
    protected dropUniqueConstraintSql(
28✔
4793
        table: Table,
×
4794
        uniqueOrName: TableUnique | string,
×
4795
        ifExists?: boolean,
×
4796
    ): Query {
×
4797
        const uniqueName = InstanceChecker.isTableUnique(uniqueOrName)
×
4798
            ? uniqueOrName.name
×
4799
            : uniqueOrName
×
4800
        const ifExistsClause = ifExists ? "IF EXISTS " : ""
×
4801
        return new Query(
×
4802
            `ALTER TABLE ${this.escapePath(
×
4803
                table,
×
4804
            )} DROP CONSTRAINT ${ifExistsClause}"${uniqueName}"`,
×
4805
        )
×
4806
    }
×
4807

28✔
4808
    /**
28✔
4809
     * Builds create check constraint sql.
28✔
4810
     * @param table
28✔
4811
     * @param checkConstraint
28✔
4812
     */
28✔
4813
    protected createCheckConstraintSql(
28✔
4814
        table: Table,
×
4815
        checkConstraint: TableCheck,
×
4816
    ): Query {
×
4817
        return new Query(
×
4818
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
×
4819
                checkConstraint.name
×
4820
            }" CHECK (${checkConstraint.expression})`,
×
4821
        )
×
4822
    }
×
4823

28✔
4824
    /**
28✔
4825
     * Builds drop check constraint sql.
28✔
4826
     * @param table
28✔
4827
     * @param checkOrName
28✔
4828
     * @param ifExists
28✔
4829
     */
28✔
4830
    protected dropCheckConstraintSql(
28✔
4831
        table: Table,
×
4832
        checkOrName: TableCheck | string,
×
4833
        ifExists?: boolean,
×
4834
    ): Query {
×
4835
        const checkName = InstanceChecker.isTableCheck(checkOrName)
×
4836
            ? checkOrName.name
×
4837
            : checkOrName
×
4838
        const ifExistsClause = ifExists ? "IF EXISTS " : ""
×
4839
        return new Query(
×
4840
            `ALTER TABLE ${this.escapePath(
×
4841
                table,
×
4842
            )} DROP CONSTRAINT ${ifExistsClause}"${checkName}"`,
×
4843
        )
×
4844
    }
×
4845

28✔
4846
    /**
28✔
4847
     * Builds create exclusion constraint sql.
28✔
4848
     * @param table
28✔
4849
     * @param exclusionConstraint
28✔
4850
     */
28✔
4851
    protected createExclusionConstraintSql(
28✔
4852
        table: Table,
×
4853
        exclusionConstraint: TableExclusion,
×
4854
    ): Query {
×
4855
        let sql = `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
×
4856
            exclusionConstraint.name
×
4857
        }" EXCLUDE ${exclusionConstraint.expression}`
×
4858

×
4859
        if (exclusionConstraint.deferrable)
×
4860
            sql += ` DEFERRABLE ${exclusionConstraint.deferrable}`
×
4861

×
4862
        return new Query(sql)
×
4863
    }
×
4864

28✔
4865
    /**
28✔
4866
     * Builds drop exclusion constraint sql.
28✔
4867
     * @param table
28✔
4868
     * @param exclusionOrName
28✔
4869
     * @param ifExists
28✔
4870
     */
28✔
4871
    protected dropExclusionConstraintSql(
28✔
4872
        table: Table,
×
4873
        exclusionOrName: TableExclusion | string,
×
4874
        ifExists?: boolean,
×
4875
    ): Query {
×
4876
        const exclusionName = InstanceChecker.isTableExclusion(exclusionOrName)
×
4877
            ? exclusionOrName.name
×
4878
            : exclusionOrName
×
4879
        const ifExistsClause = ifExists ? "IF EXISTS " : ""
×
4880
        return new Query(
×
4881
            `ALTER TABLE ${this.escapePath(
×
4882
                table,
×
4883
            )} DROP CONSTRAINT ${ifExistsClause}"${exclusionName}"`,
×
4884
        )
×
4885
    }
×
4886

28✔
4887
    /**
28✔
4888
     * Builds create foreign key sql.
28✔
4889
     * @param table
28✔
4890
     * @param foreignKey
28✔
4891
     */
28✔
4892
    protected createForeignKeySql(
28✔
4893
        table: Table,
24✔
4894
        foreignKey: TableForeignKey,
24✔
4895
    ): Query {
24✔
4896
        const columnNames = foreignKey.columnNames
24✔
4897
            .map((column) => `"` + column + `"`)
24✔
4898
            .join(", ")
24✔
4899
        const referencedColumnNames = foreignKey.referencedColumnNames
24✔
4900
            .map((column) => `"` + column + `"`)
24✔
4901
            .join(",")
24✔
4902
        let sql =
24✔
4903
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
24✔
4904
                foreignKey.name
24✔
4905
            }" FOREIGN KEY (${columnNames}) ` +
24✔
4906
            `REFERENCES ${this.escapePath(
24✔
4907
                this.getTablePath(foreignKey),
24✔
4908
            )}(${referencedColumnNames})`
24✔
4909
        if (foreignKey.onDelete) sql += ` ON DELETE ${foreignKey.onDelete}`
24✔
4910
        if (foreignKey.onUpdate) sql += ` ON UPDATE ${foreignKey.onUpdate}`
24✔
4911
        if (foreignKey.deferrable) sql += ` DEFERRABLE ${foreignKey.deferrable}`
24!
4912

24✔
4913
        return new Query(sql)
24✔
4914
    }
24✔
4915

28✔
4916
    /**
28✔
4917
     * Builds drop foreign key sql.
28✔
4918
     * @param table
28✔
4919
     * @param foreignKeyOrName
28✔
4920
     * @param ifExists
28✔
4921
     */
28✔
4922
    protected dropForeignKeySql(
28✔
4923
        table: Table,
24✔
4924
        foreignKeyOrName: TableForeignKey | string,
24✔
4925
        ifExists?: boolean,
24✔
4926
    ): Query {
24✔
4927
        const foreignKeyName = InstanceChecker.isTableForeignKey(
24✔
4928
            foreignKeyOrName,
24✔
4929
        )
24✔
4930
            ? foreignKeyOrName.name
24✔
4931
            : foreignKeyOrName
24!
4932
        const ifExistsClause = ifExists ? "IF EXISTS " : ""
24!
4933
        return new Query(
24✔
4934
            `ALTER TABLE ${this.escapePath(
24✔
4935
                table,
24✔
4936
            )} DROP CONSTRAINT ${ifExistsClause}"${foreignKeyName}"`,
24✔
4937
        )
24✔
4938
    }
24✔
4939

28✔
4940
    /**
28✔
4941
     * Builds sequence name from given table and column.
28✔
4942
     * @param table
28✔
4943
     * @param columnOrName
28✔
4944
     */
28✔
4945
    protected buildSequenceName(
28✔
4946
        table: Table,
×
4947
        columnOrName: TableColumn | string,
×
4948
    ): string {
×
4949
        const { tableName } = this.driver.parseTableName(table)
×
4950

×
4951
        const columnName = InstanceChecker.isTableColumn(columnOrName)
×
4952
            ? columnOrName.name
×
4953
            : columnOrName
×
4954

×
4955
        let seqName = `${tableName}_${columnName}_seq`
×
4956

×
4957
        if (seqName.length > this.connection.driver.maxAliasLength!) {
×
4958
            // note doesn't yet handle corner cases where .length differs from number of UTF-8 bytes
×
4959
            seqName = `${tableName.substring(0, 29)}_${columnName.substring(
×
4960
                0,
×
4961
                Math.max(29, 63 - table.name.length - 5),
×
4962
            )}_seq`
×
4963
        }
×
4964

×
4965
        return seqName
×
4966
    }
×
4967

28✔
4968
    protected buildSequencePath(
28✔
4969
        table: Table,
×
4970
        columnOrName: TableColumn | string,
×
4971
    ): string {
×
4972
        const { schema } = this.driver.parseTableName(table)
×
4973

×
4974
        return schema
×
4975
            ? `${schema}.${this.buildSequenceName(table, columnOrName)}`
×
4976
            : this.buildSequenceName(table, columnOrName)
×
4977
    }
×
4978

28✔
4979
    /**
28✔
4980
     * Builds ENUM type name from given table and column.
28✔
4981
     * @param table
28✔
4982
     * @param column
28✔
4983
     * @param withSchema
28✔
4984
     * @param disableEscape
28✔
4985
     * @param toOld
28✔
4986
     */
28✔
4987
    protected buildEnumName(
28✔
4988
        table: Table,
×
4989
        column: TableColumn,
×
4990
        withSchema: boolean = true,
×
4991
        disableEscape?: boolean,
×
4992
        toOld?: boolean,
×
4993
    ): string {
×
4994
        const { schema, tableName } = this.driver.parseTableName(table)
×
4995
        let enumName = column.enumName
×
4996
            ? column.enumName
×
4997
            : `${tableName}_${column.name.toLowerCase()}_enum`
×
4998
        if (schema && withSchema) enumName = `${schema}.${enumName}`
×
4999
        if (toOld) enumName = enumName + "_old"
×
5000
        return enumName
×
5001
            .split(".")
×
5002
            .map((i) => {
×
5003
                return disableEscape ? i : `"${i}"`
×
5004
            })
×
5005
            .join(".")
×
5006
    }
×
5007

28✔
5008
    protected async getUserDefinedTypeName(table: Table, column: TableColumn) {
28✔
5009
        let { schema, tableName: name } = this.driver.parseTableName(table)
×
5010

×
5011
        if (!schema) {
×
5012
            schema = await this.getCurrentSchema()
×
5013
        }
×
5014

×
5015
        const result = await this.query(
×
5016
            `SELECT "udt_schema", "udt_name" ` +
×
5017
                `FROM "information_schema"."columns" WHERE "table_schema" = '${schema}' AND "table_name" = '${name}' AND "column_name"='${column.name}'`,
×
5018
        )
×
5019

×
5020
        // docs: https://www.postgresql.org/docs/current/xtypes.html
×
5021
        // When you define a new base type, PostgreSQL automatically provides support for arrays of that type.
×
5022
        // The array type typically has the same name as the base type with the underscore character (_) prepended.
×
5023
        // ----
×
5024
        // so, we must remove this underscore character from enum type name
×
5025
        let udtName = result[0]["udt_name"]
×
5026
        if (udtName.indexOf("_") === 0) {
×
5027
            udtName = udtName.substr(1, udtName.length)
×
5028
        }
×
5029
        return {
×
5030
            schema: result[0]["udt_schema"],
×
5031
            name: udtName,
×
5032
        }
×
5033
    }
×
5034

28✔
5035
    /**
28✔
5036
     * Escapes a given comment so it's safe to include in a query.
28✔
5037
     * @param comment
28✔
5038
     */
28✔
5039
    protected escapeComment(comment?: string) {
28✔
5040
        if (!comment || comment.length === 0) {
×
5041
            return "NULL"
×
5042
        }
×
5043

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

×
5046
        return `'${comment}'`
×
5047
    }
×
5048

28✔
5049
    /**
28✔
5050
     * Escapes given table or view path.
28✔
5051
     * @param target
28✔
5052
     */
28✔
5053
    protected escapePath(target: Table | View | string): string {
28✔
5054
        const { schema, tableName } = this.driver.parseTableName(target)
136✔
5055

136✔
5056
        if (schema && schema !== this.driver.searchSchema) {
136!
5057
            return `"${schema}"."${tableName}"`
×
5058
        }
×
5059

136✔
5060
        return `"${tableName}"`
136✔
5061
    }
136✔
5062

28✔
5063
    /**
28✔
5064
     * Get the table name with table schema
28✔
5065
     * Note: Without ' or "
28✔
5066
     * @param target
28✔
5067
     */
28✔
5068
    protected async getTableNameWithSchema(target: Table | string) {
28✔
5069
        const tableName = InstanceChecker.isTable(target) ? target.name : target
×
5070
        if (tableName.indexOf(".") === -1) {
×
5071
            const schemaResult = await this.query(`SELECT current_schema()`)
×
5072
            const schema = schemaResult[0]["current_schema"]
×
5073
            return `${schema}.${tableName}`
×
5074
        } else {
×
5075
            return `${tableName.split(".")[0]}.${tableName.split(".")[1]}`
×
5076
        }
×
5077
    }
×
5078

28✔
5079
    /**
28✔
5080
     * Builds a query for create column.
28✔
5081
     * @param table
28✔
5082
     * @param column
28✔
5083
     */
28✔
5084
    protected buildCreateColumnSql(table: Table, column: TableColumn) {
28✔
5085
        let c = '"' + column.name + '"'
72✔
5086
        if (
72✔
5087
            column.isGenerated === true &&
72✔
5088
            column.generationStrategy !== "uuid"
8✔
5089
        ) {
72✔
5090
            if (column.generationStrategy === "identity") {
8!
5091
                // Postgres 10+ Identity generated column
×
5092
                const generatedIdentityOrDefault =
×
5093
                    column.generatedIdentity || "BY DEFAULT"
×
5094
                c += ` ${column.type} GENERATED ${generatedIdentityOrDefault} AS IDENTITY`
×
5095
            } else {
8✔
5096
                // classic SERIAL primary column
8✔
5097
                if (
8✔
5098
                    column.type === "integer" ||
8!
5099
                    column.type === "int" ||
8!
5100
                    column.type === "int4"
×
5101
                )
8✔
5102
                    c += " SERIAL"
8✔
5103
                if (column.type === "smallint" || column.type === "int2")
8✔
5104
                    c += " SMALLSERIAL"
8!
5105
                if (column.type === "bigint" || column.type === "int8")
8✔
5106
                    c += " BIGSERIAL"
8!
5107
            }
8✔
5108
        }
8✔
5109
        if (column.type === "enum" || column.type === "simple-enum") {
72!
5110
            c += " " + this.buildEnumName(table, column)
×
5111
            if (column.isArray) c += " array"
×
5112
        } else if (!column.isGenerated || column.type === "uuid") {
72✔
5113
            c += " " + this.connection.driver.createFullType(column)
64✔
5114
        }
64✔
5115

72✔
5116
        // Postgres only supports the stored generated column type
72✔
5117
        if (column.generatedType === "STORED" && column.asExpression) {
72!
5118
            c += ` GENERATED ALWAYS AS (${column.asExpression}) STORED`
×
5119
        }
×
5120

72✔
5121
        if (column.charset) c += ' CHARACTER SET "' + column.charset + '"'
72!
5122
        if (column.collation) c += ' COLLATE "' + column.collation + '"'
72!
5123
        if (column.isNullable !== true) c += " NOT NULL"
72✔
5124
        if (column.default !== undefined && column.default !== null)
72!
5125
            c += " DEFAULT " + column.default
72!
5126
        if (
72✔
5127
            column.isGenerated &&
72✔
5128
            column.generationStrategy === "uuid" &&
72!
5129
            !column.default
×
5130
        )
72✔
5131
            c += ` DEFAULT ${this.driver.uuidGenerator}`
72!
5132

72✔
5133
        return c
72✔
5134
    }
72✔
5135

28✔
5136
    /**
28✔
5137
     * Checks if the PostgreSQL server has support for partitioned tables
28✔
5138
     */
28✔
5139
    protected async hasSupportForPartitionedTables() {
28✔
5140
        const result = await this.query(
×
5141
            `SELECT TRUE FROM information_schema.columns WHERE table_name = 'pg_class' and column_name = 'relispartition'`,
×
5142
        )
×
5143
        return result.length ? true : false
×
5144
    }
×
5145

28✔
5146
    /**
28✔
5147
     * Change table comment.
28✔
5148
     * @param tableOrName
28✔
5149
     * @param newComment
28✔
5150
     */
28✔
5151
    async changeTableComment(
28✔
5152
        tableOrName: Table | string,
×
5153
        newComment?: string,
×
5154
    ): Promise<void> {
×
5155
        const upQueries: Query[] = []
×
5156
        const downQueries: Query[] = []
×
5157

×
5158
        const table = InstanceChecker.isTable(tableOrName)
×
5159
            ? tableOrName
×
5160
            : await this.getCachedTable(tableOrName)
×
5161

×
5162
        const escapedNewComment = this.escapeComment(newComment)
×
5163
        const escapedComment = this.escapeComment(table.comment)
×
5164

×
5165
        if (escapedNewComment === escapedComment) {
×
5166
            return
×
5167
        }
×
5168

×
5169
        const newTable = table.clone()
×
5170
        newTable.comment = newComment
×
5171

×
5172
        upQueries.push(
×
5173
            new Query(
×
5174
                `COMMENT ON TABLE ${this.escapePath(
×
5175
                    newTable,
×
5176
                )} IS ${escapedNewComment}`,
×
5177
            ),
×
5178
        )
×
5179

×
5180
        downQueries.push(
×
5181
            new Query(
×
5182
                `COMMENT ON TABLE ${this.escapePath(table)} IS ${escapedComment}`,
×
5183
            ),
×
5184
        )
×
5185

×
5186
        await this.executeQueries(upQueries, downQueries)
×
5187

×
5188
        table.comment = newTable.comment
×
5189
        this.replaceCachedTable(table, newTable)
×
5190
    }
×
5191
}
28✔
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