Skip to content

Commit

Permalink
fix: disable SQLite FK checks in synchronize / migrations (#7922)
Browse files Browse the repository at this point in the history
* fix: disable SQLite FK checks in synchronize / migrations

* test: add case for changing column in a table with foreign keys
  • Loading branch information
imnotjames committed Feb 16, 2022
1 parent d0f32b3 commit f24822e
Show file tree
Hide file tree
Showing 16 changed files with 199 additions and 3 deletions.
14 changes: 14 additions & 0 deletions src/driver/better-sqlite3/BetterSqlite3QueryRunner.ts
Expand Up @@ -58,6 +58,20 @@ export class BetterSqlite3QueryRunner extends AbstractSqliteQueryRunner {
}
}

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = OFF`);
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = ON`);
}

/**
* Executes a given SQL query.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/driver/capacitor/CapacitorQueryRunner.ts
Expand Up @@ -26,6 +26,20 @@ export class CapacitorQueryRunner extends AbstractSqliteQueryRunner {
this.broadcaster = new Broadcaster(this);
}

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = OFF`);
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = ON`);
}

async executeSet(set: { statement: string; values?: any[] }[]) {
if (this.isReleased) throw new QueryRunnerAlreadyReleasedError();

Expand Down
14 changes: 14 additions & 0 deletions src/driver/cordova/CordovaQueryRunner.ts
Expand Up @@ -28,6 +28,20 @@ export class CordovaQueryRunner extends AbstractSqliteQueryRunner {
this.broadcaster = new Broadcaster(this);
}

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = OFF`);
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = ON`);
}

/**
* Executes a given SQL query.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/driver/expo/ExpoQueryRunner.ts
Expand Up @@ -113,6 +113,20 @@ export class ExpoQueryRunner extends AbstractSqliteQueryRunner {
await this.broadcaster.broadcast('AfterTransactionRollback');
}

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = OFF`);
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = ON`);
}

/**
* Executes a given SQL query.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/driver/mongodb/MongoQueryRunner.ts
Expand Up @@ -121,6 +121,20 @@ export class MongoQueryRunner implements QueryRunner {
// Public Methods
// -------------------------------------------------------------------------

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
// Do nothing
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
// Do nothing
}

/**
* Creates a cursor for a query that can be used to iterate over results from MongoDB.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/driver/nativescript/NativescriptQueryRunner.ts
Expand Up @@ -27,6 +27,20 @@ export class NativescriptQueryRunner extends AbstractSqliteQueryRunner {
this.broadcaster = new Broadcaster(this);
}

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = OFF`);
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = ON`);
}

/**
* Executes a given SQL query.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/driver/react-native/ReactNativeQueryRunner.ts
Expand Up @@ -27,6 +27,20 @@ export class ReactNativeQueryRunner extends AbstractSqliteQueryRunner {
this.broadcaster = new Broadcaster(this);
}

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = OFF`);
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = ON`);
}

/**
* Executes a given SQL query.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/driver/sqlite/SqliteQueryRunner.ts
Expand Up @@ -31,6 +31,20 @@ export class SqliteQueryRunner extends AbstractSqliteQueryRunner {
this.broadcaster = new Broadcaster(this);
}

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = OFF`);
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = ON`);
}

/**
* Executes a given SQL query.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/driver/sqljs/SqljsQueryRunner.ts
Expand Up @@ -35,6 +35,20 @@ export class SqljsQueryRunner extends AbstractSqliteQueryRunner {
// Public methods
// -------------------------------------------------------------------------

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = OFF`);
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
await this.query(`PRAGMA foreign_keys = ON`);
}

private async flush() {
if (this.isDirty) {
await this.driver.autoSave();
Expand Down
5 changes: 5 additions & 0 deletions src/migration/MigrationExecutor.ts
Expand Up @@ -61,7 +61,9 @@ export class MigrationExecutor {
public async executeMigration(migration: Migration): Promise<Migration> {
return this.withQueryRunner(async (queryRunner) => {
await this.createMigrationsTableIfNotExist(queryRunner);
await queryRunner.beforeMigration();
await (migration.instance as any).up(queryRunner);
await queryRunner.afterMigration();
await this.insertExecutedMigration(queryRunner, migration);

return migration;
Expand Down Expand Up @@ -309,7 +311,10 @@ export class MigrationExecutor {
}

try {
await queryRunner.beforeMigration();
await migrationToRevert.instance!.down(queryRunner);
await queryRunner.afterMigration();

await this.deleteExecutedMigration(queryRunner, migrationToRevert);
this.connection.logger.logSchemaBuild(`Migration ${migrationToRevert.name} has been reverted successfully.`);

Expand Down
14 changes: 14 additions & 0 deletions src/query-runner/BaseQueryRunner.ts
Expand Up @@ -112,6 +112,20 @@ export abstract class BaseQueryRunner {
// Public Methods
// -------------------------------------------------------------------------

/**
* Called before migrations are run.
*/
async beforeMigration(): Promise<void> {
// Do nothing
}

/**
* Called after migrations are run.
*/
async afterMigration(): Promise<void> {
// Do nothing
}

/**
* Loads given table's data from the database.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/query-runner/QueryRunner.ts
Expand Up @@ -73,6 +73,16 @@ export interface QueryRunner {
*/
connect(): Promise<any>;

/**
* Called before migrations are run.
*/
beforeMigration(): Promise<void>;

/**
* Called after migrations are run.
*/
afterMigration(): Promise<void>;

/**
* Releases used database connection.
* You cannot use query runner methods after connection is released.
Expand Down
5 changes: 5 additions & 0 deletions src/schema-builder/RdbmsSchemaBuilder.ts
Expand Up @@ -72,6 +72,8 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
this.connection.options.migrationsTransactionMode !== "none"
);

await this.queryRunner.beforeMigration();

if (isUsingTransactions) {
await this.queryRunner.startTransaction();
}
Expand Down Expand Up @@ -104,6 +106,9 @@ export class RdbmsSchemaBuilder implements SchemaBuilder {
throw error;

} finally {

await this.queryRunner.afterMigration();

await this.queryRunner.release();
}
}
Expand Down
36 changes: 36 additions & 0 deletions test/functional/schema-builder/change-column.ts
Expand Up @@ -297,4 +297,40 @@ describe("schema builder > change column", () => {
expect(teacherTableB!.findColumnByName("id")!.comment).to.be.undefined;

})));

it("should correctly change column type when FK relationships impact it", () => Promise.all(connections.map(async connection => {
await connection.getRepository(Post)
.insert({
id: 1234,
version: '5',
text: 'a',
tag: 'b',
likesCount: 45
});

const post = await connection.getRepository(Post).findOneOrFail(1234);

await connection.getRepository(PostVersion)
.insert({
id: 1,
post,
details: 'Example'
})

const postMetadata = connection.getMetadata(Post);
const nameColumn = postMetadata.findColumnWithPropertyName("name")!;
nameColumn.length = "500";

await connection.synchronize();

const queryRunner = connection.createQueryRunner();
const postVersionTable = await queryRunner.getTable("post_version");
await queryRunner.release();

postVersionTable!.foreignKeys.length.should.be.equal(1);

// revert changes
nameColumn.length = "255";
})));

});
2 changes: 1 addition & 1 deletion test/functional/schema-builder/entity/Post.ts
Expand Up @@ -20,7 +20,7 @@ export class Post {
@Column({ default: "My post" })
name: string;

@Column()
@Column({ nullable: true })
text: string;

@Column()
Expand Down
4 changes: 2 additions & 2 deletions test/functional/sqljs/auto-save.ts
Expand Up @@ -34,7 +34,7 @@ describe("sqljs driver > autosave", () => {
await connection.createQueryBuilder().insert().into(Post).values(posts).execute();
await connection.createQueryBuilder().update(Post).set({title: "Many posts"}).execute();
await connection.createQueryBuilder().delete().from(Post).where("title = ?", {title: "third post"}).execute();

const repository = connection.getRepository(Post);
let post = new Post();
post.title = "A post";
Expand All @@ -52,7 +52,7 @@ describe("sqljs driver > autosave", () => {

await connection.close();

expect(saves).to.be.equal(7);
expect(saves).to.be.equal(8);
})));
});

Expand Down

0 comments on commit f24822e

Please sign in to comment.