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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

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

31
/**
32
 * Runs queries on a single mysql database connection.
33
 */
34
export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
4✔
35
    // -------------------------------------------------------------------------
36
    // Public Implemented Properties
37
    // -------------------------------------------------------------------------
38

39
    /**
40
     * Database driver used by connection.
41
     */
42
    driver: MysqlDriver
43

44
    // -------------------------------------------------------------------------
45
    // Protected Properties
46
    // -------------------------------------------------------------------------
47

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

53
    // -------------------------------------------------------------------------
54
    // Constructor
55
    // -------------------------------------------------------------------------
56

57
    constructor(driver: MysqlDriver, mode: ReplicationMode) {
58
        super()
×
59
        this.driver = driver
×
60
        this.connection = driver.connection
×
61
        this.broadcaster = new Broadcaster(this)
×
62
        this.mode = mode
×
63
    }
64

65
    // -------------------------------------------------------------------------
66
    // Public Methods
67
    // -------------------------------------------------------------------------
68

69
    /**
70
     * Creates/uses database connection from the connection pool to perform further operations.
71
     * Returns obtained database connection.
72
     */
73
    connect(): Promise<any> {
74
        if (this.databaseConnection)
×
75
            return Promise.resolve(this.databaseConnection)
×
76

77
        if (this.databaseConnectionPromise)
×
78
            return this.databaseConnectionPromise
×
79

80
        if (this.mode === "slave" && this.driver.isReplicated) {
×
81
            this.databaseConnectionPromise = this.driver
×
82
                .obtainSlaveConnection()
83
                .then((connection) => {
84
                    this.databaseConnection = connection
×
85
                    return this.databaseConnection
×
86
                })
87
        } else {
88
            // master
89
            this.databaseConnectionPromise = this.driver
×
90
                .obtainMasterConnection()
91
                .then((connection) => {
92
                    this.databaseConnection = connection
×
93
                    return this.databaseConnection
×
94
                })
95
        }
96

97
        return this.databaseConnectionPromise
×
98
    }
99

100
    /**
101
     * Releases used database connection.
102
     * You cannot use query runner methods once its released.
103
     */
104
    release(): Promise<void> {
105
        this.isReleased = true
×
106
        if (this.databaseConnection) this.databaseConnection.release()
×
107
        return Promise.resolve()
×
108
    }
109

110
    /**
111
     * Starts transaction on the current connection.
112
     */
113
    async startTransaction(isolationLevel?: IsolationLevel): Promise<void> {
114
        this.isTransactionActive = true
×
115
        try {
×
116
            await this.broadcaster.broadcast("BeforeTransactionStart")
×
117
        } catch (err) {
118
            this.isTransactionActive = false
×
119
            throw err
×
120
        }
121
        if (this.transactionDepth === 0) {
×
122
            if (isolationLevel) {
×
123
                await this.query(
×
124
                    "SET TRANSACTION ISOLATION LEVEL " + isolationLevel,
125
                )
126
            }
127
            await this.query("START TRANSACTION")
×
128
        } else {
129
            await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
×
130
        }
131
        this.transactionDepth += 1
×
132

133
        await this.broadcaster.broadcast("AfterTransactionStart")
×
134
    }
135

136
    /**
137
     * Commits transaction.
138
     * Error will be thrown if transaction was not started.
139
     */
140
    async commitTransaction(): Promise<void> {
141
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
×
142

143
        await this.broadcaster.broadcast("BeforeTransactionCommit")
×
144

145
        if (this.transactionDepth > 1) {
×
146
            await this.query(
×
147
                `RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
148
            )
149
        } else {
150
            await this.query("COMMIT")
×
151
            this.isTransactionActive = false
×
152
        }
153
        this.transactionDepth -= 1
×
154

155
        await this.broadcaster.broadcast("AfterTransactionCommit")
×
156
    }
157

158
    /**
159
     * Rollbacks transaction.
160
     * Error will be thrown if transaction was not started.
161
     */
162
    async rollbackTransaction(): Promise<void> {
163
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
×
164

165
        await this.broadcaster.broadcast("BeforeTransactionRollback")
×
166

167
        if (this.transactionDepth > 1) {
×
168
            await this.query(
×
169
                `ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
170
            )
171
        } else {
172
            await this.query("ROLLBACK")
×
173
            this.isTransactionActive = false
×
174
        }
175
        this.transactionDepth -= 1
×
176

177
        await this.broadcaster.broadcast("AfterTransactionRollback")
×
178
    }
179

180
    /**
181
     * Executes a raw SQL query.
182
     */
183
    async query(
184
        query: string,
185
        parameters?: any[],
186
        useStructuredResult = false,
×
187
    ): Promise<any> {
188
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
×
189

190
        const databaseConnection = await this.connect()
×
191

192
        this.driver.connection.logger.logQuery(query, parameters, this)
×
193
        await this.broadcaster.broadcast("BeforeQuery", query, parameters)
×
194

195
        const broadcasterResult = new BroadcasterResult()
×
196
        const queryStartTime = Date.now()
×
197

198
        return new Promise(async (ok, fail) => {
×
199
            try {
×
200
                const enableQueryTimeout =
201
                    this.driver.options.enableQueryTimeout
×
202
                const maxQueryExecutionTime =
203
                    this.driver.options.maxQueryExecutionTime
×
204
                const queryPayload =
205
                    enableQueryTimeout && maxQueryExecutionTime
×
206
                        ? { sql: query, timeout: maxQueryExecutionTime }
207
                        : query
208
                databaseConnection.query(
×
209
                    queryPayload,
210
                    parameters,
211
                    async (err: any, raw: any) => {
212
                        // log slow queries if maxQueryExecution time is set
213
                        const maxQueryExecutionTime =
214
                            this.driver.options.maxQueryExecutionTime
×
215
                        const queryEndTime = Date.now()
×
216
                        const queryExecutionTime = queryEndTime - queryStartTime
×
217

218
                        if (
×
219
                            maxQueryExecutionTime &&
×
220
                            queryExecutionTime > maxQueryExecutionTime
221
                        )
222
                            this.driver.connection.logger.logQuerySlow(
×
223
                                queryExecutionTime,
224
                                query,
225
                                parameters,
226
                                this,
227
                            )
228

229
                        if (err) {
×
230
                            this.driver.connection.logger.logQueryError(
×
231
                                err,
232
                                query,
233
                                parameters,
234
                                this,
235
                            )
236
                            this.broadcaster.broadcastAfterQueryEvent(
×
237
                                broadcasterResult,
238
                                query,
239
                                parameters,
240
                                false,
241
                                undefined,
242
                                undefined,
243
                                err,
244
                            )
245

246
                            return fail(
×
247
                                new QueryFailedError(query, parameters, err),
248
                            )
249
                        }
250

251
                        this.broadcaster.broadcastAfterQueryEvent(
×
252
                            broadcasterResult,
253
                            query,
254
                            parameters,
255
                            true,
256
                            queryExecutionTime,
257
                            raw,
258
                            undefined,
259
                        )
260

261
                        const result = new QueryResult()
×
262

263
                        result.raw = raw
×
264

265
                        try {
×
266
                            result.records = Array.from(raw)
×
267
                        } catch {
268
                            // Do nothing.
269
                        }
270

271
                        if (raw?.hasOwnProperty("affectedRows")) {
×
272
                            result.affected = raw.affectedRows
×
273
                        }
274

275
                        if (useStructuredResult) {
×
276
                            ok(result)
×
277
                        } else {
278
                            ok(result.raw)
×
279
                        }
280
                    },
281
                )
282
            } catch (err) {
283
                fail(err)
×
284
            } finally {
285
                await broadcasterResult.wait()
×
286
            }
287
        })
288
    }
289

290
    /**
291
     * Returns raw data stream.
292
     */
293
    stream(
294
        query: string,
295
        parameters?: any[],
296
        onEnd?: Function,
297
        onError?: Function,
298
    ): Promise<ReadStream> {
299
        if (this.isReleased) throw new QueryRunnerAlreadyReleasedError()
×
300

301
        return new Promise(async (ok, fail) => {
×
302
            try {
×
303
                const databaseConnection = await this.connect()
×
304
                this.driver.connection.logger.logQuery(query, parameters, this)
×
305
                const databaseQuery = databaseConnection.query(
×
306
                    query,
307
                    parameters,
308
                )
309
                if (onEnd) databaseQuery.on("end", onEnd)
×
310
                if (onError) databaseQuery.on("error", onError)
×
311
                ok(databaseQuery.stream())
×
312
            } catch (err) {
313
                fail(err)
×
314
            }
315
        })
316
    }
317

318
    /**
319
     * Returns all available database names including system databases.
320
     */
321
    async getDatabases(): Promise<string[]> {
322
        return Promise.resolve([])
×
323
    }
324

325
    /**
326
     * Returns all available schema names including system schemas.
327
     * If database parameter specified, returns schemas of that database.
328
     */
329
    async getSchemas(database?: string): Promise<string[]> {
330
        throw new TypeORMError(`MySql driver does not support table schemas`)
×
331
    }
332

333
    /**
334
     * Checks if database with the given name exist.
335
     */
336
    async hasDatabase(database: string): Promise<boolean> {
337
        const result = await this.query(
×
338
            `SELECT * FROM \`INFORMATION_SCHEMA\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\` = '${database}'`,
339
        )
340
        return result.length ? true : false
×
341
    }
342

343
    /**
344
     * Loads currently using database
345
     */
346
    async getCurrentDatabase(): Promise<string> {
347
        const query = await this.query(`SELECT DATABASE() AS \`db_name\``)
×
348
        return query[0]["db_name"]
×
349
    }
350

351
    /**
352
     * Checks if schema with the given name exist.
353
     */
354
    async hasSchema(schema: string): Promise<boolean> {
355
        throw new TypeORMError(`MySql driver does not support table schemas`)
×
356
    }
357

358
    /**
359
     * Loads currently using database schema
360
     */
361
    async getCurrentSchema(): Promise<string> {
362
        const query = await this.query(`SELECT SCHEMA() AS \`schema_name\``)
×
363
        return query[0]["schema_name"]
×
364
    }
365

366
    /**
367
     * Checks if table with the given name exist in the database.
368
     */
369
    async hasTable(tableOrName: Table | string): Promise<boolean> {
370
        const parsedTableName = this.driver.parseTableName(tableOrName)
×
371
        const sql = `SELECT * FROM \`INFORMATION_SCHEMA\`.\`COLUMNS\` WHERE \`TABLE_SCHEMA\` = '${parsedTableName.database}' AND \`TABLE_NAME\` = '${parsedTableName.tableName}'`
×
372
        const result = await this.query(sql)
×
373
        return result.length ? true : false
×
374
    }
375

376
    /**
377
     * Checks if column with the given name exist in the given table.
378
     */
379
    async hasColumn(
380
        tableOrName: Table | string,
381
        column: TableColumn | string,
382
    ): Promise<boolean> {
383
        const parsedTableName = this.driver.parseTableName(tableOrName)
×
384
        const columnName = InstanceChecker.isTableColumn(column)
×
385
            ? column.name
386
            : column
387
        const sql = `SELECT * FROM \`INFORMATION_SCHEMA\`.\`COLUMNS\` WHERE \`TABLE_SCHEMA\` = '${parsedTableName.database}' AND \`TABLE_NAME\` = '${parsedTableName.tableName}' AND \`COLUMN_NAME\` = '${columnName}'`
×
388
        const result = await this.query(sql)
×
389
        return result.length ? true : false
×
390
    }
391

392
    /**
393
     * Creates a new database.
394
     */
395
    async createDatabase(
396
        database: string,
397
        ifNotExist?: boolean,
398
    ): Promise<void> {
399
        const up = ifNotExist
×
400
            ? `CREATE DATABASE IF NOT EXISTS \`${database}\``
401
            : `CREATE DATABASE \`${database}\``
402
        const down = `DROP DATABASE \`${database}\``
×
403
        await this.executeQueries(new Query(up), new Query(down))
×
404
    }
405

406
    /**
407
     * Drops database.
408
     */
409
    async dropDatabase(database: string, ifExist?: boolean): Promise<void> {
410
        const up = ifExist
×
411
            ? `DROP DATABASE IF EXISTS \`${database}\``
412
            : `DROP DATABASE \`${database}\``
413
        const down = `CREATE DATABASE \`${database}\``
×
414
        await this.executeQueries(new Query(up), new Query(down))
×
415
    }
416

417
    /**
418
     * Creates a new table schema.
419
     */
420
    async createSchema(
421
        schemaPath: string,
422
        ifNotExist?: boolean,
423
    ): Promise<void> {
424
        throw new TypeORMError(
×
425
            `Schema create queries are not supported by MySql driver.`,
426
        )
427
    }
428

429
    /**
430
     * Drops table schema.
431
     */
432
    async dropSchema(schemaPath: string, ifExist?: boolean): Promise<void> {
433
        throw new TypeORMError(
×
434
            `Schema drop queries are not supported by MySql driver.`,
435
        )
436
    }
437

438
    /**
439
     * Creates a new table.
440
     */
441
    async createTable(
442
        table: Table,
443
        ifNotExist: boolean = false,
×
444
        createForeignKeys: boolean = true,
×
445
    ): Promise<void> {
446
        if (ifNotExist) {
×
447
            const isTableExist = await this.hasTable(table)
×
448
            if (isTableExist) return Promise.resolve()
×
449
        }
450
        const upQueries: Query[] = []
×
451
        const downQueries: Query[] = []
×
452

453
        upQueries.push(this.createTableSql(table, createForeignKeys))
×
454
        downQueries.push(this.dropTableSql(table))
×
455

456
        // we must first drop indices, than drop foreign keys, because drop queries runs in reversed order
457
        // and foreign keys will be dropped first as indices. This order is very important, because we can't drop index
458
        // if it related to the foreign key.
459

460
        // createTable does not need separate method to create indices, because it create indices in the same query with table creation.
461
        table.indices.forEach((index) =>
×
462
            downQueries.push(this.dropIndexSql(table, index)),
×
463
        )
464

465
        // if createForeignKeys is true, we must drop created foreign keys in down query.
466
        // createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
467
        if (createForeignKeys)
×
468
            table.foreignKeys.forEach((foreignKey) =>
×
469
                downQueries.push(this.dropForeignKeySql(table, foreignKey)),
×
470
            )
471

472
        // if table has column with generated type, we must add the expression to the metadata table
473
        const generatedColumns = table.columns.filter(
×
474
            (column) => column.generatedType && column.asExpression,
×
475
        )
476

477
        for (const column of generatedColumns) {
×
478
            const currentDatabase = await this.getCurrentDatabase()
×
479

480
            const insertQuery = this.insertTypeormMetadataSql({
×
481
                schema: currentDatabase,
482
                table: table.name,
483
                type: MetadataTableType.GENERATED_COLUMN,
484
                name: column.name,
485
                value: column.asExpression,
486
            })
487

488
            const deleteQuery = this.deleteTypeormMetadataSql({
×
489
                schema: currentDatabase,
490
                table: table.name,
491
                type: MetadataTableType.GENERATED_COLUMN,
492
                name: column.name,
493
            })
494

495
            upQueries.push(insertQuery)
×
496
            downQueries.push(deleteQuery)
×
497
        }
498

499
        return this.executeQueries(upQueries, downQueries)
×
500
    }
501

502
    /**
503
     * Drop the table.
504
     */
505
    async dropTable(
506
        target: Table | string,
507
        ifExist?: boolean,
508
        dropForeignKeys: boolean = true,
×
509
    ): Promise<void> {
510
        // It needs because if table does not exist and dropForeignKeys or dropIndices is true, we don't need
511
        // to perform drop queries for foreign keys and indices.
512
        if (ifExist) {
×
513
            const isTableExist = await this.hasTable(target)
×
514
            if (!isTableExist) return Promise.resolve()
×
515
        }
516

517
        // if dropTable called with dropForeignKeys = true, we must create foreign keys in down query.
518
        const createForeignKeys: boolean = dropForeignKeys
×
519
        const tablePath = this.getTablePath(target)
×
520
        const table = await this.getCachedTable(tablePath)
×
521
        const upQueries: Query[] = []
×
522
        const downQueries: Query[] = []
×
523

524
        if (dropForeignKeys)
×
525
            table.foreignKeys.forEach((foreignKey) =>
×
526
                upQueries.push(this.dropForeignKeySql(table, foreignKey)),
×
527
            )
528

529
        table.indices.forEach((index) =>
×
530
            upQueries.push(this.dropIndexSql(table, index)),
×
531
        )
532

533
        upQueries.push(this.dropTableSql(table))
×
534
        downQueries.push(this.createTableSql(table, createForeignKeys))
×
535

536
        // if table had columns with generated type, we must remove the expression from the metadata table
537
        const generatedColumns = table.columns.filter(
×
538
            (column) => column.generatedType && column.asExpression,
×
539
        )
540

541
        for (const column of generatedColumns) {
×
542
            const currentDatabase = await this.getCurrentDatabase()
×
543

544
            const deleteQuery = this.deleteTypeormMetadataSql({
×
545
                schema: currentDatabase,
546
                table: table.name,
547
                type: MetadataTableType.GENERATED_COLUMN,
548
                name: column.name,
549
            })
550

551
            const insertQuery = this.insertTypeormMetadataSql({
×
552
                schema: currentDatabase,
553
                table: table.name,
554
                type: MetadataTableType.GENERATED_COLUMN,
555
                name: column.name,
556
                value: column.asExpression,
557
            })
558

559
            upQueries.push(deleteQuery)
×
560
            downQueries.push(insertQuery)
×
561
        }
562

563
        await this.executeQueries(upQueries, downQueries)
×
564
    }
565

566
    /**
567
     * Creates a new view.
568
     */
569
    async createView(
570
        view: View,
571
        syncWithMetadata: boolean = false,
×
572
    ): Promise<void> {
573
        const upQueries: Query[] = []
×
574
        const downQueries: Query[] = []
×
575
        upQueries.push(this.createViewSql(view))
×
576
        if (syncWithMetadata)
×
577
            upQueries.push(await this.insertViewDefinitionSql(view))
×
578
        downQueries.push(this.dropViewSql(view))
×
579
        if (syncWithMetadata)
×
580
            downQueries.push(await this.deleteViewDefinitionSql(view))
×
581
        await this.executeQueries(upQueries, downQueries)
×
582
    }
583

584
    /**
585
     * Drops the view.
586
     */
587
    async dropView(target: View | string): Promise<void> {
588
        const viewName = InstanceChecker.isView(target) ? target.name : target
×
589
        const view = await this.getCachedView(viewName)
×
590

591
        const upQueries: Query[] = []
×
592
        const downQueries: Query[] = []
×
593
        upQueries.push(await this.deleteViewDefinitionSql(view))
×
594
        upQueries.push(this.dropViewSql(view))
×
595
        downQueries.push(await this.insertViewDefinitionSql(view))
×
596
        downQueries.push(this.createViewSql(view))
×
597
        await this.executeQueries(upQueries, downQueries)
×
598
    }
599

600
    /**
601
     * Renames a table.
602
     */
603
    async renameTable(
604
        oldTableOrName: Table | string,
605
        newTableName: string,
606
    ): Promise<void> {
607
        const upQueries: Query[] = []
×
608
        const downQueries: Query[] = []
×
609
        const oldTable = InstanceChecker.isTable(oldTableOrName)
×
610
            ? oldTableOrName
611
            : await this.getCachedTable(oldTableOrName)
612
        const newTable = oldTable.clone()
×
613

614
        const { database } = this.driver.parseTableName(oldTable)
×
615

616
        newTable.name = database ? `${database}.${newTableName}` : newTableName
×
617

618
        // rename table
619
        upQueries.push(
×
620
            new Query(
621
                `RENAME TABLE ${this.escapePath(oldTable)} TO ${this.escapePath(
622
                    newTable,
623
                )}`,
624
            ),
625
        )
626
        downQueries.push(
×
627
            new Query(
628
                `RENAME TABLE ${this.escapePath(newTable)} TO ${this.escapePath(
629
                    oldTable,
630
                )}`,
631
            ),
632
        )
633

634
        // rename index constraints
635
        newTable.indices.forEach((index) => {
×
636
            const oldIndexName = this.connection.namingStrategy.indexName(
×
637
                oldTable,
638
                index.columnNames,
639
            )
640

641
            // Skip renaming if Index has user defined constraint name
642
            if (index.name !== oldIndexName) return
×
643

644
            // build new constraint name
645
            const columnNames = index.columnNames
×
646
                .map((column) => `\`${column}\``)
×
647
                .join(", ")
648
            const newIndexName = this.connection.namingStrategy.indexName(
×
649
                newTable,
650
                index.columnNames,
651
                index.where,
652
            )
653

654
            // build queries
655
            let indexType = ""
×
656
            if (index.isUnique) indexType += "UNIQUE "
×
657
            if (index.isSpatial) indexType += "SPATIAL "
×
658
            if (index.isFulltext) indexType += "FULLTEXT "
×
659
            const indexParser =
660
                index.isFulltext && index.parser
×
661
                    ? ` WITH PARSER ${index.parser}`
662
                    : ""
663

664
            upQueries.push(
×
665
                new Query(
666
                    `ALTER TABLE ${this.escapePath(newTable)} DROP INDEX \`${
667
                        index.name
668
                    }\`, ADD ${indexType}INDEX \`${newIndexName}\` (${columnNames})${indexParser}`,
669
                ),
670
            )
671
            downQueries.push(
×
672
                new Query(
673
                    `ALTER TABLE ${this.escapePath(
674
                        newTable,
675
                    )} DROP INDEX \`${newIndexName}\`, ADD ${indexType}INDEX \`${
676
                        index.name
677
                    }\` (${columnNames})${indexParser}`,
678
                ),
679
            )
680

681
            // replace constraint name
682
            index.name = newIndexName
×
683
        })
684

685
        // rename foreign key constraint
686
        newTable.foreignKeys.forEach((foreignKey) => {
×
687
            const oldForeignKeyName =
688
                this.connection.namingStrategy.foreignKeyName(
×
689
                    oldTable,
690
                    foreignKey.columnNames,
691
                    this.getTablePath(foreignKey),
692
                    foreignKey.referencedColumnNames,
693
                )
694

695
            // Skip renaming if foreign key has user defined constraint name
696
            if (foreignKey.name !== oldForeignKeyName) return
×
697

698
            // build new constraint name
699
            const columnNames = foreignKey.columnNames
×
700
                .map((column) => `\`${column}\``)
×
701
                .join(", ")
702
            const referencedColumnNames = foreignKey.referencedColumnNames
×
703
                .map((column) => `\`${column}\``)
×
704
                .join(",")
705
            const newForeignKeyName =
706
                this.connection.namingStrategy.foreignKeyName(
×
707
                    newTable,
708
                    foreignKey.columnNames,
709
                    this.getTablePath(foreignKey),
710
                    foreignKey.referencedColumnNames,
711
                )
712

713
            // build queries
714
            let up =
715
                `ALTER TABLE ${this.escapePath(newTable)} DROP FOREIGN KEY \`${
×
716
                    foreignKey.name
717
                }\`, ADD CONSTRAINT \`${newForeignKeyName}\` FOREIGN KEY (${columnNames}) ` +
718
                `REFERENCES ${this.escapePath(
719
                    this.getTablePath(foreignKey),
720
                )}(${referencedColumnNames})`
721
            if (foreignKey.onDelete) up += ` ON DELETE ${foreignKey.onDelete}`
×
722
            if (foreignKey.onUpdate) up += ` ON UPDATE ${foreignKey.onUpdate}`
×
723

724
            let down =
725
                `ALTER TABLE ${this.escapePath(
×
726
                    newTable,
727
                )} DROP FOREIGN KEY \`${newForeignKeyName}\`, ADD CONSTRAINT \`${
728
                    foreignKey.name
729
                }\` FOREIGN KEY (${columnNames}) ` +
730
                `REFERENCES ${this.escapePath(
731
                    this.getTablePath(foreignKey),
732
                )}(${referencedColumnNames})`
733
            if (foreignKey.onDelete) down += ` ON DELETE ${foreignKey.onDelete}`
×
734
            if (foreignKey.onUpdate) down += ` ON UPDATE ${foreignKey.onUpdate}`
×
735

736
            upQueries.push(new Query(up))
×
737
            downQueries.push(new Query(down))
×
738

739
            // replace constraint name
740
            foreignKey.name = newForeignKeyName
×
741
        })
742

743
        await this.executeQueries(upQueries, downQueries)
×
744

745
        // rename old table and replace it in cached tabled;
746
        oldTable.name = newTable.name
×
747
        this.replaceCachedTable(oldTable, newTable)
×
748
    }
749

750
    /**
751
     * Change table comment.
752
     */
753
    async changeTableComment(
754
        tableOrName: Table | string,
755
        newComment?: string,
756
    ): Promise<void> {
757
        const upQueries: Query[] = []
×
758
        const downQueries: Query[] = []
×
759

760
        const table = InstanceChecker.isTable(tableOrName)
×
761
            ? tableOrName
762
            : await this.getCachedTable(tableOrName)
763

764
        newComment = this.escapeComment(newComment)
×
765
        const comment = this.escapeComment(table.comment)
×
766

767
        if (newComment === comment) {
×
768
            return
×
769
        }
770

771
        const newTable = table.clone()
×
772

773
        upQueries.push(
×
774
            new Query(
775
                `ALTER TABLE ${this.escapePath(
776
                    newTable,
777
                )} COMMENT ${newComment}`,
778
            ),
779
        )
780
        downQueries.push(
×
781
            new Query(
782
                `ALTER TABLE ${this.escapePath(table)} COMMENT ${comment}`,
783
            ),
784
        )
785

786
        await this.executeQueries(upQueries, downQueries)
×
787

788
        // change table comment and replace it in cached tabled;
789
        table.comment = newTable.comment
×
790
        this.replaceCachedTable(table, newTable)
×
791
    }
792

793
    /**
794
     * Creates a new column from the column in the table.
795
     */
796
    async addColumn(
797
        tableOrName: Table | string,
798
        column: TableColumn,
799
    ): Promise<void> {
800
        const table = InstanceChecker.isTable(tableOrName)
×
801
            ? tableOrName
802
            : await this.getCachedTable(tableOrName)
803
        const clonedTable = table.clone()
×
804
        const upQueries: Query[] = []
×
805
        const downQueries: Query[] = []
×
806
        const skipColumnLevelPrimary = clonedTable.primaryColumns.length > 0
×
807

808
        upQueries.push(
×
809
            new Query(
810
                `ALTER TABLE ${this.escapePath(
811
                    table,
812
                )} ADD ${this.buildCreateColumnSql(
813
                    column,
814
                    skipColumnLevelPrimary,
815
                    false,
816
                )}`,
817
            ),
818
        )
819
        downQueries.push(
×
820
            new Query(
821
                `ALTER TABLE ${this.escapePath(table)} DROP COLUMN \`${
822
                    column.name
823
                }\``,
824
            ),
825
        )
826

827
        // create or update primary key constraint
828
        if (column.isPrimary && skipColumnLevelPrimary) {
×
829
            // if we already have generated column, we must temporary drop AUTO_INCREMENT property.
830
            const generatedColumn = clonedTable.columns.find(
×
831
                (column) =>
832
                    column.isGenerated &&
×
833
                    column.generationStrategy === "increment",
834
            )
835
            if (generatedColumn) {
×
836
                const nonGeneratedColumn = generatedColumn.clone()
×
837
                nonGeneratedColumn.isGenerated = false
×
838
                nonGeneratedColumn.generationStrategy = undefined
×
839
                upQueries.push(
×
840
                    new Query(
841
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
842
                            column.name
843
                        }\` ${this.buildCreateColumnSql(
844
                            nonGeneratedColumn,
845
                            true,
846
                        )}`,
847
                    ),
848
                )
849
                downQueries.push(
×
850
                    new Query(
851
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
852
                            nonGeneratedColumn.name
853
                        }\` ${this.buildCreateColumnSql(column, true)}`,
854
                    ),
855
                )
856
            }
857

858
            const primaryColumns = clonedTable.primaryColumns
×
859
            let columnNames = primaryColumns
×
860
                .map((column) => `\`${column.name}\``)
×
861
                .join(", ")
862
            upQueries.push(
×
863
                new Query(
864
                    `ALTER TABLE ${this.escapePath(table)} DROP PRIMARY KEY`,
865
                ),
866
            )
867
            downQueries.push(
×
868
                new Query(
869
                    `ALTER TABLE ${this.escapePath(
870
                        table,
871
                    )} ADD PRIMARY KEY (${columnNames})`,
872
                ),
873
            )
874

875
            primaryColumns.push(column)
×
876
            columnNames = primaryColumns
×
877
                .map((column) => `\`${column.name}\``)
×
878
                .join(", ")
879
            upQueries.push(
×
880
                new Query(
881
                    `ALTER TABLE ${this.escapePath(
882
                        table,
883
                    )} ADD PRIMARY KEY (${columnNames})`,
884
                ),
885
            )
886
            downQueries.push(
×
887
                new Query(
888
                    `ALTER TABLE ${this.escapePath(table)} DROP PRIMARY KEY`,
889
                ),
890
            )
891

892
            // if we previously dropped AUTO_INCREMENT property, we must bring it back
893
            if (generatedColumn) {
×
894
                const nonGeneratedColumn = generatedColumn.clone()
×
895
                nonGeneratedColumn.isGenerated = false
×
896
                nonGeneratedColumn.generationStrategy = undefined
×
897
                upQueries.push(
×
898
                    new Query(
899
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
900
                            nonGeneratedColumn.name
901
                        }\` ${this.buildCreateColumnSql(column, true)}`,
902
                    ),
903
                )
904
                downQueries.push(
×
905
                    new Query(
906
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
907
                            column.name
908
                        }\` ${this.buildCreateColumnSql(
909
                            nonGeneratedColumn,
910
                            true,
911
                        )}`,
912
                    ),
913
                )
914
            }
915
        }
916

917
        if (column.generatedType && column.asExpression) {
×
918
            const currentDatabase = await this.getCurrentDatabase()
×
919
            const insertQuery = this.insertTypeormMetadataSql({
×
920
                schema: currentDatabase,
921
                table: table.name,
922
                type: MetadataTableType.GENERATED_COLUMN,
923
                name: column.name,
924
                value: column.asExpression,
925
            })
926

927
            const deleteQuery = this.deleteTypeormMetadataSql({
×
928
                schema: currentDatabase,
929
                table: table.name,
930
                type: MetadataTableType.GENERATED_COLUMN,
931
                name: column.name,
932
            })
933

934
            upQueries.push(insertQuery)
×
935
            downQueries.push(deleteQuery)
×
936
        }
937

938
        // create column index
939
        const columnIndex = clonedTable.indices.find(
×
940
            (index) =>
941
                index.columnNames.length === 1 &&
×
942
                index.columnNames[0] === column.name,
943
        )
944
        if (columnIndex) {
×
945
            upQueries.push(this.createIndexSql(table, columnIndex))
×
946
            downQueries.push(this.dropIndexSql(table, columnIndex))
×
947
        } else if (column.isUnique) {
×
948
            const uniqueIndex = new TableIndex({
×
949
                name: this.connection.namingStrategy.indexName(table, [
950
                    column.name,
951
                ]),
952
                columnNames: [column.name],
953
                isUnique: true,
954
            })
955
            clonedTable.indices.push(uniqueIndex)
×
956
            clonedTable.uniques.push(
×
957
                new TableUnique({
958
                    name: uniqueIndex.name,
959
                    columnNames: uniqueIndex.columnNames,
960
                }),
961
            )
962
            upQueries.push(
×
963
                new Query(
964
                    `ALTER TABLE ${this.escapePath(table)} ADD UNIQUE INDEX \`${
965
                        uniqueIndex.name
966
                    }\` (\`${column.name}\`)`,
967
                ),
968
            )
969
            downQueries.push(
×
970
                new Query(
971
                    `ALTER TABLE ${this.escapePath(table)} DROP INDEX \`${
972
                        uniqueIndex.name
973
                    }\``,
974
                ),
975
            )
976
        }
977

978
        await this.executeQueries(upQueries, downQueries)
×
979

980
        clonedTable.addColumn(column)
×
981
        this.replaceCachedTable(table, clonedTable)
×
982
    }
983

984
    /**
985
     * Creates a new columns from the column in the table.
986
     */
987
    async addColumns(
988
        tableOrName: Table | string,
989
        columns: TableColumn[],
990
    ): Promise<void> {
991
        for (const column of columns) {
×
992
            await this.addColumn(tableOrName, column)
×
993
        }
994
    }
995

996
    /**
997
     * Renames column in the given table.
998
     */
999
    async renameColumn(
1000
        tableOrName: Table | string,
1001
        oldTableColumnOrName: TableColumn | string,
1002
        newTableColumnOrName: TableColumn | string,
1003
    ): Promise<void> {
1004
        const table = InstanceChecker.isTable(tableOrName)
×
1005
            ? tableOrName
1006
            : await this.getCachedTable(tableOrName)
1007
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
×
1008
            ? oldTableColumnOrName
1009
            : table.columns.find((c) => c.name === oldTableColumnOrName)
×
1010
        if (!oldColumn)
×
1011
            throw new TypeORMError(
×
1012
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
1013
            )
1014

1015
        let newColumn: TableColumn | undefined = undefined
×
1016
        if (InstanceChecker.isTableColumn(newTableColumnOrName)) {
×
1017
            newColumn = newTableColumnOrName
×
1018
        } else {
1019
            newColumn = oldColumn.clone()
×
1020
            newColumn.name = newTableColumnOrName
×
1021
        }
1022

1023
        await this.changeColumn(table, oldColumn, newColumn)
×
1024
    }
1025

1026
    /**
1027
     * Changes a column in the table.
1028
     */
1029
    async changeColumn(
1030
        tableOrName: Table | string,
1031
        oldColumnOrName: TableColumn | string,
1032
        newColumn: TableColumn,
1033
    ): Promise<void> {
1034
        const table = InstanceChecker.isTable(tableOrName)
×
1035
            ? tableOrName
1036
            : await this.getCachedTable(tableOrName)
1037
        let clonedTable = table.clone()
×
1038
        const upQueries: Query[] = []
×
1039
        const downQueries: Query[] = []
×
1040

1041
        const oldColumn = InstanceChecker.isTableColumn(oldColumnOrName)
×
1042
            ? oldColumnOrName
1043
            : table.columns.find((column) => column.name === oldColumnOrName)
×
1044
        if (!oldColumn)
×
1045
            throw new TypeORMError(
×
1046
                `Column "${oldColumnOrName}" was not found in the "${table.name}" table.`,
1047
            )
1048

1049
        if (
×
1050
            (newColumn.isGenerated !== oldColumn.isGenerated &&
×
1051
                newColumn.generationStrategy !== "uuid") ||
1052
            oldColumn.type !== newColumn.type ||
1053
            oldColumn.length !== newColumn.length ||
1054
            (oldColumn.generatedType &&
1055
                newColumn.generatedType &&
1056
                oldColumn.generatedType !== newColumn.generatedType) ||
1057
            (!oldColumn.generatedType &&
1058
                newColumn.generatedType === "VIRTUAL") ||
1059
            (oldColumn.generatedType === "VIRTUAL" && !newColumn.generatedType)
1060
        ) {
1061
            await this.dropColumn(table, oldColumn)
×
1062
            await this.addColumn(table, newColumn)
×
1063

1064
            // update cloned table
1065
            clonedTable = table.clone()
×
1066
        } else {
1067
            if (newColumn.name !== oldColumn.name) {
×
1068
                // We don't change any column properties, just rename it.
1069
                upQueries.push(
×
1070
                    new Query(
1071
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1072
                            oldColumn.name
1073
                        }\` \`${newColumn.name}\` ${this.buildCreateColumnSql(
1074
                            oldColumn,
1075
                            true,
1076
                            true,
1077
                        )}`,
1078
                    ),
1079
                )
1080
                downQueries.push(
×
1081
                    new Query(
1082
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1083
                            newColumn.name
1084
                        }\` \`${oldColumn.name}\` ${this.buildCreateColumnSql(
1085
                            oldColumn,
1086
                            true,
1087
                            true,
1088
                        )}`,
1089
                    ),
1090
                )
1091

1092
                // rename index constraints
1093
                clonedTable.findColumnIndices(oldColumn).forEach((index) => {
×
1094
                    const oldUniqueName =
1095
                        this.connection.namingStrategy.indexName(
×
1096
                            clonedTable,
1097
                            index.columnNames,
1098
                        )
1099

1100
                    // Skip renaming if Index has user defined constraint name
1101
                    if (index.name !== oldUniqueName) return
×
1102

1103
                    // build new constraint name
1104
                    index.columnNames.splice(
×
1105
                        index.columnNames.indexOf(oldColumn.name),
1106
                        1,
1107
                    )
1108
                    index.columnNames.push(newColumn.name)
×
1109
                    const columnNames = index.columnNames
×
1110
                        .map((column) => `\`${column}\``)
×
1111
                        .join(", ")
1112
                    const newIndexName =
1113
                        this.connection.namingStrategy.indexName(
×
1114
                            clonedTable,
1115
                            index.columnNames,
1116
                            index.where,
1117
                        )
1118

1119
                    // build queries
1120
                    let indexType = ""
×
1121
                    if (index.isUnique) indexType += "UNIQUE "
×
1122
                    if (index.isSpatial) indexType += "SPATIAL "
×
1123
                    if (index.isFulltext) indexType += "FULLTEXT "
×
1124
                    const indexParser =
1125
                        index.isFulltext && index.parser
×
1126
                            ? ` WITH PARSER ${index.parser}`
1127
                            : ""
1128

1129
                    upQueries.push(
×
1130
                        new Query(
1131
                            `ALTER TABLE ${this.escapePath(
1132
                                table,
1133
                            )} DROP INDEX \`${
1134
                                index.name
1135
                            }\`, ADD ${indexType}INDEX \`${newIndexName}\` (${columnNames})${indexParser}`,
1136
                        ),
1137
                    )
1138
                    downQueries.push(
×
1139
                        new Query(
1140
                            `ALTER TABLE ${this.escapePath(
1141
                                table,
1142
                            )} DROP INDEX \`${newIndexName}\`, ADD ${indexType}INDEX \`${
1143
                                index.name
1144
                            }\` (${columnNames})${indexParser}`,
1145
                        ),
1146
                    )
1147

1148
                    // replace constraint name
1149
                    index.name = newIndexName
×
1150
                })
1151

1152
                // rename foreign key constraints
1153
                clonedTable
×
1154
                    .findColumnForeignKeys(oldColumn)
1155
                    .forEach((foreignKey) => {
1156
                        const foreignKeyName =
1157
                            this.connection.namingStrategy.foreignKeyName(
×
1158
                                clonedTable,
1159
                                foreignKey.columnNames,
1160
                                this.getTablePath(foreignKey),
1161
                                foreignKey.referencedColumnNames,
1162
                            )
1163

1164
                        // Skip renaming if foreign key has user defined constraint name
1165
                        if (foreignKey.name !== foreignKeyName) return
×
1166

1167
                        // build new constraint name
1168
                        foreignKey.columnNames.splice(
×
1169
                            foreignKey.columnNames.indexOf(oldColumn.name),
1170
                            1,
1171
                        )
1172
                        foreignKey.columnNames.push(newColumn.name)
×
1173
                        const columnNames = foreignKey.columnNames
×
1174
                            .map((column) => `\`${column}\``)
×
1175
                            .join(", ")
1176
                        const referencedColumnNames =
1177
                            foreignKey.referencedColumnNames
×
1178
                                .map((column) => `\`${column}\``)
×
1179
                                .join(",")
1180
                        const newForeignKeyName =
1181
                            this.connection.namingStrategy.foreignKeyName(
×
1182
                                clonedTable,
1183
                                foreignKey.columnNames,
1184
                                this.getTablePath(foreignKey),
1185
                                foreignKey.referencedColumnNames,
1186
                            )
1187

1188
                        // build queries
1189
                        let up =
1190
                            `ALTER TABLE ${this.escapePath(
×
1191
                                table,
1192
                            )} DROP FOREIGN KEY \`${
1193
                                foreignKey.name
1194
                            }\`, ADD CONSTRAINT \`${newForeignKeyName}\` FOREIGN KEY (${columnNames}) ` +
1195
                            `REFERENCES ${this.escapePath(
1196
                                this.getTablePath(foreignKey),
1197
                            )}(${referencedColumnNames})`
1198
                        if (foreignKey.onDelete)
×
1199
                            up += ` ON DELETE ${foreignKey.onDelete}`
×
1200
                        if (foreignKey.onUpdate)
×
1201
                            up += ` ON UPDATE ${foreignKey.onUpdate}`
×
1202

1203
                        let down =
1204
                            `ALTER TABLE ${this.escapePath(
×
1205
                                table,
1206
                            )} DROP FOREIGN KEY \`${newForeignKeyName}\`, ADD CONSTRAINT \`${
1207
                                foreignKey.name
1208
                            }\` FOREIGN KEY (${columnNames}) ` +
1209
                            `REFERENCES ${this.escapePath(
1210
                                this.getTablePath(foreignKey),
1211
                            )}(${referencedColumnNames})`
1212
                        if (foreignKey.onDelete)
×
1213
                            down += ` ON DELETE ${foreignKey.onDelete}`
×
1214
                        if (foreignKey.onUpdate)
×
1215
                            down += ` ON UPDATE ${foreignKey.onUpdate}`
×
1216

1217
                        upQueries.push(new Query(up))
×
1218
                        downQueries.push(new Query(down))
×
1219

1220
                        // replace constraint name
1221
                        foreignKey.name = newForeignKeyName
×
1222
                    })
1223

1224
                // rename old column in the Table object
1225
                const oldTableColumn = clonedTable.columns.find(
×
1226
                    (column) => column.name === oldColumn.name,
×
1227
                )
1228
                clonedTable.columns[
×
1229
                    clonedTable.columns.indexOf(oldTableColumn!)
1230
                ].name = newColumn.name
1231
                oldColumn.name = newColumn.name
×
1232
            }
1233

1234
            if (this.isColumnChanged(oldColumn, newColumn, true, true)) {
×
1235
                upQueries.push(
×
1236
                    new Query(
1237
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1238
                            oldColumn.name
1239
                        }\` ${this.buildCreateColumnSql(newColumn, true)}`,
1240
                    ),
1241
                )
1242
                downQueries.push(
×
1243
                    new Query(
1244
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1245
                            newColumn.name
1246
                        }\` ${this.buildCreateColumnSql(oldColumn, true)}`,
1247
                    ),
1248
                )
1249

1250
                if (oldColumn.generatedType && !newColumn.generatedType) {
×
1251
                    // if column changed from generated to non-generated, delete record from typeorm metadata
1252

1253
                    const currentDatabase = await this.getCurrentDatabase()
×
1254
                    const deleteQuery = this.deleteTypeormMetadataSql({
×
1255
                        schema: currentDatabase,
1256
                        table: table.name,
1257
                        type: MetadataTableType.GENERATED_COLUMN,
1258
                        name: oldColumn.name,
1259
                    })
1260
                    const insertQuery = this.insertTypeormMetadataSql({
×
1261
                        schema: currentDatabase,
1262
                        table: table.name,
1263
                        type: MetadataTableType.GENERATED_COLUMN,
1264
                        name: oldColumn.name,
1265
                        value: oldColumn.asExpression,
1266
                    })
1267

1268
                    upQueries.push(deleteQuery)
×
1269
                    downQueries.push(insertQuery)
×
1270
                } else if (
×
1271
                    !oldColumn.generatedType &&
×
1272
                    newColumn.generatedType
1273
                ) {
1274
                    // if column changed from non-generated to generated, insert record into typeorm metadata
1275

1276
                    const currentDatabase = await this.getCurrentDatabase()
×
1277
                    const insertQuery = this.insertTypeormMetadataSql({
×
1278
                        schema: currentDatabase,
1279
                        table: table.name,
1280
                        type: MetadataTableType.GENERATED_COLUMN,
1281
                        name: newColumn.name,
1282
                        value: newColumn.asExpression,
1283
                    })
1284
                    const deleteQuery = this.deleteTypeormMetadataSql({
×
1285
                        schema: currentDatabase,
1286
                        table: table.name,
1287
                        type: MetadataTableType.GENERATED_COLUMN,
1288
                        name: newColumn.name,
1289
                    })
1290

1291
                    upQueries.push(insertQuery)
×
1292
                    downQueries.push(deleteQuery)
×
1293
                } else if (oldColumn.asExpression !== newColumn.asExpression) {
×
1294
                    // if only expression changed, just update it in typeorm_metadata table
1295
                    const currentDatabase = await this.getCurrentDatabase()
×
1296
                    const updateQuery = this.connection
×
1297
                        .createQueryBuilder()
1298
                        .update(this.getTypeormMetadataTableName())
1299
                        .set({ value: newColumn.asExpression })
1300
                        .where("`type` = :type", {
1301
                            type: MetadataTableType.GENERATED_COLUMN,
1302
                        })
1303
                        .andWhere("`name` = :name", { name: oldColumn.name })
1304
                        .andWhere("`schema` = :schema", {
1305
                            schema: currentDatabase,
1306
                        })
1307
                        .andWhere("`table` = :table", { table: table.name })
1308
                        .getQueryAndParameters()
1309

1310
                    const revertUpdateQuery = this.connection
×
1311
                        .createQueryBuilder()
1312
                        .update(this.getTypeormMetadataTableName())
1313
                        .set({ value: oldColumn.asExpression })
1314
                        .where("`type` = :type", {
1315
                            type: MetadataTableType.GENERATED_COLUMN,
1316
                        })
1317
                        .andWhere("`name` = :name", { name: newColumn.name })
1318
                        .andWhere("`schema` = :schema", {
1319
                            schema: currentDatabase,
1320
                        })
1321
                        .andWhere("`table` = :table", { table: table.name })
1322
                        .getQueryAndParameters()
1323

1324
                    upQueries.push(new Query(updateQuery[0], updateQuery[1]))
×
1325
                    downQueries.push(
×
1326
                        new Query(revertUpdateQuery[0], revertUpdateQuery[1]),
1327
                    )
1328
                }
1329
            }
1330

1331
            if (newColumn.isPrimary !== oldColumn.isPrimary) {
×
1332
                // if table have generated column, we must drop AUTO_INCREMENT before changing primary constraints.
1333
                const generatedColumn = clonedTable.columns.find(
×
1334
                    (column) =>
1335
                        column.isGenerated &&
×
1336
                        column.generationStrategy === "increment",
1337
                )
1338
                if (generatedColumn) {
×
1339
                    const nonGeneratedColumn = generatedColumn.clone()
×
1340
                    nonGeneratedColumn.isGenerated = false
×
1341
                    nonGeneratedColumn.generationStrategy = undefined
×
1342

1343
                    upQueries.push(
×
1344
                        new Query(
1345
                            `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1346
                                generatedColumn.name
1347
                            }\` ${this.buildCreateColumnSql(
1348
                                nonGeneratedColumn,
1349
                                true,
1350
                            )}`,
1351
                        ),
1352
                    )
1353
                    downQueries.push(
×
1354
                        new Query(
1355
                            `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1356
                                nonGeneratedColumn.name
1357
                            }\` ${this.buildCreateColumnSql(
1358
                                generatedColumn,
1359
                                true,
1360
                            )}`,
1361
                        ),
1362
                    )
1363
                }
1364

1365
                const primaryColumns = clonedTable.primaryColumns
×
1366

1367
                // if primary column state changed, we must always drop existed constraint.
1368
                if (primaryColumns.length > 0) {
×
1369
                    const columnNames = primaryColumns
×
1370
                        .map((column) => `\`${column.name}\``)
×
1371
                        .join(", ")
1372
                    upQueries.push(
×
1373
                        new Query(
1374
                            `ALTER TABLE ${this.escapePath(
1375
                                table,
1376
                            )} DROP PRIMARY KEY`,
1377
                        ),
1378
                    )
1379
                    downQueries.push(
×
1380
                        new Query(
1381
                            `ALTER TABLE ${this.escapePath(
1382
                                table,
1383
                            )} ADD PRIMARY KEY (${columnNames})`,
1384
                        ),
1385
                    )
1386
                }
1387

1388
                if (newColumn.isPrimary === true) {
×
1389
                    primaryColumns.push(newColumn)
×
1390
                    // update column in table
1391
                    const column = clonedTable.columns.find(
×
1392
                        (column) => column.name === newColumn.name,
×
1393
                    )
1394
                    column!.isPrimary = true
×
1395
                    const columnNames = primaryColumns
×
1396
                        .map((column) => `\`${column.name}\``)
×
1397
                        .join(", ")
1398
                    upQueries.push(
×
1399
                        new Query(
1400
                            `ALTER TABLE ${this.escapePath(
1401
                                table,
1402
                            )} ADD PRIMARY KEY (${columnNames})`,
1403
                        ),
1404
                    )
1405
                    downQueries.push(
×
1406
                        new Query(
1407
                            `ALTER TABLE ${this.escapePath(
1408
                                table,
1409
                            )} DROP PRIMARY KEY`,
1410
                        ),
1411
                    )
1412
                } else {
1413
                    const primaryColumn = primaryColumns.find(
×
1414
                        (c) => c.name === newColumn.name,
×
1415
                    )
1416
                    primaryColumns.splice(
×
1417
                        primaryColumns.indexOf(primaryColumn!),
1418
                        1,
1419
                    )
1420
                    // update column in table
1421
                    const column = clonedTable.columns.find(
×
1422
                        (column) => column.name === newColumn.name,
×
1423
                    )
1424
                    column!.isPrimary = false
×
1425

1426
                    // if we have another primary keys, we must recreate constraint.
1427
                    if (primaryColumns.length > 0) {
×
1428
                        const columnNames = primaryColumns
×
1429
                            .map((column) => `\`${column.name}\``)
×
1430
                            .join(", ")
1431
                        upQueries.push(
×
1432
                            new Query(
1433
                                `ALTER TABLE ${this.escapePath(
1434
                                    table,
1435
                                )} ADD PRIMARY KEY (${columnNames})`,
1436
                            ),
1437
                        )
1438
                        downQueries.push(
×
1439
                            new Query(
1440
                                `ALTER TABLE ${this.escapePath(
1441
                                    table,
1442
                                )} DROP PRIMARY KEY`,
1443
                            ),
1444
                        )
1445
                    }
1446
                }
1447

1448
                // if we have generated column, and we dropped AUTO_INCREMENT property before, we must bring it back
1449
                if (generatedColumn) {
×
1450
                    const nonGeneratedColumn = generatedColumn.clone()
×
1451
                    nonGeneratedColumn.isGenerated = false
×
1452
                    nonGeneratedColumn.generationStrategy = undefined
×
1453

1454
                    upQueries.push(
×
1455
                        new Query(
1456
                            `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1457
                                nonGeneratedColumn.name
1458
                            }\` ${this.buildCreateColumnSql(
1459
                                generatedColumn,
1460
                                true,
1461
                            )}`,
1462
                        ),
1463
                    )
1464
                    downQueries.push(
×
1465
                        new Query(
1466
                            `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1467
                                generatedColumn.name
1468
                            }\` ${this.buildCreateColumnSql(
1469
                                nonGeneratedColumn,
1470
                                true,
1471
                            )}`,
1472
                        ),
1473
                    )
1474
                }
1475
            }
1476

1477
            if (newColumn.isUnique !== oldColumn.isUnique) {
×
1478
                if (newColumn.isUnique === true) {
×
1479
                    const uniqueIndex = new TableIndex({
×
1480
                        name: this.connection.namingStrategy.indexName(table, [
1481
                            newColumn.name,
1482
                        ]),
1483
                        columnNames: [newColumn.name],
1484
                        isUnique: true,
1485
                    })
1486
                    clonedTable.indices.push(uniqueIndex)
×
1487
                    clonedTable.uniques.push(
×
1488
                        new TableUnique({
1489
                            name: uniqueIndex.name,
1490
                            columnNames: uniqueIndex.columnNames,
1491
                        }),
1492
                    )
1493
                    upQueries.push(
×
1494
                        new Query(
1495
                            `ALTER TABLE ${this.escapePath(
1496
                                table,
1497
                            )} ADD UNIQUE INDEX \`${uniqueIndex.name}\` (\`${
1498
                                newColumn.name
1499
                            }\`)`,
1500
                        ),
1501
                    )
1502
                    downQueries.push(
×
1503
                        new Query(
1504
                            `ALTER TABLE ${this.escapePath(
1505
                                table,
1506
                            )} DROP INDEX \`${uniqueIndex.name}\``,
1507
                        ),
1508
                    )
1509
                } else {
1510
                    const uniqueIndex = clonedTable.indices.find((index) => {
×
1511
                        return (
×
1512
                            index.columnNames.length === 1 &&
×
1513
                            index.isUnique === true &&
1514
                            !!index.columnNames.find(
1515
                                (columnName) => columnName === newColumn.name,
×
1516
                            )
1517
                        )
1518
                    })
1519
                    clonedTable.indices.splice(
×
1520
                        clonedTable.indices.indexOf(uniqueIndex!),
1521
                        1,
1522
                    )
1523

1524
                    const tableUnique = clonedTable.uniques.find(
×
1525
                        (unique) => unique.name === uniqueIndex!.name,
×
1526
                    )
1527
                    clonedTable.uniques.splice(
×
1528
                        clonedTable.uniques.indexOf(tableUnique!),
1529
                        1,
1530
                    )
1531

1532
                    upQueries.push(
×
1533
                        new Query(
1534
                            `ALTER TABLE ${this.escapePath(
1535
                                table,
1536
                            )} DROP INDEX \`${uniqueIndex!.name}\``,
1537
                        ),
1538
                    )
1539
                    downQueries.push(
×
1540
                        new Query(
1541
                            `ALTER TABLE ${this.escapePath(
1542
                                table,
1543
                            )} ADD UNIQUE INDEX \`${uniqueIndex!.name}\` (\`${
1544
                                newColumn.name
1545
                            }\`)`,
1546
                        ),
1547
                    )
1548
                }
1549
            }
1550
        }
1551

1552
        await this.executeQueries(upQueries, downQueries)
×
1553
        this.replaceCachedTable(table, clonedTable)
×
1554
    }
1555

1556
    /**
1557
     * Changes a column in the table.
1558
     */
1559
    async changeColumns(
1560
        tableOrName: Table | string,
1561
        changedColumns: { newColumn: TableColumn; oldColumn: TableColumn }[],
1562
    ): Promise<void> {
1563
        for (const { oldColumn, newColumn } of changedColumns) {
×
1564
            await this.changeColumn(tableOrName, oldColumn, newColumn)
×
1565
        }
1566
    }
1567

1568
    /**
1569
     * Drops column in the table.
1570
     */
1571
    async dropColumn(
1572
        tableOrName: Table | string,
1573
        columnOrName: TableColumn | string,
1574
    ): Promise<void> {
1575
        const table = InstanceChecker.isTable(tableOrName)
×
1576
            ? tableOrName
1577
            : await this.getCachedTable(tableOrName)
1578
        const column = InstanceChecker.isTableColumn(columnOrName)
×
1579
            ? columnOrName
1580
            : table.findColumnByName(columnOrName)
1581
        if (!column)
×
1582
            throw new TypeORMError(
×
1583
                `Column "${columnOrName}" was not found in table "${table.name}"`,
1584
            )
1585

1586
        const clonedTable = table.clone()
×
1587
        const upQueries: Query[] = []
×
1588
        const downQueries: Query[] = []
×
1589

1590
        // drop primary key constraint
1591
        if (column.isPrimary) {
×
1592
            // if table have generated column, we must drop AUTO_INCREMENT before changing primary constraints.
1593
            const generatedColumn = clonedTable.columns.find(
×
1594
                (column) =>
1595
                    column.isGenerated &&
×
1596
                    column.generationStrategy === "increment",
1597
            )
1598
            if (generatedColumn) {
×
1599
                const nonGeneratedColumn = generatedColumn.clone()
×
1600
                nonGeneratedColumn.isGenerated = false
×
1601
                nonGeneratedColumn.generationStrategy = undefined
×
1602

1603
                upQueries.push(
×
1604
                    new Query(
1605
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1606
                            generatedColumn.name
1607
                        }\` ${this.buildCreateColumnSql(
1608
                            nonGeneratedColumn,
1609
                            true,
1610
                        )}`,
1611
                    ),
1612
                )
1613
                downQueries.push(
×
1614
                    new Query(
1615
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1616
                            nonGeneratedColumn.name
1617
                        }\` ${this.buildCreateColumnSql(
1618
                            generatedColumn,
1619
                            true,
1620
                        )}`,
1621
                    ),
1622
                )
1623
            }
1624

1625
            // dropping primary key constraint
1626
            const columnNames = clonedTable.primaryColumns
×
1627
                .map((primaryColumn) => `\`${primaryColumn.name}\``)
×
1628
                .join(", ")
1629
            upQueries.push(
×
1630
                new Query(
1631
                    `ALTER TABLE ${this.escapePath(
1632
                        clonedTable,
1633
                    )} DROP PRIMARY KEY`,
1634
                ),
1635
            )
1636
            downQueries.push(
×
1637
                new Query(
1638
                    `ALTER TABLE ${this.escapePath(
1639
                        clonedTable,
1640
                    )} ADD PRIMARY KEY (${columnNames})`,
1641
                ),
1642
            )
1643

1644
            // update column in table
1645
            const tableColumn = clonedTable.findColumnByName(column.name)
×
1646
            tableColumn!.isPrimary = false
×
1647

1648
            // if primary key have multiple columns, we must recreate it without dropped column
1649
            if (clonedTable.primaryColumns.length > 0) {
×
1650
                const columnNames = clonedTable.primaryColumns
×
1651
                    .map((primaryColumn) => `\`${primaryColumn.name}\``)
×
1652
                    .join(", ")
1653
                upQueries.push(
×
1654
                    new Query(
1655
                        `ALTER TABLE ${this.escapePath(
1656
                            clonedTable,
1657
                        )} ADD PRIMARY KEY (${columnNames})`,
1658
                    ),
1659
                )
1660
                downQueries.push(
×
1661
                    new Query(
1662
                        `ALTER TABLE ${this.escapePath(
1663
                            clonedTable,
1664
                        )} DROP PRIMARY KEY`,
1665
                    ),
1666
                )
1667
            }
1668

1669
            // if we have generated column, and we dropped AUTO_INCREMENT property before, and this column is not current dropping column, we must bring it back
1670
            if (generatedColumn && generatedColumn.name !== column.name) {
×
1671
                const nonGeneratedColumn = generatedColumn.clone()
×
1672
                nonGeneratedColumn.isGenerated = false
×
1673
                nonGeneratedColumn.generationStrategy = undefined
×
1674

1675
                upQueries.push(
×
1676
                    new Query(
1677
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1678
                            nonGeneratedColumn.name
1679
                        }\` ${this.buildCreateColumnSql(
1680
                            generatedColumn,
1681
                            true,
1682
                        )}`,
1683
                    ),
1684
                )
1685
                downQueries.push(
×
1686
                    new Query(
1687
                        `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1688
                            generatedColumn.name
1689
                        }\` ${this.buildCreateColumnSql(
1690
                            nonGeneratedColumn,
1691
                            true,
1692
                        )}`,
1693
                    ),
1694
                )
1695
            }
1696
        }
1697

1698
        // drop column index
1699
        const columnIndex = clonedTable.indices.find(
×
1700
            (index) =>
1701
                index.columnNames.length === 1 &&
×
1702
                index.columnNames[0] === column.name,
1703
        )
1704
        if (columnIndex) {
×
1705
            clonedTable.indices.splice(
×
1706
                clonedTable.indices.indexOf(columnIndex),
1707
                1,
1708
            )
1709
            upQueries.push(this.dropIndexSql(table, columnIndex))
×
1710
            downQueries.push(this.createIndexSql(table, columnIndex))
×
1711
        } else if (column.isUnique) {
×
1712
            // we splice constraints both from table uniques and indices.
1713
            const uniqueName =
1714
                this.connection.namingStrategy.uniqueConstraintName(table, [
×
1715
                    column.name,
1716
                ])
1717
            const foundUnique = clonedTable.uniques.find(
×
1718
                (unique) => unique.name === uniqueName,
×
1719
            )
1720
            if (foundUnique)
×
1721
                clonedTable.uniques.splice(
×
1722
                    clonedTable.uniques.indexOf(foundUnique),
1723
                    1,
1724
                )
1725

1726
            const indexName = this.connection.namingStrategy.indexName(table, [
×
1727
                column.name,
1728
            ])
1729
            const foundIndex = clonedTable.indices.find(
×
1730
                (index) => index.name === indexName,
×
1731
            )
1732
            if (foundIndex)
×
1733
                clonedTable.indices.splice(
×
1734
                    clonedTable.indices.indexOf(foundIndex),
1735
                    1,
1736
                )
1737

1738
            upQueries.push(
×
1739
                new Query(
1740
                    `ALTER TABLE ${this.escapePath(
1741
                        table,
1742
                    )} DROP INDEX \`${indexName}\``,
1743
                ),
1744
            )
1745
            downQueries.push(
×
1746
                new Query(
1747
                    `ALTER TABLE ${this.escapePath(
1748
                        table,
1749
                    )} ADD UNIQUE INDEX \`${indexName}\` (\`${column.name}\`)`,
1750
                ),
1751
            )
1752
        }
1753

1754
        upQueries.push(
×
1755
            new Query(
1756
                `ALTER TABLE ${this.escapePath(table)} DROP COLUMN \`${
1757
                    column.name
1758
                }\``,
1759
            ),
1760
        )
1761
        downQueries.push(
×
1762
            new Query(
1763
                `ALTER TABLE ${this.escapePath(
1764
                    table,
1765
                )} ADD ${this.buildCreateColumnSql(column, true)}`,
1766
            ),
1767
        )
1768

1769
        if (column.generatedType && column.asExpression) {
×
1770
            const currentDatabase = await this.getCurrentDatabase()
×
1771
            const deleteQuery = this.deleteTypeormMetadataSql({
×
1772
                schema: currentDatabase,
1773
                table: table.name,
1774
                type: MetadataTableType.GENERATED_COLUMN,
1775
                name: column.name,
1776
            })
1777
            const insertQuery = this.insertTypeormMetadataSql({
×
1778
                schema: currentDatabase,
1779
                table: table.name,
1780
                type: MetadataTableType.GENERATED_COLUMN,
1781
                name: column.name,
1782
                value: column.asExpression,
1783
            })
1784

1785
            upQueries.push(deleteQuery)
×
1786
            downQueries.push(insertQuery)
×
1787
        }
1788

1789
        await this.executeQueries(upQueries, downQueries)
×
1790

1791
        clonedTable.removeColumn(column)
×
1792
        this.replaceCachedTable(table, clonedTable)
×
1793
    }
1794

1795
    /**
1796
     * Drops the columns in the table.
1797
     */
1798
    async dropColumns(
1799
        tableOrName: Table | string,
1800
        columns: TableColumn[] | string[],
1801
    ): Promise<void> {
1802
        for (const column of columns) {
×
1803
            await this.dropColumn(tableOrName, column)
×
1804
        }
1805
    }
1806

1807
    /**
1808
     * Creates a new primary key.
1809
     */
1810
    async createPrimaryKey(
1811
        tableOrName: Table | string,
1812
        columnNames: string[],
1813
    ): Promise<void> {
1814
        const table = InstanceChecker.isTable(tableOrName)
×
1815
            ? tableOrName
1816
            : await this.getCachedTable(tableOrName)
1817
        const clonedTable = table.clone()
×
1818

1819
        const up = this.createPrimaryKeySql(table, columnNames)
×
1820
        const down = this.dropPrimaryKeySql(table)
×
1821

1822
        await this.executeQueries(up, down)
×
1823
        clonedTable.columns.forEach((column) => {
×
1824
            if (columnNames.find((columnName) => columnName === column.name))
×
1825
                column.isPrimary = true
×
1826
        })
1827
        this.replaceCachedTable(table, clonedTable)
×
1828
    }
1829

1830
    /**
1831
     * Updates composite primary keys.
1832
     */
1833
    async updatePrimaryKeys(
1834
        tableOrName: Table | string,
1835
        columns: TableColumn[],
1836
    ): Promise<void> {
1837
        const table = InstanceChecker.isTable(tableOrName)
×
1838
            ? tableOrName
1839
            : await this.getCachedTable(tableOrName)
1840
        const clonedTable = table.clone()
×
1841
        const columnNames = columns.map((column) => column.name)
×
1842
        const upQueries: Query[] = []
×
1843
        const downQueries: Query[] = []
×
1844

1845
        // if table have generated column, we must drop AUTO_INCREMENT before changing primary constraints.
1846
        const generatedColumn = clonedTable.columns.find(
×
1847
            (column) =>
1848
                column.isGenerated && column.generationStrategy === "increment",
×
1849
        )
1850
        if (generatedColumn) {
×
1851
            const nonGeneratedColumn = generatedColumn.clone()
×
1852
            nonGeneratedColumn.isGenerated = false
×
1853
            nonGeneratedColumn.generationStrategy = undefined
×
1854

1855
            upQueries.push(
×
1856
                new Query(
1857
                    `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1858
                        generatedColumn.name
1859
                    }\` ${this.buildCreateColumnSql(nonGeneratedColumn, true)}`,
1860
                ),
1861
            )
1862
            downQueries.push(
×
1863
                new Query(
1864
                    `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1865
                        nonGeneratedColumn.name
1866
                    }\` ${this.buildCreateColumnSql(generatedColumn, true)}`,
1867
                ),
1868
            )
1869
        }
1870

1871
        // if table already have primary columns, we must drop them.
1872
        const primaryColumns = clonedTable.primaryColumns
×
1873
        if (primaryColumns.length > 0) {
×
1874
            const columnNames = primaryColumns
×
1875
                .map((column) => `\`${column.name}\``)
×
1876
                .join(", ")
1877
            upQueries.push(
×
1878
                new Query(
1879
                    `ALTER TABLE ${this.escapePath(table)} DROP PRIMARY KEY`,
1880
                ),
1881
            )
1882
            downQueries.push(
×
1883
                new Query(
1884
                    `ALTER TABLE ${this.escapePath(
1885
                        table,
1886
                    )} ADD PRIMARY KEY (${columnNames})`,
1887
                ),
1888
            )
1889
        }
1890

1891
        // update columns in table.
1892
        clonedTable.columns
×
1893
            .filter((column) => columnNames.indexOf(column.name) !== -1)
×
1894
            .forEach((column) => (column.isPrimary = true))
×
1895

1896
        const columnNamesString = columnNames
×
1897
            .map((columnName) => `\`${columnName}\``)
×
1898
            .join(", ")
1899
        upQueries.push(
×
1900
            new Query(
1901
                `ALTER TABLE ${this.escapePath(
1902
                    table,
1903
                )} ADD PRIMARY KEY (${columnNamesString})`,
1904
            ),
1905
        )
1906
        downQueries.push(
×
1907
            new Query(`ALTER TABLE ${this.escapePath(table)} DROP PRIMARY KEY`),
1908
        )
1909

1910
        // if we already have generated column or column is changed to generated, and we dropped AUTO_INCREMENT property before, we must bring it back
1911
        const newOrExistGeneratedColumn = generatedColumn
×
1912
            ? generatedColumn
1913
            : columns.find(
1914
                  (column) =>
1915
                      column.isGenerated &&
×
1916
                      column.generationStrategy === "increment",
1917
              )
1918
        if (newOrExistGeneratedColumn) {
×
1919
            const nonGeneratedColumn = newOrExistGeneratedColumn.clone()
×
1920
            nonGeneratedColumn.isGenerated = false
×
1921
            nonGeneratedColumn.generationStrategy = undefined
×
1922

1923
            upQueries.push(
×
1924
                new Query(
1925
                    `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1926
                        nonGeneratedColumn.name
1927
                    }\` ${this.buildCreateColumnSql(
1928
                        newOrExistGeneratedColumn,
1929
                        true,
1930
                    )}`,
1931
                ),
1932
            )
1933
            downQueries.push(
×
1934
                new Query(
1935
                    `ALTER TABLE ${this.escapePath(table)} CHANGE \`${
1936
                        newOrExistGeneratedColumn.name
1937
                    }\` ${this.buildCreateColumnSql(nonGeneratedColumn, true)}`,
1938
                ),
1939
            )
1940

1941
            // if column changed to generated, we must update it in table
1942
            const changedGeneratedColumn = clonedTable.columns.find(
×
1943
                (column) => column.name === newOrExistGeneratedColumn.name,
×
1944
            )
1945
            changedGeneratedColumn!.isGenerated = true
×
1946
            changedGeneratedColumn!.generationStrategy = "increment"
×
1947
        }
1948

1949
        await this.executeQueries(upQueries, downQueries)
×
1950
        this.replaceCachedTable(table, clonedTable)
×
1951
    }
1952

1953
    /**
1954
     * Drops a primary key.
1955
     */
1956
    async dropPrimaryKey(tableOrName: Table | string): Promise<void> {
1957
        const table = InstanceChecker.isTable(tableOrName)
×
1958
            ? tableOrName
1959
            : await this.getCachedTable(tableOrName)
1960
        const up = this.dropPrimaryKeySql(table)
×
1961
        const down = this.createPrimaryKeySql(
×
1962
            table,
1963
            table.primaryColumns.map((column) => column.name),
×
1964
        )
1965
        await this.executeQueries(up, down)
×
1966
        table.primaryColumns.forEach((column) => {
×
1967
            column.isPrimary = false
×
1968
        })
1969
    }
1970

1971
    /**
1972
     * Creates a new unique constraint.
1973
     */
1974
    async createUniqueConstraint(
1975
        tableOrName: Table | string,
1976
        uniqueConstraint: TableUnique,
1977
    ): Promise<void> {
1978
        throw new TypeORMError(
×
1979
            `MySql does not support unique constraints. Use unique index instead.`,
1980
        )
1981
    }
1982

1983
    /**
1984
     * Creates a new unique constraints.
1985
     */
1986
    async createUniqueConstraints(
1987
        tableOrName: Table | string,
1988
        uniqueConstraints: TableUnique[],
1989
    ): Promise<void> {
1990
        throw new TypeORMError(
×
1991
            `MySql does not support unique constraints. Use unique index instead.`,
1992
        )
1993
    }
1994

1995
    /**
1996
     * Drops an unique constraint.
1997
     */
1998
    async dropUniqueConstraint(
1999
        tableOrName: Table | string,
2000
        uniqueOrName: TableUnique | string,
2001
    ): Promise<void> {
2002
        throw new TypeORMError(
×
2003
            `MySql does not support unique constraints. Use unique index instead.`,
2004
        )
2005
    }
2006

2007
    /**
2008
     * Drops an unique constraints.
2009
     */
2010
    async dropUniqueConstraints(
2011
        tableOrName: Table | string,
2012
        uniqueConstraints: TableUnique[],
2013
    ): Promise<void> {
2014
        throw new TypeORMError(
×
2015
            `MySql does not support unique constraints. Use unique index instead.`,
2016
        )
2017
    }
2018

2019
    /**
2020
     * Creates a new check constraint.
2021
     */
2022
    async createCheckConstraint(
2023
        tableOrName: Table | string,
2024
        checkConstraint: TableCheck,
2025
    ): Promise<void> {
2026
        throw new TypeORMError(`MySql does not support check constraints.`)
×
2027
    }
2028

2029
    /**
2030
     * Creates a new check constraints.
2031
     */
2032
    async createCheckConstraints(
2033
        tableOrName: Table | string,
2034
        checkConstraints: TableCheck[],
2035
    ): Promise<void> {
2036
        throw new TypeORMError(`MySql does not support check constraints.`)
×
2037
    }
2038

2039
    /**
2040
     * Drops check constraint.
2041
     */
2042
    async dropCheckConstraint(
2043
        tableOrName: Table | string,
2044
        checkOrName: TableCheck | string,
2045
    ): Promise<void> {
2046
        throw new TypeORMError(`MySql does not support check constraints.`)
×
2047
    }
2048

2049
    /**
2050
     * Drops check constraints.
2051
     */
2052
    async dropCheckConstraints(
2053
        tableOrName: Table | string,
2054
        checkConstraints: TableCheck[],
2055
    ): Promise<void> {
2056
        throw new TypeORMError(`MySql does not support check constraints.`)
×
2057
    }
2058

2059
    /**
2060
     * Creates a new exclusion constraint.
2061
     */
2062
    async createExclusionConstraint(
2063
        tableOrName: Table | string,
2064
        exclusionConstraint: TableExclusion,
2065
    ): Promise<void> {
2066
        throw new TypeORMError(`MySql does not support exclusion constraints.`)
×
2067
    }
2068

2069
    /**
2070
     * Creates a new exclusion constraints.
2071
     */
2072
    async createExclusionConstraints(
2073
        tableOrName: Table | string,
2074
        exclusionConstraints: TableExclusion[],
2075
    ): Promise<void> {
2076
        throw new TypeORMError(`MySql does not support exclusion constraints.`)
×
2077
    }
2078

2079
    /**
2080
     * Drops exclusion constraint.
2081
     */
2082
    async dropExclusionConstraint(
2083
        tableOrName: Table | string,
2084
        exclusionOrName: TableExclusion | string,
2085
    ): Promise<void> {
2086
        throw new TypeORMError(`MySql does not support exclusion constraints.`)
×
2087
    }
2088

2089
    /**
2090
     * Drops exclusion constraints.
2091
     */
2092
    async dropExclusionConstraints(
2093
        tableOrName: Table | string,
2094
        exclusionConstraints: TableExclusion[],
2095
    ): Promise<void> {
2096
        throw new TypeORMError(`MySql does not support exclusion constraints.`)
×
2097
    }
2098

2099
    /**
2100
     * Creates a new foreign key.
2101
     */
2102
    async createForeignKey(
2103
        tableOrName: Table | string,
2104
        foreignKey: TableForeignKey,
2105
    ): Promise<void> {
2106
        const table = InstanceChecker.isTable(tableOrName)
×
2107
            ? tableOrName
2108
            : await this.getCachedTable(tableOrName)
2109

2110
        // new FK may be passed without name. In this case we generate FK name manually.
2111
        if (!foreignKey.name)
×
2112
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
×
2113
                table,
2114
                foreignKey.columnNames,
2115
                this.getTablePath(foreignKey),
2116
                foreignKey.referencedColumnNames,
2117
            )
2118

2119
        const up = this.createForeignKeySql(table, foreignKey)
×
2120
        const down = this.dropForeignKeySql(table, foreignKey)
×
2121
        await this.executeQueries(up, down)
×
2122
        table.addForeignKey(foreignKey)
×
2123
    }
2124

2125
    /**
2126
     * Creates a new foreign keys.
2127
     */
2128
    async createForeignKeys(
2129
        tableOrName: Table | string,
2130
        foreignKeys: TableForeignKey[],
2131
    ): Promise<void> {
2132
        const promises = foreignKeys.map((foreignKey) =>
×
2133
            this.createForeignKey(tableOrName, foreignKey),
×
2134
        )
2135
        await Promise.all(promises)
×
2136
    }
2137

2138
    /**
2139
     * Drops a foreign key.
2140
     */
2141
    async dropForeignKey(
2142
        tableOrName: Table | string,
2143
        foreignKeyOrName: TableForeignKey | string,
2144
    ): Promise<void> {
2145
        const table = InstanceChecker.isTable(tableOrName)
×
2146
            ? tableOrName
2147
            : await this.getCachedTable(tableOrName)
2148
        const foreignKey = InstanceChecker.isTableForeignKey(foreignKeyOrName)
×
2149
            ? foreignKeyOrName
2150
            : table.foreignKeys.find((fk) => fk.name === foreignKeyOrName)
×
2151
        if (!foreignKey)
×
2152
            throw new TypeORMError(
×
2153
                `Supplied foreign key was not found in table ${table.name}`,
2154
            )
2155

2156
        const up = this.dropForeignKeySql(table, foreignKey)
×
2157
        const down = this.createForeignKeySql(table, foreignKey)
×
2158
        await this.executeQueries(up, down)
×
2159
        table.removeForeignKey(foreignKey)
×
2160
    }
2161

2162
    /**
2163
     * Drops a foreign keys from the table.
2164
     */
2165
    async dropForeignKeys(
2166
        tableOrName: Table | string,
2167
        foreignKeys: TableForeignKey[],
2168
    ): Promise<void> {
2169
        const promises = foreignKeys.map((foreignKey) =>
×
2170
            this.dropForeignKey(tableOrName, foreignKey),
×
2171
        )
2172
        await Promise.all(promises)
×
2173
    }
2174

2175
    /**
2176
     * Creates a new index.
2177
     */
2178
    async createIndex(
2179
        tableOrName: Table | string,
2180
        index: TableIndex,
2181
    ): Promise<void> {
2182
        const table = InstanceChecker.isTable(tableOrName)
×
2183
            ? tableOrName
2184
            : await this.getCachedTable(tableOrName)
2185

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

2189
        const up = this.createIndexSql(table, index)
×
2190
        const down = this.dropIndexSql(table, index)
×
2191
        await this.executeQueries(up, down)
×
2192
        table.addIndex(index, true)
×
2193
    }
2194

2195
    /**
2196
     * Creates a new indices
2197
     */
2198
    async createIndices(
2199
        tableOrName: Table | string,
2200
        indices: TableIndex[],
2201
    ): Promise<void> {
2202
        const promises = indices.map((index) =>
×
2203
            this.createIndex(tableOrName, index),
×
2204
        )
2205
        await Promise.all(promises)
×
2206
    }
2207

2208
    /**
2209
     * Drops an index.
2210
     */
2211
    async dropIndex(
2212
        tableOrName: Table | string,
2213
        indexOrName: TableIndex | string,
2214
    ): Promise<void> {
2215
        const table = InstanceChecker.isTable(tableOrName)
×
2216
            ? tableOrName
2217
            : await this.getCachedTable(tableOrName)
2218
        const index = InstanceChecker.isTableIndex(indexOrName)
×
2219
            ? indexOrName
2220
            : table.indices.find((i) => i.name === indexOrName)
×
2221
        if (!index)
×
2222
            throw new TypeORMError(
×
2223
                `Supplied index ${indexOrName} was not found in table ${table.name}`,
2224
            )
2225

2226
        // old index may be passed without name. In this case we generate index name manually.
2227
        if (!index.name) index.name = this.generateIndexName(table, index)
×
2228

2229
        const up = this.dropIndexSql(table, index)
×
2230
        const down = this.createIndexSql(table, index)
×
2231
        await this.executeQueries(up, down)
×
2232
        table.removeIndex(index, true)
×
2233
    }
2234

2235
    /**
2236
     * Drops an indices from the table.
2237
     */
2238
    async dropIndices(
2239
        tableOrName: Table | string,
2240
        indices: TableIndex[],
2241
    ): Promise<void> {
2242
        const promises = indices.map((index) =>
×
2243
            this.dropIndex(tableOrName, index),
×
2244
        )
2245
        await Promise.all(promises)
×
2246
    }
2247

2248
    /**
2249
     * Clears all table contents.
2250
     * Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
2251
     */
2252
    async clearTable(tableOrName: Table | string): Promise<void> {
2253
        await this.query(`TRUNCATE TABLE ${this.escapePath(tableOrName)}`)
×
2254
    }
2255

2256
    /**
2257
     * Removes all tables from the currently connected database.
2258
     * Be careful using this method and avoid using it in production or migrations
2259
     * (because it can clear all your database).
2260
     */
2261
    async clearDatabase(database?: string): Promise<void> {
2262
        const dbName = database ? database : this.driver.database
×
2263
        if (dbName) {
×
2264
            const isDatabaseExist = await this.hasDatabase(dbName)
×
2265
            if (!isDatabaseExist) return Promise.resolve()
×
2266
        } else {
2267
            throw new TypeORMError(
×
2268
                `Can not clear database. No database is specified`,
2269
            )
2270
        }
2271

2272
        const isAnotherTransactionActive = this.isTransactionActive
×
2273
        if (!isAnotherTransactionActive) await this.startTransaction()
×
2274
        try {
×
2275
            const selectViewDropsQuery = `SELECT concat('DROP VIEW IF EXISTS \`', table_schema, '\`.\`', table_name, '\`') AS \`query\` FROM \`INFORMATION_SCHEMA\`.\`VIEWS\` WHERE \`TABLE_SCHEMA\` = '${dbName}'`
×
2276
            const dropViewQueries: ObjectLiteral[] = await this.query(
×
2277
                selectViewDropsQuery,
2278
            )
2279
            await Promise.all(
×
2280
                dropViewQueries.map((q) => this.query(q["query"])),
×
2281
            )
2282

2283
            const disableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 0;`
×
2284
            const dropTablesQuery = `SELECT concat('DROP TABLE IF EXISTS \`', table_schema, '\`.\`', table_name, '\`') AS \`query\` FROM \`INFORMATION_SCHEMA\`.\`TABLES\` WHERE \`TABLE_SCHEMA\` = '${dbName}'`
×
2285
            const enableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 1;`
×
2286

2287
            await this.query(disableForeignKeysCheckQuery)
×
2288
            const dropQueries: ObjectLiteral[] = await this.query(
×
2289
                dropTablesQuery,
2290
            )
2291
            await Promise.all(
×
2292
                dropQueries.map((query) => this.query(query["query"])),
×
2293
            )
2294
            await this.query(enableForeignKeysCheckQuery)
×
2295

2296
            if (!isAnotherTransactionActive) await this.commitTransaction()
×
2297
        } catch (error) {
2298
            try {
×
2299
                // we throw original error even if rollback thrown an error
2300
                if (!isAnotherTransactionActive)
×
2301
                    await this.rollbackTransaction()
×
2302
            } catch (rollbackError) {}
2303
            throw error
×
2304
        }
2305
    }
2306

2307
    // -------------------------------------------------------------------------
2308
    // Protected Methods
2309
    // -------------------------------------------------------------------------
2310

2311
    protected async loadViews(viewNames?: string[]): Promise<View[]> {
2312
        const hasTable = await this.hasTable(this.getTypeormMetadataTableName())
×
2313
        if (!hasTable) {
×
2314
            return []
×
2315
        }
2316

2317
        if (!viewNames) {
×
2318
            viewNames = []
×
2319
        }
2320

2321
        const currentDatabase = await this.getCurrentDatabase()
×
2322
        const viewsCondition = viewNames
×
2323
            .map((tableName) => {
2324
                let { database, tableName: name } =
2325
                    this.driver.parseTableName(tableName)
×
2326

2327
                if (!database) {
×
2328
                    database = currentDatabase
×
2329
                }
2330

2331
                return `(\`t\`.\`schema\` = '${database}' AND \`t\`.\`name\` = '${name}')`
×
2332
            })
2333
            .join(" OR ")
2334

2335
        const query =
2336
            `SELECT \`t\`.*, \`v\`.\`check_option\` FROM ${this.escapePath(
×
2337
                this.getTypeormMetadataTableName(),
2338
            )} \`t\` ` +
2339
            `INNER JOIN \`information_schema\`.\`views\` \`v\` ON \`v\`.\`table_schema\` = \`t\`.\`schema\` AND \`v\`.\`table_name\` = \`t\`.\`name\` WHERE \`t\`.\`type\` = '${
2340
                MetadataTableType.VIEW
2341
            }' ${viewsCondition ? `AND (${viewsCondition})` : ""}`
×
2342
        const dbViews = await this.query(query)
×
2343
        return dbViews.map((dbView: any) => {
×
2344
            const view = new View()
×
2345
            const db =
2346
                dbView["schema"] === currentDatabase
×
2347
                    ? undefined
2348
                    : dbView["schema"]
2349
            view.database = dbView["schema"]
×
2350
            view.name = this.driver.buildTableName(
×
2351
                dbView["name"],
2352
                undefined,
2353
                db,
2354
            )
2355
            view.expression = dbView["value"]
×
2356
            return view
×
2357
        })
2358
    }
2359

2360
    /**
2361
     * Loads all tables (with given names) from the database and creates a Table from them.
2362
     */
2363
    protected async loadTables(tableNames?: string[]): Promise<Table[]> {
2364
        if (tableNames && tableNames.length === 0) {
×
2365
            return []
×
2366
        }
2367

2368
        const currentDatabase = await this.getCurrentDatabase()
×
2369

2370
        // The following SQL brought to you by:
2371
        //   A terrible understanding of https://dev.mysql.com/doc/refman/8.0/en/information-schema-optimization.html
2372
        //
2373
        // Short Version:
2374
        // INFORMATION_SCHEMA is a weird metadata virtual table and follows VERY FEW of the normal
2375
        // query optimization rules.  Depending on the columns you query against & the columns you're SELECTing
2376
        // there can be a drastically different query performance - this is because the tables map to
2377
        // data on the disk and some pieces of data require a scan of the data directory, the database files, etc
2378

2379
        // With most of these, you'll want to do an `EXPLAIN` when making changes to make sure
2380
        // the changes you're making aren't changing the query performance profile negatively
2381
        // When you do the explain you'll want to look at the `Extra` field -
2382
        // It will look something like: "Using where; {FILE_OPENING}; Scanned {DB_NUM} databases"
2383
        // FILE_OPENING will commonly be OPEN_FRM_ONLY or OPEN_FULL_TABLE - you want to aim to NOT do
2384
        // an OPEN_FULL_TABLE unless necessary. DB_NUM may be a number or "all" - you really want to
2385
        // keep this to 0 or 1.  Ideally 0. "All" means you've scanned all databases - not good.
2386
        //
2387
        // For more info, see the above link to the MySQL docs.
2388
        //
2389
        // Something not noted in the docs is that complex `WHERE` clauses - such as `OR` expressions -
2390
        // will cause the query to not hit the optimizations & do full scans.  This is why
2391
        // a number of queries below do `UNION`s of single `WHERE` clauses.
2392

2393
        const dbTables: {
2394
            TABLE_SCHEMA: string
2395
            TABLE_NAME: string
2396
            TABLE_COMMENT: string
2397
        }[] = []
×
2398

2399
        if (!tableNames) {
×
2400
            // Since we don't have any of this data we have to do a scan
2401
            const tablesSql = `SELECT \`TABLE_SCHEMA\`, \`TABLE_NAME\`, \`TABLE_COMMENT\` FROM \`INFORMATION_SCHEMA\`.\`TABLES\``
×
2402

2403
            dbTables.push(...(await this.query(tablesSql)))
×
2404
        } else {
2405
            // Avoid data directory scan: TABLE_SCHEMA
2406
            // Avoid database directory scan: TABLE_NAME
2407
            // We only use `TABLE_SCHEMA` and `TABLE_NAME` which is `SKIP_OPEN_TABLE`
2408
            const tablesSql = tableNames
×
2409
                .filter((tableName) => tableName)
×
2410
                .map((tableName) => {
2411
                    let { database, tableName: name } =
2412
                        this.driver.parseTableName(tableName)
×
2413

2414
                    if (!database) {
×
2415
                        database = currentDatabase
×
2416
                    }
2417

2418
                    return `SELECT \`TABLE_SCHEMA\`, \`TABLE_NAME\`, \`TABLE_COMMENT\` FROM \`INFORMATION_SCHEMA\`.\`TABLES\` WHERE \`TABLE_SCHEMA\` = '${database}' AND \`TABLE_NAME\` = '${name}'`
×
2419
                })
2420
                .join(" UNION ")
2421

2422
            dbTables.push(...(await this.query(tablesSql)))
×
2423
        }
2424

2425
        // if tables were not found in the db, no need to proceed
2426
        if (!dbTables.length) return []
×
2427

2428
        // Avoid data directory scan: TABLE_SCHEMA
2429
        // Avoid database directory scan: TABLE_NAME
2430
        // Full columns: CARDINALITY & INDEX_TYPE - everything else is FRM only
2431
        const statsSubquerySql = dbTables
×
2432
            .map(({ TABLE_SCHEMA, TABLE_NAME }) => {
2433
                return `
×
2434
                SELECT
2435
                    *
2436
                FROM \`INFORMATION_SCHEMA\`.\`STATISTICS\`
2437
                WHERE
2438
                    \`TABLE_SCHEMA\` = '${TABLE_SCHEMA}'
2439
                    AND
2440
                    \`TABLE_NAME\` = '${TABLE_NAME}'
2441
            `
2442
            })
2443
            .join(" UNION ")
2444

2445
        // Avoid data directory scan: TABLE_SCHEMA
2446
        // Avoid database directory scan: TABLE_NAME
2447
        // All columns will hit the full table.
2448
        const kcuSubquerySql = dbTables
×
2449
            .map(({ TABLE_SCHEMA, TABLE_NAME }) => {
2450
                return `
×
2451
                SELECT
2452
                    *
2453
                FROM \`INFORMATION_SCHEMA\`.\`KEY_COLUMN_USAGE\` \`kcu\`
2454
                WHERE
2455
                    \`kcu\`.\`TABLE_SCHEMA\` = '${TABLE_SCHEMA}'
2456
                    AND
2457
                    \`kcu\`.\`TABLE_NAME\` = '${TABLE_NAME}'
2458
            `
2459
            })
2460
            .join(" UNION ")
2461

2462
        // Avoid data directory scan: CONSTRAINT_SCHEMA
2463
        // Avoid database directory scan: TABLE_NAME
2464
        // All columns will hit the full table.
2465
        const rcSubquerySql = dbTables
×
2466
            .map(({ TABLE_SCHEMA, TABLE_NAME }) => {
2467
                return `
×
2468
                SELECT
2469
                    *
2470
                FROM \`INFORMATION_SCHEMA\`.\`REFERENTIAL_CONSTRAINTS\`
2471
                WHERE
2472
                    \`CONSTRAINT_SCHEMA\` = '${TABLE_SCHEMA}'
2473
                    AND
2474
                    \`TABLE_NAME\` = '${TABLE_NAME}'
2475
            `
2476
            })
2477
            .join(" UNION ")
2478

2479
        // Avoid data directory scan: TABLE_SCHEMA
2480
        // Avoid database directory scan: TABLE_NAME
2481
        // OPEN_FRM_ONLY applies to all columns
2482
        const columnsSql = dbTables
×
2483
            .map(({ TABLE_SCHEMA, TABLE_NAME }) => {
2484
                return `
×
2485
                SELECT
2486
                    *
2487
                FROM
2488
                    \`INFORMATION_SCHEMA\`.\`COLUMNS\`
2489
                WHERE
2490
                    \`TABLE_SCHEMA\` = '${TABLE_SCHEMA}'
2491
                    AND
2492
                    \`TABLE_NAME\` = '${TABLE_NAME}'
2493
                `
2494
            })
2495
            .join(" UNION ")
2496

2497
        // No Optimizations are available for COLLATIONS
2498
        const collationsSql = `
×
2499
            SELECT
2500
                \`SCHEMA_NAME\`,
2501
                \`DEFAULT_CHARACTER_SET_NAME\` as \`CHARSET\`,
2502
                \`DEFAULT_COLLATION_NAME\` AS \`COLLATION\`
2503
            FROM \`INFORMATION_SCHEMA\`.\`SCHEMATA\`
2504
            `
2505

2506
        // Key Column Usage but only for PKs
2507
        const primaryKeySql = `SELECT * FROM (${kcuSubquerySql}) \`kcu\` WHERE \`CONSTRAINT_NAME\` = 'PRIMARY'`
×
2508

2509
        // Combine stats & referential constraints
2510
        const indicesSql = `
×
2511
            SELECT
2512
                \`s\`.*
2513
            FROM (${statsSubquerySql}) \`s\`
2514
            LEFT JOIN (${rcSubquerySql}) \`rc\`
2515
                ON
2516
                    \`s\`.\`INDEX_NAME\` = \`rc\`.\`CONSTRAINT_NAME\`
2517
                    AND
2518
                    \`s\`.\`TABLE_SCHEMA\` = \`rc\`.\`CONSTRAINT_SCHEMA\`
2519
            WHERE
2520
                \`s\`.\`INDEX_NAME\` != 'PRIMARY'
2521
                AND
2522
                \`rc\`.\`CONSTRAINT_NAME\` IS NULL
2523
            `
2524

2525
        // Combine Key Column Usage & Referential Constraints
2526
        const foreignKeysSql = `
×
2527
            SELECT
2528
                \`kcu\`.\`TABLE_SCHEMA\`,
2529
                \`kcu\`.\`TABLE_NAME\`,
2530
                \`kcu\`.\`CONSTRAINT_NAME\`,
2531
                \`kcu\`.\`COLUMN_NAME\`,
2532
                \`kcu\`.\`REFERENCED_TABLE_SCHEMA\`,
2533
                \`kcu\`.\`REFERENCED_TABLE_NAME\`,
2534
                \`kcu\`.\`REFERENCED_COLUMN_NAME\`,
2535
                \`rc\`.\`DELETE_RULE\` \`ON_DELETE\`,
2536
                \`rc\`.\`UPDATE_RULE\` \`ON_UPDATE\`
2537
            FROM (${kcuSubquerySql}) \`kcu\`
2538
            INNER JOIN (${rcSubquerySql}) \`rc\`
2539
                ON
2540
                    \`rc\`.\`CONSTRAINT_SCHEMA\` = \`kcu\`.\`CONSTRAINT_SCHEMA\`
2541
                    AND
2542
                    \`rc\`.\`TABLE_NAME\` = \`kcu\`.\`TABLE_NAME\`
2543
                    AND
2544
                    \`rc\`.\`CONSTRAINT_NAME\` = \`kcu\`.\`CONSTRAINT_NAME\`
2545
            `
2546

2547
        const [
2548
            dbColumns,
2549
            dbPrimaryKeys,
2550
            dbCollations,
2551
            dbIndices,
2552
            dbForeignKeys,
2553
        ]: ObjectLiteral[][] = await Promise.all([
×
2554
            this.query(columnsSql),
2555
            this.query(primaryKeySql),
2556
            this.query(collationsSql),
2557
            this.query(indicesSql),
2558
            this.query(foreignKeysSql),
2559
        ])
2560

2561
        const isMariaDb = this.driver.options.type === "mariadb"
×
2562
        const dbVersion = this.driver.version
×
2563

2564
        // create tables for loaded tables
2565
        return Promise.all(
×
2566
            dbTables.map(async (dbTable) => {
2567
                const table = new Table()
×
2568

2569
                const dbCollation = dbCollations.find(
×
2570
                    (coll) => coll["SCHEMA_NAME"] === dbTable["TABLE_SCHEMA"],
×
2571
                )!
2572
                const defaultCollation = dbCollation["COLLATION"]
×
2573
                const defaultCharset = dbCollation["CHARSET"]
×
2574

2575
                // We do not need to join database name, when database is by default.
2576
                const db =
2577
                    dbTable["TABLE_SCHEMA"] === currentDatabase
×
2578
                        ? undefined
2579
                        : dbTable["TABLE_SCHEMA"]
2580
                table.database = dbTable["TABLE_SCHEMA"]
×
2581
                table.name = this.driver.buildTableName(
×
2582
                    dbTable["TABLE_NAME"],
2583
                    undefined,
2584
                    db,
2585
                )
2586

2587
                // create columns from the loaded columns
2588
                table.columns = await Promise.all(
×
2589
                    dbColumns
2590
                        .filter(
2591
                            (dbColumn) =>
2592
                                dbColumn["TABLE_NAME"] ===
×
2593
                                    dbTable["TABLE_NAME"] &&
2594
                                dbColumn["TABLE_SCHEMA"] ===
2595
                                    dbTable["TABLE_SCHEMA"],
2596
                        )
2597
                        .map(async (dbColumn) => {
2598
                            const columnUniqueIndices = dbIndices.filter(
×
2599
                                (dbIndex) => {
2600
                                    return (
×
2601
                                        dbIndex["TABLE_NAME"] ===
×
2602
                                            dbTable["TABLE_NAME"] &&
2603
                                        dbIndex["TABLE_SCHEMA"] ===
2604
                                            dbTable["TABLE_SCHEMA"] &&
2605
                                        dbIndex["COLUMN_NAME"] ===
2606
                                            dbColumn["COLUMN_NAME"] &&
2607
                                        parseInt(dbIndex["NON_UNIQUE"], 10) ===
2608
                                            0
2609
                                    )
2610
                                },
2611
                            )
2612

2613
                            const tableMetadata =
2614
                                this.connection.entityMetadatas.find(
×
2615
                                    (metadata) =>
2616
                                        this.getTablePath(table) ===
×
2617
                                        this.getTablePath(metadata),
2618
                                )
2619
                            const hasIgnoredIndex =
2620
                                columnUniqueIndices.length > 0 &&
×
2621
                                tableMetadata &&
2622
                                tableMetadata.indices.some((index) => {
2623
                                    return columnUniqueIndices.some(
×
2624
                                        (uniqueIndex) => {
2625
                                            return (
×
2626
                                                index.name ===
×
2627
                                                    uniqueIndex["INDEX_NAME"] &&
2628
                                                index.synchronize === false
2629
                                            )
2630
                                        },
2631
                                    )
2632
                                })
2633

2634
                            const isConstraintComposite =
2635
                                columnUniqueIndices.every((uniqueIndex) => {
×
2636
                                    return dbIndices.some(
×
2637
                                        (dbIndex) =>
2638
                                            dbIndex["INDEX_NAME"] ===
×
2639
                                                uniqueIndex["INDEX_NAME"] &&
2640
                                            dbIndex["COLUMN_NAME"] !==
2641
                                                dbColumn["COLUMN_NAME"],
2642
                                    )
2643
                                })
2644

2645
                            const tableColumn = new TableColumn()
×
2646
                            tableColumn.name = dbColumn["COLUMN_NAME"]
×
2647
                            tableColumn.type =
×
2648
                                dbColumn["DATA_TYPE"].toLowerCase()
2649

2650
                            // since mysql 8.0, "geometrycollection" returned as "geomcollection"
2651
                            // typeorm still use "geometrycollection"
2652
                            if (tableColumn.type === "geomcollection") {
×
2653
                                tableColumn.type = "geometrycollection"
×
2654
                            }
2655

2656
                            tableColumn.zerofill =
×
2657
                                dbColumn["COLUMN_TYPE"].indexOf("zerofill") !==
2658
                                -1
2659
                            tableColumn.unsigned = tableColumn.zerofill
×
2660
                                ? true
2661
                                : dbColumn["COLUMN_TYPE"].indexOf(
2662
                                      "unsigned",
2663
                                  ) !== -1
2664
                            if (
×
2665
                                this.driver.withWidthColumnTypes.indexOf(
2666
                                    tableColumn.type as ColumnType,
2667
                                ) !== -1
2668
                            ) {
2669
                                const width = dbColumn["COLUMN_TYPE"].substring(
×
2670
                                    dbColumn["COLUMN_TYPE"].indexOf("(") + 1,
2671
                                    dbColumn["COLUMN_TYPE"].indexOf(")"),
2672
                                )
2673
                                tableColumn.width =
×
2674
                                    width &&
×
2675
                                    !this.isDefaultColumnWidth(
2676
                                        table,
2677
                                        tableColumn,
2678
                                        parseInt(width),
2679
                                    )
2680
                                        ? parseInt(width)
2681
                                        : undefined
2682
                            }
2683

2684
                            if (
×
2685
                                dbColumn["COLUMN_DEFAULT"] === null ||
×
2686
                                dbColumn["COLUMN_DEFAULT"] === undefined ||
2687
                                (isMariaDb &&
2688
                                    dbColumn["COLUMN_DEFAULT"] === "NULL")
2689
                            ) {
2690
                                tableColumn.default = undefined
×
2691
                            } else if (
×
2692
                                /^CURRENT_TIMESTAMP(\([0-9]*\))?$/i.test(
2693
                                    dbColumn["COLUMN_DEFAULT"],
2694
                                )
2695
                            ) {
2696
                                // New versions of MariaDB return expressions in lowercase.  We need to set it in
2697
                                // uppercase so the comparison in MysqlDriver#compareDefaultValues does not fail.
2698
                                tableColumn.default =
×
2699
                                    dbColumn["COLUMN_DEFAULT"].toUpperCase()
2700
                            } else if (
×
2701
                                isMariaDb &&
×
2702
                                VersionUtils.isGreaterOrEqual(
2703
                                    dbVersion,
2704
                                    "10.2.7",
2705
                                )
2706
                            ) {
2707
                                // MariaDB started adding quotes to literals in COLUMN_DEFAULT since version 10.2.7
2708
                                // See https://mariadb.com/kb/en/library/information-schema-columns-table/
2709
                                tableColumn.default = dbColumn["COLUMN_DEFAULT"]
×
2710
                            } else {
2711
                                tableColumn.default = `'${dbColumn["COLUMN_DEFAULT"]}'`
×
2712
                            }
2713

2714
                            if (dbColumn["EXTRA"].indexOf("on update") !== -1) {
×
2715
                                // New versions of MariaDB return expressions in lowercase.  We need to set it in
2716
                                // uppercase so the comparison in MysqlDriver#compareExtraValues does not fail.
2717
                                tableColumn.onUpdate = dbColumn["EXTRA"]
×
2718
                                    .substring(
2719
                                        dbColumn["EXTRA"].indexOf("on update") +
2720
                                            10,
2721
                                    )
2722
                                    .toUpperCase()
2723
                            }
2724

2725
                            if (dbColumn["GENERATION_EXPRESSION"]) {
×
2726
                                tableColumn.generatedType =
×
2727
                                    dbColumn["EXTRA"].indexOf("VIRTUAL") !== -1
×
2728
                                        ? "VIRTUAL"
2729
                                        : "STORED"
2730

2731
                                // We cannot relay on information_schema.columns.generation_expression, because it is formatted different.
2732
                                const asExpressionQuery =
2733
                                    this.selectTypeormMetadataSql({
×
2734
                                        schema: dbTable["TABLE_SCHEMA"],
2735
                                        table: dbTable["TABLE_NAME"],
2736
                                        type: MetadataTableType.GENERATED_COLUMN,
2737
                                        name: tableColumn.name,
2738
                                    })
2739

2740
                                const results = await this.query(
×
2741
                                    asExpressionQuery.query,
2742
                                    asExpressionQuery.parameters,
2743
                                )
2744
                                if (results[0] && results[0].value) {
×
2745
                                    tableColumn.asExpression = results[0].value
×
2746
                                } else {
2747
                                    tableColumn.asExpression = ""
×
2748
                                }
2749
                            }
2750

2751
                            tableColumn.isUnique =
×
2752
                                columnUniqueIndices.length > 0 &&
×
2753
                                !hasIgnoredIndex &&
2754
                                !isConstraintComposite
2755

2756
                            if (isMariaDb && tableColumn.generatedType) {
×
2757
                                // do nothing - MariaDB does not support NULL/NOT NULL expressions for generated columns
2758
                            } else {
2759
                                tableColumn.isNullable =
×
2760
                                    dbColumn["IS_NULLABLE"] === "YES"
2761
                            }
2762

2763
                            tableColumn.isPrimary = dbPrimaryKeys.some(
×
2764
                                (dbPrimaryKey) => {
2765
                                    return (
×
2766
                                        dbPrimaryKey["TABLE_NAME"] ===
×
2767
                                            dbColumn["TABLE_NAME"] &&
2768
                                        dbPrimaryKey["TABLE_SCHEMA"] ===
2769
                                            dbColumn["TABLE_SCHEMA"] &&
2770
                                        dbPrimaryKey["COLUMN_NAME"] ===
2771
                                            dbColumn["COLUMN_NAME"]
2772
                                    )
2773
                                },
2774
                            )
2775
                            tableColumn.isGenerated =
×
2776
                                dbColumn["EXTRA"].indexOf("auto_increment") !==
2777
                                -1
2778
                            if (tableColumn.isGenerated)
×
2779
                                tableColumn.generationStrategy = "increment"
×
2780

2781
                            tableColumn.comment =
×
2782
                                typeof dbColumn["COLUMN_COMMENT"] ===
×
2783
                                    "string" &&
2784
                                dbColumn["COLUMN_COMMENT"].length === 0
2785
                                    ? undefined
2786
                                    : dbColumn["COLUMN_COMMENT"]
2787
                            if (dbColumn["CHARACTER_SET_NAME"])
×
2788
                                tableColumn.charset =
×
2789
                                    dbColumn["CHARACTER_SET_NAME"] ===
×
2790
                                    defaultCharset
2791
                                        ? undefined
2792
                                        : dbColumn["CHARACTER_SET_NAME"]
2793
                            if (dbColumn["COLLATION_NAME"])
×
2794
                                tableColumn.collation =
×
2795
                                    dbColumn["COLLATION_NAME"] ===
×
2796
                                    defaultCollation
2797
                                        ? undefined
2798
                                        : dbColumn["COLLATION_NAME"]
2799

2800
                            // check only columns that have length property
2801
                            if (
×
2802
                                this.driver.withLengthColumnTypes.indexOf(
×
2803
                                    tableColumn.type as ColumnType,
2804
                                ) !== -1 &&
2805
                                dbColumn["CHARACTER_MAXIMUM_LENGTH"]
2806
                            ) {
2807
                                const length =
2808
                                    dbColumn[
×
2809
                                        "CHARACTER_MAXIMUM_LENGTH"
2810
                                    ].toString()
2811
                                tableColumn.length =
×
2812
                                    !this.isDefaultColumnLength(
×
2813
                                        table,
2814
                                        tableColumn,
2815
                                        length,
2816
                                    )
2817
                                        ? length
2818
                                        : ""
2819
                            }
2820

2821
                            if (
×
2822
                                tableColumn.type === "decimal" ||
×
2823
                                tableColumn.type === "double" ||
2824
                                tableColumn.type === "float"
2825
                            ) {
2826
                                if (
×
2827
                                    dbColumn["NUMERIC_PRECISION"] !== null &&
×
2828
                                    !this.isDefaultColumnPrecision(
2829
                                        table,
2830
                                        tableColumn,
2831
                                        dbColumn["NUMERIC_PRECISION"],
2832
                                    )
2833
                                )
2834
                                    tableColumn.precision = parseInt(
×
2835
                                        dbColumn["NUMERIC_PRECISION"],
2836
                                    )
2837
                                if (
×
2838
                                    dbColumn["NUMERIC_SCALE"] !== null &&
×
2839
                                    !this.isDefaultColumnScale(
2840
                                        table,
2841
                                        tableColumn,
2842
                                        dbColumn["NUMERIC_SCALE"],
2843
                                    )
2844
                                )
2845
                                    tableColumn.scale = parseInt(
×
2846
                                        dbColumn["NUMERIC_SCALE"],
2847
                                    )
2848
                            }
2849

2850
                            if (
×
2851
                                tableColumn.type === "enum" ||
×
2852
                                tableColumn.type === "simple-enum" ||
2853
                                tableColumn.type === "set"
2854
                            ) {
2855
                                const colType = dbColumn["COLUMN_TYPE"]
×
2856
                                const items = colType
×
2857
                                    .substring(
2858
                                        colType.indexOf("(") + 1,
2859
                                        colType.lastIndexOf(")"),
2860
                                    )
2861
                                    .split(",")
2862
                                tableColumn.enum = (items as string[]).map(
×
2863
                                    (item) => {
2864
                                        return item.substring(
×
2865
                                            1,
2866
                                            item.length - 1,
2867
                                        )
2868
                                    },
2869
                                )
2870
                                tableColumn.length = ""
×
2871
                            }
2872

2873
                            if (
×
2874
                                (tableColumn.type === "datetime" ||
×
2875
                                    tableColumn.type === "time" ||
2876
                                    tableColumn.type === "timestamp") &&
2877
                                dbColumn["DATETIME_PRECISION"] !== null &&
2878
                                dbColumn["DATETIME_PRECISION"] !== undefined &&
2879
                                !this.isDefaultColumnPrecision(
2880
                                    table,
2881
                                    tableColumn,
2882
                                    parseInt(dbColumn["DATETIME_PRECISION"]),
2883
                                )
2884
                            ) {
2885
                                tableColumn.precision = parseInt(
×
2886
                                    dbColumn["DATETIME_PRECISION"],
2887
                                )
2888
                            }
2889

2890
                            return tableColumn
×
2891
                        }),
2892
                )
2893

2894
                // find foreign key constraints of table, group them by constraint name and build TableForeignKey.
2895
                const tableForeignKeyConstraints = OrmUtils.uniq(
×
2896
                    dbForeignKeys.filter((dbForeignKey) => {
2897
                        return (
×
2898
                            dbForeignKey["TABLE_NAME"] ===
×
2899
                                dbTable["TABLE_NAME"] &&
2900
                            dbForeignKey["TABLE_SCHEMA"] ===
2901
                                dbTable["TABLE_SCHEMA"]
2902
                        )
2903
                    }),
2904
                    (dbForeignKey) => dbForeignKey["CONSTRAINT_NAME"],
×
2905
                )
2906

2907
                table.foreignKeys = tableForeignKeyConstraints.map(
×
2908
                    (dbForeignKey) => {
2909
                        const foreignKeys = dbForeignKeys.filter(
×
2910
                            (dbFk) =>
2911
                                dbFk["CONSTRAINT_NAME"] ===
×
2912
                                dbForeignKey["CONSTRAINT_NAME"],
2913
                        )
2914

2915
                        // if referenced table located in currently used db, we don't need to concat db name to table name.
2916
                        const database =
2917
                            dbForeignKey["REFERENCED_TABLE_SCHEMA"] ===
×
2918
                            currentDatabase
2919
                                ? undefined
2920
                                : dbForeignKey["REFERENCED_TABLE_SCHEMA"]
2921
                        const referencedTableName = this.driver.buildTableName(
×
2922
                            dbForeignKey["REFERENCED_TABLE_NAME"],
2923
                            undefined,
2924
                            database,
2925
                        )
2926

2927
                        return new TableForeignKey({
×
2928
                            name: dbForeignKey["CONSTRAINT_NAME"],
2929
                            columnNames: foreignKeys.map(
2930
                                (dbFk) => dbFk["COLUMN_NAME"],
×
2931
                            ),
2932
                            referencedDatabase:
2933
                                dbForeignKey["REFERENCED_TABLE_SCHEMA"],
2934
                            referencedTableName: referencedTableName,
2935
                            referencedColumnNames: foreignKeys.map(
2936
                                (dbFk) => dbFk["REFERENCED_COLUMN_NAME"],
×
2937
                            ),
2938
                            onDelete: dbForeignKey["ON_DELETE"],
2939
                            onUpdate: dbForeignKey["ON_UPDATE"],
2940
                        })
2941
                    },
2942
                )
2943

2944
                // find index constraints of table, group them by constraint name and build TableIndex.
2945
                const tableIndexConstraints = OrmUtils.uniq(
×
2946
                    dbIndices.filter(
2947
                        (dbIndex) =>
2948
                            dbIndex["TABLE_NAME"] === dbTable["TABLE_NAME"] &&
×
2949
                            dbIndex["TABLE_SCHEMA"] === dbTable["TABLE_SCHEMA"],
2950
                    ),
2951
                    (dbIndex) => dbIndex["INDEX_NAME"],
×
2952
                )
2953

2954
                table.indices = tableIndexConstraints.map((constraint) => {
×
2955
                    const indices = dbIndices.filter((index) => {
×
2956
                        return (
×
2957
                            index["TABLE_SCHEMA"] ===
×
2958
                                constraint["TABLE_SCHEMA"] &&
2959
                            index["TABLE_NAME"] === constraint["TABLE_NAME"] &&
2960
                            index["INDEX_NAME"] === constraint["INDEX_NAME"]
2961
                        )
2962
                    })
2963

2964
                    const nonUnique = parseInt(constraint["NON_UNIQUE"], 10)
×
2965

2966
                    return new TableIndex(<TableIndexOptions>{
×
2967
                        table: table,
2968
                        name: constraint["INDEX_NAME"],
2969
                        columnNames: indices.map((i) => i["COLUMN_NAME"]),
×
2970
                        isUnique: nonUnique === 0,
2971
                        isSpatial: constraint["INDEX_TYPE"] === "SPATIAL",
2972
                        isFulltext: constraint["INDEX_TYPE"] === "FULLTEXT",
2973
                    })
2974
                })
2975

2976
                table.comment = dbTable["TABLE_COMMENT"]
×
2977

2978
                return table
×
2979
            }),
2980
        )
2981
    }
2982

2983
    /**
2984
     * Builds create table sql
2985
     */
2986
    protected createTableSql(table: Table, createForeignKeys?: boolean): Query {
2987
        const columnDefinitions = table.columns
×
2988
            .map((column) => this.buildCreateColumnSql(column, true))
×
2989
            .join(", ")
2990
        let sql = `CREATE TABLE ${this.escapePath(table)} (${columnDefinitions}`
×
2991

2992
        // we create unique indexes instead of unique constraints, because MySql does not have unique constraints.
2993
        // if we mark column as Unique, it means that we create UNIQUE INDEX.
2994
        table.columns
×
2995
            .filter((column) => column.isUnique)
×
2996
            .forEach((column) => {
2997
                const isUniqueIndexExist = table.indices.some((index) => {
×
2998
                    return (
×
2999
                        index.columnNames.length === 1 &&
×
3000
                        !!index.isUnique &&
3001
                        index.columnNames.indexOf(column.name) !== -1
3002
                    )
3003
                })
3004
                const isUniqueConstraintExist = table.uniques.some((unique) => {
×
3005
                    return (
×
3006
                        unique.columnNames.length === 1 &&
×
3007
                        unique.columnNames.indexOf(column.name) !== -1
3008
                    )
3009
                })
3010
                if (!isUniqueIndexExist && !isUniqueConstraintExist)
×
3011
                    table.indices.push(
×
3012
                        new TableIndex({
3013
                            name: this.connection.namingStrategy.uniqueConstraintName(
3014
                                table,
3015
                                [column.name],
3016
                            ),
3017
                            columnNames: [column.name],
3018
                            isUnique: true,
3019
                        }),
3020
                    )
3021
            })
3022

3023
        // as MySql does not have unique constraints, we must create table indices from table uniques and mark them as unique.
3024
        if (table.uniques.length > 0) {
×
3025
            table.uniques.forEach((unique) => {
×
3026
                const uniqueExist = table.indices.some(
×
3027
                    (index) => index.name === unique.name,
×
3028
                )
3029
                if (!uniqueExist) {
×
3030
                    table.indices.push(
×
3031
                        new TableIndex({
3032
                            name: unique.name,
3033
                            columnNames: unique.columnNames,
3034
                            isUnique: true,
3035
                        }),
3036
                    )
3037
                }
3038
            })
3039
        }
3040

3041
        if (table.indices.length > 0) {
×
3042
            const indicesSql = table.indices
×
3043
                .map((index) => {
3044
                    const columnNames = index.columnNames
×
3045
                        .map((columnName) => `\`${columnName}\``)
×
3046
                        .join(", ")
3047
                    if (!index.name)
×
3048
                        index.name = this.connection.namingStrategy.indexName(
×
3049
                            table,
3050
                            index.columnNames,
3051
                            index.where,
3052
                        )
3053

3054
                    let indexType = ""
×
3055
                    if (index.isUnique) indexType += "UNIQUE "
×
3056
                    if (index.isSpatial) indexType += "SPATIAL "
×
3057
                    if (index.isFulltext) indexType += "FULLTEXT "
×
3058
                    const indexParser =
3059
                        index.isFulltext && index.parser
×
3060
                            ? ` WITH PARSER ${index.parser}`
3061
                            : ""
3062

3063
                    return `${indexType}INDEX \`${index.name}\` (${columnNames})${indexParser}`
×
3064
                })
3065
                .join(", ")
3066

3067
            sql += `, ${indicesSql}`
×
3068
        }
3069

3070
        if (table.foreignKeys.length > 0 && createForeignKeys) {
×
3071
            const foreignKeysSql = table.foreignKeys
×
3072
                .map((fk) => {
3073
                    const columnNames = fk.columnNames
×
3074
                        .map((columnName) => `\`${columnName}\``)
×
3075
                        .join(", ")
3076
                    if (!fk.name)
×
3077
                        fk.name = this.connection.namingStrategy.foreignKeyName(
×
3078
                            table,
3079
                            fk.columnNames,
3080
                            this.getTablePath(fk),
3081
                            fk.referencedColumnNames,
3082
                        )
3083
                    const referencedColumnNames = fk.referencedColumnNames
×
3084
                        .map((columnName) => `\`${columnName}\``)
×
3085
                        .join(", ")
3086

3087
                    let constraint = `CONSTRAINT \`${
×
3088
                        fk.name
3089
                    }\` FOREIGN KEY (${columnNames}) REFERENCES ${this.escapePath(
3090
                        this.getTablePath(fk),
3091
                    )} (${referencedColumnNames})`
3092
                    if (fk.onDelete) constraint += ` ON DELETE ${fk.onDelete}`
×
3093
                    if (fk.onUpdate) constraint += ` ON UPDATE ${fk.onUpdate}`
×
3094

3095
                    return constraint
×
3096
                })
3097
                .join(", ")
3098

3099
            sql += `, ${foreignKeysSql}`
×
3100
        }
3101

3102
        if (table.primaryColumns.length > 0) {
×
3103
            const columnNames = table.primaryColumns
×
3104
                .map((column) => `\`${column.name}\``)
×
3105
                .join(", ")
3106
            sql += `, PRIMARY KEY (${columnNames})`
×
3107
        }
3108

3109
        sql += `) ENGINE=${table.engine || "InnoDB"}`
×
3110

3111
        if (table.comment) {
×
3112
            sql += ` COMMENT="${table.comment}"`
×
3113
        }
3114

3115
        return new Query(sql)
×
3116
    }
3117

3118
    /**
3119
     * Builds drop table sql
3120
     */
3121
    protected dropTableSql(tableOrName: Table | string): Query {
3122
        return new Query(`DROP TABLE ${this.escapePath(tableOrName)}`)
×
3123
    }
3124

3125
    protected createViewSql(view: View): Query {
3126
        if (typeof view.expression === "string") {
×
3127
            return new Query(
×
3128
                `CREATE VIEW ${this.escapePath(view)} AS ${view.expression}`,
3129
            )
3130
        } else {
3131
            return new Query(
×
3132
                `CREATE VIEW ${this.escapePath(view)} AS ${view
3133
                    .expression(this.connection)
3134
                    .getQuery()}`,
3135
            )
3136
        }
3137
    }
3138

3139
    protected async insertViewDefinitionSql(view: View): Promise<Query> {
3140
        const currentDatabase = await this.getCurrentDatabase()
×
3141
        const expression =
3142
            typeof view.expression === "string"
×
3143
                ? view.expression.trim()
3144
                : view.expression(this.connection).getQuery()
3145
        return this.insertTypeormMetadataSql({
×
3146
            type: MetadataTableType.VIEW,
3147
            schema: currentDatabase,
3148
            name: view.name,
3149
            value: expression,
3150
        })
3151
    }
3152

3153
    /**
3154
     * Builds drop view sql.
3155
     */
3156
    protected dropViewSql(viewOrPath: View | string): Query {
3157
        return new Query(`DROP VIEW ${this.escapePath(viewOrPath)}`)
×
3158
    }
3159

3160
    /**
3161
     * Builds remove view sql.
3162
     */
3163
    protected async deleteViewDefinitionSql(
3164
        viewOrPath: View | string,
3165
    ): Promise<Query> {
3166
        const currentDatabase = await this.getCurrentDatabase()
×
3167
        const viewName = InstanceChecker.isView(viewOrPath)
×
3168
            ? viewOrPath.name
3169
            : viewOrPath
3170
        return this.deleteTypeormMetadataSql({
×
3171
            type: MetadataTableType.VIEW,
3172
            schema: currentDatabase,
3173
            name: viewName,
3174
        })
3175
    }
3176

3177
    /**
3178
     * Builds create index sql.
3179
     */
3180
    protected createIndexSql(table: Table, index: TableIndex): Query {
3181
        const columns = index.columnNames
×
3182
            .map((columnName) => `\`${columnName}\``)
×
3183
            .join(", ")
3184
        let indexType = ""
×
3185
        if (index.isUnique) indexType += "UNIQUE "
×
3186
        if (index.isSpatial) indexType += "SPATIAL "
×
3187
        if (index.isFulltext) indexType += "FULLTEXT "
×
3188
        const indexParser =
3189
            index.isFulltext && index.parser
×
3190
                ? ` WITH PARSER ${index.parser}`
3191
                : ""
3192

3193
        return new Query(
×
3194
            `CREATE ${indexType}INDEX \`${index.name}\` ON ${this.escapePath(
3195
                table,
3196
            )} (${columns})${indexParser}`,
3197
        )
3198
    }
3199

3200
    /**
3201
     * Builds drop index sql.
3202
     */
3203
    protected dropIndexSql(
3204
        table: Table,
3205
        indexOrName: TableIndex | string,
3206
    ): Query {
3207
        const indexName = InstanceChecker.isTableIndex(indexOrName)
×
3208
            ? indexOrName.name
3209
            : indexOrName
3210
        return new Query(
×
3211
            `DROP INDEX \`${indexName}\` ON ${this.escapePath(table)}`,
3212
        )
3213
    }
3214

3215
    /**
3216
     * Builds create primary key sql.
3217
     */
3218
    protected createPrimaryKeySql(table: Table, columnNames: string[]): Query {
3219
        const columnNamesString = columnNames
×
3220
            .map((columnName) => `\`${columnName}\``)
×
3221
            .join(", ")
3222
        return new Query(
×
3223
            `ALTER TABLE ${this.escapePath(
3224
                table,
3225
            )} ADD PRIMARY KEY (${columnNamesString})`,
3226
        )
3227
    }
3228

3229
    /**
3230
     * Builds drop primary key sql.
3231
     */
3232
    protected dropPrimaryKeySql(table: Table): Query {
3233
        return new Query(
×
3234
            `ALTER TABLE ${this.escapePath(table)} DROP PRIMARY KEY`,
3235
        )
3236
    }
3237

3238
    /**
3239
     * Builds create foreign key sql.
3240
     */
3241
    protected createForeignKeySql(
3242
        table: Table,
3243
        foreignKey: TableForeignKey,
3244
    ): Query {
3245
        const columnNames = foreignKey.columnNames
×
3246
            .map((column) => `\`${column}\``)
×
3247
            .join(", ")
3248
        const referencedColumnNames = foreignKey.referencedColumnNames
×
3249
            .map((column) => `\`${column}\``)
×
3250
            .join(",")
3251
        let sql =
3252
            `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT \`${
×
3253
                foreignKey.name
3254
            }\` FOREIGN KEY (${columnNames}) ` +
3255
            `REFERENCES ${this.escapePath(
3256
                this.getTablePath(foreignKey),
3257
            )}(${referencedColumnNames})`
3258
        if (foreignKey.onDelete) sql += ` ON DELETE ${foreignKey.onDelete}`
×
3259
        if (foreignKey.onUpdate) sql += ` ON UPDATE ${foreignKey.onUpdate}`
×
3260

3261
        return new Query(sql)
×
3262
    }
3263

3264
    /**
3265
     * Builds drop foreign key sql.
3266
     */
3267
    protected dropForeignKeySql(
3268
        table: Table,
3269
        foreignKeyOrName: TableForeignKey | string,
3270
    ): Query {
3271
        const foreignKeyName = InstanceChecker.isTableForeignKey(
×
3272
            foreignKeyOrName,
3273
        )
3274
            ? foreignKeyOrName.name
3275
            : foreignKeyOrName
3276
        return new Query(
×
3277
            `ALTER TABLE ${this.escapePath(
3278
                table,
3279
            )} DROP FOREIGN KEY \`${foreignKeyName}\``,
3280
        )
3281
    }
3282

3283
    /**
3284
     * Escapes a given comment so it's safe to include in a query.
3285
     */
3286
    protected escapeComment(comment?: string) {
3287
        if (!comment || comment.length === 0) {
×
3288
            return `''`
×
3289
        }
3290

3291
        comment = comment
×
3292
            .replace(/\\/g, "\\\\") // MySQL allows escaping characters via backslashes
3293
            .replace(/'/g, "''")
3294
            .replace(/\u0000/g, "") // Null bytes aren't allowed in comments
3295

3296
        return `'${comment}'`
×
3297
    }
3298

3299
    /**
3300
     * Escapes given table or view path.
3301
     */
3302
    protected escapePath(target: Table | View | string): string {
3303
        const { database, tableName } = this.driver.parseTableName(target)
×
3304

3305
        if (database && database !== this.driver.database) {
×
3306
            return `\`${database}\`.\`${tableName}\``
×
3307
        }
3308

3309
        return `\`${tableName}\``
×
3310
    }
3311

3312
    /**
3313
     * Builds a part of query to create/change a column.
3314
     */
3315
    protected buildCreateColumnSql(
3316
        column: TableColumn,
3317
        skipPrimary: boolean,
3318
        skipName: boolean = false,
×
3319
    ) {
3320
        let c = ""
×
3321
        if (skipName) {
×
3322
            c = this.connection.driver.createFullType(column)
×
3323
        } else {
3324
            c = `\`${column.name}\` ${this.connection.driver.createFullType(
×
3325
                column,
3326
            )}`
3327
        }
3328

3329
        if (column.charset) c += ` CHARACTER SET "${column.charset}"`
×
3330
        if (column.collation) c += ` COLLATE "${column.collation}"`
×
3331

3332
        if (column.asExpression)
×
3333
            c += ` AS (${column.asExpression}) ${
×
3334
                column.generatedType ? column.generatedType : "VIRTUAL"
×
3335
            }`
3336

3337
        // if you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to that column.
3338
        if (column.zerofill) {
×
3339
            c += " ZEROFILL"
×
3340
        } else if (column.unsigned) {
×
3341
            c += " UNSIGNED"
×
3342
        }
3343
        if (column.enum)
×
3344
            c += ` (${column.enum
×
3345
                .map((value) => "'" + value.replace(/'/g, "''") + "'")
×
3346
                .join(", ")})`
3347

3348
        const isMariaDb = this.driver.options.type === "mariadb"
×
3349
        if (
×
3350
            isMariaDb &&
×
3351
            column.asExpression &&
3352
            ["VIRTUAL", "STORED"].includes(column.generatedType || "VIRTUAL")
×
3353
        ) {
3354
            // do nothing - MariaDB does not support NULL/NOT NULL expressions for VIRTUAL columns and STORED columns
3355
        } else {
3356
            if (!column.isNullable) c += " NOT NULL"
×
3357
            if (column.isNullable) c += " NULL"
×
3358
        }
3359

3360
        if (column.isPrimary && !skipPrimary) c += " PRIMARY KEY"
×
3361
        if (column.isGenerated && column.generationStrategy === "increment")
×
3362
            // don't use skipPrimary here since updates can update already exist primary without auto inc.
3363
            c += " AUTO_INCREMENT"
×
3364
        if (column.comment && column.comment.length > 0)
×
3365
            c += ` COMMENT ${this.escapeComment(column.comment)}`
×
3366
        if (column.default !== undefined && column.default !== null)
×
3367
            c += ` DEFAULT ${column.default}`
×
3368
        if (column.onUpdate) c += ` ON UPDATE ${column.onUpdate}`
×
3369

3370
        return c
×
3371
    }
3372

3373
    async getVersion(): Promise<string> {
3374
        const result: [{ version: string }] = await this.query(
×
3375
            `SELECT VERSION() AS \`version\``,
3376
        )
3377

3378
        // MariaDB: https://mariadb.com/kb/en/version/
3379
        // - "10.2.27-MariaDB-10.2.27+maria~jessie-log"
3380
        // MySQL: https://dev.mysql.com/doc/refman/8.4/en/information-functions.html#function_version
3381
        // - "8.4.3"
3382
        // - "8.4.4-standard"
3383
        const versionString = result[0].version
×
3384

3385
        return versionString.replace(/^([\d.]+).*$/, "$1")
×
3386
    }
3387

3388
    /**
3389
     * Checks if column display width is by default.
3390
     */
3391
    protected isDefaultColumnWidth(
3392
        table: Table,
3393
        column: TableColumn,
3394
        width: number,
3395
    ): boolean {
3396
        // if table have metadata, we check if length is specified in column metadata
3397
        if (this.connection.hasMetadata(table.name)) {
×
3398
            const metadata = this.connection.getMetadata(table.name)
×
3399
            const columnMetadata = metadata.findColumnWithDatabaseName(
×
3400
                column.name,
3401
            )
3402
            if (columnMetadata && columnMetadata.width) return false
×
3403
        }
3404

3405
        const defaultWidthForType =
3406
            this.connection.driver.dataTypeDefaults &&
×
3407
            this.connection.driver.dataTypeDefaults[column.type] &&
3408
            this.connection.driver.dataTypeDefaults[column.type].width
3409

3410
        if (defaultWidthForType) {
×
3411
            // In MariaDB & MySQL 5.7, the default widths of certain numeric types are 1 less than
3412
            // the usual defaults when the column is unsigned.
3413
            const typesWithReducedUnsignedDefault = [
×
3414
                "int",
3415
                "tinyint",
3416
                "smallint",
3417
                "mediumint",
3418
            ]
3419
            const needsAdjustment =
3420
                typesWithReducedUnsignedDefault.indexOf(column.type) !== -1
×
3421
            if (column.unsigned && needsAdjustment) {
×
3422
                return defaultWidthForType - 1 === width
×
3423
            } else {
3424
                return defaultWidthForType === width
×
3425
            }
3426
        }
3427

3428
        return false
×
3429
    }
3430
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc