• 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

75.08
/src/schema-builder/RdbmsSchemaBuilder.ts
1
import { Table } from "./table/Table"
4✔
2
import { TableColumn } from "./table/TableColumn"
4✔
3
import { TableForeignKey } from "./table/TableForeignKey"
4✔
4
import { TableIndex } from "./table/TableIndex"
4✔
5
import { QueryRunner } from "../query-runner/QueryRunner"
6
import { ColumnMetadata } from "../metadata/ColumnMetadata"
7
import { EntityMetadata } from "../metadata/EntityMetadata"
8
import { DataSource } from "../data-source/DataSource"
9
import { SchemaBuilder } from "./SchemaBuilder"
10
import { SqlInMemory } from "../driver/SqlInMemory"
11
import { TableUtils } from "./util/TableUtils"
4✔
12
import { TableColumnOptions } from "./options/TableColumnOptions"
13
import { TableUnique } from "./table/TableUnique"
4✔
14
import { TableCheck } from "./table/TableCheck"
4✔
15
import { TableExclusion } from "./table/TableExclusion"
4✔
16
import { View } from "./view/View"
4✔
17
import { ViewUtils } from "./util/ViewUtils"
4✔
18
import { DriverUtils } from "../driver/DriverUtils"
4✔
19
import { PostgresQueryRunner } from "../driver/postgres/PostgresQueryRunner"
20

21
/**
22
 * Creates complete tables schemas in the database based on the entity metadatas.
23
 *
24
 * Steps how schema is being built:
25
 * 1. load list of all tables with complete column and keys information from the db
26
 * 2. drop all (old) foreign keys that exist in the table, but does not exist in the metadata
27
 * 3. create new tables that does not exist in the db, but exist in the metadata
28
 * 4. drop all columns exist (left old) in the db table, but does not exist in the metadata
29
 * 5. add columns from metadata which does not exist in the table
30
 * 6. update all exist columns which metadata has changed
31
 * 7. update primary keys - update old and create new primary key from changed columns
32
 * 8. create foreign keys which does not exist in the table yet
33
 * 9. create indices which are missing in db yet, and drops indices which exist in the db, but does not exist in the metadata anymore
34
 */
35
export class RdbmsSchemaBuilder implements SchemaBuilder {
4✔
36
    readonly "@instanceof" = Symbol.for("RdbmsSchemaBuilder")
5,270✔
37

38
    /**
39
     * Used to execute schema creation queries in a single connection.
40
     */
41
    protected queryRunner: QueryRunner
42

43
    private currentDatabase?: string
44

45
    private currentSchema?: string
46

47
    // -------------------------------------------------------------------------
48
    // Constructor
49
    // -------------------------------------------------------------------------
50

51
    constructor(protected connection: DataSource) {}
5,270✔
52

53
    // -------------------------------------------------------------------------
54
    // Public Methods
55
    // -------------------------------------------------------------------------
56

57
    /**
58
     * Creates complete schemas for the given entity metadatas.
59
     */
60
    async build(): Promise<void> {
61
        this.queryRunner = this.connection.createQueryRunner()
5,144✔
62

63
        // this.connection.driver.database || this.currentDatabase;
64
        this.currentDatabase = this.connection.driver.database
5,144✔
65
        this.currentSchema = this.connection.driver.schema
5,144✔
66

67
        // CockroachDB implements asynchronous schema sync operations which can not been executed in transaction.
68
        // E.g. if you try to DROP column and ADD it again in the same transaction, crdb throws error.
69
        // In Spanner queries against the INFORMATION_SCHEMA can be used in a read-only transaction,
70
        // but not in a read-write transaction.
71
        const isUsingTransactions =
72
            !(this.connection.driver.options.type === "cockroachdb") &&
5,144✔
73
            !(this.connection.driver.options.type === "spanner") &&
74
            this.connection.options.migrationsTransactionMode !== "none"
75

76
        await this.queryRunner.beforeMigration()
5,144✔
77

78
        if (isUsingTransactions) {
5,144✔
79
            await this.queryRunner.startTransaction()
5,144✔
80
        }
81

82
        try {
5,144✔
83
            await this.createMetadataTableIfNecessary(this.queryRunner)
5,144✔
84
            // Flush the queryrunner table & view cache
85
            const tablePaths = this.entityToSyncMetadatas.map((metadata) =>
5,144✔
86
                this.getTablePath(metadata),
17,894✔
87
            )
88
            const viewPaths = this.viewEntityToSyncMetadatas.map((metadata) =>
5,144✔
89
                this.getTablePath(metadata),
45✔
90
            )
91

92
            await this.queryRunner.getTables(tablePaths)
5,144✔
93
            await this.queryRunner.getViews(viewPaths)
5,144✔
94

95
            await this.executeSchemaSyncOperationsInProperOrder()
5,144✔
96

97
            // if cache is enabled then perform cache-synchronization as well
98
            if (this.connection.queryResultCache)
5,144✔
99
                await this.connection.queryResultCache.synchronize(
64✔
100
                    this.queryRunner,
101
                )
102

103
            if (isUsingTransactions) {
5,144✔
104
                await this.queryRunner.commitTransaction()
5,144✔
105
            }
106
        } catch (error) {
107
            try {
×
108
                // we throw original error even if rollback thrown an error
109
                if (isUsingTransactions) {
×
110
                    await this.queryRunner.rollbackTransaction()
×
111
                }
112
            } catch (rollbackError) {}
113
            throw error
×
114
        } finally {
115
            await this.queryRunner.afterMigration()
5,144✔
116

117
            await this.queryRunner.release()
5,144✔
118
        }
119
    }
120

121
    /**
122
     * Create the typeorm_metadata table if necessary.
123
     */
124
    async createMetadataTableIfNecessary(
125
        queryRunner: QueryRunner,
126
    ): Promise<void> {
127
        if (
5,179✔
128
            this.viewEntityToSyncMetadatas.length > 0 ||
10,336✔
129
            this.hasGeneratedColumns()
130
        ) {
131
            await this.createTypeormMetadataTable(queryRunner)
36✔
132
        }
133
    }
134

135
    /**
136
     * Returns sql queries to be executed by schema builder.
137
     */
138
    async log(): Promise<SqlInMemory> {
139
        this.queryRunner = this.connection.createQueryRunner()
91✔
140
        try {
91✔
141
            // Flush the queryrunner table & view cache
142
            const tablePaths = this.entityToSyncMetadatas.map((metadata) =>
91✔
143
                this.getTablePath(metadata),
125✔
144
            )
145
            const viewPaths = this.viewEntityToSyncMetadatas.map((metadata) =>
91✔
146
                this.getTablePath(metadata),
2✔
147
            )
148
            await this.queryRunner.getTables(tablePaths)
91✔
149
            await this.queryRunner.getViews(viewPaths)
91✔
150

151
            this.queryRunner.enableSqlMemory()
91✔
152
            await this.executeSchemaSyncOperationsInProperOrder()
91✔
153

154
            // if cache is enabled then perform cache-synchronization as well
155
            if (this.connection.queryResultCache)
91!
156
                // todo: check this functionality
157
                await this.connection.queryResultCache.synchronize(
×
158
                    this.queryRunner,
159
                )
160

161
            return this.queryRunner.getMemorySql()
91✔
162
        } finally {
163
            // its important to disable this mode despite the fact we are release query builder
164
            // because there exist drivers which reuse same query runner. Also its important to disable
165
            // sql memory after call of getMemorySql() method because last one flushes sql memory.
166
            this.queryRunner.disableSqlMemory()
91✔
167
            await this.queryRunner.release()
91✔
168
        }
169
    }
170

171
    // -------------------------------------------------------------------------
172
    // Protected Methods
173
    // -------------------------------------------------------------------------
174

175
    /**
176
     * Returns only entities that should be synced in the database.
177
     */
178
    protected get entityToSyncMetadatas(): EntityMetadata[] {
179
        return this.connection.entityMetadatas.filter(
83,760✔
180
            (metadata) =>
181
                metadata.synchronize &&
301,616✔
182
                metadata.tableType !== "entity-child" &&
183
                metadata.tableType !== "view",
184
        )
185
    }
186

187
    /**
188
     * Returns only entities that should be synced in the database.
189
     */
190
    protected get viewEntityToSyncMetadatas(): EntityMetadata[] {
191
        return (
20,884✔
192
            this.connection.entityMetadatas
193
                .filter(
194
                    (metadata) =>
195
                        metadata.tableType === "view" && metadata.synchronize,
75,286✔
196
                )
197
                // sort views in creation order by dependencies
198
                .sort(ViewUtils.viewMetadataCmp)
199
        )
200
    }
201

202
    /**
203
     * Checks if there are at least one generated column.
204
     */
205
    protected hasGeneratedColumns(): boolean {
206
        return this.connection.entityMetadatas.some((entityMetadata) => {
5,157✔
207
            return entityMetadata.columns.some((column) => column.generatedType)
57,910✔
208
        })
209
    }
210

211
    /**
212
     * Executes schema sync operations in a proper order.
213
     * Order of operations matter here.
214
     */
215
    protected async executeSchemaSyncOperationsInProperOrder(): Promise<void> {
216
        await this.dropOldViews()
5,235✔
217
        await this.dropOldForeignKeys()
5,235✔
218
        await this.dropOldIndices()
5,235✔
219
        await this.dropOldChecks()
5,235✔
220
        await this.dropOldExclusions()
5,235✔
221
        await this.dropCompositeUniqueConstraints()
5,235✔
222
        // await this.renameTables();
223
        await this.renameColumns()
5,235✔
224
        await this.changeTableComment()
5,235✔
225
        await this.createNewTables()
5,235✔
226
        await this.dropRemovedColumns()
5,235✔
227
        await this.addNewColumns()
5,235✔
228
        await this.updatePrimaryKeys()
5,235✔
229
        await this.updateExistColumns()
5,235✔
230
        await this.createNewIndices()
5,235✔
231
        await this.createNewChecks()
5,235✔
232
        await this.createNewExclusions()
5,235✔
233
        await this.createCompositeUniqueConstraints()
5,235✔
234
        await this.createForeignKeys()
5,235✔
235
        await this.createViews()
5,235✔
236
        await this.createNewViewIndices()
5,235✔
237
    }
238

239
    private getTablePath(
240
        target: EntityMetadata | Table | View | TableForeignKey | string,
241
    ): string {
242
        const parsed = this.connection.driver.parseTableName(target)
1,154,617✔
243

244
        return this.connection.driver.buildTableName(
1,154,617✔
245
            parsed.tableName,
246
            parsed.schema || this.currentSchema,
2,021,468✔
247
            parsed.database || this.currentDatabase,
1,436,056✔
248
        )
249
    }
250

251
    /**
252
     * Drops all (old) foreign keys that exist in the tables, but do not exist in the entity metadata.
253
     */
254
    protected async dropOldForeignKeys(): Promise<void> {
255
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
256
            const table = this.queryRunner.loadedTables.find(
18,019✔
257
                (table) =>
258
                    this.getTablePath(table) === this.getTablePath(metadata),
2,761✔
259
            )
260
            if (!table) continue
18,019✔
261

262
            // find foreign keys that exist in the schemas but does not exist in the entity metadata
263
            const tableForeignKeysToDrop = table.foreignKeys.filter(
771✔
264
                (tableForeignKey) => {
265
                    const metadataFK = metadata.foreignKeys.find(
304✔
266
                        (metadataForeignKey) =>
267
                            tableForeignKey.name === metadataForeignKey.name &&
397✔
268
                            this.getTablePath(tableForeignKey) ===
269
                                this.getTablePath(
270
                                    metadataForeignKey.referencedEntityMetadata,
271
                                ),
272
                    )
273
                    return (
304✔
274
                        !metadataFK ||
1,496✔
275
                        (metadataFK.onDelete &&
276
                            metadataFK.onDelete !== tableForeignKey.onDelete) ||
277
                        (metadataFK.onUpdate &&
278
                            metadataFK.onUpdate !== tableForeignKey.onUpdate)
279
                    )
280
                },
281
            )
282
            if (tableForeignKeysToDrop.length === 0) continue
771✔
283

284
            this.connection.logger.logSchemaBuild(
8✔
285
                `dropping old foreign keys of ${
286
                    table.name
287
                }: ${tableForeignKeysToDrop
288
                    .map((dbForeignKey) => dbForeignKey.name)
8✔
289
                    .join(", ")}`,
290
            )
291

292
            // drop foreign keys from the database
293
            await this.queryRunner.dropForeignKeys(
8✔
294
                table,
295
                tableForeignKeysToDrop,
296
            )
297
        }
298
    }
299

300
    /**
301
     * Rename tables
302
     */
303
    protected async renameTables(): Promise<void> {
304
        // for (const metadata of this.entityToSyncMetadatas) {
305
        //     const table = this.queryRunner.loadedTables.find(table => this.getTablePath(table) === this.getTablePath(metadata));
306
        // }
307
    }
308

309
    /**
310
     * Renames columns.
311
     * Works if only one column per table was changed.
312
     * Changes only column name. If something besides name was changed, these changes will be ignored.
313
     */
314
    protected async renameColumns(): Promise<void> {
315
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
316
            const table = this.queryRunner.loadedTables.find(
18,019✔
317
                (table) =>
318
                    this.getTablePath(table) === this.getTablePath(metadata),
2,761✔
319
            )
320
            if (!table) continue
18,019✔
321

322
            if (metadata.columns.length !== table.columns.length) continue
771✔
323

324
            const renamedMetadataColumns = metadata.columns
755✔
325
                .filter((c) => !c.isVirtualProperty)
2,473✔
326
                .filter((column) => {
327
                    return !table.columns.find((tableColumn) => {
2,473✔
328
                        return (
6,874✔
329
                            tableColumn.name === column.databaseName &&
14,241✔
330
                            tableColumn.type ===
331
                                this.connection.driver.normalizeType(column) &&
332
                            tableColumn.isNullable === column.isNullable &&
333
                            tableColumn.isUnique ===
334
                                this.connection.driver.normalizeIsUnique(column)
335
                        )
336
                    })
337
                })
338

339
            if (
755✔
340
                renamedMetadataColumns.length === 0 ||
783✔
341
                renamedMetadataColumns.length > 1
342
            )
343
                continue
727✔
344

345
            const renamedTableColumns = table.columns.filter((tableColumn) => {
28✔
346
                return !metadata.columns.find((column) => {
116✔
347
                    return (
392✔
348
                        !column.isVirtualProperty &&
1,080✔
349
                        column.databaseName === tableColumn.name &&
350
                        this.connection.driver.normalizeType(column) ===
351
                            tableColumn.type &&
352
                        column.isNullable === tableColumn.isNullable &&
353
                        this.connection.driver.normalizeIsUnique(column) ===
354
                            tableColumn.isUnique
355
                    )
356
                })
357
            })
358

359
            if (
28!
360
                renamedTableColumns.length === 0 ||
56✔
361
                renamedTableColumns.length > 1
362
            )
363
                continue
×
364

365
            const renamedColumn = renamedTableColumns[0].clone()
28✔
366
            renamedColumn.name = renamedMetadataColumns[0].databaseName
28✔
367

368
            this.connection.logger.logSchemaBuild(
28✔
369
                `renaming column "${renamedTableColumns[0].name}" in "${table.name}" to "${renamedColumn.name}"`,
370
            )
371
            await this.queryRunner.renameColumn(
28✔
372
                table,
373
                renamedTableColumns[0],
374
                renamedColumn,
375
            )
376
        }
377
    }
378

379
    protected async dropOldIndices(): Promise<void> {
380
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
381
            const table = this.queryRunner.loadedTables.find(
18,019✔
382
                (table) =>
383
                    this.getTablePath(table) === this.getTablePath(metadata),
2,761✔
384
            )
385
            if (!table) continue
18,019✔
386

387
            const dropQueries = table.indices
771✔
388
                .filter((tableIndex) => {
389
                    const indexMetadata = metadata.indices.find(
136✔
390
                        (index) => index.name === tableIndex.name,
131✔
391
                    )
392
                    if (indexMetadata) {
136✔
393
                        if (indexMetadata.synchronize === false) return false
120✔
394

395
                        if (indexMetadata.isUnique !== tableIndex.isUnique)
116✔
396
                            return true
16✔
397

398
                        if (indexMetadata.isSpatial !== tableIndex.isSpatial)
100!
399
                            return true
×
400

401
                        if (
100!
402
                            this.connection.driver.isFullTextColumnTypeSupported() &&
100!
403
                            indexMetadata.isFulltext !== tableIndex.isFulltext
404
                        )
405
                            return true
×
406

407
                        if (
100!
408
                            indexMetadata.columns.length !==
409
                            tableIndex.columnNames.length
410
                        )
411
                            return true
×
412

413
                        return !indexMetadata.columns.every(
100✔
414
                            (column) =>
415
                                tableIndex.columnNames.indexOf(
100✔
416
                                    column.databaseName,
417
                                ) !== -1,
418
                        )
419
                    }
420

421
                    return true
16✔
422
                })
423
                .map(async (tableIndex) => {
424
                    this.connection.logger.logSchemaBuild(
32✔
425
                        `dropping an index: "${tableIndex.name}" from table ${table.name}`,
426
                    )
427
                    await this.queryRunner.dropIndex(table, tableIndex)
32✔
428
                })
429

430
            await Promise.all(dropQueries)
771✔
431
        }
432
        if (this.connection.options.type === "postgres") {
5,235!
433
            const postgresQueryRunner: PostgresQueryRunner = <
434
                PostgresQueryRunner
435
            >this.queryRunner
×
436
            for (const metadata of this.viewEntityToSyncMetadatas) {
×
437
                const view = this.queryRunner.loadedViews.find(
×
438
                    (view) =>
439
                        this.getTablePath(view) === this.getTablePath(metadata),
×
440
                )
441
                if (!view) continue
×
442

443
                const dropQueries = view.indices
×
444
                    .filter((tableIndex) => {
445
                        const indexMetadata = metadata.indices.find(
×
446
                            (index) => index.name === tableIndex.name,
×
447
                        )
448
                        if (indexMetadata) {
×
449
                            if (indexMetadata.synchronize === false)
×
450
                                return false
×
451

452
                            if (indexMetadata.isUnique !== tableIndex.isUnique)
×
453
                                return true
×
454

455
                            if (
×
456
                                indexMetadata.isSpatial !== tableIndex.isSpatial
457
                            )
458
                                return true
×
459

460
                            if (
×
461
                                this.connection.driver.isFullTextColumnTypeSupported() &&
×
462
                                indexMetadata.isFulltext !==
463
                                    tableIndex.isFulltext
464
                            )
465
                                return true
×
466

467
                            if (
×
468
                                indexMetadata.columns.length !==
469
                                tableIndex.columnNames.length
470
                            )
471
                                return true
×
472

473
                            return !indexMetadata.columns.every(
×
474
                                (column) =>
475
                                    tableIndex.columnNames.indexOf(
×
476
                                        column.databaseName,
477
                                    ) !== -1,
478
                            )
479
                        }
480

481
                        return true
×
482
                    })
483
                    .map(async (tableIndex) => {
484
                        this.connection.logger.logSchemaBuild(
×
485
                            `dropping an index: "${tableIndex.name}" from view ${view.name}`,
486
                        )
487
                        await postgresQueryRunner.dropViewIndex(
×
488
                            view,
489
                            tableIndex,
490
                        )
491
                    })
492

493
                await Promise.all(dropQueries)
×
494
            }
495
        }
496
    }
497

498
    protected async dropOldChecks(): Promise<void> {
499
        // Mysql does not support check constraints
500
        if (
5,235!
501
            DriverUtils.isMySQLFamily(this.connection.driver) ||
10,470✔
502
            this.connection.driver.options.type === "aurora-mysql"
503
        )
504
            return
×
505

506
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
507
            const table = this.queryRunner.loadedTables.find(
18,019✔
508
                (table) =>
509
                    this.getTablePath(table) === this.getTablePath(metadata),
2,761✔
510
            )
511
            if (!table) continue
18,019✔
512

513
            const oldChecks = table.checks.filter((tableCheck) => {
771✔
514
                return !metadata.checks.find(
102✔
515
                    (checkMetadata) => checkMetadata.name === tableCheck.name,
98✔
516
                )
517
            })
518

519
            if (oldChecks.length === 0) continue
771✔
520

521
            this.connection.logger.logSchemaBuild(
8✔
522
                `dropping old check constraint: ${oldChecks
523
                    .map((check) => `"${check.name}"`)
8✔
524
                    .join(", ")} from table "${table.name}"`,
525
            )
526
            await this.queryRunner.dropCheckConstraints(table, oldChecks)
8✔
527
        }
528
    }
529

530
    protected async dropCompositeUniqueConstraints(): Promise<void> {
531
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
532
            const table = this.queryRunner.loadedTables.find(
18,019✔
533
                (table) =>
534
                    this.getTablePath(table) === this.getTablePath(metadata),
2,761✔
535
            )
536
            if (!table) continue
18,019✔
537

538
            const compositeUniques = table.uniques.filter((tableUnique) => {
771✔
539
                return (
213✔
540
                    tableUnique.columnNames.length > 1 &&
311✔
541
                    !metadata.uniques.find(
542
                        (uniqueMetadata) =>
543
                            uniqueMetadata.name === tableUnique.name,
187✔
544
                    )
545
                )
546
            })
547

548
            if (compositeUniques.length === 0) continue
771✔
549

550
            this.connection.logger.logSchemaBuild(
5✔
551
                `dropping old unique constraint: ${compositeUniques
552
                    .map((unique) => `"${unique.name}"`)
5✔
553
                    .join(", ")} from table "${table.name}"`,
554
            )
555
            await this.queryRunner.dropUniqueConstraints(
5✔
556
                table,
557
                compositeUniques,
558
            )
559
        }
560
    }
561

562
    protected async dropOldExclusions(): Promise<void> {
563
        // Only PostgreSQL supports exclusion constraints
564
        if (!(this.connection.driver.options.type === "postgres")) return
5,235✔
565

566
        for (const metadata of this.entityToSyncMetadatas) {
×
567
            const table = this.queryRunner.loadedTables.find(
×
568
                (table) =>
569
                    this.getTablePath(table) === this.getTablePath(metadata),
×
570
            )
571
            if (!table) continue
×
572

573
            const oldExclusions = table.exclusions.filter((tableExclusion) => {
×
574
                return !metadata.exclusions.find(
×
575
                    (exclusionMetadata) =>
576
                        exclusionMetadata.name === tableExclusion.name,
×
577
                )
578
            })
579

580
            if (oldExclusions.length === 0) continue
×
581

582
            this.connection.logger.logSchemaBuild(
×
583
                `dropping old exclusion constraint: ${oldExclusions
584
                    .map((exclusion) => `"${exclusion.name}"`)
×
585
                    .join(", ")} from table "${table.name}"`,
586
            )
587
            await this.queryRunner.dropExclusionConstraints(
×
588
                table,
589
                oldExclusions,
590
            )
591
        }
592
    }
593

594
    /**
595
     * change table comment
596
     */
597
    protected async changeTableComment(): Promise<void> {
598
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
599
            const table = this.queryRunner.loadedTables.find(
18,019✔
600
                (table) =>
601
                    this.getTablePath(table) === this.getTablePath(metadata),
2,761✔
602
            )
603
            if (!table) continue
18,019✔
604

605
            if (
771!
606
                DriverUtils.isMySQLFamily(this.connection.driver) ||
1,542✔
607
                this.connection.driver.options.type === "postgres"
608
            ) {
609
                const newComment = metadata.comment
×
610
                await this.queryRunner.changeTableComment(table, newComment)
×
611
            }
612
        }
613
    }
614

615
    /**
616
     * Creates tables that do not exist in the database yet.
617
     * New tables are created without foreign and primary keys.
618
     * Primary key only can be created in conclusion with auto generated column.
619
     */
620
    protected async createNewTables(): Promise<void> {
621
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
622
            // check if table does not exist yet
623
            const existTable = this.queryRunner.loadedTables.find(
18,019✔
624
                (table) =>
625
                    this.getTablePath(table) === this.getTablePath(metadata),
44,925✔
626
            )
627
            if (existTable) continue
18,019✔
628

629
            this.connection.logger.logSchemaBuild(
17,208✔
630
                `creating a new table: ${this.getTablePath(metadata)}`,
631
            )
632

633
            // create a new table and sync it in the database
634
            const table = Table.create(metadata, this.connection.driver)
17,208✔
635
            await this.queryRunner.createTable(table, false, false)
17,208✔
636
            this.queryRunner.loadedTables.push(table)
17,208✔
637
        }
638
    }
639

640
    protected async createViews(): Promise<void> {
641
        for (const metadata of this.viewEntityToSyncMetadatas) {
5,235✔
642
            // check if view does not exist yet
643
            const existView = this.queryRunner.loadedViews.find((view) => {
47✔
644
                const viewExpression =
645
                    typeof view.expression === "string"
32✔
646
                        ? view.expression.trim()
647
                        : view.expression(this.connection).getQuery()
648
                const metadataExpression =
649
                    typeof metadata.expression === "string"
32✔
650
                        ? metadata.expression.trim()
651
                        : metadata.expression!(this.connection).getQuery()
652
                return (
32✔
653
                    this.getTablePath(view) === this.getTablePath(metadata) &&
33✔
654
                    viewExpression === metadataExpression
655
                )
656
            })
657
            if (existView) continue
47✔
658

659
            this.connection.logger.logSchemaBuild(
46✔
660
                `creating a new view: ${this.getTablePath(metadata)}`,
661
            )
662

663
            // create a new view and sync it in the database
664
            const view = View.create(metadata, this.connection.driver)
46✔
665
            await this.queryRunner.createView(view, true)
46✔
666
            this.queryRunner.loadedViews.push(view)
46✔
667
        }
668
    }
669

670
    protected async dropOldViews(): Promise<void> {
671
        const droppedViews: Array<View> = []
5,235✔
672
        const viewEntityToSyncMetadatas = this.viewEntityToSyncMetadatas
5,235✔
673
        // BuIld lookup cache for finding views metadata
674
        const viewToMetadata = new Map<View, EntityMetadata>()
5,235✔
675
        for (const view of this.queryRunner.loadedViews) {
5,235✔
676
            const viewMetadata = viewEntityToSyncMetadatas.find((metadata) => {
1✔
677
                return this.getTablePath(view) === this.getTablePath(metadata)
1✔
678
            })
679
            if (viewMetadata) {
1✔
680
                viewToMetadata.set(view, viewMetadata)
1✔
681
            }
682
        }
683
        // Gather all changed view, that need a drop
684
        for (const view of this.queryRunner.loadedViews) {
5,235✔
685
            const viewMetadata = viewToMetadata.get(view)
1✔
686
            if (!viewMetadata) {
1!
687
                continue
×
688
            }
689
            const viewExpression =
690
                typeof view.expression === "string"
1!
691
                    ? view.expression.trim()
692
                    : view.expression(this.connection).getQuery()
693
            const metadataExpression =
694
                typeof viewMetadata.expression === "string"
1!
695
                    ? viewMetadata.expression.trim()
696
                    : viewMetadata.expression!(this.connection).getQuery()
697

698
            if (viewExpression === metadataExpression) continue
1✔
699

700
            this.connection.logger.logSchemaBuild(
×
701
                `dropping an old view: ${view.name}`,
702
            )
703

704
            // Collect view to be dropped
705
            droppedViews.push(view)
×
706
        }
707

708
        // Helper function that for a given view, will recursively return list of the view and all views that depend on it
709
        const viewDependencyChain = (view: View): View[] => {
5,235✔
710
            // Get the view metadata
711
            const viewMetadata = viewToMetadata.get(view)
×
712
            let viewWithDependencies = [view]
×
713
            // If no metadata is known for the view, simply return the view itself
714
            if (!viewMetadata) {
×
715
                return viewWithDependencies
×
716
            }
717
            // Iterate over all known views
718
            for (const [
×
719
                currentView,
720
                currentMetadata,
721
            ] of viewToMetadata.entries()) {
722
                // Ignore self reference
723
                if (currentView === view) {
×
724
                    continue
×
725
                }
726
                // If the currently iterated view depends on the passed in view
727
                if (
×
728
                    currentMetadata.dependsOn &&
×
729
                    (currentMetadata.dependsOn.has(viewMetadata.target) ||
730
                        currentMetadata.dependsOn.has(viewMetadata.name))
731
                ) {
732
                    // Recursively add currently iterate view and its dependents
733
                    viewWithDependencies = viewWithDependencies.concat(
×
734
                        viewDependencyChain(currentView),
735
                    )
736
                }
737
            }
738
            // Return all collected views
739
            return viewWithDependencies
×
740
        }
741

742
        // Collect final list of views to be dropped in a Set so there are no duplicates
743
        const droppedViewsWithDependencies: Set<View> = new Set(
5,235✔
744
            // Collect all dropped views, and their dependencies
745
            droppedViews
746
                .map((view) => viewDependencyChain(view))
×
747
                // Flattened to single Array ( can be replaced with flatMap, once supported)
748
                .reduce((all, segment) => {
749
                    return all.concat(segment)
×
750
                }, [])
751
                // Sort the views to be dropped in creation order
752
                .sort((a, b) => {
753
                    return ViewUtils.viewMetadataCmp(
×
754
                        viewToMetadata.get(a),
755
                        viewToMetadata.get(b),
756
                    )
757
                })
758
                // reverse order to get drop order
759
                .reverse(),
760
        )
761

762
        // Finally emit all drop views
763
        for (const view of droppedViewsWithDependencies) {
5,235✔
764
            await this.queryRunner.dropView(view)
×
765
        }
766
        this.queryRunner.loadedViews = this.queryRunner.loadedViews.filter(
5,235✔
767
            (view) => !droppedViewsWithDependencies.has(view),
1✔
768
        )
769
    }
770

771
    /**
772
     * Drops all columns that exist in the table, but does not exist in the metadata (left old).
773
     * We drop their keys too, since it should be safe.
774
     */
775
    protected async dropRemovedColumns(): Promise<void> {
776
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
777
            const table = this.queryRunner.loadedTables.find(
18,019✔
778
                (table) =>
779
                    this.getTablePath(table) === this.getTablePath(metadata),
62,133✔
780
            )
781
            if (!table) continue
18,019!
782

783
            // find columns that exist in the database but does not exist in the metadata
784
            const droppedTableColumns = table.columns.filter((tableColumn) => {
18,019✔
785
                return !metadata.columns.find(
55,914✔
786
                    (columnMetadata) =>
787
                        columnMetadata.isVirtualProperty ||
143,975✔
788
                        columnMetadata.databaseName === tableColumn.name,
789
                )
790
            })
791
            if (droppedTableColumns.length === 0) continue
18,019✔
792

793
            this.connection.logger.logSchemaBuild(
8✔
794
                `columns dropped in ${table.name}: ` +
795
                    droppedTableColumns.map((column) => column.name).join(", "),
12✔
796
            )
797

798
            // drop columns from the database
799
            await this.queryRunner.dropColumns(table, droppedTableColumns)
8✔
800
        }
801
    }
802

803
    /**
804
     * Adds columns from metadata which does not exist in the table.
805
     * Columns are created without keys.
806
     */
807
    protected async addNewColumns(): Promise<void> {
808
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
809
            const table = this.queryRunner.loadedTables.find(
18,019✔
810
                (table) =>
811
                    this.getTablePath(table) === this.getTablePath(metadata),
62,133✔
812
            )
813
            if (!table) continue
18,019!
814

815
            // find which columns are new
816
            const newColumnMetadatas = metadata.columns.filter(
18,019✔
817
                (columnMetadata) => {
818
                    return (
55,934✔
819
                        !columnMetadata.isVirtualProperty &&
111,856✔
820
                        !table.columns.find(
821
                            (tableColumn) =>
822
                                tableColumn.name ===
144,039✔
823
                                columnMetadata.databaseName,
824
                        )
825
                    )
826
                },
827
            )
828
            if (newColumnMetadatas.length === 0) continue
18,019✔
829

830
            // create columns in the database
831
            const newTableColumnOptions =
832
                this.metadataColumnsToTableColumnOptions(newColumnMetadatas)
16✔
833
            const newTableColumns = newTableColumnOptions.map(
16✔
834
                (option) => new TableColumn(option),
20✔
835
            )
836

837
            if (newTableColumns.length === 0) continue
16!
838

839
            this.connection.logger.logSchemaBuild(
16✔
840
                `new columns added: ` +
841
                    newColumnMetadatas
842
                        .map((column) => column.databaseName)
20✔
843
                        .join(", "),
844
            )
845
            await this.queryRunner.addColumns(table, newTableColumns)
16✔
846
        }
847
    }
848

849
    /**
850
     * Updates composite primary keys.
851
     */
852
    protected async updatePrimaryKeys(): Promise<void> {
853
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
854
            const table = this.queryRunner.loadedTables.find(
18,019✔
855
                (table) =>
856
                    this.getTablePath(table) === this.getTablePath(metadata),
62,133✔
857
            )
858
            if (!table) continue
18,019!
859

860
            const primaryMetadataColumns = metadata.columns.filter(
18,019✔
861
                (column) => column.isPrimary,
55,934✔
862
            )
863
            const primaryTableColumns = table.columns.filter(
18,019✔
864
                (column) => column.isPrimary,
55,938✔
865
            )
866
            if (
18,019✔
867
                primaryTableColumns.length !== primaryMetadataColumns.length &&
18,024✔
868
                primaryMetadataColumns.length > 1
869
            ) {
870
                const changedPrimaryColumns = primaryMetadataColumns.map(
5✔
871
                    (primaryMetadataColumn) => {
872
                        return new TableColumn(
10✔
873
                            TableUtils.createTableColumnOptions(
874
                                primaryMetadataColumn,
875
                                this.connection.driver,
876
                            ),
877
                        )
878
                    },
879
                )
880
                await this.queryRunner.updatePrimaryKeys(
5✔
881
                    table,
882
                    changedPrimaryColumns,
883
                )
884
            }
885
        }
886
    }
887

888
    /**
889
     * Update all exist columns which metadata has changed.
890
     * Still don't create keys. Also we don't touch foreign keys of the changed columns.
891
     */
892
    protected async updateExistColumns(): Promise<void> {
893
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
894
            const table = this.queryRunner.loadedTables.find(
18,019✔
895
                (table) =>
896
                    this.getTablePath(table) === this.getTablePath(metadata),
62,133✔
897
            )
898
            if (!table) continue
18,019!
899

900
            const changedColumns = this.connection.driver.findChangedColumns(
18,019✔
901
                table.columns,
902
                metadata.columns,
903
            )
904
            if (changedColumns.length === 0) continue
18,019✔
905

906
            // drop all foreign keys that point to this column
907
            for (const changedColumn of changedColumns) {
51✔
908
                await this.dropColumnReferencedForeignKeys(
65✔
909
                    this.getTablePath(metadata),
910
                    changedColumn.databaseName,
911
                )
912
            }
913

914
            // drop all composite indices related to this column
915
            for (const changedColumn of changedColumns) {
51✔
916
                await this.dropColumnCompositeIndices(
65✔
917
                    this.getTablePath(metadata),
918
                    changedColumn.databaseName,
919
                )
920
            }
921

922
            // drop all composite uniques related to this column
923
            // Mysql does not support unique constraints.
924
            if (
51✔
925
                !(
926
                    DriverUtils.isMySQLFamily(this.connection.driver) ||
153✔
927
                    this.connection.driver.options.type === "aurora-mysql" ||
928
                    this.connection.driver.options.type === "spanner"
929
                )
930
            ) {
931
                for (const changedColumn of changedColumns) {
51✔
932
                    await this.dropColumnCompositeUniques(
65✔
933
                        this.getTablePath(metadata),
934
                        changedColumn.databaseName,
935
                    )
936
                }
937
            }
938

939
            // generate a map of new/old columns
940
            const newAndOldTableColumns = changedColumns.map(
51✔
941
                (changedColumn) => {
942
                    const oldTableColumn = table.columns.find(
65✔
943
                        (column) => column.name === changedColumn.databaseName,
177✔
944
                    )!
945
                    const newTableColumnOptions =
946
                        TableUtils.createTableColumnOptions(
65✔
947
                            changedColumn,
948
                            this.connection.driver,
949
                        )
950
                    const newTableColumn = new TableColumn(
65✔
951
                        newTableColumnOptions,
952
                    )
953

954
                    return {
65✔
955
                        oldColumn: oldTableColumn,
956
                        newColumn: newTableColumn,
957
                    }
958
                },
959
            )
960

961
            if (newAndOldTableColumns.length === 0) continue
51!
962

963
            this.connection.logger.logSchemaBuild(
51✔
964
                `columns changed in "${table.name}". updating: ` +
965
                    changedColumns
966
                        .map((column) => column.databaseName)
65✔
967
                        .join(", "),
968
            )
969
            await this.queryRunner.changeColumns(table, newAndOldTableColumns)
51✔
970
        }
971
    }
972

973
    /**
974
     * Creates composite indices which are missing in db yet.
975
     */
976
    protected async createNewIndices(): Promise<void> {
977
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
978
            const table = this.queryRunner.loadedTables.find(
18,019✔
979
                (table) =>
980
                    this.getTablePath(table) === this.getTablePath(metadata),
62,133✔
981
            )
982
            if (!table) continue
18,019!
983

984
            const newIndices = metadata.indices
18,019✔
985
                .filter(
986
                    (indexMetadata) =>
987
                        !table.indices.find(
6,624✔
988
                            (tableIndex) =>
989
                                tableIndex.name === indexMetadata.name,
9,294✔
990
                        ) && indexMetadata.synchronize === true,
991
                )
992
                .map((indexMetadata) => TableIndex.create(indexMetadata))
24✔
993

994
            if (newIndices.length === 0) continue
18,019✔
995

996
            this.connection.logger.logSchemaBuild(
24✔
997
                `adding new indices ${newIndices
998
                    .map((index) => `"${index.name}"`)
24✔
999
                    .join(", ")} in table "${table.name}"`,
1000
            )
1001
            await this.queryRunner.createIndices(table, newIndices)
24✔
1002
        }
1003
    }
1004

1005
    /**
1006
     * Creates indices for materialized views.
1007
     */
1008
    protected async createNewViewIndices(): Promise<void> {
1009
        // Only PostgreSQL supports indices for materialized views.
1010
        if (
5,235✔
1011
            this.connection.options.type !== "postgres" ||
5,235!
1012
            !DriverUtils.isPostgresFamily(this.connection.driver)
1013
        ) {
1014
            return
5,235✔
1015
        }
1016
        const postgresQueryRunner: PostgresQueryRunner = <PostgresQueryRunner>(
1017
            this.queryRunner
×
1018
        )
1019
        for (const metadata of this.viewEntityToSyncMetadatas) {
×
1020
            // check if view does not exist yet
1021
            const view = this.queryRunner.loadedViews.find((view) => {
×
1022
                const viewExpression =
1023
                    typeof view.expression === "string"
×
1024
                        ? view.expression.trim()
1025
                        : view.expression(this.connection).getQuery()
1026
                const metadataExpression =
1027
                    typeof metadata.expression === "string"
×
1028
                        ? metadata.expression.trim()
1029
                        : metadata.expression!(this.connection).getQuery()
1030
                return (
×
1031
                    this.getTablePath(view) === this.getTablePath(metadata) &&
×
1032
                    viewExpression === metadataExpression
1033
                )
1034
            })
1035
            if (!view || !view.materialized) continue
×
1036

1037
            const newIndices = metadata.indices
×
1038
                .filter(
1039
                    (indexMetadata) =>
1040
                        !view.indices.find(
×
1041
                            (tableIndex) =>
1042
                                tableIndex.name === indexMetadata.name,
×
1043
                        ) && indexMetadata.synchronize === true,
1044
                )
1045
                .map((indexMetadata) => TableIndex.create(indexMetadata))
×
1046

1047
            if (newIndices.length === 0) continue
×
1048

1049
            this.connection.logger.logSchemaBuild(
×
1050
                `adding new indices ${newIndices
1051
                    .map((index) => `"${index.name}"`)
×
1052
                    .join(", ")} in view "${view.name}"`,
1053
            )
1054
            await postgresQueryRunner.createViewIndices(view, newIndices)
×
1055
        }
1056
    }
1057

1058
    protected async createNewChecks(): Promise<void> {
1059
        // Mysql does not support check constraints
1060
        if (
5,235!
1061
            DriverUtils.isMySQLFamily(this.connection.driver) ||
10,470✔
1062
            this.connection.driver.options.type === "aurora-mysql"
1063
        )
1064
            return
×
1065

1066
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
1067
            const table = this.queryRunner.loadedTables.find(
18,019✔
1068
                (table) =>
1069
                    this.getTablePath(table) === this.getTablePath(metadata),
62,133✔
1070
            )
1071
            if (!table) continue
18,019!
1072

1073
            const newChecks = metadata.checks
18,019✔
1074
                .filter(
1075
                    (checkMetadata) =>
1076
                        !table.checks.find(
353✔
1077
                            (tableCheck) =>
1078
                                tableCheck.name === checkMetadata.name,
346✔
1079
                        ),
1080
                )
1081
                .map((checkMetadata) => TableCheck.create(checkMetadata))
8✔
1082

1083
            if (newChecks.length === 0) continue
18,019✔
1084

1085
            this.connection.logger.logSchemaBuild(
8✔
1086
                `adding new check constraints: ${newChecks
1087
                    .map((index) => `"${index.name}"`)
8✔
1088
                    .join(", ")} in table "${table.name}"`,
1089
            )
1090
            await this.queryRunner.createCheckConstraints(table, newChecks)
8✔
1091
        }
1092
    }
1093

1094
    /**
1095
     * Creates composite uniques which are missing in db yet.
1096
     */
1097
    protected async createCompositeUniqueConstraints(): Promise<void> {
1098
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
1099
            const table = this.queryRunner.loadedTables.find(
18,019✔
1100
                (table) =>
1101
                    this.getTablePath(table) === this.getTablePath(metadata),
62,133✔
1102
            )
1103
            if (!table) continue
18,019!
1104

1105
            const compositeUniques = metadata.uniques
18,019✔
1106
                .filter(
1107
                    (uniqueMetadata) =>
1108
                        uniqueMetadata.columns.length > 1 &&
3,480✔
1109
                        !table.uniques.find(
1110
                            (tableUnique) =>
1111
                                tableUnique.name === uniqueMetadata.name,
1,445✔
1112
                        ),
1113
                )
1114
                .map((uniqueMetadata) => TableUnique.create(uniqueMetadata))
5✔
1115

1116
            if (compositeUniques.length === 0) continue
18,019✔
1117

1118
            this.connection.logger.logSchemaBuild(
5✔
1119
                `adding new unique constraints: ${compositeUniques
1120
                    .map((unique) => `"${unique.name}"`)
5✔
1121
                    .join(", ")} in table "${table.name}"`,
1122
            )
1123
            await this.queryRunner.createUniqueConstraints(
5✔
1124
                table,
1125
                compositeUniques,
1126
            )
1127
        }
1128
    }
1129

1130
    /**
1131
     * Creates exclusions which are missing in db yet.
1132
     */
1133
    protected async createNewExclusions(): Promise<void> {
1134
        // Only PostgreSQL supports exclusion constraints
1135
        if (!(this.connection.driver.options.type === "postgres")) return
5,235✔
1136

1137
        for (const metadata of this.entityToSyncMetadatas) {
×
1138
            const table = this.queryRunner.loadedTables.find(
×
1139
                (table) =>
1140
                    this.getTablePath(table) === this.getTablePath(metadata),
×
1141
            )
1142
            if (!table) continue
×
1143

1144
            const newExclusions = metadata.exclusions
×
1145
                .filter(
1146
                    (exclusionMetadata) =>
1147
                        !table.exclusions.find(
×
1148
                            (tableExclusion) =>
1149
                                tableExclusion.name === exclusionMetadata.name,
×
1150
                        ),
1151
                )
1152
                .map((exclusionMetadata) =>
1153
                    TableExclusion.create(exclusionMetadata),
×
1154
                )
1155

1156
            if (newExclusions.length === 0) continue
×
1157

1158
            this.connection.logger.logSchemaBuild(
×
1159
                `adding new exclusion constraints: ${newExclusions
1160
                    .map((exclusion) => `"${exclusion.name}"`)
×
1161
                    .join(", ")} in table "${table.name}"`,
1162
            )
1163
            await this.queryRunner.createExclusionConstraints(
×
1164
                table,
1165
                newExclusions,
1166
            )
1167
        }
1168
    }
1169

1170
    /**
1171
     * Creates foreign keys which does not exist in the table yet.
1172
     */
1173
    protected async createForeignKeys(): Promise<void> {
1174
        for (const metadata of this.entityToSyncMetadatas) {
5,235✔
1175
            const table = this.queryRunner.loadedTables.find(
18,019✔
1176
                (table) =>
1177
                    this.getTablePath(table) === this.getTablePath(metadata),
62,133✔
1178
            )
1179
            if (!table) continue
18,019!
1180

1181
            const newKeys = metadata.foreignKeys.filter((foreignKey) => {
18,019✔
1182
                return !table.foreignKeys.find(
11,487✔
1183
                    (dbForeignKey) =>
1184
                        dbForeignKey.name === foreignKey.name &&
461✔
1185
                        this.getTablePath(dbForeignKey) ===
1186
                            this.getTablePath(
1187
                                foreignKey.referencedEntityMetadata,
1188
                            ),
1189
                )
1190
            })
1191
            if (newKeys.length === 0) continue
18,019✔
1192

1193
            const dbForeignKeys = newKeys.map((foreignKeyMetadata) =>
6,342✔
1194
                TableForeignKey.create(
11,145✔
1195
                    foreignKeyMetadata,
1196
                    this.connection.driver,
1197
                ),
1198
            )
1199
            this.connection.logger.logSchemaBuild(
6,342✔
1200
                `creating a foreign keys: ${newKeys
1201
                    .map((key) => key.name)
11,145✔
1202
                    .join(", ")} on table "${table.name}"`,
1203
            )
1204
            await this.queryRunner.createForeignKeys(table, dbForeignKeys)
6,342✔
1205
        }
1206
    }
1207

1208
    /**
1209
     * Drops all foreign keys where given column of the given table is being used.
1210
     */
1211
    protected async dropColumnReferencedForeignKeys(
1212
        tablePath: string,
1213
        columnName: string,
1214
    ): Promise<void> {
1215
        const table = this.queryRunner.loadedTables.find(
65✔
1216
            (table) => this.getTablePath(table) === tablePath,
167✔
1217
        )
1218
        if (!table) return
65!
1219

1220
        const tablesWithFK: Table[] = []
65✔
1221
        const columnForeignKey = table.foreignKeys.find(
65✔
1222
            (foreignKey) => foreignKey.columnNames.indexOf(columnName) !== -1,
5✔
1223
        )
1224
        if (columnForeignKey) {
65✔
1225
            const clonedTable = table.clone()
4✔
1226
            clonedTable.foreignKeys = [columnForeignKey]
4✔
1227
            tablesWithFK.push(clonedTable)
4✔
1228
            table.removeForeignKey(columnForeignKey)
4✔
1229
        }
1230

1231
        for (const loadedTable of this.queryRunner.loadedTables) {
65✔
1232
            const dependForeignKeys = loadedTable.foreignKeys.filter(
377✔
1233
                (foreignKey) => {
1234
                    return (
141✔
1235
                        this.getTablePath(foreignKey) === tablePath &&
178✔
1236
                        foreignKey.referencedColumnNames.indexOf(columnName) !==
1237
                            -1
1238
                    )
1239
                },
1240
            )
1241

1242
            if (dependForeignKeys.length > 0) {
377✔
1243
                const clonedTable = loadedTable.clone()
8✔
1244
                clonedTable.foreignKeys = dependForeignKeys
8✔
1245
                tablesWithFK.push(clonedTable)
8✔
1246
                dependForeignKeys.forEach((dependForeignKey) =>
8✔
1247
                    loadedTable.removeForeignKey(dependForeignKey),
8✔
1248
                )
1249
            }
1250
        }
1251

1252
        if (tablesWithFK.length > 0) {
65✔
1253
            for (const tableWithFK of tablesWithFK) {
12✔
1254
                this.connection.logger.logSchemaBuild(
12✔
1255
                    `dropping related foreign keys of ${
1256
                        tableWithFK.name
1257
                    }: ${tableWithFK.foreignKeys
1258
                        .map((foreignKey) => foreignKey.name)
12✔
1259
                        .join(", ")}`,
1260
                )
1261
                await this.queryRunner.dropForeignKeys(
12✔
1262
                    tableWithFK,
1263
                    tableWithFK.foreignKeys,
1264
                )
1265
            }
1266
        }
1267
    }
1268

1269
    /**
1270
     * Drops all composite indices, related to given column.
1271
     */
1272
    protected async dropColumnCompositeIndices(
1273
        tablePath: string,
1274
        columnName: string,
1275
    ): Promise<void> {
1276
        const table = this.queryRunner.loadedTables.find(
65✔
1277
            (table) => this.getTablePath(table) === tablePath,
167✔
1278
        )
1279
        if (!table) return
65!
1280

1281
        const relatedIndices = table.indices.filter(
65✔
1282
            (index) =>
1283
                index.columnNames.length > 1 &&
4!
1284
                index.columnNames.indexOf(columnName) !== -1,
1285
        )
1286
        if (relatedIndices.length === 0) return
65✔
1287

1288
        this.connection.logger.logSchemaBuild(
×
1289
            `dropping related indices of "${tablePath}"."${columnName}": ${relatedIndices
1290
                .map((index) => index.name)
×
1291
                .join(", ")}`,
1292
        )
1293
        await this.queryRunner.dropIndices(table, relatedIndices)
×
1294
    }
1295

1296
    /**
1297
     * Drops all composite uniques, related to given column.
1298
     */
1299
    protected async dropColumnCompositeUniques(
1300
        tablePath: string,
1301
        columnName: string,
1302
    ): Promise<void> {
1303
        const table = this.queryRunner.loadedTables.find(
65✔
1304
            (table) => this.getTablePath(table) === tablePath,
167✔
1305
        )
1306
        if (!table) return
65!
1307

1308
        const relatedUniques = table.uniques.filter(
65✔
1309
            (unique) =>
1310
                unique.columnNames.length > 1 &&
58✔
1311
                unique.columnNames.indexOf(columnName) !== -1,
1312
        )
1313
        if (relatedUniques.length === 0) return
65✔
1314

1315
        this.connection.logger.logSchemaBuild(
4✔
1316
            `dropping related unique constraints of "${tablePath}"."${columnName}": ${relatedUniques
1317
                .map((unique) => unique.name)
4✔
1318
                .join(", ")}`,
1319
        )
1320
        await this.queryRunner.dropUniqueConstraints(table, relatedUniques)
4✔
1321
    }
1322

1323
    /**
1324
     * Creates new columns from the given column metadatas.
1325
     */
1326
    protected metadataColumnsToTableColumnOptions(
1327
        columns: ColumnMetadata[],
1328
    ): TableColumnOptions[] {
1329
        return columns.map((columnMetadata) =>
16✔
1330
            TableUtils.createTableColumnOptions(
20✔
1331
                columnMetadata,
1332
                this.connection.driver,
1333
            ),
1334
        )
1335
    }
1336

1337
    /**
1338
     * Creates typeorm service table for storing user defined Views and generate columns.
1339
     */
1340
    protected async createTypeormMetadataTable(queryRunner: QueryRunner) {
1341
        const schema = this.currentSchema
36✔
1342
        const database = this.currentDatabase
36✔
1343
        const typeormMetadataTable = this.connection.driver.buildTableName(
36✔
1344
            this.connection.metadataTableName,
1345
            schema,
1346
            database,
1347
        )
1348

1349
        // Spanner requires at least one primary key in a table.
1350
        // Since we don't have unique column in "typeorm_metadata" table
1351
        // and we should avoid breaking changes, we mark all columns as primary for Spanner driver.
1352
        const isPrimary = this.connection.driver.options.type === "spanner"
36✔
1353
        await queryRunner.createTable(
36✔
1354
            new Table({
1355
                database: database,
1356
                schema: schema,
1357
                name: typeormMetadataTable,
1358
                columns: [
1359
                    {
1360
                        name: "type",
1361
                        type: this.connection.driver.normalizeType({
1362
                            type: this.connection.driver.mappedDataTypes
1363
                                .metadataType,
1364
                        }),
1365
                        isNullable: false,
1366
                        isPrimary,
1367
                    },
1368
                    {
1369
                        name: "database",
1370
                        type: this.connection.driver.normalizeType({
1371
                            type: this.connection.driver.mappedDataTypes
1372
                                .metadataDatabase,
1373
                        }),
1374
                        isNullable: true,
1375
                        isPrimary,
1376
                    },
1377
                    {
1378
                        name: "schema",
1379
                        type: this.connection.driver.normalizeType({
1380
                            type: this.connection.driver.mappedDataTypes
1381
                                .metadataSchema,
1382
                        }),
1383
                        isNullable: true,
1384
                        isPrimary,
1385
                    },
1386
                    {
1387
                        name: "table",
1388
                        type: this.connection.driver.normalizeType({
1389
                            type: this.connection.driver.mappedDataTypes
1390
                                .metadataTable,
1391
                        }),
1392
                        isNullable: true,
1393
                        isPrimary,
1394
                    },
1395
                    {
1396
                        name: "name",
1397
                        type: this.connection.driver.normalizeType({
1398
                            type: this.connection.driver.mappedDataTypes
1399
                                .metadataName,
1400
                        }),
1401
                        isNullable: true,
1402
                        isPrimary,
1403
                    },
1404
                    {
1405
                        name: "value",
1406
                        type: this.connection.driver.normalizeType({
1407
                            type: this.connection.driver.mappedDataTypes
1408
                                .metadataValue,
1409
                        }),
1410
                        isNullable: true,
1411
                        isPrimary,
1412
                    },
1413
                ],
1414
            }),
1415
            true,
1416
        )
1417
    }
1418
}
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