fix: prevent foreign key support during migration batch under sqlite (#9775)

* fix: prevent foreign keys support during migration batch under sqlite

Schema changes in migrations batch cannot be done on table which has referring
 foreign keys with ON DELETE CASCADE without deleting its content.

Closes: #9770

* Update MigrationExecutor.ts

* Update command.ts

---------

Co-authored-by: Umed Khudoiberdiev <pleerock.me@gmail.com>
This commit is contained in:
sinopsysHK 2023-04-06 16:12:10 +08:00 committed by GitHub
parent 4ac8c00117
commit 197cc05e90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 3 deletions

View File

@ -311,6 +311,7 @@ export class MigrationExecutor {
// start transaction if its not started yet
let transactionStartedByUs = false
if (this.transaction === "all" && !queryRunner.isTransactionActive) {
await queryRunner.beforeMigration()
await queryRunner.startTransaction()
transactionStartedByUs = true
}
@ -327,6 +328,7 @@ export class MigrationExecutor {
}
if (migration.transaction && !queryRunner.isTransactionActive) {
await queryRunner.beforeMigration()
await queryRunner.startTransaction()
transactionStartedByUs = true
}
@ -347,8 +349,10 @@ export class MigrationExecutor {
migration,
)
// commit transaction if we started it
if (migration.transaction && transactionStartedByUs)
if (migration.transaction && transactionStartedByUs) {
await queryRunner.commitTransaction()
await queryRunner.afterMigration()
}
})
.then(() => {
// informative log about migration success
@ -362,8 +366,10 @@ export class MigrationExecutor {
}
// commit transaction if we started it
if (this.transaction === "all" && transactionStartedByUs)
if (this.transaction === "all" && transactionStartedByUs) {
await queryRunner.commitTransaction()
await queryRunner.afterMigration()
}
} catch (err) {
// rollback transaction if we started it
if (transactionStartedByUs) {

View File

@ -12,7 +12,7 @@ describe("migrations > show command", () => {
async () =>
(connections = await createTestingConnections({
migrations: [__dirname + "/migration/*.js"],
enabledDrivers: ["postgres"],
enabledDrivers: ["postgres", "sqlite"],
schemaCreate: true,
dropSchema: true,
})),

View File

@ -0,0 +1,26 @@
import {
Entity,
Column,
ManyToOne,
JoinColumn,
PrimaryGeneratedColumn,
} from "../../../../src"
import { Foo } from "./Foo"
@Entity()
export class Bar {
@PrimaryGeneratedColumn()
id: number
@ManyToOne(() => Foo, {
cascade: true,
onDelete: "CASCADE",
nullable: false,
})
@JoinColumn()
foo!: Foo
@Column()
data: string
}

View File

@ -0,0 +1,9 @@
import { Entity, Column, PrimaryGeneratedColumn } from "../../../../src"
@Entity()
export class Foo {
@PrimaryGeneratedColumn() id: number
@Column()
data: string
}

View File

@ -0,0 +1,87 @@
import "reflect-metadata"
import { expect } from "chai"
import { DataSource } from "../../../src"
//import { DataSource, TableColumn } from "../../../src"
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../utils/test-utils"
import { Foo } from "./entity/Foo"
import { Bar } from "./entity/Bar"
describe("github issues > #9770 check for referencing foreign keys when altering a table using sqlite", () => {
let dataSources: DataSource[]
before(async () => {
dataSources = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
migrations: [__dirname + "/migration/*{.js,.ts}"],
enabledDrivers: ["sqlite", "better-sqlite3"],
schemaCreate: true,
dropSchema: true,
logging: true,
})
})
beforeEach(() => reloadTestingDatabases(dataSources))
after(() => closeTestingConnections(dataSources))
it("shouldn't loose dependant table data", () =>
Promise.all(
dataSources.map(async (dataSource) => {
const manager = dataSource.manager
// Insert records in the tables
const foo = new Foo()
foo.data = "foo"
await manager.save(foo)
const foundFoo = await manager.findOne(Foo, {
where: {
id: 1,
},
})
expect(foundFoo).not.to.be.null
if (!foundFoo) return
const bar = new Bar()
bar.foo = foundFoo
bar.data = "bar"
await manager.save(bar)
const foundBar = await manager.findOne(Bar, {
where: {
foo: {
id: foundFoo.id,
},
},
})
expect(foundBar).not.to.be.null
// check current state (migrations pending and entries in db)
const migrations = await dataSource.showMigrations()
migrations.should.be.equal(true)
const queryRunner = dataSource.createQueryRunner()
let barRecords = await queryRunner.query(`SELECT * FROM "bar"`)
expect(barRecords).to.have.lengthOf.above(0)
// run migrations which contains a table drop
await dataSource.runMigrations()
// check post migration (no more pending migration and data still in db)
const migrations2 = await dataSource.showMigrations()
migrations2.should.be.equal(false)
// check if data still exists in dependant table
barRecords = await queryRunner.query(`SELECT * FROM "bar"`)
expect(barRecords).to.have.lengthOf.above(0)
// revert changes
await queryRunner.executeMemoryDownSql()
await queryRunner.release()
}),
))
})

View File

@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner, TableColumn } from "../../../../src"
export class amendFoo1675779246631 implements MigrationInterface {
public async up(q: QueryRunner): Promise<void> {
await q.addColumn(
"foo",
new TableColumn({
name: "comment",
type: "varchar",
isNullable: true,
default: null,
}),
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("foo", "comment")
}
}