• 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.5
/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts
1
import { QueryRunner } from "../../query-runner/QueryRunner"
2
import { ObjectLiteral } from "../../common/ObjectLiteral"
3
import { TransactionNotStartedError } from "../../error/TransactionNotStartedError"
1✔
4
import { TableColumn } from "../../schema-builder/table/TableColumn"
1✔
5
import { Table } from "../../schema-builder/table/Table"
1✔
6
import { TableIndex } from "../../schema-builder/table/TableIndex"
1✔
7
import { TableForeignKey } from "../../schema-builder/table/TableForeignKey"
1✔
8
import { View } from "../../schema-builder/view/View"
1✔
9
import { Query } from "../Query"
1✔
10
import { AbstractSqliteDriver } from "./AbstractSqliteDriver"
11
import { ReadStream } from "../../platform/PlatformTools"
12
import { TableIndexOptions } from "../../schema-builder/options/TableIndexOptions"
13
import { TableUnique } from "../../schema-builder/table/TableUnique"
1✔
14
import { BaseQueryRunner } from "../../query-runner/BaseQueryRunner"
1✔
15
import { OrmUtils } from "../../util/OrmUtils"
1✔
16
import { TableCheck } from "../../schema-builder/table/TableCheck"
1✔
17
import { IsolationLevel } from "../types/IsolationLevel"
18
import { TableExclusion } from "../../schema-builder/table/TableExclusion"
19
import { TransactionAlreadyStartedError, TypeORMError } from "../../error"
1✔
20
import { MetadataTableType } from "../types/MetadataTableType"
1✔
21
import { InstanceChecker } from "../../util/InstanceChecker"
1✔
22

23
/**
24
 * Runs queries on a single sqlite database connection.
25
 */
26
export abstract class AbstractSqliteQueryRunner
1✔
27
    extends BaseQueryRunner
28
    implements QueryRunner
29
{
30
    // -------------------------------------------------------------------------
31
    // Public Implemented Properties
32
    // -------------------------------------------------------------------------
33

34
    /**
35
     * Database driver used by connection.
36
     */
37
    driver: AbstractSqliteDriver
38

UNCOV
39
    protected transactionPromise: Promise<any> | null = null
×
40

41
    // -------------------------------------------------------------------------
42
    // Constructor
43
    // -------------------------------------------------------------------------
44

45
    constructor() {
UNCOV
46
        super()
×
47
    }
48

49
    // -------------------------------------------------------------------------
50
    // Public Methods
51
    // -------------------------------------------------------------------------
52

53
    /**
54
     * Creates/uses database connection from the connection pool to perform further operations.
55
     * Returns obtained database connection.
56
     */
57
    connect(): Promise<any> {
UNCOV
58
        return Promise.resolve(this.driver.databaseConnection)
×
59
    }
60

61
    /**
62
     * Releases used database connection.
63
     * We just clear loaded tables and sql in memory, because sqlite do not support multiple connections thus query runners.
64
     */
65
    release(): Promise<void> {
UNCOV
66
        this.loadedTables = []
×
UNCOV
67
        this.clearSqlMemory()
×
UNCOV
68
        return Promise.resolve()
×
69
    }
70

71
    /**
72
     * Starts transaction.
73
     */
74
    async startTransaction(isolationLevel?: IsolationLevel): Promise<void> {
UNCOV
75
        if (this.driver.transactionSupport === "none")
×
76
            throw new TypeORMError(
×
77
                `Transactions aren't supported by ${this.connection.driver.options.type}.`,
78
            )
79

UNCOV
80
        if (
×
81
            this.isTransactionActive &&
×
82
            this.driver.transactionSupport === "simple"
83
        )
84
            throw new TransactionAlreadyStartedError()
×
85

UNCOV
86
        if (
×
87
            isolationLevel &&
×
88
            isolationLevel !== "READ UNCOMMITTED" &&
89
            isolationLevel !== "SERIALIZABLE"
90
        )
91
            throw new TypeORMError(
×
92
                `SQLite only supports SERIALIZABLE and READ UNCOMMITTED isolation`,
93
            )
94

UNCOV
95
        this.isTransactionActive = true
×
UNCOV
96
        try {
×
UNCOV
97
            await this.broadcaster.broadcast("BeforeTransactionStart")
×
98
        } catch (err) {
99
            this.isTransactionActive = false
×
100
            throw err
×
101
        }
102

UNCOV
103
        if (this.transactionDepth === 0) {
×
UNCOV
104
            if (isolationLevel) {
×
UNCOV
105
                if (isolationLevel === "READ UNCOMMITTED") {
×
UNCOV
106
                    await this.query("PRAGMA read_uncommitted = true")
×
107
                } else {
UNCOV
108
                    await this.query("PRAGMA read_uncommitted = false")
×
109
                }
110
            }
UNCOV
111
            await this.query("BEGIN TRANSACTION")
×
112
        } else {
UNCOV
113
            await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
×
114
        }
UNCOV
115
        this.transactionDepth += 1
×
116

UNCOV
117
        await this.broadcaster.broadcast("AfterTransactionStart")
×
118
    }
119

120
    /**
121
     * Commits transaction.
122
     * Error will be thrown if transaction was not started.
123
     */
124
    async commitTransaction(): Promise<void> {
UNCOV
125
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
×
126

UNCOV
127
        await this.broadcaster.broadcast("BeforeTransactionCommit")
×
128

UNCOV
129
        if (this.transactionDepth > 1) {
×
UNCOV
130
            await this.query(
×
131
                `RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
132
            )
133
        } else {
UNCOV
134
            await this.query("COMMIT")
×
UNCOV
135
            this.isTransactionActive = false
×
136
        }
UNCOV
137
        this.transactionDepth -= 1
×
138

UNCOV
139
        await this.broadcaster.broadcast("AfterTransactionCommit")
×
140
    }
141

142
    /**
143
     * Rollbacks transaction.
144
     * Error will be thrown if transaction was not started.
145
     */
146
    async rollbackTransaction(): Promise<void> {
UNCOV
147
        if (!this.isTransactionActive) throw new TransactionNotStartedError()
×
148

UNCOV
149
        await this.broadcaster.broadcast("BeforeTransactionRollback")
×
150

UNCOV
151
        if (this.transactionDepth > 1) {
×
UNCOV
152
            await this.query(
×
153
                `ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
154
            )
155
        } else {
UNCOV
156
            await this.query("ROLLBACK")
×
UNCOV
157
            this.isTransactionActive = false
×
158
        }
UNCOV
159
        this.transactionDepth -= 1
×
160

UNCOV
161
        await this.broadcaster.broadcast("AfterTransactionRollback")
×
162
    }
163

164
    /**
165
     * Returns raw data stream.
166
     */
167
    stream(
168
        query: string,
169
        parameters?: any[],
170
        onEnd?: Function,
171
        onError?: Function,
172
    ): Promise<ReadStream> {
173
        throw new TypeORMError(`Stream is not supported by sqlite driver.`)
×
174
    }
175

176
    /**
177
     * Returns all available database names including system databases.
178
     */
179
    async getDatabases(): Promise<string[]> {
180
        return Promise.resolve([])
×
181
    }
182

183
    /**
184
     * Returns all available schema names including system schemas.
185
     * If database parameter specified, returns schemas of that database.
186
     */
187
    async getSchemas(database?: string): Promise<string[]> {
188
        return Promise.resolve([])
×
189
    }
190

191
    /**
192
     * Checks if database with the given name exist.
193
     */
194
    async hasDatabase(database: string): Promise<boolean> {
195
        return Promise.resolve(false)
×
196
    }
197

198
    /**
199
     * Loads currently using database
200
     */
201
    async getCurrentDatabase(): Promise<undefined> {
202
        return Promise.resolve(undefined)
×
203
    }
204

205
    /**
206
     * Checks if schema with the given name exist.
207
     */
208
    async hasSchema(schema: string): Promise<boolean> {
209
        throw new TypeORMError(`This driver does not support table schemas`)
×
210
    }
211

212
    /**
213
     * Loads currently using database schema
214
     */
215
    async getCurrentSchema(): Promise<undefined> {
216
        return Promise.resolve(undefined)
×
217
    }
218

219
    /**
220
     * Checks if table with the given name exist in the database.
221
     */
222
    async hasTable(tableOrName: Table | string): Promise<boolean> {
UNCOV
223
        const tableName = InstanceChecker.isTable(tableOrName)
×
224
            ? tableOrName.name
225
            : tableOrName
UNCOV
226
        const sql = `SELECT * FROM "sqlite_master" WHERE "type" = 'table' AND "name" = '${tableName}'`
×
UNCOV
227
        const result = await this.query(sql)
×
UNCOV
228
        return result.length ? true : false
×
229
    }
230

231
    /**
232
     * Checks if column with the given name exist in the given table.
233
     */
234
    async hasColumn(
235
        tableOrName: Table | string,
236
        columnName: string,
237
    ): Promise<boolean> {
UNCOV
238
        const tableName = InstanceChecker.isTable(tableOrName)
×
239
            ? tableOrName.name
240
            : tableOrName
UNCOV
241
        const sql = `PRAGMA table_xinfo(${this.escapePath(tableName)})`
×
UNCOV
242
        const columns: ObjectLiteral[] = await this.query(sql)
×
UNCOV
243
        return !!columns.find((column) => column["name"] === columnName)
×
244
    }
245

246
    /**
247
     * Creates a new database.
248
     */
249
    async createDatabase(
250
        database: string,
251
        ifNotExist?: boolean,
252
    ): Promise<void> {
UNCOV
253
        return Promise.resolve()
×
254
    }
255

256
    /**
257
     * Drops database.
258
     */
259
    async dropDatabase(database: string, ifExist?: boolean): Promise<void> {
260
        return Promise.resolve()
×
261
    }
262

263
    /**
264
     * Creates a new table schema.
265
     */
266
    async createSchema(
267
        schemaPath: string,
268
        ifNotExist?: boolean,
269
    ): Promise<void> {
270
        return Promise.resolve()
×
271
    }
272

273
    /**
274
     * Drops table schema.
275
     */
276
    async dropSchema(schemaPath: string, ifExist?: boolean): Promise<void> {
277
        return Promise.resolve()
×
278
    }
279

280
    /**
281
     * Creates a new table.
282
     */
283
    async createTable(
284
        table: Table,
285
        ifNotExist: boolean = false,
×
286
        createForeignKeys: boolean = true,
×
287
        createIndices: boolean = true,
×
288
    ): Promise<void> {
UNCOV
289
        const upQueries: Query[] = []
×
UNCOV
290
        const downQueries: Query[] = []
×
291

UNCOV
292
        if (ifNotExist) {
×
UNCOV
293
            const isTableExist = await this.hasTable(table)
×
UNCOV
294
            if (isTableExist) return Promise.resolve()
×
295
        }
296

UNCOV
297
        upQueries.push(this.createTableSql(table, createForeignKeys))
×
UNCOV
298
        downQueries.push(this.dropTableSql(table))
×
299

UNCOV
300
        if (createIndices) {
×
UNCOV
301
            table.indices.forEach((index) => {
×
302
                // new index may be passed without name. In this case we generate index name manually.
UNCOV
303
                if (!index.name)
×
UNCOV
304
                    index.name = this.connection.namingStrategy.indexName(
×
305
                        table,
306
                        index.columnNames,
307
                        index.where,
308
                    )
UNCOV
309
                upQueries.push(this.createIndexSql(table, index))
×
UNCOV
310
                downQueries.push(this.dropIndexSql(index))
×
311
            })
312
        }
313

314
        // if table have column with generated type, we must add the expression to the metadata table
UNCOV
315
        const generatedColumns = table.columns.filter(
×
UNCOV
316
            (column) => column.generatedType && column.asExpression,
×
317
        )
318

UNCOV
319
        for (const column of generatedColumns) {
×
UNCOV
320
            const insertQuery = this.insertTypeormMetadataSql({
×
321
                table: table.name,
322
                type: MetadataTableType.GENERATED_COLUMN,
323
                name: column.name,
324
                value: column.asExpression,
325
            })
326

UNCOV
327
            const deleteQuery = this.deleteTypeormMetadataSql({
×
328
                table: table.name,
329
                type: MetadataTableType.GENERATED_COLUMN,
330
                name: column.name,
331
            })
332

UNCOV
333
            upQueries.push(insertQuery)
×
UNCOV
334
            downQueries.push(deleteQuery)
×
335
        }
336

UNCOV
337
        await this.executeQueries(upQueries, downQueries)
×
338
    }
339

340
    /**
341
     * Drops the table.
342
     */
343
    async dropTable(
344
        tableOrName: Table | string,
345
        ifExist?: boolean,
346
        dropForeignKeys: boolean = true,
×
347
        dropIndices: boolean = true,
×
348
    ): Promise<void> {
UNCOV
349
        if (ifExist) {
×
350
            const isTableExist = await this.hasTable(tableOrName)
×
351
            if (!isTableExist) return Promise.resolve()
×
352
        }
353

354
        // if dropTable called with dropForeignKeys = true, we must create foreign keys in down query.
UNCOV
355
        const createForeignKeys: boolean = dropForeignKeys
×
UNCOV
356
        const table = InstanceChecker.isTable(tableOrName)
×
357
            ? tableOrName
358
            : await this.getCachedTable(tableOrName)
UNCOV
359
        const upQueries: Query[] = []
×
UNCOV
360
        const downQueries: Query[] = []
×
361

UNCOV
362
        if (dropIndices) {
×
UNCOV
363
            table.indices.forEach((index) => {
×
UNCOV
364
                upQueries.push(this.dropIndexSql(index))
×
UNCOV
365
                downQueries.push(this.createIndexSql(table, index))
×
366
            })
367
        }
368

UNCOV
369
        upQueries.push(this.dropTableSql(table, ifExist))
×
UNCOV
370
        downQueries.push(this.createTableSql(table, createForeignKeys))
×
371

372
        // if table had columns with generated type, we must remove the expression from the metadata table
UNCOV
373
        const generatedColumns = table.columns.filter(
×
UNCOV
374
            (column) => column.generatedType && column.asExpression,
×
375
        )
376

UNCOV
377
        for (const column of generatedColumns) {
×
UNCOV
378
            const deleteQuery = this.deleteTypeormMetadataSql({
×
379
                table: table.name,
380
                type: MetadataTableType.GENERATED_COLUMN,
381
                name: column.name,
382
            })
383

UNCOV
384
            const insertQuery = this.insertTypeormMetadataSql({
×
385
                table: table.name,
386
                type: MetadataTableType.GENERATED_COLUMN,
387
                name: column.name,
388
                value: column.asExpression,
389
            })
390

UNCOV
391
            upQueries.push(deleteQuery)
×
UNCOV
392
            downQueries.push(insertQuery)
×
393
        }
394

UNCOV
395
        await this.executeQueries(upQueries, downQueries)
×
396
    }
397

398
    /**
399
     * Creates a new view.
400
     */
401
    async createView(
402
        view: View,
403
        syncWithMetadata: boolean = false,
×
404
    ): Promise<void> {
UNCOV
405
        const upQueries: Query[] = []
×
UNCOV
406
        const downQueries: Query[] = []
×
UNCOV
407
        upQueries.push(this.createViewSql(view))
×
UNCOV
408
        if (syncWithMetadata) upQueries.push(this.insertViewDefinitionSql(view))
×
UNCOV
409
        downQueries.push(this.dropViewSql(view))
×
UNCOV
410
        if (syncWithMetadata)
×
UNCOV
411
            downQueries.push(this.deleteViewDefinitionSql(view))
×
UNCOV
412
        await this.executeQueries(upQueries, downQueries)
×
413
    }
414

415
    /**
416
     * Drops the view.
417
     */
418
    async dropView(target: View | string): Promise<void> {
419
        const viewName = InstanceChecker.isView(target) ? target.name : target
×
420
        const view = await this.getCachedView(viewName)
×
421

422
        const upQueries: Query[] = []
×
423
        const downQueries: Query[] = []
×
424
        upQueries.push(this.deleteViewDefinitionSql(view))
×
425
        upQueries.push(this.dropViewSql(view))
×
426
        downQueries.push(this.insertViewDefinitionSql(view))
×
427
        downQueries.push(this.createViewSql(view))
×
428
        await this.executeQueries(upQueries, downQueries)
×
429
    }
430

431
    /**
432
     * Renames the given table.
433
     */
434
    async renameTable(
435
        oldTableOrName: Table | string,
436
        newTableName: string,
437
    ): Promise<void> {
UNCOV
438
        const oldTable = InstanceChecker.isTable(oldTableOrName)
×
439
            ? oldTableOrName
440
            : await this.getCachedTable(oldTableOrName)
UNCOV
441
        const newTable = oldTable.clone()
×
442

UNCOV
443
        newTable.name = newTableName
×
444

445
        // rename table
UNCOV
446
        const up = new Query(
×
447
            `ALTER TABLE ${this.escapePath(
448
                oldTable.name,
449
            )} RENAME TO ${this.escapePath(newTableName)}`,
450
        )
UNCOV
451
        const down = new Query(
×
452
            `ALTER TABLE ${this.escapePath(
453
                newTableName,
454
            )} RENAME TO ${this.escapePath(oldTable.name)}`,
455
        )
UNCOV
456
        await this.executeQueries(up, down)
×
457

458
        // rename unique constraints
UNCOV
459
        newTable.uniques.forEach((unique) => {
×
460
            const oldUniqueName =
UNCOV
461
                this.connection.namingStrategy.uniqueConstraintName(
×
462
                    oldTable,
463
                    unique.columnNames,
464
                )
465

466
            // Skip renaming if Unique has user defined constraint name
UNCOV
467
            if (unique.name !== oldUniqueName) return
×
468

UNCOV
469
            unique.name = this.connection.namingStrategy.uniqueConstraintName(
×
470
                newTable,
471
                unique.columnNames,
472
            )
473
        })
474

475
        // rename foreign key constraints
UNCOV
476
        newTable.foreignKeys.forEach((foreignKey) => {
×
477
            const oldForeignKeyName =
UNCOV
478
                this.connection.namingStrategy.foreignKeyName(
×
479
                    oldTable,
480
                    foreignKey.columnNames,
481
                    this.getTablePath(foreignKey),
482
                    foreignKey.referencedColumnNames,
483
                )
484

485
            // Skip renaming if foreign key has user defined constraint name
UNCOV
486
            if (foreignKey.name !== oldForeignKeyName) return
×
487

UNCOV
488
            foreignKey.name = this.connection.namingStrategy.foreignKeyName(
×
489
                newTable,
490
                foreignKey.columnNames,
491
                this.getTablePath(foreignKey),
492
                foreignKey.referencedColumnNames,
493
            )
494
        })
495

496
        // rename indices
UNCOV
497
        newTable.indices.forEach((index) => {
×
UNCOV
498
            const oldIndexName = this.connection.namingStrategy.indexName(
×
499
                oldTable,
500
                index.columnNames,
501
                index.where,
502
            )
503

504
            // Skip renaming if Index has user defined constraint name
UNCOV
505
            if (index.name !== oldIndexName) return
×
506

UNCOV
507
            index.name = this.connection.namingStrategy.indexName(
×
508
                newTable,
509
                index.columnNames,
510
                index.where,
511
            )
512
        })
513

514
        // rename old table;
UNCOV
515
        oldTable.name = newTable.name
×
516

517
        // recreate table with new constraint names
UNCOV
518
        await this.recreateTable(newTable, oldTable)
×
519
    }
520

521
    /**
522
     * Creates a new column from the column in the table.
523
     */
524
    async addColumn(
525
        tableOrName: Table | string,
526
        column: TableColumn,
527
    ): Promise<void> {
UNCOV
528
        const table = InstanceChecker.isTable(tableOrName)
×
529
            ? tableOrName
530
            : await this.getCachedTable(tableOrName)
UNCOV
531
        return this.addColumns(table!, [column])
×
532
    }
533

534
    /**
535
     * Creates a new columns from the column in the table.
536
     */
537
    async addColumns(
538
        tableOrName: Table | string,
539
        columns: TableColumn[],
540
    ): Promise<void> {
UNCOV
541
        const table = InstanceChecker.isTable(tableOrName)
×
542
            ? tableOrName
543
            : await this.getCachedTable(tableOrName)
UNCOV
544
        const changedTable = table.clone()
×
UNCOV
545
        columns.forEach((column) => changedTable.addColumn(column))
×
UNCOV
546
        await this.recreateTable(changedTable, table)
×
547
    }
548

549
    /**
550
     * Renames column in the given table.
551
     */
552
    async renameColumn(
553
        tableOrName: Table | string,
554
        oldTableColumnOrName: TableColumn | string,
555
        newTableColumnOrName: TableColumn | string,
556
    ): Promise<void> {
UNCOV
557
        const table = InstanceChecker.isTable(tableOrName)
×
558
            ? tableOrName
559
            : await this.getCachedTable(tableOrName)
UNCOV
560
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
×
561
            ? oldTableColumnOrName
UNCOV
562
            : table.columns.find((c) => c.name === oldTableColumnOrName)
×
UNCOV
563
        if (!oldColumn)
×
564
            throw new TypeORMError(
×
565
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
566
            )
567

UNCOV
568
        let newColumn: TableColumn | undefined = undefined
×
UNCOV
569
        if (InstanceChecker.isTableColumn(newTableColumnOrName)) {
×
UNCOV
570
            newColumn = newTableColumnOrName
×
571
        } else {
UNCOV
572
            newColumn = oldColumn.clone()
×
UNCOV
573
            newColumn.name = newTableColumnOrName
×
574
        }
575

UNCOV
576
        return this.changeColumn(table, oldColumn, newColumn)
×
577
    }
578

579
    /**
580
     * Changes a column in the table.
581
     */
582
    async changeColumn(
583
        tableOrName: Table | string,
584
        oldTableColumnOrName: TableColumn | string,
585
        newColumn: TableColumn,
586
    ): Promise<void> {
UNCOV
587
        const table = InstanceChecker.isTable(tableOrName)
×
588
            ? tableOrName
589
            : await this.getCachedTable(tableOrName)
UNCOV
590
        const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
×
591
            ? oldTableColumnOrName
592
            : table.columns.find((c) => c.name === oldTableColumnOrName)
×
UNCOV
593
        if (!oldColumn)
×
594
            throw new TypeORMError(
×
595
                `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
596
            )
597

UNCOV
598
        await this.changeColumns(table, [{ oldColumn, newColumn }])
×
599
    }
600

601
    /**
602
     * Changes a column in the table.
603
     * Changed column looses all its keys in the db.
604
     */
605
    async changeColumns(
606
        tableOrName: Table | string,
607
        changedColumns: { oldColumn: TableColumn; newColumn: TableColumn }[],
608
    ): Promise<void> {
UNCOV
609
        const table = InstanceChecker.isTable(tableOrName)
×
610
            ? tableOrName
611
            : await this.getCachedTable(tableOrName)
UNCOV
612
        const changedTable = table.clone()
×
UNCOV
613
        changedColumns.forEach((changedColumnSet) => {
×
UNCOV
614
            if (
×
615
                changedColumnSet.newColumn.name !==
616
                changedColumnSet.oldColumn.name
617
            ) {
UNCOV
618
                changedTable
×
619
                    .findColumnUniques(changedColumnSet.oldColumn)
620
                    .forEach((unique) => {
621
                        const uniqueName =
UNCOV
622
                            this.connection.namingStrategy.uniqueConstraintName(
×
623
                                table,
624
                                unique.columnNames,
625
                            )
626

UNCOV
627
                        unique.columnNames.splice(
×
628
                            unique.columnNames.indexOf(
629
                                changedColumnSet.oldColumn.name,
630
                            ),
631
                            1,
632
                        )
UNCOV
633
                        unique.columnNames.push(changedColumnSet.newColumn.name)
×
634

635
                        // rename Unique only if it has default constraint name
UNCOV
636
                        if (unique.name === uniqueName) {
×
UNCOV
637
                            unique.name =
×
638
                                this.connection.namingStrategy.uniqueConstraintName(
639
                                    changedTable,
640
                                    unique.columnNames,
641
                                )
642
                        }
643
                    })
644

UNCOV
645
                changedTable
×
646
                    .findColumnForeignKeys(changedColumnSet.oldColumn)
647
                    .forEach((foreignKey) => {
648
                        const foreignKeyName =
UNCOV
649
                            this.connection.namingStrategy.foreignKeyName(
×
650
                                table,
651
                                foreignKey.columnNames,
652
                                this.getTablePath(foreignKey),
653
                                foreignKey.referencedColumnNames,
654
                            )
655

UNCOV
656
                        foreignKey.columnNames.splice(
×
657
                            foreignKey.columnNames.indexOf(
658
                                changedColumnSet.oldColumn.name,
659
                            ),
660
                            1,
661
                        )
UNCOV
662
                        foreignKey.columnNames.push(
×
663
                            changedColumnSet.newColumn.name,
664
                        )
665

666
                        // rename FK only if it has default constraint name
UNCOV
667
                        if (foreignKey.name === foreignKeyName) {
×
UNCOV
668
                            foreignKey.name =
×
669
                                this.connection.namingStrategy.foreignKeyName(
670
                                    changedTable,
671
                                    foreignKey.columnNames,
672
                                    this.getTablePath(foreignKey),
673
                                    foreignKey.referencedColumnNames,
674
                                )
675
                        }
676
                    })
677

UNCOV
678
                changedTable
×
679
                    .findColumnIndices(changedColumnSet.oldColumn)
680
                    .forEach((index) => {
681
                        const indexName =
UNCOV
682
                            this.connection.namingStrategy.indexName(
×
683
                                table,
684
                                index.columnNames,
685
                                index.where,
686
                            )
687

UNCOV
688
                        index.columnNames.splice(
×
689
                            index.columnNames.indexOf(
690
                                changedColumnSet.oldColumn.name,
691
                            ),
692
                            1,
693
                        )
UNCOV
694
                        index.columnNames.push(changedColumnSet.newColumn.name)
×
695

696
                        // rename Index only if it has default constraint name
UNCOV
697
                        if (index.name === indexName) {
×
UNCOV
698
                            index.name =
×
699
                                this.connection.namingStrategy.indexName(
700
                                    changedTable,
701
                                    index.columnNames,
702
                                    index.where,
703
                                )
704
                        }
705
                    })
706
            }
UNCOV
707
            const originalColumn = changedTable.columns.find(
×
UNCOV
708
                (column) => column.name === changedColumnSet.oldColumn.name,
×
709
            )
UNCOV
710
            if (originalColumn)
×
UNCOV
711
                changedTable.columns[
×
712
                    changedTable.columns.indexOf(originalColumn)
713
                ] = changedColumnSet.newColumn
714
        })
715

UNCOV
716
        await this.recreateTable(changedTable, table)
×
717
    }
718

719
    /**
720
     * Drops column in the table.
721
     */
722
    async dropColumn(
723
        tableOrName: Table | string,
724
        columnOrName: TableColumn | string,
725
    ): Promise<void> {
UNCOV
726
        const table = InstanceChecker.isTable(tableOrName)
×
727
            ? tableOrName
728
            : await this.getCachedTable(tableOrName)
UNCOV
729
        const column = InstanceChecker.isTableColumn(columnOrName)
×
730
            ? columnOrName
731
            : table.findColumnByName(columnOrName)
UNCOV
732
        if (!column)
×
UNCOV
733
            throw new TypeORMError(
×
734
                `Column "${columnOrName}" was not found in table "${table.name}"`,
735
            )
736

UNCOV
737
        await this.dropColumns(table, [column])
×
738
    }
739

740
    /**
741
     * Drops the columns in the table.
742
     */
743
    async dropColumns(
744
        tableOrName: Table | string,
745
        columns: TableColumn[] | string[],
746
    ): Promise<void> {
UNCOV
747
        const table = InstanceChecker.isTable(tableOrName)
×
748
            ? tableOrName
749
            : await this.getCachedTable(tableOrName)
750

751
        // clone original table and remove column and its constraints from cloned table
UNCOV
752
        const changedTable = table.clone()
×
UNCOV
753
        columns.forEach((column: TableColumn | string) => {
×
UNCOV
754
            const columnInstance = InstanceChecker.isTableColumn(column)
×
755
                ? column
756
                : table.findColumnByName(column)
UNCOV
757
            if (!columnInstance)
×
758
                throw new Error(
×
759
                    `Column "${column}" was not found in table "${table.name}"`,
760
                )
761

UNCOV
762
            changedTable.removeColumn(columnInstance)
×
UNCOV
763
            changedTable
×
764
                .findColumnUniques(columnInstance)
765
                .forEach((unique) =>
UNCOV
766
                    changedTable.removeUniqueConstraint(unique),
×
767
                )
UNCOV
768
            changedTable
×
769
                .findColumnIndices(columnInstance)
770
                .forEach((index) => changedTable.removeIndex(index))
×
UNCOV
771
            changedTable
×
772
                .findColumnForeignKeys(columnInstance)
773
                .forEach((fk) => changedTable.removeForeignKey(fk))
×
774
        })
775

UNCOV
776
        await this.recreateTable(changedTable, table)
×
777
    }
778

779
    /**
780
     * Creates a new primary key.
781
     */
782
    async createPrimaryKey(
783
        tableOrName: Table | string,
784
        columnNames: string[],
785
    ): Promise<void> {
UNCOV
786
        const table = InstanceChecker.isTable(tableOrName)
×
787
            ? tableOrName
788
            : await this.getCachedTable(tableOrName)
789
        // clone original table and mark columns as primary
UNCOV
790
        const changedTable = table.clone()
×
UNCOV
791
        changedTable.columns.forEach((column) => {
×
UNCOV
792
            if (columnNames.find((columnName) => columnName === column.name))
×
UNCOV
793
                column.isPrimary = true
×
794
        })
795

UNCOV
796
        await this.recreateTable(changedTable, table)
×
797
        // mark columns as primary in original table
UNCOV
798
        table.columns.forEach((column) => {
×
UNCOV
799
            if (columnNames.find((columnName) => columnName === column.name))
×
UNCOV
800
                column.isPrimary = true
×
801
        })
802
    }
803

804
    /**
805
     * Updates composite primary keys.
806
     */
807
    async updatePrimaryKeys(
808
        tableOrName: Table | string,
809
        columns: TableColumn[],
810
    ): Promise<void> {
UNCOV
811
        await Promise.resolve()
×
812
    }
813

814
    /**
815
     * Drops a primary key.
816
     */
817
    async dropPrimaryKey(tableOrName: Table | string): Promise<void> {
UNCOV
818
        const table = InstanceChecker.isTable(tableOrName)
×
819
            ? tableOrName
820
            : await this.getCachedTable(tableOrName)
821
        // clone original table and mark primary columns as non-primary
UNCOV
822
        const changedTable = table.clone()
×
UNCOV
823
        changedTable.primaryColumns.forEach((column) => {
×
UNCOV
824
            column.isPrimary = false
×
825
        })
826

UNCOV
827
        await this.recreateTable(changedTable, table)
×
828
        // mark primary columns as non-primary in original table
UNCOV
829
        table.primaryColumns.forEach((column) => {
×
830
            column.isPrimary = false
×
831
        })
832
    }
833

834
    /**
835
     * Creates a new unique constraint.
836
     */
837
    async createUniqueConstraint(
838
        tableOrName: Table | string,
839
        uniqueConstraint: TableUnique,
840
    ): Promise<void> {
UNCOV
841
        await this.createUniqueConstraints(tableOrName, [uniqueConstraint])
×
842
    }
843

844
    /**
845
     * Creates a new unique constraints.
846
     */
847
    async createUniqueConstraints(
848
        tableOrName: Table | string,
849
        uniqueConstraints: TableUnique[],
850
    ): Promise<void> {
UNCOV
851
        const table = InstanceChecker.isTable(tableOrName)
×
852
            ? tableOrName
853
            : await this.getCachedTable(tableOrName)
854

855
        // clone original table and add unique constraints in to cloned table
UNCOV
856
        const changedTable = table.clone()
×
UNCOV
857
        uniqueConstraints.forEach((uniqueConstraint) =>
×
UNCOV
858
            changedTable.addUniqueConstraint(uniqueConstraint),
×
859
        )
UNCOV
860
        await this.recreateTable(changedTable, table)
×
861
    }
862

863
    /**
864
     * Drops an unique constraint.
865
     */
866
    async dropUniqueConstraint(
867
        tableOrName: Table | string,
868
        uniqueOrName: TableUnique | string,
869
    ): Promise<void> {
UNCOV
870
        const table = InstanceChecker.isTable(tableOrName)
×
871
            ? tableOrName
872
            : await this.getCachedTable(tableOrName)
UNCOV
873
        const uniqueConstraint = InstanceChecker.isTableUnique(uniqueOrName)
×
874
            ? uniqueOrName
875
            : table.uniques.find((u) => u.name === uniqueOrName)
×
UNCOV
876
        if (!uniqueConstraint)
×
877
            throw new TypeORMError(
×
878
                `Supplied unique constraint was not found in table ${table.name}`,
879
            )
880

UNCOV
881
        await this.dropUniqueConstraints(table, [uniqueConstraint])
×
882
    }
883

884
    /**
885
     * Creates an unique constraints.
886
     */
887
    async dropUniqueConstraints(
888
        tableOrName: Table | string,
889
        uniqueConstraints: TableUnique[],
890
    ): Promise<void> {
UNCOV
891
        const table = InstanceChecker.isTable(tableOrName)
×
892
            ? tableOrName
893
            : await this.getCachedTable(tableOrName)
894

895
        // clone original table and remove unique constraints from cloned table
UNCOV
896
        const changedTable = table.clone()
×
UNCOV
897
        uniqueConstraints.forEach((uniqueConstraint) =>
×
UNCOV
898
            changedTable.removeUniqueConstraint(uniqueConstraint),
×
899
        )
900

UNCOV
901
        await this.recreateTable(changedTable, table)
×
902
    }
903

904
    /**
905
     * Creates new check constraint.
906
     */
907
    async createCheckConstraint(
908
        tableOrName: Table | string,
909
        checkConstraint: TableCheck,
910
    ): Promise<void> {
UNCOV
911
        await this.createCheckConstraints(tableOrName, [checkConstraint])
×
912
    }
913

914
    /**
915
     * Creates new check constraints.
916
     */
917
    async createCheckConstraints(
918
        tableOrName: Table | string,
919
        checkConstraints: TableCheck[],
920
    ): Promise<void> {
UNCOV
921
        const table = InstanceChecker.isTable(tableOrName)
×
922
            ? tableOrName
923
            : await this.getCachedTable(tableOrName)
924

925
        // clone original table and add check constraints in to cloned table
UNCOV
926
        const changedTable = table.clone()
×
UNCOV
927
        checkConstraints.forEach((checkConstraint) =>
×
UNCOV
928
            changedTable.addCheckConstraint(checkConstraint),
×
929
        )
UNCOV
930
        await this.recreateTable(changedTable, table)
×
931
    }
932

933
    /**
934
     * Drops check constraint.
935
     */
936
    async dropCheckConstraint(
937
        tableOrName: Table | string,
938
        checkOrName: TableCheck | string,
939
    ): Promise<void> {
UNCOV
940
        const table = InstanceChecker.isTable(tableOrName)
×
941
            ? tableOrName
942
            : await this.getCachedTable(tableOrName)
UNCOV
943
        const checkConstraint = InstanceChecker.isTableCheck(checkOrName)
×
944
            ? checkOrName
945
            : table.checks.find((c) => c.name === checkOrName)
×
UNCOV
946
        if (!checkConstraint)
×
947
            throw new TypeORMError(
×
948
                `Supplied check constraint was not found in table ${table.name}`,
949
            )
950

UNCOV
951
        await this.dropCheckConstraints(table, [checkConstraint])
×
952
    }
953

954
    /**
955
     * Drops check constraints.
956
     */
957
    async dropCheckConstraints(
958
        tableOrName: Table | string,
959
        checkConstraints: TableCheck[],
960
    ): Promise<void> {
UNCOV
961
        const table = InstanceChecker.isTable(tableOrName)
×
962
            ? tableOrName
963
            : await this.getCachedTable(tableOrName)
964

965
        // clone original table and remove check constraints from cloned table
UNCOV
966
        const changedTable = table.clone()
×
UNCOV
967
        checkConstraints.forEach((checkConstraint) =>
×
UNCOV
968
            changedTable.removeCheckConstraint(checkConstraint),
×
969
        )
970

UNCOV
971
        await this.recreateTable(changedTable, table)
×
972
    }
973

974
    /**
975
     * Creates a new exclusion constraint.
976
     */
977
    async createExclusionConstraint(
978
        tableOrName: Table | string,
979
        exclusionConstraint: TableExclusion,
980
    ): Promise<void> {
981
        throw new TypeORMError(`Sqlite does not support exclusion constraints.`)
×
982
    }
983

984
    /**
985
     * Creates a new exclusion constraints.
986
     */
987
    async createExclusionConstraints(
988
        tableOrName: Table | string,
989
        exclusionConstraints: TableExclusion[],
990
    ): Promise<void> {
991
        throw new TypeORMError(`Sqlite does not support exclusion constraints.`)
×
992
    }
993

994
    /**
995
     * Drops exclusion constraint.
996
     */
997
    async dropExclusionConstraint(
998
        tableOrName: Table | string,
999
        exclusionOrName: TableExclusion | string,
1000
    ): Promise<void> {
1001
        throw new TypeORMError(`Sqlite does not support exclusion constraints.`)
×
1002
    }
1003

1004
    /**
1005
     * Drops exclusion constraints.
1006
     */
1007
    async dropExclusionConstraints(
1008
        tableOrName: Table | string,
1009
        exclusionConstraints: TableExclusion[],
1010
    ): Promise<void> {
1011
        throw new TypeORMError(`Sqlite does not support exclusion constraints.`)
×
1012
    }
1013

1014
    /**
1015
     * Creates a new foreign key.
1016
     */
1017
    async createForeignKey(
1018
        tableOrName: Table | string,
1019
        foreignKey: TableForeignKey,
1020
    ): Promise<void> {
UNCOV
1021
        await this.createForeignKeys(tableOrName, [foreignKey])
×
1022
    }
1023

1024
    /**
1025
     * Creates a new foreign keys.
1026
     */
1027
    async createForeignKeys(
1028
        tableOrName: Table | string,
1029
        foreignKeys: TableForeignKey[],
1030
    ): Promise<void> {
UNCOV
1031
        const table = InstanceChecker.isTable(tableOrName)
×
1032
            ? tableOrName
1033
            : await this.getCachedTable(tableOrName)
1034
        // clone original table and add foreign keys in to cloned table
UNCOV
1035
        const changedTable = table.clone()
×
UNCOV
1036
        foreignKeys.forEach((foreignKey) =>
×
UNCOV
1037
            changedTable.addForeignKey(foreignKey),
×
1038
        )
1039

UNCOV
1040
        await this.recreateTable(changedTable, table)
×
1041
    }
1042

1043
    /**
1044
     * Drops a foreign key from the table.
1045
     */
1046
    async dropForeignKey(
1047
        tableOrName: Table | string,
1048
        foreignKeyOrName: TableForeignKey | string,
1049
    ): Promise<void> {
UNCOV
1050
        const table = InstanceChecker.isTable(tableOrName)
×
1051
            ? tableOrName
1052
            : await this.getCachedTable(tableOrName)
UNCOV
1053
        const foreignKey = InstanceChecker.isTableForeignKey(foreignKeyOrName)
×
1054
            ? foreignKeyOrName
1055
            : table.foreignKeys.find((fk) => fk.name === foreignKeyOrName)
×
UNCOV
1056
        if (!foreignKey)
×
1057
            throw new TypeORMError(
×
1058
                `Supplied foreign key was not found in table ${table.name}`,
1059
            )
1060

UNCOV
1061
        await this.dropForeignKeys(tableOrName, [foreignKey])
×
1062
    }
1063

1064
    /**
1065
     * Drops a foreign keys from the table.
1066
     */
1067
    async dropForeignKeys(
1068
        tableOrName: Table | string,
1069
        foreignKeys: TableForeignKey[],
1070
    ): Promise<void> {
UNCOV
1071
        const table = InstanceChecker.isTable(tableOrName)
×
1072
            ? tableOrName
1073
            : await this.getCachedTable(tableOrName)
1074

1075
        // clone original table and remove foreign keys from cloned table
UNCOV
1076
        const changedTable = table.clone()
×
UNCOV
1077
        foreignKeys.forEach((foreignKey) =>
×
UNCOV
1078
            changedTable.removeForeignKey(foreignKey),
×
1079
        )
1080

UNCOV
1081
        await this.recreateTable(changedTable, table)
×
1082
    }
1083

1084
    /**
1085
     * Creates a new index.
1086
     */
1087
    async createIndex(
1088
        tableOrName: Table | string,
1089
        index: TableIndex,
1090
    ): Promise<void> {
UNCOV
1091
        const table = InstanceChecker.isTable(tableOrName)
×
1092
            ? tableOrName
1093
            : await this.getCachedTable(tableOrName)
1094

1095
        // new index may be passed without name. In this case we generate index name manually.
UNCOV
1096
        if (!index.name) index.name = this.generateIndexName(table, index)
×
1097

UNCOV
1098
        const up = this.createIndexSql(table, index)
×
UNCOV
1099
        const down = this.dropIndexSql(index)
×
UNCOV
1100
        await this.executeQueries(up, down)
×
UNCOV
1101
        table.addIndex(index)
×
1102
    }
1103

1104
    /**
1105
     * Creates a new indices
1106
     */
1107
    async createIndices(
1108
        tableOrName: Table | string,
1109
        indices: TableIndex[],
1110
    ): Promise<void> {
UNCOV
1111
        const promises = indices.map((index) =>
×
UNCOV
1112
            this.createIndex(tableOrName, index),
×
1113
        )
UNCOV
1114
        await Promise.all(promises)
×
1115
    }
1116

1117
    /**
1118
     * Drops an index from the table.
1119
     */
1120
    async dropIndex(
1121
        tableOrName: Table | string,
1122
        indexOrName: TableIndex | string,
1123
    ): Promise<void> {
UNCOV
1124
        const table = InstanceChecker.isTable(tableOrName)
×
1125
            ? tableOrName
1126
            : await this.getCachedTable(tableOrName)
UNCOV
1127
        const index = InstanceChecker.isTableIndex(indexOrName)
×
1128
            ? indexOrName
1129
            : table.indices.find((i) => i.name === indexOrName)
×
UNCOV
1130
        if (!index)
×
1131
            throw new TypeORMError(
×
1132
                `Supplied index ${indexOrName} was not found in table ${table.name}`,
1133
            )
1134

1135
        // old index may be passed without name. In this case we generate index name manually.
UNCOV
1136
        if (!index.name) index.name = this.generateIndexName(table, index)
×
1137

UNCOV
1138
        const up = this.dropIndexSql(index)
×
UNCOV
1139
        const down = this.createIndexSql(table, index)
×
UNCOV
1140
        await this.executeQueries(up, down)
×
UNCOV
1141
        table.removeIndex(index)
×
1142
    }
1143

1144
    /**
1145
     * Drops an indices from the table.
1146
     */
1147
    async dropIndices(
1148
        tableOrName: Table | string,
1149
        indices: TableIndex[],
1150
    ): Promise<void> {
UNCOV
1151
        const promises = indices.map((index) =>
×
UNCOV
1152
            this.dropIndex(tableOrName, index),
×
1153
        )
UNCOV
1154
        await Promise.all(promises)
×
1155
    }
1156

1157
    /**
1158
     * Clears all table contents.
1159
     * Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
1160
     */
1161
    async clearTable(tableName: string): Promise<void> {
UNCOV
1162
        await this.query(`DELETE FROM ${this.escapePath(tableName)}`)
×
1163
    }
1164

1165
    /**
1166
     * Removes all tables from the currently connected database.
1167
     */
1168
    async clearDatabase(database?: string): Promise<void> {
UNCOV
1169
        let dbPath: string | undefined = undefined
×
UNCOV
1170
        if (
×
1171
            database &&
×
1172
            this.driver.getAttachedDatabaseHandleByRelativePath(database)
1173
        ) {
UNCOV
1174
            dbPath =
×
1175
                this.driver.getAttachedDatabaseHandleByRelativePath(database)
1176
        }
1177

UNCOV
1178
        await this.query(`PRAGMA foreign_keys = OFF`)
×
1179

UNCOV
1180
        const isAnotherTransactionActive = this.isTransactionActive
×
UNCOV
1181
        if (!isAnotherTransactionActive) await this.startTransaction()
×
UNCOV
1182
        try {
×
UNCOV
1183
            const selectViewDropsQuery = dbPath
×
1184
                ? `SELECT 'DROP VIEW "${dbPath}"."' || name || '";' as query FROM "${dbPath}"."sqlite_master" WHERE "type" = 'view'`
1185
                : `SELECT 'DROP VIEW "' || name || '";' as query FROM "sqlite_master" WHERE "type" = 'view'`
UNCOV
1186
            const dropViewQueries: ObjectLiteral[] = await this.query(
×
1187
                selectViewDropsQuery,
1188
            )
UNCOV
1189
            await Promise.all(
×
UNCOV
1190
                dropViewQueries.map((q) => this.query(q["query"])),
×
1191
            )
1192

UNCOV
1193
            const selectTableDropsQuery = dbPath
×
1194
                ? `SELECT 'DROP TABLE "${dbPath}"."' || name || '";' as query FROM "${dbPath}"."sqlite_master" WHERE "type" = 'table' AND "name" != 'sqlite_sequence'`
1195
                : `SELECT 'DROP TABLE "' || name || '";' as query FROM "sqlite_master" WHERE "type" = 'table' AND "name" != 'sqlite_sequence'`
UNCOV
1196
            const dropTableQueries: ObjectLiteral[] = await this.query(
×
1197
                selectTableDropsQuery,
1198
            )
UNCOV
1199
            await Promise.all(
×
UNCOV
1200
                dropTableQueries.map((q) => this.query(q["query"])),
×
1201
            )
1202

UNCOV
1203
            if (!isAnotherTransactionActive) await this.commitTransaction()
×
1204
        } catch (error) {
1205
            try {
×
1206
                // we throw original error even if rollback thrown an error
1207
                if (!isAnotherTransactionActive)
×
1208
                    await this.rollbackTransaction()
×
1209
            } catch (rollbackError) {}
1210
            throw error
×
1211
        } finally {
UNCOV
1212
            await this.query(`PRAGMA foreign_keys = ON`)
×
1213
        }
1214
    }
1215

1216
    // -------------------------------------------------------------------------
1217
    // Protected Methods
1218
    // -------------------------------------------------------------------------
1219

1220
    protected async loadViews(viewNames?: string[]): Promise<View[]> {
UNCOV
1221
        const hasTable = await this.hasTable(this.getTypeormMetadataTableName())
×
UNCOV
1222
        if (!hasTable) {
×
UNCOV
1223
            return []
×
1224
        }
1225

UNCOV
1226
        if (!viewNames) {
×
1227
            viewNames = []
×
1228
        }
1229

UNCOV
1230
        const viewNamesString = viewNames
×
UNCOV
1231
            .map((name) => "'" + name + "'")
×
1232
            .join(", ")
UNCOV
1233
        let query = `SELECT "t".* FROM "${this.getTypeormMetadataTableName()}" "t" INNER JOIN "sqlite_master" s ON "s"."name" = "t"."name" AND "s"."type" = 'view' WHERE "t"."type" = '${
×
1234
            MetadataTableType.VIEW
1235
        }'`
UNCOV
1236
        if (viewNamesString.length > 0)
×
UNCOV
1237
            query += ` AND "t"."name" IN (${viewNamesString})`
×
UNCOV
1238
        const dbViews = await this.query(query)
×
UNCOV
1239
        return dbViews.map((dbView: any) => {
×
UNCOV
1240
            const view = new View()
×
UNCOV
1241
            view.name = dbView["name"]
×
UNCOV
1242
            view.expression = dbView["value"]
×
UNCOV
1243
            return view
×
1244
        })
1245
    }
1246

1247
    protected async loadTableRecords(
1248
        tablePath: string,
1249
        tableOrIndex: "table" | "index",
1250
    ) {
UNCOV
1251
        let database: string | undefined = undefined
×
UNCOV
1252
        const [schema, tableName] = this.splitTablePath(tablePath)
×
UNCOV
1253
        if (
×
1254
            schema &&
×
1255
            this.driver.getAttachedDatabasePathRelativeByHandle(schema)
1256
        ) {
UNCOV
1257
            database =
×
1258
                this.driver.getAttachedDatabasePathRelativeByHandle(schema)
1259
        }
UNCOV
1260
        return this.query(
×
1261
            `SELECT ${database ? `'${database}'` : null} as database, ${
×
1262
                schema ? `'${schema}'` : null
×
1263
            } as schema, * FROM ${
1264
                schema ? `"${schema}".` : ""
×
1265
            }${this.escapePath(
1266
                `sqlite_master`,
1267
            )} WHERE "type" = '${tableOrIndex}' AND "${
1268
                tableOrIndex === "table" ? "name" : "tbl_name"
×
1269
            }" IN ('${tableName}')`,
1270
        )
1271
    }
1272

1273
    protected async loadPragmaRecords(tablePath: string, pragma: string) {
UNCOV
1274
        const [, tableName] = this.splitTablePath(tablePath)
×
UNCOV
1275
        return this.query(`PRAGMA ${pragma}("${tableName}")`)
×
1276
    }
1277

1278
    /**
1279
     * Loads all tables (with given names) from the database and creates a Table from them.
1280
     */
1281
    protected async loadTables(tableNames?: string[]): Promise<Table[]> {
1282
        // if no tables given then no need to proceed
UNCOV
1283
        if (tableNames && tableNames.length === 0) {
×
UNCOV
1284
            return []
×
1285
        }
1286

UNCOV
1287
        let dbTables: { database?: string; name: string; sql: string }[] = []
×
1288
        let dbIndicesDef: ObjectLiteral[]
1289

UNCOV
1290
        if (!tableNames) {
×
UNCOV
1291
            const tablesSql = `SELECT * FROM "sqlite_master" WHERE "type" = 'table'`
×
UNCOV
1292
            dbTables.push(...(await this.query(tablesSql)))
×
1293

UNCOV
1294
            const tableNamesString = dbTables
×
UNCOV
1295
                .map(({ name }) => `'${name}'`)
×
1296
                .join(", ")
UNCOV
1297
            dbIndicesDef = await this.query(
×
1298
                `SELECT * FROM "sqlite_master" WHERE "type" = 'index' AND "tbl_name" IN (${tableNamesString})`,
1299
            )
1300
        } else {
UNCOV
1301
            const tableNamesWithoutDot = tableNames
×
1302
                .filter((tableName) => {
UNCOV
1303
                    return tableName.split(".").length === 1
×
1304
                })
UNCOV
1305
                .map((tableName) => `'${tableName}'`)
×
1306

UNCOV
1307
            const tableNamesWithDot = tableNames.filter((tableName) => {
×
UNCOV
1308
                return tableName.split(".").length > 1
×
1309
            })
1310

UNCOV
1311
            const queryPromises = (type: "table" | "index") => {
×
UNCOV
1312
                const promises = [
×
1313
                    ...tableNamesWithDot.map((tableName) =>
UNCOV
1314
                        this.loadTableRecords(tableName, type),
×
1315
                    ),
1316
                ]
1317

UNCOV
1318
                if (tableNamesWithoutDot.length) {
×
UNCOV
1319
                    promises.push(
×
1320
                        this.query(
1321
                            `SELECT * FROM "sqlite_master" WHERE "type" = '${type}' AND "${
1322
                                type === "table" ? "name" : "tbl_name"
×
1323
                            }" IN (${tableNamesWithoutDot})`,
1324
                        ),
1325
                    )
1326
                }
1327

UNCOV
1328
                return promises
×
1329
            }
UNCOV
1330
            dbTables = (await Promise.all(queryPromises("table")))
×
UNCOV
1331
                .reduce((acc, res) => [...acc, ...res], [])
×
1332
                .filter(Boolean)
UNCOV
1333
            dbIndicesDef = (await Promise.all(queryPromises("index")))
×
UNCOV
1334
                .reduce((acc, res) => [...acc, ...res], [])
×
1335
                .filter(Boolean)
1336
        }
1337

1338
        // if tables were not found in the db, no need to proceed
UNCOV
1339
        if (dbTables.length === 0) {
×
UNCOV
1340
            return []
×
1341
        }
1342

1343
        // create table schemas for loaded tables
UNCOV
1344
        return Promise.all(
×
1345
            dbTables.map(async (dbTable) => {
1346
                const tablePath =
UNCOV
1347
                    dbTable["database"] &&
×
1348
                    this.driver.getAttachedDatabaseHandleByRelativePath(
1349
                        dbTable["database"],
1350
                    )
1351
                        ? `${this.driver.getAttachedDatabaseHandleByRelativePath(
1352
                              dbTable["database"],
1353
                          )}.${dbTable["name"]}`
1354
                        : dbTable["name"]
1355

UNCOV
1356
                const sql = dbTable["sql"]
×
1357

UNCOV
1358
                const withoutRowid = sql.includes("WITHOUT ROWID")
×
UNCOV
1359
                const table = new Table({ name: tablePath, withoutRowid })
×
1360

1361
                // load columns and indices
1362
                const [dbColumns, dbIndices, dbForeignKeys]: ObjectLiteral[][] =
UNCOV
1363
                    await Promise.all([
×
1364
                        this.loadPragmaRecords(tablePath, `table_xinfo`),
1365
                        this.loadPragmaRecords(tablePath, `index_list`),
1366
                        this.loadPragmaRecords(tablePath, `foreign_key_list`),
1367
                    ])
1368

1369
                // find column name with auto increment
UNCOV
1370
                let autoIncrementColumnName: string | undefined = undefined
×
UNCOV
1371
                const tableSql: string = dbTable["sql"]
×
UNCOV
1372
                const autoIncrementIndex = tableSql
×
1373
                    .toUpperCase()
1374
                    .indexOf("AUTOINCREMENT")
UNCOV
1375
                if (autoIncrementIndex !== -1) {
×
UNCOV
1376
                    autoIncrementColumnName = tableSql.substr(
×
1377
                        0,
1378
                        autoIncrementIndex,
1379
                    )
UNCOV
1380
                    const comma = autoIncrementColumnName.lastIndexOf(",")
×
UNCOV
1381
                    const bracket = autoIncrementColumnName.lastIndexOf("(")
×
UNCOV
1382
                    if (comma !== -1) {
×
1383
                        autoIncrementColumnName =
×
1384
                            autoIncrementColumnName.substr(comma)
1385
                        autoIncrementColumnName =
×
1386
                            autoIncrementColumnName.substr(
1387
                                0,
1388
                                autoIncrementColumnName.lastIndexOf('"'),
1389
                            )
1390
                        autoIncrementColumnName =
×
1391
                            autoIncrementColumnName.substr(
1392
                                autoIncrementColumnName.indexOf('"') + 1,
1393
                            )
UNCOV
1394
                    } else if (bracket !== -1) {
×
UNCOV
1395
                        autoIncrementColumnName =
×
1396
                            autoIncrementColumnName.substr(bracket)
UNCOV
1397
                        autoIncrementColumnName =
×
1398
                            autoIncrementColumnName.substr(
1399
                                0,
1400
                                autoIncrementColumnName.lastIndexOf('"'),
1401
                            )
UNCOV
1402
                        autoIncrementColumnName =
×
1403
                            autoIncrementColumnName.substr(
1404
                                autoIncrementColumnName.indexOf('"') + 1,
1405
                            )
1406
                    }
1407
                }
1408

1409
                // create columns from the loaded columns
UNCOV
1410
                table.columns = await Promise.all(
×
1411
                    dbColumns.map(async (dbColumn) => {
UNCOV
1412
                        const tableColumn = new TableColumn()
×
UNCOV
1413
                        tableColumn.name = dbColumn["name"]
×
UNCOV
1414
                        tableColumn.type = dbColumn["type"].toLowerCase()
×
UNCOV
1415
                        tableColumn.default =
×
1416
                            dbColumn["dflt_value"] !== null &&
×
1417
                            dbColumn["dflt_value"] !== undefined
1418
                                ? dbColumn["dflt_value"]
1419
                                : undefined
UNCOV
1420
                        tableColumn.isNullable = dbColumn["notnull"] === 0
×
1421
                        // primary keys are numbered starting with 1, columns that aren't primary keys are marked with 0
UNCOV
1422
                        tableColumn.isPrimary = dbColumn["pk"] > 0
×
UNCOV
1423
                        tableColumn.comment = "" // SQLite does not support column comments
×
UNCOV
1424
                        tableColumn.isGenerated =
×
1425
                            autoIncrementColumnName === dbColumn["name"]
UNCOV
1426
                        if (tableColumn.isGenerated) {
×
UNCOV
1427
                            tableColumn.generationStrategy = "increment"
×
1428
                        }
1429

UNCOV
1430
                        if (
×
1431
                            dbColumn["hidden"] === 2 ||
×
1432
                            dbColumn["hidden"] === 3
1433
                        ) {
UNCOV
1434
                            tableColumn.generatedType =
×
1435
                                dbColumn["hidden"] === 2 ? "VIRTUAL" : "STORED"
×
1436

1437
                            const asExpressionQuery =
UNCOV
1438
                                this.selectTypeormMetadataSql({
×
1439
                                    table: table.name,
1440
                                    type: MetadataTableType.GENERATED_COLUMN,
1441
                                    name: tableColumn.name,
1442
                                })
1443

UNCOV
1444
                            const results = await this.query(
×
1445
                                asExpressionQuery.query,
1446
                                asExpressionQuery.parameters,
1447
                            )
UNCOV
1448
                            if (results[0] && results[0].value) {
×
UNCOV
1449
                                tableColumn.asExpression = results[0].value
×
1450
                            } else {
1451
                                tableColumn.asExpression = ""
×
1452
                            }
1453
                        }
1454

UNCOV
1455
                        if (tableColumn.type === "varchar") {
×
UNCOV
1456
                            tableColumn.enum = OrmUtils.parseSqlCheckExpression(
×
1457
                                sql,
1458
                                tableColumn.name,
1459
                            )
1460
                        }
1461

1462
                        // parse datatype and attempt to retrieve length, precision and scale
UNCOV
1463
                        const pos = tableColumn.type.indexOf("(")
×
UNCOV
1464
                        if (pos !== -1) {
×
UNCOV
1465
                            const fullType = tableColumn.type
×
UNCOV
1466
                            const dataType = fullType.substr(0, pos)
×
UNCOV
1467
                            if (
×
1468
                                this.driver.withLengthColumnTypes.find(
UNCOV
1469
                                    (col) => col === dataType,
×
1470
                                )
1471
                            ) {
UNCOV
1472
                                const len = parseInt(
×
1473
                                    fullType.substring(
1474
                                        pos + 1,
1475
                                        fullType.length - 1,
1476
                                    ),
1477
                                )
UNCOV
1478
                                if (len) {
×
UNCOV
1479
                                    tableColumn.length = len.toString()
×
UNCOV
1480
                                    tableColumn.type = dataType // remove the length part from the datatype
×
1481
                                }
1482
                            }
UNCOV
1483
                            if (
×
1484
                                this.driver.withPrecisionColumnTypes.find(
UNCOV
1485
                                    (col) => col === dataType,
×
1486
                                )
1487
                            ) {
UNCOV
1488
                                const re = new RegExp(
×
1489
                                    `^${dataType}\\((\\d+),?\\s?(\\d+)?\\)`,
1490
                                )
UNCOV
1491
                                const matches = fullType.match(re)
×
UNCOV
1492
                                if (matches && matches[1]) {
×
UNCOV
1493
                                    tableColumn.precision = +matches[1]
×
1494
                                }
UNCOV
1495
                                if (
×
1496
                                    this.driver.withScaleColumnTypes.find(
UNCOV
1497
                                        (col) => col === dataType,
×
1498
                                    )
1499
                                ) {
UNCOV
1500
                                    if (matches && matches[2]) {
×
UNCOV
1501
                                        tableColumn.scale = +matches[2]
×
1502
                                    }
1503
                                }
UNCOV
1504
                                tableColumn.type = dataType // remove the precision/scale part from the datatype
×
1505
                            }
1506
                        }
1507

UNCOV
1508
                        return tableColumn
×
1509
                    }),
1510
                )
1511

1512
                // find foreign key constraints from CREATE TABLE sql
1513
                let fkResult
1514
                const fkMappings: {
1515
                    name: string
1516
                    columns: string[]
1517
                    referencedTableName: string
UNCOV
1518
                }[] = []
×
1519
                const fkRegex =
UNCOV
1520
                    /CONSTRAINT "([^"]*)" FOREIGN KEY ?\((.*?)\) REFERENCES "([^"]*)"/g
×
UNCOV
1521
                while ((fkResult = fkRegex.exec(sql)) !== null) {
×
UNCOV
1522
                    fkMappings.push({
×
1523
                        name: fkResult[1],
1524
                        columns: fkResult[2]
1525
                            .substr(1, fkResult[2].length - 2)
1526
                            .split(`", "`),
1527
                        referencedTableName: fkResult[3],
1528
                    })
1529
                }
1530

1531
                // build foreign keys
UNCOV
1532
                const tableForeignKeyConstraints = OrmUtils.uniq(
×
1533
                    dbForeignKeys,
UNCOV
1534
                    (dbForeignKey) => dbForeignKey["id"],
×
1535
                )
1536

UNCOV
1537
                table.foreignKeys = tableForeignKeyConstraints.map(
×
1538
                    (foreignKey) => {
UNCOV
1539
                        const ownForeignKeys = dbForeignKeys.filter(
×
1540
                            (dbForeignKey) =>
UNCOV
1541
                                dbForeignKey["id"] === foreignKey["id"] &&
×
1542
                                dbForeignKey["table"] === foreignKey["table"],
1543
                        )
UNCOV
1544
                        const columnNames = ownForeignKeys.map(
×
UNCOV
1545
                            (dbForeignKey) => dbForeignKey["from"],
×
1546
                        )
UNCOV
1547
                        const referencedColumnNames = ownForeignKeys.map(
×
UNCOV
1548
                            (dbForeignKey) => dbForeignKey["to"],
×
1549
                        )
1550

1551
                        // find related foreign key mapping
UNCOV
1552
                        const fkMapping = fkMappings.find(
×
1553
                            (it) =>
UNCOV
1554
                                it.referencedTableName ===
×
1555
                                    foreignKey["table"] &&
1556
                                it.columns.every(
1557
                                    (column) =>
UNCOV
1558
                                        columnNames.indexOf(column) !== -1,
×
1559
                                ),
1560
                        )
1561

UNCOV
1562
                        return new TableForeignKey({
×
1563
                            name: fkMapping?.name,
1564
                            columnNames: columnNames,
1565
                            referencedTableName: foreignKey["table"],
1566
                            referencedColumnNames: referencedColumnNames,
1567
                            onDelete: foreignKey["on_delete"],
1568
                            onUpdate: foreignKey["on_update"],
1569
                        })
1570
                    },
1571
                )
1572

1573
                // find unique constraints from CREATE TABLE sql
1574
                let uniqueRegexResult
UNCOV
1575
                const uniqueMappings: { name: string; columns: string[] }[] = []
×
UNCOV
1576
                const uniqueRegex = /CONSTRAINT "([^"]*)" UNIQUE ?\((.*?)\)/g
×
UNCOV
1577
                while ((uniqueRegexResult = uniqueRegex.exec(sql)) !== null) {
×
UNCOV
1578
                    uniqueMappings.push({
×
1579
                        name: uniqueRegexResult[1],
1580
                        columns: uniqueRegexResult[2]
1581
                            .substr(1, uniqueRegexResult[2].length - 2)
1582
                            .split(`", "`),
1583
                    })
1584
                }
1585

1586
                // build unique constraints
UNCOV
1587
                const tableUniquePromises = dbIndices
×
UNCOV
1588
                    .filter((dbIndex) => dbIndex["origin"] === "u")
×
UNCOV
1589
                    .map((dbIndex) => dbIndex["name"])
×
1590
                    .filter(
UNCOV
1591
                        (value, index, self) => self.indexOf(value) === index,
×
1592
                    )
1593
                    .map(async (dbIndexName) => {
UNCOV
1594
                        const dbIndex = dbIndices.find(
×
UNCOV
1595
                            (dbIndex) => dbIndex["name"] === dbIndexName,
×
1596
                        )
UNCOV
1597
                        const indexInfos: ObjectLiteral[] = await this.query(
×
1598
                            `PRAGMA index_info("${dbIndex!["name"]}")`,
1599
                        )
UNCOV
1600
                        const indexColumns = indexInfos
×
1601
                            .sort(
1602
                                (indexInfo1, indexInfo2) =>
UNCOV
1603
                                    parseInt(indexInfo1["seqno"]) -
×
1604
                                    parseInt(indexInfo2["seqno"]),
1605
                            )
UNCOV
1606
                            .map((indexInfo) => indexInfo["name"])
×
UNCOV
1607
                        if (indexColumns.length === 1) {
×
UNCOV
1608
                            const column = table.columns.find((column) => {
×
UNCOV
1609
                                return !!indexColumns.find(
×
1610
                                    (indexColumn) =>
UNCOV
1611
                                        indexColumn === column.name,
×
1612
                                )
1613
                            })
UNCOV
1614
                            if (column) column.isUnique = true
×
1615
                        }
1616

1617
                        // find existent mapping by a column names
UNCOV
1618
                        const foundMapping = uniqueMappings.find((mapping) => {
×
UNCOV
1619
                            return mapping!.columns.every(
×
UNCOV
1620
                                (column) => indexColumns.indexOf(column) !== -1,
×
1621
                            )
1622
                        })
1623

UNCOV
1624
                        return new TableUnique({
×
1625
                            name: foundMapping
×
1626
                                ? foundMapping.name
1627
                                : this.connection.namingStrategy.uniqueConstraintName(
1628
                                      table,
1629
                                      indexColumns,
1630
                                  ),
1631
                            columnNames: indexColumns,
1632
                        })
1633
                    })
UNCOV
1634
                table.uniques = (await Promise.all(
×
1635
                    tableUniquePromises,
1636
                )) as TableUnique[]
1637

1638
                // build checks
1639
                let result
1640
                const regexp =
UNCOV
1641
                    /CONSTRAINT "([^"]*)" CHECK ?(\(.*?\))([,]|[)]$)/g
×
UNCOV
1642
                while ((result = regexp.exec(sql)) !== null) {
×
UNCOV
1643
                    table.checks.push(
×
1644
                        new TableCheck({
1645
                            name: result[1],
1646
                            expression: result[2],
1647
                        }),
1648
                    )
1649
                }
1650

1651
                // build indices
UNCOV
1652
                const indicesPromises = dbIndices
×
UNCOV
1653
                    .filter((dbIndex) => dbIndex["origin"] === "c")
×
UNCOV
1654
                    .map((dbIndex) => dbIndex["name"])
×
1655
                    .filter(
UNCOV
1656
                        (value, index, self) => self.indexOf(value) === index,
×
1657
                    ) // unqiue
1658
                    .map(async (dbIndexName) => {
UNCOV
1659
                        const indexDef = dbIndicesDef.find(
×
UNCOV
1660
                            (dbIndexDef) => dbIndexDef["name"] === dbIndexName,
×
1661
                        )
UNCOV
1662
                        const condition = /WHERE (.*)/.exec(indexDef!["sql"])
×
UNCOV
1663
                        const dbIndex = dbIndices.find(
×
UNCOV
1664
                            (dbIndex) => dbIndex["name"] === dbIndexName,
×
1665
                        )
UNCOV
1666
                        const indexInfos: ObjectLiteral[] = await this.query(
×
1667
                            `PRAGMA index_info("${dbIndex!["name"]}")`,
1668
                        )
UNCOV
1669
                        const indexColumns = indexInfos
×
1670
                            .sort(
1671
                                (indexInfo1, indexInfo2) =>
UNCOV
1672
                                    parseInt(indexInfo1["seqno"]) -
×
1673
                                    parseInt(indexInfo2["seqno"]),
1674
                            )
UNCOV
1675
                            .map((indexInfo) => indexInfo["name"])
×
UNCOV
1676
                        const dbIndexPath = `${
×
1677
                            dbTable["database"] ? `${dbTable["database"]}.` : ""
×
1678
                        }${dbIndex!["name"]}`
1679

1680
                        const isUnique =
UNCOV
1681
                            dbIndex!["unique"] === "1" ||
×
1682
                            dbIndex!["unique"] === 1
UNCOV
1683
                        return new TableIndex(<TableIndexOptions>{
×
1684
                            table: table,
1685
                            name: dbIndexPath,
1686
                            columnNames: indexColumns,
1687
                            isUnique: isUnique,
1688
                            where: condition ? condition[1] : undefined,
×
1689
                        })
1690
                    })
UNCOV
1691
                const indices = await Promise.all(indicesPromises)
×
UNCOV
1692
                table.indices = indices.filter(
×
UNCOV
1693
                    (index) => !!index,
×
1694
                ) as TableIndex[]
1695

UNCOV
1696
                return table
×
1697
            }),
1698
        )
1699
    }
1700

1701
    /**
1702
     * Builds create table sql.
1703
     */
1704
    protected createTableSql(
1705
        table: Table,
1706
        createForeignKeys?: boolean,
1707
        temporaryTable?: boolean,
1708
    ): Query {
UNCOV
1709
        const primaryColumns = table.columns.filter(
×
UNCOV
1710
            (column) => column.isPrimary,
×
1711
        )
UNCOV
1712
        const hasAutoIncrement = primaryColumns.find(
×
1713
            (column) =>
UNCOV
1714
                column.isGenerated && column.generationStrategy === "increment",
×
1715
        )
UNCOV
1716
        const skipPrimary = primaryColumns.length > 1
×
UNCOV
1717
        if (skipPrimary && hasAutoIncrement)
×
1718
            throw new TypeORMError(
×
1719
                `Sqlite does not support AUTOINCREMENT on composite primary key`,
1720
            )
1721

UNCOV
1722
        const columnDefinitions = table.columns
×
UNCOV
1723
            .map((column) => this.buildCreateColumnSql(column, skipPrimary))
×
1724
            .join(", ")
UNCOV
1725
        const [database] = this.splitTablePath(table.name)
×
UNCOV
1726
        let sql = `CREATE TABLE ${this.escapePath(
×
1727
            table.name,
1728
        )} (${columnDefinitions}`
1729

UNCOV
1730
        const [databaseNew, tableName] = this.splitTablePath(table.name)
×
UNCOV
1731
        const newTableName = temporaryTable
×
1732
            ? `${databaseNew ? `${databaseNew}.` : ""}${tableName.replace(
×
1733
                  /^temporary_/,
1734
                  "",
1735
              )}`
1736
            : table.name
1737

1738
        // need for `addColumn()` method, because it recreates table.
UNCOV
1739
        table.columns
×
UNCOV
1740
            .filter((column) => column.isUnique)
×
1741
            .forEach((column) => {
UNCOV
1742
                const isUniqueExist = table.uniques.some(
×
1743
                    (unique) =>
UNCOV
1744
                        unique.columnNames.length === 1 &&
×
1745
                        unique.columnNames[0] === column.name,
1746
                )
UNCOV
1747
                if (!isUniqueExist)
×
UNCOV
1748
                    table.uniques.push(
×
1749
                        new TableUnique({
1750
                            name: this.connection.namingStrategy.uniqueConstraintName(
1751
                                table,
1752
                                [column.name],
1753
                            ),
1754
                            columnNames: [column.name],
1755
                        }),
1756
                    )
1757
            })
1758

UNCOV
1759
        if (table.uniques.length > 0) {
×
UNCOV
1760
            const uniquesSql = table.uniques
×
1761
                .map((unique) => {
UNCOV
1762
                    const uniqueName = unique.name
×
1763
                        ? unique.name
1764
                        : this.connection.namingStrategy.uniqueConstraintName(
1765
                              newTableName,
1766
                              unique.columnNames,
1767
                          )
UNCOV
1768
                    const columnNames = unique.columnNames
×
UNCOV
1769
                        .map((columnName) => `"${columnName}"`)
×
1770
                        .join(", ")
UNCOV
1771
                    return `CONSTRAINT "${uniqueName}" UNIQUE (${columnNames})`
×
1772
                })
1773
                .join(", ")
1774

UNCOV
1775
            sql += `, ${uniquesSql}`
×
1776
        }
1777

UNCOV
1778
        if (table.checks.length > 0) {
×
UNCOV
1779
            const checksSql = table.checks
×
1780
                .map((check) => {
UNCOV
1781
                    const checkName = check.name
×
1782
                        ? check.name
1783
                        : this.connection.namingStrategy.checkConstraintName(
1784
                              newTableName,
1785
                              check.expression!,
1786
                          )
UNCOV
1787
                    return `CONSTRAINT "${checkName}" CHECK (${check.expression})`
×
1788
                })
1789
                .join(", ")
1790

UNCOV
1791
            sql += `, ${checksSql}`
×
1792
        }
1793

UNCOV
1794
        if (table.foreignKeys.length > 0 && createForeignKeys) {
×
UNCOV
1795
            const foreignKeysSql = table.foreignKeys
×
1796
                .filter((fk) => {
UNCOV
1797
                    const [referencedDatabase] = this.splitTablePath(
×
1798
                        fk.referencedTableName,
1799
                    )
UNCOV
1800
                    if (referencedDatabase !== database) {
×
1801
                        return false
×
1802
                    }
UNCOV
1803
                    return true
×
1804
                })
1805
                .map((fk) => {
UNCOV
1806
                    const [, referencedTable] = this.splitTablePath(
×
1807
                        fk.referencedTableName,
1808
                    )
UNCOV
1809
                    const columnNames = fk.columnNames
×
UNCOV
1810
                        .map((columnName) => `"${columnName}"`)
×
1811
                        .join(", ")
UNCOV
1812
                    if (!fk.name)
×
UNCOV
1813
                        fk.name = this.connection.namingStrategy.foreignKeyName(
×
1814
                            newTableName,
1815
                            fk.columnNames,
1816
                            this.getTablePath(fk),
1817
                            fk.referencedColumnNames,
1818
                        )
UNCOV
1819
                    const referencedColumnNames = fk.referencedColumnNames
×
UNCOV
1820
                        .map((columnName) => `"${columnName}"`)
×
1821
                        .join(", ")
1822

UNCOV
1823
                    let constraint = `CONSTRAINT "${fk.name}" FOREIGN KEY (${columnNames}) REFERENCES "${referencedTable}" (${referencedColumnNames})`
×
UNCOV
1824
                    if (fk.onDelete) constraint += ` ON DELETE ${fk.onDelete}`
×
UNCOV
1825
                    if (fk.onUpdate) constraint += ` ON UPDATE ${fk.onUpdate}`
×
UNCOV
1826
                    if (fk.deferrable)
×
UNCOV
1827
                        constraint += ` DEFERRABLE ${fk.deferrable}`
×
1828

UNCOV
1829
                    return constraint
×
1830
                })
1831
                .join(", ")
1832

UNCOV
1833
            sql += `, ${foreignKeysSql}`
×
1834
        }
1835

UNCOV
1836
        if (primaryColumns.length > 1) {
×
UNCOV
1837
            const columnNames = primaryColumns
×
UNCOV
1838
                .map((column) => `"${column.name}"`)
×
1839
                .join(", ")
UNCOV
1840
            sql += `, PRIMARY KEY (${columnNames})`
×
1841
        }
1842

UNCOV
1843
        sql += `)`
×
1844

UNCOV
1845
        if (table.withoutRowid) {
×
UNCOV
1846
            sql += " WITHOUT ROWID"
×
1847
        }
1848

UNCOV
1849
        return new Query(sql)
×
1850
    }
1851

1852
    /**
1853
     * Builds drop table sql.
1854
     */
1855
    protected dropTableSql(
1856
        tableOrName: Table | string,
1857
        ifExist?: boolean,
1858
    ): Query {
UNCOV
1859
        const tableName = InstanceChecker.isTable(tableOrName)
×
1860
            ? tableOrName.name
1861
            : tableOrName
UNCOV
1862
        const query = ifExist
×
1863
            ? `DROP TABLE IF EXISTS ${this.escapePath(tableName)}`
1864
            : `DROP TABLE ${this.escapePath(tableName)}`
UNCOV
1865
        return new Query(query)
×
1866
    }
1867

1868
    protected createViewSql(view: View): Query {
UNCOV
1869
        if (typeof view.expression === "string") {
×
UNCOV
1870
            return new Query(`CREATE VIEW "${view.name}" AS ${view.expression}`)
×
1871
        } else {
UNCOV
1872
            return new Query(
×
1873
                `CREATE VIEW "${view.name}" AS ${view
1874
                    .expression(this.connection)
1875
                    .getQuery()}`,
1876
            )
1877
        }
1878
    }
1879

1880
    protected insertViewDefinitionSql(view: View): Query {
1881
        const expression =
UNCOV
1882
            typeof view.expression === "string"
×
1883
                ? view.expression.trim()
1884
                : view.expression(this.connection).getQuery()
UNCOV
1885
        return this.insertTypeormMetadataSql({
×
1886
            type: MetadataTableType.VIEW,
1887
            name: view.name,
1888
            value: expression,
1889
        })
1890
    }
1891

1892
    /**
1893
     * Builds drop view sql.
1894
     */
1895
    protected dropViewSql(viewOrPath: View | string): Query {
UNCOV
1896
        const viewName = InstanceChecker.isView(viewOrPath)
×
1897
            ? viewOrPath.name
1898
            : viewOrPath
UNCOV
1899
        return new Query(`DROP VIEW "${viewName}"`)
×
1900
    }
1901

1902
    /**
1903
     * Builds remove view sql.
1904
     */
1905
    protected deleteViewDefinitionSql(viewOrPath: View | string): Query {
UNCOV
1906
        const viewName = InstanceChecker.isView(viewOrPath)
×
1907
            ? viewOrPath.name
1908
            : viewOrPath
UNCOV
1909
        return this.deleteTypeormMetadataSql({
×
1910
            type: MetadataTableType.VIEW,
1911
            name: viewName,
1912
        })
1913
    }
1914

1915
    /**
1916
     * Builds create index sql.
1917
     */
1918
    protected createIndexSql(table: Table, index: TableIndex): Query {
UNCOV
1919
        const columns = index.columnNames
×
UNCOV
1920
            .map((columnName) => `"${columnName}"`)
×
1921
            .join(", ")
UNCOV
1922
        const [database, tableName] = this.splitTablePath(table.name)
×
UNCOV
1923
        return new Query(
×
1924
            `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX ${
×
1925
                database ? `"${database}".` : ""
×
1926
            }${this.escapePath(index.name!)} ON "${tableName}" (${columns}) ${
1927
                index.where ? "WHERE " + index.where : ""
×
1928
            }`,
1929
        )
1930
    }
1931

1932
    /**
1933
     * Builds drop index sql.
1934
     */
1935
    protected dropIndexSql(indexOrName: TableIndex | string): Query {
UNCOV
1936
        const indexName = InstanceChecker.isTableIndex(indexOrName)
×
1937
            ? indexOrName.name
1938
            : indexOrName
UNCOV
1939
        return new Query(`DROP INDEX ${this.escapePath(indexName!)}`)
×
1940
    }
1941

1942
    /**
1943
     * Builds a query for create column.
1944
     */
1945
    protected buildCreateColumnSql(
1946
        column: TableColumn,
1947
        skipPrimary?: boolean,
1948
    ): string {
UNCOV
1949
        let c = '"' + column.name + '"'
×
UNCOV
1950
        if (InstanceChecker.isColumnMetadata(column)) {
×
1951
            c += " " + this.driver.normalizeType(column)
×
1952
        } else {
UNCOV
1953
            c += " " + this.connection.driver.createFullType(column)
×
1954
        }
1955

UNCOV
1956
        if (column.enum)
×
UNCOV
1957
            c +=
×
1958
                ' CHECK( "' +
1959
                column.name +
1960
                '" IN (' +
UNCOV
1961
                column.enum.map((val) => "'" + val + "'").join(",") +
×
1962
                ") )"
UNCOV
1963
        if (column.isPrimary && !skipPrimary) c += " PRIMARY KEY"
×
UNCOV
1964
        if (
×
1965
            column.isGenerated === true &&
×
1966
            column.generationStrategy === "increment"
1967
        )
1968
            // don't use skipPrimary here since updates can update already exist primary without auto inc.
UNCOV
1969
            c += " AUTOINCREMENT"
×
UNCOV
1970
        if (column.collation) c += " COLLATE " + column.collation
×
UNCOV
1971
        if (column.isNullable !== true) c += " NOT NULL"
×
1972

UNCOV
1973
        if (column.asExpression) {
×
UNCOV
1974
            c += ` AS (${column.asExpression}) ${
×
1975
                column.generatedType ? column.generatedType : "VIRTUAL"
×
1976
            }`
1977
        } else {
UNCOV
1978
            if (column.default !== undefined && column.default !== null)
×
UNCOV
1979
                c += " DEFAULT (" + column.default + ")"
×
1980
        }
1981

UNCOV
1982
        return c
×
1983
    }
1984

1985
    protected async recreateTable(
1986
        newTable: Table,
1987
        oldTable: Table,
1988
        migrateData = true,
×
1989
    ): Promise<void> {
UNCOV
1990
        const upQueries: Query[] = []
×
UNCOV
1991
        const downQueries: Query[] = []
×
1992

1993
        // drop old table indices
UNCOV
1994
        oldTable.indices.forEach((index) => {
×
UNCOV
1995
            upQueries.push(this.dropIndexSql(index))
×
UNCOV
1996
            downQueries.push(this.createIndexSql(oldTable, index))
×
1997
        })
1998

1999
        // change table name into 'temporary_table'
UNCOV
2000
        let [databaseNew, tableNameNew] = this.splitTablePath(newTable.name)
×
UNCOV
2001
        const [, tableNameOld] = this.splitTablePath(oldTable.name)
×
UNCOV
2002
        newTable.name = tableNameNew = `${
×
2003
            databaseNew ? `${databaseNew}.` : ""
×
2004
        }temporary_${tableNameNew}`
2005

2006
        // create new table
UNCOV
2007
        upQueries.push(this.createTableSql(newTable, true, true))
×
UNCOV
2008
        downQueries.push(this.dropTableSql(newTable))
×
2009

2010
        // migrate all data from the old table into new table
UNCOV
2011
        if (migrateData) {
×
UNCOV
2012
            let newColumnNames = newTable.columns
×
UNCOV
2013
                .filter((column) => !column.generatedType)
×
UNCOV
2014
                .map((column) => `"${column.name}"`)
×
2015

UNCOV
2016
            let oldColumnNames = oldTable.columns
×
UNCOV
2017
                .filter((column) => !column.generatedType)
×
UNCOV
2018
                .map((column) => `"${column.name}"`)
×
2019

UNCOV
2020
            if (oldColumnNames.length < newColumnNames.length) {
×
UNCOV
2021
                newColumnNames = newTable.columns
×
2022
                    .filter((column) => {
UNCOV
2023
                        const oldColumn = oldTable.columns.find(
×
UNCOV
2024
                            (c) => c.name === column.name,
×
2025
                        )
UNCOV
2026
                        if (oldColumn && oldColumn.generatedType) return false
×
UNCOV
2027
                        return !column.generatedType && oldColumn
×
2028
                    })
UNCOV
2029
                    .map((column) => `"${column.name}"`)
×
UNCOV
2030
            } else if (oldColumnNames.length > newColumnNames.length) {
×
UNCOV
2031
                oldColumnNames = oldTable.columns
×
2032
                    .filter((column) => {
UNCOV
2033
                        return (
×
2034
                            !column.generatedType &&
×
UNCOV
2035
                            newTable.columns.find((c) => c.name === column.name)
×
2036
                        )
2037
                    })
UNCOV
2038
                    .map((column) => `"${column.name}"`)
×
2039
            }
2040

UNCOV
2041
            upQueries.push(
×
2042
                new Query(
2043
                    `INSERT INTO ${this.escapePath(
2044
                        newTable.name,
2045
                    )}(${newColumnNames.join(
2046
                        ", ",
2047
                    )}) SELECT ${oldColumnNames.join(
2048
                        ", ",
2049
                    )} FROM ${this.escapePath(oldTable.name)}`,
2050
                ),
2051
            )
UNCOV
2052
            downQueries.push(
×
2053
                new Query(
2054
                    `INSERT INTO ${this.escapePath(
2055
                        oldTable.name,
2056
                    )}(${oldColumnNames.join(
2057
                        ", ",
2058
                    )}) SELECT ${newColumnNames.join(
2059
                        ", ",
2060
                    )} FROM ${this.escapePath(newTable.name)}`,
2061
                ),
2062
            )
2063
        }
2064

2065
        // drop old table
UNCOV
2066
        upQueries.push(this.dropTableSql(oldTable))
×
UNCOV
2067
        downQueries.push(this.createTableSql(oldTable, true))
×
2068

2069
        // rename old table
UNCOV
2070
        upQueries.push(
×
2071
            new Query(
2072
                `ALTER TABLE ${this.escapePath(
2073
                    newTable.name,
2074
                )} RENAME TO ${this.escapePath(tableNameOld)}`,
2075
            ),
2076
        )
UNCOV
2077
        downQueries.push(
×
2078
            new Query(
2079
                `ALTER TABLE ${this.escapePath(
2080
                    oldTable.name,
2081
                )} RENAME TO ${this.escapePath(tableNameNew)}`,
2082
            ),
2083
        )
2084

UNCOV
2085
        newTable.name = oldTable.name
×
2086

2087
        // recreate table indices
UNCOV
2088
        newTable.indices.forEach((index) => {
×
2089
            // new index may be passed without name. In this case we generate index name manually.
UNCOV
2090
            if (!index.name)
×
2091
                index.name = this.connection.namingStrategy.indexName(
×
2092
                    newTable,
2093
                    index.columnNames,
2094
                    index.where,
2095
                )
UNCOV
2096
            upQueries.push(this.createIndexSql(newTable, index))
×
UNCOV
2097
            downQueries.push(this.dropIndexSql(index))
×
2098
        })
2099

2100
        // update generated columns in "typeorm_metadata" table
2101
        // Step 1: clear data for removed generated columns
UNCOV
2102
        oldTable.columns
×
2103
            .filter((column) => {
UNCOV
2104
                const newTableColumn = newTable.columns.find(
×
UNCOV
2105
                    (c) => c.name === column.name,
×
2106
                )
2107
                // we should delete record from "typeorm_metadata" if generated column was removed
2108
                // or it was changed to non-generated
UNCOV
2109
                return (
×
2110
                    column.generatedType &&
×
2111
                    column.asExpression &&
2112
                    (!newTableColumn ||
2113
                        (!newTableColumn.generatedType &&
2114
                            !newTableColumn.asExpression))
2115
                )
2116
            })
2117
            .forEach((column) => {
UNCOV
2118
                const deleteQuery = this.deleteTypeormMetadataSql({
×
2119
                    table: oldTable.name,
2120
                    type: MetadataTableType.GENERATED_COLUMN,
2121
                    name: column.name,
2122
                })
2123

UNCOV
2124
                const insertQuery = this.insertTypeormMetadataSql({
×
2125
                    table: oldTable.name,
2126
                    type: MetadataTableType.GENERATED_COLUMN,
2127
                    name: column.name,
2128
                    value: column.asExpression,
2129
                })
2130

UNCOV
2131
                upQueries.push(deleteQuery)
×
UNCOV
2132
                downQueries.push(insertQuery)
×
2133
            })
2134

2135
        // Step 2: add data for new generated columns
UNCOV
2136
        newTable.columns
×
2137
            .filter(
2138
                (column) =>
UNCOV
2139
                    column.generatedType &&
×
2140
                    column.asExpression &&
UNCOV
2141
                    !oldTable.columns.some((c) => c.name === column.name),
×
2142
            )
2143
            .forEach((column) => {
UNCOV
2144
                const insertQuery = this.insertTypeormMetadataSql({
×
2145
                    table: newTable.name,
2146
                    type: MetadataTableType.GENERATED_COLUMN,
2147
                    name: column.name,
2148
                    value: column.asExpression,
2149
                })
2150

UNCOV
2151
                const deleteQuery = this.deleteTypeormMetadataSql({
×
2152
                    table: newTable.name,
2153
                    type: MetadataTableType.GENERATED_COLUMN,
2154
                    name: column.name,
2155
                })
2156

UNCOV
2157
                upQueries.push(insertQuery)
×
UNCOV
2158
                downQueries.push(deleteQuery)
×
2159
            })
2160

2161
        // Step 3: update changed expressions
UNCOV
2162
        newTable.columns
×
UNCOV
2163
            .filter((column) => column.generatedType && column.asExpression)
×
2164
            .forEach((column) => {
UNCOV
2165
                const oldColumn = oldTable.columns.find(
×
2166
                    (c) =>
UNCOV
2167
                        c.name === column.name &&
×
2168
                        c.generatedType &&
2169
                        column.generatedType &&
2170
                        c.asExpression !== column.asExpression,
2171
                )
2172

UNCOV
2173
                if (!oldColumn) return
×
2174

2175
                // update expression
UNCOV
2176
                const deleteQuery = this.deleteTypeormMetadataSql({
×
2177
                    table: oldTable.name,
2178
                    type: MetadataTableType.GENERATED_COLUMN,
2179
                    name: oldColumn.name,
2180
                })
2181

UNCOV
2182
                const insertQuery = this.insertTypeormMetadataSql({
×
2183
                    table: newTable.name,
2184
                    type: MetadataTableType.GENERATED_COLUMN,
2185
                    name: column.name,
2186
                    value: column.asExpression,
2187
                })
2188

UNCOV
2189
                upQueries.push(deleteQuery)
×
UNCOV
2190
                upQueries.push(insertQuery)
×
2191

2192
                // revert update
UNCOV
2193
                const revertInsertQuery = this.insertTypeormMetadataSql({
×
2194
                    table: newTable.name,
2195
                    type: MetadataTableType.GENERATED_COLUMN,
2196
                    name: oldColumn.name,
2197
                    value: oldColumn.asExpression,
2198
                })
2199

UNCOV
2200
                const revertDeleteQuery = this.deleteTypeormMetadataSql({
×
2201
                    table: oldTable.name,
2202
                    type: MetadataTableType.GENERATED_COLUMN,
2203
                    name: column.name,
2204
                })
2205

UNCOV
2206
                downQueries.push(revertInsertQuery)
×
UNCOV
2207
                downQueries.push(revertDeleteQuery)
×
2208
            })
2209

UNCOV
2210
        await this.executeQueries(upQueries, downQueries)
×
UNCOV
2211
        this.replaceCachedTable(oldTable, newTable)
×
2212
    }
2213

2214
    /**
2215
     * tablePath e.g. "myDB.myTable", "myTable"
2216
     */
2217
    protected splitTablePath(tablePath: string): [string | undefined, string] {
UNCOV
2218
        return (
×
2219
            tablePath.indexOf(".") !== -1
×
2220
                ? tablePath.split(".")
2221
                : [undefined, tablePath]
2222
        ) as [string | undefined, string]
2223
    }
2224

2225
    /**
2226
     * Escapes given table or view path. Tolerates leading/trailing dots
2227
     */
2228
    protected escapePath(
2229
        target: Table | View | string,
2230
        disableEscape?: boolean,
2231
    ): string {
2232
        const tableName =
UNCOV
2233
            InstanceChecker.isTable(target) || InstanceChecker.isView(target)
×
2234
                ? target.name
2235
                : target
UNCOV
2236
        return tableName
×
2237
            .replace(/^\.+|\.+$/g, "")
2238
            .split(".")
UNCOV
2239
            .map((i) => (disableEscape ? i : `"${i}"`))
×
2240
            .join(".")
2241
    }
2242

2243
    /**
2244
     * Change table comment.
2245
     */
2246
    changeTableComment(
2247
        tableOrName: Table | string,
2248
        comment?: string,
2249
    ): Promise<void> {
2250
        throw new TypeORMError(`sqlit driver does not support change comment.`)
×
2251
    }
2252
}
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