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

typeorm / typeorm / 15089093306

17 May 2025 09:03PM UTC coverage: 50.109% (-26.2%) from 76.346%
15089093306

Pull #11437

github

naorpeled
add comment about vector <#>
Pull Request #11437: feat(postgres): support vector data type

5836 of 12767 branches covered (45.71%)

Branch coverage included in aggregate %.

16 of 17 new or added lines in 4 files covered. (94.12%)

6283 existing lines in 64 files now uncovered.

12600 of 24025 relevant lines covered (52.45%)

28708.0 hits per line

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

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

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

76
        await this.queryRunner.beforeMigration()
8,209✔
77

78
        if (isUsingTransactions) {
8,209✔
79
            await this.queryRunner.startTransaction()
8,207✔
80
        }
81

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

92
            await this.queryRunner.getTables(tablePaths)
8,209✔
93
            await this.queryRunner.getViews(viewPaths)
8,209✔
94

95
            await this.executeSchemaSyncOperationsInProperOrder()
8,209✔
96

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

103
            if (isUsingTransactions) {
8,209✔
104
                await this.queryRunner.commitTransaction()
8,207✔
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 {
115
            await this.queryRunner.afterMigration()
8,209✔
116

117
            await this.queryRunner.release()
8,209✔
118
        }
119
    }
120

121
    /**
122
     * Create the typeorm_metadata table if necessary.
123
     */
124
    async createMetadataTableIfNecessary(
125
        queryRunner: QueryRunner,
126
    ): Promise<void> {
127
        if (
8,271✔
128
            this.viewEntityToSyncMetadatas.length > 0 ||
16,506✔
129
            this.hasGeneratedColumns()
130
        ) {
131
            await this.createTypeormMetadataTable(queryRunner)
50✔
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()
167✔
140
        try {
167✔
141
            // Flush the queryrunner table & view cache
142
            const tablePaths = this.entityToSyncMetadatas.map((metadata) =>
167✔
143
                this.getTablePath(metadata),
218✔
144
            )
145
            const viewPaths = this.viewEntityToSyncMetadatas.map((metadata) =>
167✔
146
                this.getTablePath(metadata),
6✔
147
            )
148
            await this.queryRunner.getTables(tablePaths)
167✔
149
            await this.queryRunner.getViews(viewPaths)
167✔
150

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

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

161
            return this.queryRunner.getMemorySql()
167✔
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()
167✔
167
            await this.queryRunner.release()
167✔
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(
137,690✔
180
            (metadata) =>
181
                metadata.synchronize &&
482,888✔
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 (
37,073✔
192
            this.connection.entityMetadatas
193
                .filter(
194
                    (metadata) =>
195
                        metadata.tableType === "view" && metadata.synchronize,
129,485✔
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) => {
8,235✔
207
            return entityMetadata.columns.some((column) => column.generatedType)
90,851✔
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()
8,376✔
217
        await this.dropOldForeignKeys()
8,376✔
218
        await this.dropOldIndices()
8,376✔
219
        await this.dropOldChecks()
8,376✔
220
        await this.dropOldExclusions()
8,376✔
221
        await this.dropCompositeUniqueConstraints()
8,376✔
222
        // await this.renameTables();
223
        await this.renameColumns()
8,376✔
224
        await this.changeTableComment()
8,376✔
225
        await this.createNewTables()
8,376✔
226
        await this.dropRemovedColumns()
8,376✔
227
        await this.addNewColumns()
8,376✔
228
        await this.updatePrimaryKeys()
8,376✔
229
        await this.updateExistColumns()
8,376✔
230
        await this.createNewIndices()
8,376✔
231
        await this.createNewChecks()
8,376✔
232
        await this.createNewExclusions()
8,376✔
233
        await this.createCompositeUniqueConstraints()
8,376✔
234
        await this.createForeignKeys()
8,376✔
235
        await this.createViews()
8,376✔
236
        await this.createNewViewIndices()
8,376✔
237
    }
238

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

244
        return this.connection.driver.buildTableName(
1,822,517✔
245
            parsed.tableName,
246
            parsed.schema || this.currentSchema,
3,259,673✔
247
            parsed.database || this.currentDatabase,
2,386,295✔
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) {
8,376✔
256
            const table = this.queryRunner.loadedTables.find(
28,138✔
257
                (table) =>
258
                    this.getTablePath(table) === this.getTablePath(metadata),
4,315✔
259
            )
260
            if (!table) continue
28,138✔
261

262
            // find foreign keys that exist in the schemas but does not exist in the entity metadata
263
            const tableForeignKeysToDrop = table.foreignKeys.filter(
1,242✔
264
                (tableForeignKey) => {
265
                    const metadataFK = metadata.foreignKeys.find(
479✔
266
                        (metadataForeignKey) =>
267
                            tableForeignKey.name === metadataForeignKey.name &&
622✔
268
                            this.getTablePath(tableForeignKey) ===
269
                                this.getTablePath(
270
                                    metadataForeignKey.referencedEntityMetadata,
271
                                ),
272
                    )
273
                    return (
479✔
274
                        !metadataFK ||
2,339✔
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
1,242✔
283

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

292
            // drop foreign keys from the database
293
            await this.queryRunner.dropForeignKeys(
17✔
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) {
8,376✔
316
            const table = this.queryRunner.loadedTables.find(
28,138✔
317
                (table) =>
318
                    this.getTablePath(table) === this.getTablePath(metadata),
4,315✔
319
            )
320
            if (!table) continue
28,138✔
321

322
            if (metadata.columns.length !== table.columns.length) continue
1,242✔
323

324
            const renamedMetadataColumns = metadata.columns
1,216✔
325
                .filter((c) => !c.isVirtualProperty)
3,960✔
326
                .filter((column) => {
327
                    return !table.columns.find((tableColumn) => {
3,960✔
328
                        return (
10,831✔
329
                            tableColumn.name === column.databaseName &&
22,628✔
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 (
1,216✔
340
                renamedMetadataColumns.length === 0 ||
1,261✔
341
                renamedMetadataColumns.length > 1
342
            )
343
                continue
1,171✔
344

345
            const renamedTableColumns = table.columns.filter((tableColumn) => {
45✔
346
                return !metadata.columns.find((column) => {
188✔
347
                    return (
635✔
348
                        !column.isVirtualProperty &&
1,751✔
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 (
45!
360
                renamedTableColumns.length === 0 ||
90✔
361
                renamedTableColumns.length > 1
362
            )
363
                continue
×
364

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

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

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

387
            const dropQueries = table.indices
1,242✔
388
                .filter((tableIndex) => {
389
                    const indexMetadata = metadata.indices.find(
226✔
390
                        (index) => index.name === tableIndex.name,
259✔
391
                    )
392
                    if (indexMetadata) {
226✔
393
                        if (indexMetadata.synchronize === false) return false
200✔
394

395
                        if (indexMetadata.isUnique !== tableIndex.isUnique)
194✔
396
                            return true
24✔
397

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

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

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

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

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

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

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

452
                            if (indexMetadata.isUnique !== tableIndex.isUnique)
1✔
453
                                return true
1✔
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
2✔
482
                    })
483
                    .map(async (tableIndex) => {
484
                        this.connection.logger.logSchemaBuild(
3✔
485
                            `dropping an index: "${tableIndex.name}" from view ${view.name}`,
486
                        )
487
                        await postgresQueryRunner.dropViewIndex(
3✔
488
                            view,
489
                            tableIndex,
490
                        )
491
                    })
492

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

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

506
        for (const metadata of this.entityToSyncMetadatas) {
8,376✔
507
            const table = this.queryRunner.loadedTables.find(
28,138✔
508
                (table) =>
509
                    this.getTablePath(table) === this.getTablePath(metadata),
4,315✔
510
            )
511
            if (!table) continue
28,138✔
512

513
            const oldChecks = table.checks.filter((tableCheck) => {
1,242✔
514
                return !metadata.checks.find(
156✔
515
                    (checkMetadata) => checkMetadata.name === tableCheck.name,
150✔
516
                )
517
            })
518

519
            if (oldChecks.length === 0) continue
1,242✔
520

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

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

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

548
            if (compositeUniques.length === 0) continue
1,242✔
549

550
            this.connection.logger.logSchemaBuild(
7✔
551
                `dropping old unique constraint: ${compositeUniques
552
                    .map((unique) => `"${unique.name}"`)
7✔
553
                    .join(", ")} from table "${table.name}"`,
554
            )
555
            await this.queryRunner.dropUniqueConstraints(
7✔
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
8,376✔
565

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

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

580
            if (oldExclusions.length === 0) continue
310✔
581

582
            this.connection.logger.logSchemaBuild(
2✔
583
                `dropping old exclusion constraint: ${oldExclusions
584
                    .map((exclusion) => `"${exclusion.name}"`)
2✔
585
                    .join(", ")} from table "${table.name}"`,
586
            )
587
            await this.queryRunner.dropExclusionConstraints(
2✔
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) {
8,376✔
599
            const table = this.queryRunner.loadedTables.find(
28,138✔
600
                (table) =>
601
                    this.getTablePath(table) === this.getTablePath(metadata),
4,315✔
602
            )
603
            if (!table) continue
28,138✔
604

605
            if (
1,242✔
606
                DriverUtils.isMySQLFamily(this.connection.driver) ||
2,484✔
607
                this.connection.driver.options.type === "postgres"
608
            ) {
609
                const newComment = metadata.comment
310✔
610
                await this.queryRunner.changeTableComment(table, newComment)
310✔
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) {
8,376✔
622
            // check if table does not exist yet
623
            const existTable = this.queryRunner.loadedTables.find(
28,138✔
624
                (table) =>
625
                    this.getTablePath(table) === this.getTablePath(metadata),
69,078✔
626
            )
627
            if (existTable) continue
28,138✔
628

629
            this.connection.logger.logSchemaBuild(
26,836✔
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)
26,836✔
635
            await this.queryRunner.createTable(table, false, false)
26,836✔
636
            this.queryRunner.loadedTables.push(table)
26,836✔
637
        }
638
    }
639

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

659
            this.connection.logger.logSchemaBuild(
75✔
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)
75✔
665
            await this.queryRunner.createView(view, true)
75✔
666
            this.queryRunner.loadedViews.push(view)
75✔
667
        }
668
    }
669

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

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

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

704
            // Collect view to be dropped
705
            droppedViews.push(view)
4✔
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[] => {
8,376✔
710
            // Get the view metadata
711
            const viewMetadata = viewToMetadata.get(view)
6✔
712
            let viewWithDependencies = [view]
6✔
713
            // If no metadata is known for the view, simply return the view itself
714
            if (!viewMetadata) {
6!
715
                return viewWithDependencies
×
716
            }
717
            // Iterate over all known views
718
            for (const [
6✔
719
                currentView,
720
                currentMetadata,
721
            ] of viewToMetadata.entries()) {
722
                // Ignore self reference
723
                if (currentView === view) {
18✔
724
                    continue
6✔
725
                }
726
                // If the currently iterated view depends on the passed in view
727
                if (
12✔
728
                    currentMetadata.dependsOn &&
20✔
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(
2✔
734
                        viewDependencyChain(currentView),
735
                    )
736
                }
737
            }
738
            // Return all collected views
739
            return viewWithDependencies
6✔
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(
8,376✔
744
            // Collect all dropped views, and their dependencies
745
            droppedViews
746
                .map((view) => viewDependencyChain(view))
4✔
747
                // Flattened to single Array ( can be replaced with flatMap, once supported)
748
                .reduce((all, segment) => {
749
                    return all.concat(segment)
4✔
750
                }, [])
751
                // Sort the views to be dropped in creation order
752
                .sort((a, b) => {
753
                    return ViewUtils.viewMetadataCmp(
4✔
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) {
8,376✔
764
            await this.queryRunner.dropView(view)
6✔
765
        }
766
        this.queryRunner.loadedViews = this.queryRunner.loadedViews.filter(
8,376✔
767
            (view) => !droppedViewsWithDependencies.has(view),
10✔
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) {
8,376✔
777
            const table = this.queryRunner.loadedTables.find(
28,138✔
778
                (table) =>
779
                    this.getTablePath(table) === this.getTablePath(metadata),
95,914✔
780
            )
781
            if (!table) continue
28,138!
782

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

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

798
            // drop columns from the database
799
            await this.queryRunner.dropColumns(table, droppedTableColumns)
14✔
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) {
8,376✔
809
            const table = this.queryRunner.loadedTables.find(
28,138✔
810
                (table) =>
811
                    this.getTablePath(table) === this.getTablePath(metadata),
95,914✔
812
            )
813
            if (!table) continue
28,138!
814

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

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

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

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

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

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

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

906
            // drop all foreign keys that point to this column
907
            for (const changedColumn of changedColumns) {
109✔
908
                await this.dropColumnReferencedForeignKeys(
143✔
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) {
109✔
916
                await this.dropColumnCompositeIndices(
143✔
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 (
109✔
925
                !(
926
                    DriverUtils.isMySQLFamily(this.connection.driver) ||
327✔
927
                    this.connection.driver.options.type === "aurora-mysql" ||
928
                    this.connection.driver.options.type === "spanner"
929
                )
930
            ) {
931
                for (const changedColumn of changedColumns) {
109✔
932
                    await this.dropColumnCompositeUniques(
143✔
933
                        this.getTablePath(metadata),
934
                        changedColumn.databaseName,
935
                    )
936
                }
937
            }
938

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

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

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

963
            this.connection.logger.logSchemaBuild(
109✔
964
                `columns changed in "${table.name}". updating: ` +
965
                    changedColumns
966
                        .map((column) => column.databaseName)
143✔
967
                        .join(", "),
968
            )
969
            await this.queryRunner.changeColumns(table, newAndOldTableColumns)
109✔
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) {
8,376✔
978
            const table = this.queryRunner.loadedTables.find(
28,138✔
979
                (table) =>
980
                    this.getTablePath(table) === this.getTablePath(metadata),
95,914✔
981
            )
982
            if (!table) continue
28,138!
983

984
            const newIndices = metadata.indices
28,138✔
985
                .filter(
986
                    (indexMetadata) =>
987
                        !table.indices.find(
10,377✔
988
                            (tableIndex) =>
989
                                tableIndex.name === indexMetadata.name,
14,665✔
990
                        ) && indexMetadata.synchronize === true,
991
                )
992
                .map((indexMetadata) => TableIndex.create(indexMetadata))
38✔
993

994
            if (newIndices.length === 0) continue
28,138✔
995

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

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

1047
            if (newIndices.length === 0) continue
14✔
1048

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

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

1066
        for (const metadata of this.entityToSyncMetadatas) {
8,376✔
1067
            const table = this.queryRunner.loadedTables.find(
28,138✔
1068
                (table) =>
1069
                    this.getTablePath(table) === this.getTablePath(metadata),
95,914✔
1070
            )
1071
            if (!table) continue
28,138!
1072

1073
            const newChecks = metadata.checks
28,138✔
1074
                .filter(
1075
                    (checkMetadata) =>
1076
                        !table.checks.find(
540✔
1077
                            (tableCheck) =>
1078
                                tableCheck.name === checkMetadata.name,
529✔
1079
                        ),
1080
                )
1081
                .map((checkMetadata) => TableCheck.create(checkMetadata))
12✔
1082

1083
            if (newChecks.length === 0) continue
28,138✔
1084

1085
            this.connection.logger.logSchemaBuild(
12✔
1086
                `adding new check constraints: ${newChecks
1087
                    .map((index) => `"${index.name}"`)
12✔
1088
                    .join(", ")} in table "${table.name}"`,
1089
            )
1090
            await this.queryRunner.createCheckConstraints(table, newChecks)
12✔
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) {
8,376✔
1099
            const table = this.queryRunner.loadedTables.find(
28,138✔
1100
                (table) =>
1101
                    this.getTablePath(table) === this.getTablePath(metadata),
95,914✔
1102
            )
1103
            if (!table) continue
28,138!
1104

1105
            const compositeUniques = metadata.uniques
28,138✔
1106
                .filter(
1107
                    (uniqueMetadata) =>
1108
                        uniqueMetadata.columns.length > 1 &&
5,357✔
1109
                        !table.uniques.find(
1110
                            (tableUnique) =>
1111
                                tableUnique.name === uniqueMetadata.name,
2,185✔
1112
                        ),
1113
                )
1114
                .map((uniqueMetadata) => TableUnique.create(uniqueMetadata))
9✔
1115

1116
            if (compositeUniques.length === 0) continue
28,138✔
1117

1118
            this.connection.logger.logSchemaBuild(
9✔
1119
                `adding new unique constraints: ${compositeUniques
1120
                    .map((unique) => `"${unique.name}"`)
9✔
1121
                    .join(", ")} in table "${table.name}"`,
1122
            )
1123
            await this.queryRunner.createUniqueConstraints(
9✔
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
8,376✔
1136

1137
        for (const metadata of this.entityToSyncMetadatas) {
1,837✔
1138
            const table = this.queryRunner.loadedTables.find(
5,686✔
1139
                (table) =>
1140
                    this.getTablePath(table) === this.getTablePath(metadata),
18,491✔
1141
            )
1142
            if (!table) continue
5,686!
1143

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

1156
            if (newExclusions.length === 0) continue
5,686✔
1157

1158
            this.connection.logger.logSchemaBuild(
2✔
1159
                `adding new exclusion constraints: ${newExclusions
1160
                    .map((exclusion) => `"${exclusion.name}"`)
2✔
1161
                    .join(", ")} in table "${table.name}"`,
1162
            )
1163
            await this.queryRunner.createExclusionConstraints(
2✔
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) {
8,376✔
1175
            const table = this.queryRunner.loadedTables.find(
28,138✔
1176
                (table) =>
1177
                    this.getTablePath(table) === this.getTablePath(metadata),
95,914✔
1178
            )
1179
            if (!table) continue
28,138!
1180

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

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

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

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

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

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

1281
        const relatedIndices = table.indices.filter(
143✔
1282
            (index) =>
1283
                index.columnNames.length > 1 &&
6!
1284
                index.columnNames.indexOf(columnName) !== -1,
1285
        )
1286
        if (relatedIndices.length === 0) return
143✔
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> {
1303
        const table = this.queryRunner.loadedTables.find(
143✔
1304
            (table) => this.getTablePath(table) === tablePath,
321✔
1305
        )
1306
        if (!table) return
143!
1307

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

1315
        this.connection.logger.logSchemaBuild(
8✔
1316
            `dropping related unique constraints of "${tablePath}"."${columnName}": ${relatedUniques
1317
                .map((unique) => unique.name)
8✔
1318
                .join(", ")}`,
1319
        )
1320
        await this.queryRunner.dropUniqueConstraints(table, relatedUniques)
8✔
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) =>
24✔
1330
            TableUtils.createTableColumnOptions(
30✔
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
50✔
1342
        const database = this.currentDatabase
50✔
1343
        const typeormMetadataTable = this.connection.driver.buildTableName(
50✔
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"
50✔
1353
        await queryRunner.createTable(
50✔
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