Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2005,6 +2005,14 @@ export abstract class AbstractSqliteQueryRunner
const upQueries: Query[] = []
const downQueries: Query[] = []

// query triggers from old table before recreation
const [, tableNameOldInitial] = this.splitTablePath(oldTable.name)
const triggerQuery = `SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?`
const triggers: { name: string; sql: string }[] = await this.query(
triggerQuery,
[tableNameOldInitial],
)

// drop old table indices
oldTable.indices.forEach((index) => {
upQueries.push(this.dropIndexSql(index))
Expand Down Expand Up @@ -2112,6 +2120,14 @@ export abstract class AbstractSqliteQueryRunner
downQueries.push(this.dropIndexSql(index))
})

// recreate table triggers
triggers.forEach((trigger) => {
// upQueries: recreate triggers on the new table (forward migration)
upQueries.push(new Query(trigger.sql))
// downQueries: recreate triggers on the old table (rollback)
downQueries.push(new Query(trigger.sql))
})

// update generated columns in "typeorm_metadata" table
// Step 1: clear data for removed generated columns
oldTable.columns
Expand Down
51 changes: 51 additions & 0 deletions test/functional/query-runner/drop-column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,57 @@ describe("query runner > drop column", () => {
))
})

it("should preserve triggers when recreating table in sqlite", () =>
Promise.all(
connections
// Only run this test for sqlite-based connections
.filter(
(connection) =>
connection.options.type === "sqlite" ||
connection.options.type === "sqlite-pooled",
)
.map(async (connection) => {
const queryRunner = connection.createQueryRunner()

// Get the post table
let table = await queryRunner.getTable("post")
expect(table).to.exist

// Create a trigger on the table
const triggerName = "test_insert_trigger"
const triggerSql = `CREATE TRIGGER ${triggerName} AFTER INSERT ON "${table!.name}" BEGIN SELECT 1; END`
await queryRunner.query(triggerSql)

// Verify trigger exists before column drop
const triggersBefore: { name: string; sql: string }[] =
await queryRunner.query(
`SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?`,
[table!.name],
)
expect(triggersBefore).to.have.length(1)
expect(triggersBefore[0].name).to.equal(triggerName)

// Drop a column, which triggers recreateTable in SQLite
await queryRunner.dropColumn(table!, "version")

// Verify trigger still exists after table recreation
table = await queryRunner.getTable("post")
const triggersAfter: { name: string; sql: string }[] =
await queryRunner.query(
`SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?`,
[table!.name],
)
expect(triggersAfter).to.have.length(1)
expect(triggersAfter[0].name).to.equal(triggerName)
expect(triggersAfter[0].sql).to.equal(triggerSql)

// Clean up
await queryRunner.query(`DROP TRIGGER ${triggerName}`)
await queryRunner.executeMemoryDownSql()
await queryRunner.release()
}),
))

it("should safely handle SQL injection in hasEnumType", () =>
Promise.all(
connections
Expand Down