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

typeorm / typeorm / 15219332477

23 May 2025 09:13PM UTC coverage: 17.216% (-59.1%) from 76.346%
15219332477

Pull #11332

github

naorpeled
cr comments - move if block
Pull Request #11332: feat: add new undefined and null behavior flags

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

0 of 31 new or added lines in 3 files covered. (0.0%)

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

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

1.92
/src/schema-builder/RdbmsSchemaBuilder.ts
1
import { Table } from "./table/Table"
1✔
2
import { TableColumn } from "./table/TableColumn"
1✔
3
import { TableForeignKey } from "./table/TableForeignKey"
1✔
4
import { TableIndex } from "./table/TableIndex"
1✔
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"
1✔
12
import { TableColumnOptions } from "./options/TableColumnOptions"
13
import { TableUnique } from "./table/TableUnique"
1✔
14
import { TableCheck } from "./table/TableCheck"
1✔
15
import { TableExclusion } from "./table/TableExclusion"
1✔
16
import { View } from "./view/View"
1✔
17
import { ViewUtils } from "./util/ViewUtils"
1✔
18
import { DriverUtils } from "../driver/DriverUtils"
1✔
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 {
1✔
UNCOV
36
    readonly "@instanceof" = Symbol.for("RdbmsSchemaBuilder")
×
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

UNCOV
51
    constructor(protected connection: DataSource) {}
×
52

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

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

63
        // this.connection.driver.database || this.currentDatabase;
UNCOV
64
        this.currentDatabase = this.connection.driver.database
×
UNCOV
65
        this.currentSchema = this.connection.driver.schema
×
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 =
UNCOV
72
            !(this.connection.driver.options.type === "cockroachdb") &&
×
73
            !(this.connection.driver.options.type === "spanner") &&
74
            this.connection.options.migrationsTransactionMode !== "none"
75

UNCOV
76
        await this.queryRunner.beforeMigration()
×
77

UNCOV
78
        if (isUsingTransactions) {
×
UNCOV
79
            await this.queryRunner.startTransaction()
×
80
        }
81

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

UNCOV
92
            await this.queryRunner.getTables(tablePaths)
×
UNCOV
93
            await this.queryRunner.getViews(viewPaths)
×
94

UNCOV
95
            await this.executeSchemaSyncOperationsInProperOrder()
×
96

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

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

UNCOV
117
            await this.queryRunner.release()
×
118
        }
119
    }
120

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

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

UNCOV
151
            this.queryRunner.enableSqlMemory()
×
UNCOV
152
            await this.executeSchemaSyncOperationsInProperOrder()
×
153

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

UNCOV
161
            return this.queryRunner.getMemorySql()
×
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.
UNCOV
166
            this.queryRunner.disableSqlMemory()
×
UNCOV
167
            await this.queryRunner.release()
×
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[] {
UNCOV
179
        return this.connection.entityMetadatas.filter(
×
180
            (metadata) =>
UNCOV
181
                metadata.synchronize &&
×
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[] {
UNCOV
191
        return (
×
192
            this.connection.entityMetadatas
193
                .filter(
194
                    (metadata) =>
UNCOV
195
                        metadata.tableType === "view" && metadata.synchronize,
×
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 {
UNCOV
206
        return this.connection.entityMetadatas.some((entityMetadata) => {
×
UNCOV
207
            return entityMetadata.columns.some((column) => column.generatedType)
×
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> {
UNCOV
216
        await this.dropOldViews()
×
UNCOV
217
        await this.dropOldForeignKeys()
×
UNCOV
218
        await this.dropOldIndices()
×
UNCOV
219
        await this.dropOldChecks()
×
UNCOV
220
        await this.dropOldExclusions()
×
UNCOV
221
        await this.dropCompositeUniqueConstraints()
×
222
        // await this.renameTables();
UNCOV
223
        await this.renameColumns()
×
UNCOV
224
        await this.changeTableComment()
×
UNCOV
225
        await this.createNewTables()
×
UNCOV
226
        await this.dropRemovedColumns()
×
UNCOV
227
        await this.addNewColumns()
×
UNCOV
228
        await this.updatePrimaryKeys()
×
UNCOV
229
        await this.updateExistColumns()
×
UNCOV
230
        await this.createNewIndices()
×
UNCOV
231
        await this.createNewChecks()
×
UNCOV
232
        await this.createNewExclusions()
×
UNCOV
233
        await this.createCompositeUniqueConstraints()
×
UNCOV
234
        await this.createForeignKeys()
×
UNCOV
235
        await this.createViews()
×
UNCOV
236
        await this.createNewViewIndices()
×
237
    }
238

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

UNCOV
244
        return this.connection.driver.buildTableName(
×
245
            parsed.tableName,
246
            parsed.schema || this.currentSchema,
×
247
            parsed.database || this.currentDatabase,
×
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> {
UNCOV
255
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
256
            const table = this.queryRunner.loadedTables.find(
×
257
                (table) =>
UNCOV
258
                    this.getTablePath(table) === this.getTablePath(metadata),
×
259
            )
UNCOV
260
            if (!table) continue
×
261

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

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

292
            // drop foreign keys from the database
UNCOV
293
            await this.queryRunner.dropForeignKeys(
×
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> {
UNCOV
315
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
316
            const table = this.queryRunner.loadedTables.find(
×
317
                (table) =>
UNCOV
318
                    this.getTablePath(table) === this.getTablePath(metadata),
×
319
            )
UNCOV
320
            if (!table) continue
×
321

UNCOV
322
            if (metadata.columns.length !== table.columns.length) continue
×
323

UNCOV
324
            const renamedMetadataColumns = metadata.columns
×
UNCOV
325
                .filter((c) => !c.isVirtualProperty)
×
326
                .filter((column) => {
UNCOV
327
                    return !table.columns.find((tableColumn) => {
×
UNCOV
328
                        return (
×
329
                            tableColumn.name === column.databaseName &&
×
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

UNCOV
339
            if (
×
340
                renamedMetadataColumns.length === 0 ||
×
341
                renamedMetadataColumns.length > 1
342
            )
UNCOV
343
                continue
×
344

UNCOV
345
            const renamedTableColumns = table.columns.filter((tableColumn) => {
×
UNCOV
346
                return !metadata.columns.find((column) => {
×
UNCOV
347
                    return (
×
348
                        !column.isVirtualProperty &&
×
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

UNCOV
359
            if (
×
360
                renamedTableColumns.length === 0 ||
×
361
                renamedTableColumns.length > 1
362
            )
363
                continue
×
364

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

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

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

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

UNCOV
395
                        if (indexMetadata.isUnique !== tableIndex.isUnique)
×
UNCOV
396
                            return true
×
397

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

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

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

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

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

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

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

UNCOV
452
                            if (indexMetadata.isUnique !== tableIndex.isUnique)
×
UNCOV
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

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

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

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

UNCOV
506
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
507
            const table = this.queryRunner.loadedTables.find(
×
508
                (table) =>
UNCOV
509
                    this.getTablePath(table) === this.getTablePath(metadata),
×
510
            )
UNCOV
511
            if (!table) continue
×
512

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

UNCOV
519
            if (oldChecks.length === 0) continue
×
520

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

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

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

UNCOV
548
            if (compositeUniques.length === 0) continue
×
549

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

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

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

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

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

UNCOV
582
            this.connection.logger.logSchemaBuild(
×
583
                `dropping old exclusion constraint: ${oldExclusions
UNCOV
584
                    .map((exclusion) => `"${exclusion.name}"`)
×
585
                    .join(", ")} from table "${table.name}"`,
586
            )
UNCOV
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> {
UNCOV
598
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
599
            const table = this.queryRunner.loadedTables.find(
×
600
                (table) =>
UNCOV
601
                    this.getTablePath(table) === this.getTablePath(metadata),
×
602
            )
UNCOV
603
            if (!table) continue
×
604

UNCOV
605
            if (
×
606
                DriverUtils.isMySQLFamily(this.connection.driver) ||
×
607
                this.connection.driver.options.type === "postgres"
608
            ) {
UNCOV
609
                const newComment = metadata.comment
×
UNCOV
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> {
UNCOV
621
        for (const metadata of this.entityToSyncMetadatas) {
×
622
            // check if table does not exist yet
UNCOV
623
            const existTable = this.queryRunner.loadedTables.find(
×
624
                (table) =>
UNCOV
625
                    this.getTablePath(table) === this.getTablePath(metadata),
×
626
            )
UNCOV
627
            if (existTable) continue
×
628

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

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

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

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

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

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

UNCOV
698
            if (viewExpression === metadataExpression) continue
×
699

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

704
            // Collect view to be dropped
UNCOV
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
UNCOV
709
        const viewDependencyChain = (view: View): View[] => {
×
710
            // Get the view metadata
UNCOV
711
            const viewMetadata = viewToMetadata.get(view)
×
UNCOV
712
            let viewWithDependencies = [view]
×
713
            // If no metadata is known for the view, simply return the view itself
UNCOV
714
            if (!viewMetadata) {
×
715
                return viewWithDependencies
×
716
            }
717
            // Iterate over all known views
UNCOV
718
            for (const [
×
719
                currentView,
720
                currentMetadata,
721
            ] of viewToMetadata.entries()) {
722
                // Ignore self reference
UNCOV
723
                if (currentView === view) {
×
UNCOV
724
                    continue
×
725
                }
726
                // If the currently iterated view depends on the passed in view
UNCOV
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
UNCOV
733
                    viewWithDependencies = viewWithDependencies.concat(
×
734
                        viewDependencyChain(currentView),
735
                    )
736
                }
737
            }
738
            // Return all collected views
UNCOV
739
            return viewWithDependencies
×
740
        }
741

742
        // Collect final list of views to be dropped in a Set so there are no duplicates
UNCOV
743
        const droppedViewsWithDependencies: Set<View> = new Set(
×
744
            // Collect all dropped views, and their dependencies
745
            droppedViews
UNCOV
746
                .map((view) => viewDependencyChain(view))
×
747
                // Flattened to single Array ( can be replaced with flatMap, once supported)
748
                .reduce((all, segment) => {
UNCOV
749
                    return all.concat(segment)
×
750
                }, [])
751
                // Sort the views to be dropped in creation order
752
                .sort((a, b) => {
UNCOV
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
UNCOV
763
        for (const view of droppedViewsWithDependencies) {
×
UNCOV
764
            await this.queryRunner.dropView(view)
×
765
        }
UNCOV
766
        this.queryRunner.loadedViews = this.queryRunner.loadedViews.filter(
×
UNCOV
767
            (view) => !droppedViewsWithDependencies.has(view),
×
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> {
UNCOV
776
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
777
            const table = this.queryRunner.loadedTables.find(
×
778
                (table) =>
UNCOV
779
                    this.getTablePath(table) === this.getTablePath(metadata),
×
780
            )
UNCOV
781
            if (!table) continue
×
782

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

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

798
            // drop columns from the database
UNCOV
799
            await this.queryRunner.dropColumns(table, droppedTableColumns)
×
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> {
UNCOV
808
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
809
            const table = this.queryRunner.loadedTables.find(
×
810
                (table) =>
UNCOV
811
                    this.getTablePath(table) === this.getTablePath(metadata),
×
812
            )
UNCOV
813
            if (!table) continue
×
814

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

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

UNCOV
837
            if (newTableColumns.length === 0) continue
×
838

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

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

UNCOV
860
            const primaryMetadataColumns = metadata.columns.filter(
×
UNCOV
861
                (column) => column.isPrimary,
×
862
            )
UNCOV
863
            const primaryTableColumns = table.columns.filter(
×
UNCOV
864
                (column) => column.isPrimary,
×
865
            )
UNCOV
866
            if (
×
867
                primaryTableColumns.length !== primaryMetadataColumns.length &&
×
868
                primaryMetadataColumns.length > 1
869
            ) {
UNCOV
870
                const changedPrimaryColumns = primaryMetadataColumns.map(
×
871
                    (primaryMetadataColumn) => {
UNCOV
872
                        return new TableColumn(
×
873
                            TableUtils.createTableColumnOptions(
874
                                primaryMetadataColumn,
875
                                this.connection.driver,
876
                            ),
877
                        )
878
                    },
879
                )
UNCOV
880
                await this.queryRunner.updatePrimaryKeys(
×
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> {
UNCOV
893
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
894
            const table = this.queryRunner.loadedTables.find(
×
895
                (table) =>
UNCOV
896
                    this.getTablePath(table) === this.getTablePath(metadata),
×
897
            )
UNCOV
898
            if (!table) continue
×
899

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

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

914
            // drop all composite indices related to this column
UNCOV
915
            for (const changedColumn of changedColumns) {
×
UNCOV
916
                await this.dropColumnCompositeIndices(
×
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.
UNCOV
924
            if (
×
925
                !(
926
                    DriverUtils.isMySQLFamily(this.connection.driver) ||
×
927
                    this.connection.driver.options.type === "aurora-mysql" ||
928
                    this.connection.driver.options.type === "spanner"
929
                )
930
            ) {
UNCOV
931
                for (const changedColumn of changedColumns) {
×
UNCOV
932
                    await this.dropColumnCompositeUniques(
×
933
                        this.getTablePath(metadata),
934
                        changedColumn.databaseName,
935
                    )
936
                }
937
            }
938

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

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

UNCOV
961
            if (newAndOldTableColumns.length === 0) continue
×
962

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

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

UNCOV
984
            const newIndices = metadata.indices
×
985
                .filter(
986
                    (indexMetadata) =>
UNCOV
987
                        !table.indices.find(
×
988
                            (tableIndex) =>
UNCOV
989
                                tableIndex.name === indexMetadata.name,
×
990
                        ) && indexMetadata.synchronize === true,
991
                )
UNCOV
992
                .map((indexMetadata) => TableIndex.create(indexMetadata))
×
993

UNCOV
994
            if (newIndices.length === 0) continue
×
995

UNCOV
996
            this.connection.logger.logSchemaBuild(
×
997
                `adding new indices ${newIndices
UNCOV
998
                    .map((index) => `"${index.name}"`)
×
999
                    .join(", ")} in table "${table.name}"`,
1000
            )
UNCOV
1001
            await this.queryRunner.createIndices(table, newIndices)
×
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.
UNCOV
1010
        if (
×
1011
            this.connection.options.type !== "postgres" ||
×
1012
            !DriverUtils.isPostgresFamily(this.connection.driver)
1013
        ) {
UNCOV
1014
            return
×
1015
        }
1016
        const postgresQueryRunner: PostgresQueryRunner = <PostgresQueryRunner>(
UNCOV
1017
            this.queryRunner
×
1018
        )
UNCOV
1019
        for (const metadata of this.viewEntityToSyncMetadatas) {
×
1020
            // check if view does not exist yet
UNCOV
1021
            const view = this.queryRunner.loadedViews.find((view) => {
×
1022
                const viewExpression =
UNCOV
1023
                    typeof view.expression === "string"
×
1024
                        ? view.expression.trim()
1025
                        : view.expression(this.connection).getQuery()
1026
                const metadataExpression =
UNCOV
1027
                    typeof metadata.expression === "string"
×
1028
                        ? metadata.expression.trim()
1029
                        : metadata.expression!(this.connection).getQuery()
UNCOV
1030
                return (
×
1031
                    this.getTablePath(view) === this.getTablePath(metadata) &&
×
1032
                    viewExpression === metadataExpression
1033
                )
1034
            })
UNCOV
1035
            if (!view || !view.materialized) continue
×
1036

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

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

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

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

UNCOV
1066
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
1067
            const table = this.queryRunner.loadedTables.find(
×
1068
                (table) =>
UNCOV
1069
                    this.getTablePath(table) === this.getTablePath(metadata),
×
1070
            )
UNCOV
1071
            if (!table) continue
×
1072

UNCOV
1073
            const newChecks = metadata.checks
×
1074
                .filter(
1075
                    (checkMetadata) =>
UNCOV
1076
                        !table.checks.find(
×
1077
                            (tableCheck) =>
UNCOV
1078
                                tableCheck.name === checkMetadata.name,
×
1079
                        ),
1080
                )
UNCOV
1081
                .map((checkMetadata) => TableCheck.create(checkMetadata))
×
1082

UNCOV
1083
            if (newChecks.length === 0) continue
×
1084

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

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

UNCOV
1105
            const compositeUniques = metadata.uniques
×
1106
                .filter(
1107
                    (uniqueMetadata) =>
UNCOV
1108
                        uniqueMetadata.columns.length > 1 &&
×
1109
                        !table.uniques.find(
1110
                            (tableUnique) =>
UNCOV
1111
                                tableUnique.name === uniqueMetadata.name,
×
1112
                        ),
1113
                )
UNCOV
1114
                .map((uniqueMetadata) => TableUnique.create(uniqueMetadata))
×
1115

UNCOV
1116
            if (compositeUniques.length === 0) continue
×
1117

UNCOV
1118
            this.connection.logger.logSchemaBuild(
×
1119
                `adding new unique constraints: ${compositeUniques
UNCOV
1120
                    .map((unique) => `"${unique.name}"`)
×
1121
                    .join(", ")} in table "${table.name}"`,
1122
            )
UNCOV
1123
            await this.queryRunner.createUniqueConstraints(
×
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
UNCOV
1135
        if (!(this.connection.driver.options.type === "postgres")) return
×
1136

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

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

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

UNCOV
1158
            this.connection.logger.logSchemaBuild(
×
1159
                `adding new exclusion constraints: ${newExclusions
UNCOV
1160
                    .map((exclusion) => `"${exclusion.name}"`)
×
1161
                    .join(", ")} in table "${table.name}"`,
1162
            )
UNCOV
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> {
UNCOV
1174
        for (const metadata of this.entityToSyncMetadatas) {
×
UNCOV
1175
            const table = this.queryRunner.loadedTables.find(
×
1176
                (table) =>
UNCOV
1177
                    this.getTablePath(table) === this.getTablePath(metadata),
×
1178
            )
UNCOV
1179
            if (!table) continue
×
1180

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

UNCOV
1193
            const dbForeignKeys = newKeys.map((foreignKeyMetadata) =>
×
UNCOV
1194
                TableForeignKey.create(
×
1195
                    foreignKeyMetadata,
1196
                    this.connection.driver,
1197
                ),
1198
            )
UNCOV
1199
            this.connection.logger.logSchemaBuild(
×
1200
                `creating a foreign keys: ${newKeys
UNCOV
1201
                    .map((key) => key.name)
×
1202
                    .join(", ")} on table "${table.name}"`,
1203
            )
UNCOV
1204
            await this.queryRunner.createForeignKeys(table, dbForeignKeys)
×
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> {
UNCOV
1215
        const table = this.queryRunner.loadedTables.find(
×
UNCOV
1216
            (table) => this.getTablePath(table) === tablePath,
×
1217
        )
UNCOV
1218
        if (!table) return
×
1219

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

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

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

UNCOV
1252
        if (tablesWithFK.length > 0) {
×
UNCOV
1253
            for (const tableWithFK of tablesWithFK) {
×
UNCOV
1254
                this.connection.logger.logSchemaBuild(
×
1255
                    `dropping related foreign keys of ${
1256
                        tableWithFK.name
1257
                    }: ${tableWithFK.foreignKeys
UNCOV
1258
                        .map((foreignKey) => foreignKey.name)
×
1259
                        .join(", ")}`,
1260
                )
UNCOV
1261
                await this.queryRunner.dropForeignKeys(
×
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> {
UNCOV
1276
        const table = this.queryRunner.loadedTables.find(
×
UNCOV
1277
            (table) => this.getTablePath(table) === tablePath,
×
1278
        )
UNCOV
1279
        if (!table) return
×
1280

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

UNCOV
1288
        this.connection.logger.logSchemaBuild(
×
1289
            `dropping related indices of "${tablePath}"."${columnName}": ${relatedIndices
UNCOV
1290
                .map((index) => index.name)
×
1291
                .join(", ")}`,
1292
        )
UNCOV
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> {
UNCOV
1303
        const table = this.queryRunner.loadedTables.find(
×
UNCOV
1304
            (table) => this.getTablePath(table) === tablePath,
×
1305
        )
UNCOV
1306
        if (!table) return
×
1307

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

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

1323
    /**
1324
     * Creates new columns from the given column metadatas.
1325
     */
1326
    protected metadataColumnsToTableColumnOptions(
1327
        columns: ColumnMetadata[],
1328
    ): TableColumnOptions[] {
UNCOV
1329
        return columns.map((columnMetadata) =>
×
UNCOV
1330
            TableUtils.createTableColumnOptions(
×
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) {
UNCOV
1341
        const schema = this.currentSchema
×
UNCOV
1342
        const database = this.currentDatabase
×
UNCOV
1343
        const typeormMetadataTable = this.connection.driver.buildTableName(
×
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.
UNCOV
1352
        const isPrimary = this.connection.driver.options.type === "spanner"
×
UNCOV
1353
        await queryRunner.createTable(
×
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