Skip to content

Commit

Permalink
fix: allow clearing database inside a transaction (#8712)
Browse files Browse the repository at this point in the history
* fix: allow clearing database inside a transaction

Closes: #8527

* feat: added clear database inside transaction to sqlite and mysql databases

* feat: added clear database inside transaction to aurora, cockroach, oracle, sap, sql server drivers

Co-authored-by: Tadas Varanauskas <tadas@bitlocus.com>
  • Loading branch information
varanauskas and tadasbtl committed Mar 2, 2022
1 parent 96ac8f7 commit f3cfdd2
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 25 deletions.
10 changes: 7 additions & 3 deletions src/driver/aurora-data-api/AuroraDataApiQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,9 @@ export class AuroraDataApiQueryRunner extends BaseQueryRunner implements QueryRu
throw new TypeORMError(`Can not clear database. No database is specified`);
}

await this.startTransaction();
const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {

const selectViewDropsQuery = `SELECT concat('DROP VIEW IF EXISTS \`', table_schema, '\`.\`', table_name, '\`') AS \`query\` FROM \`INFORMATION_SCHEMA\`.\`VIEWS\` WHERE \`TABLE_SCHEMA\` = '${dbName}'`;
Expand All @@ -1152,11 +1154,13 @@ export class AuroraDataApiQueryRunner extends BaseQueryRunner implements QueryRu
await Promise.all(dropQueries.map(query => this.query(query["query"])));
await this.query(enableForeignKeysCheckQuery);

await this.commitTransaction();
if (!isAnotherTransactionActive)
await this.commitTransaction();

} catch (error) {
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
Expand Down
10 changes: 7 additions & 3 deletions src/driver/cockroachdb/CockroachQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1349,7 +1349,9 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
return name === "current_schema()" ? name : "'" + name + "'";
}).join(", ");

await this.startTransaction();
const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {
const selectViewDropsQuery = `SELECT 'DROP VIEW IF EXISTS "' || schemaname || '"."' || viewname || '" CASCADE;' as "query" ` +
`FROM "pg_views" WHERE "schemaname" IN (${schemaNamesString})`;
Expand All @@ -1364,11 +1366,13 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
const sequenceDropQueries: ObjectLiteral[] = await this.query(selectSequenceDropsQuery);
await Promise.all(sequenceDropQueries.map(q => this.query(q["query"])));

await this.commitTransaction();
if (!isAnotherTransactionActive)
await this.commitTransaction();

} catch (error) {
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
Expand Down
10 changes: 7 additions & 3 deletions src/driver/mysql/MysqlQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1189,7 +1189,9 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
throw new TypeORMError(`Can not clear database. No database is specified`);
}

await this.startTransaction();
const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {

const selectViewDropsQuery = `SELECT concat('DROP VIEW IF EXISTS \`', table_schema, '\`.\`', table_name, '\`') AS \`query\` FROM \`INFORMATION_SCHEMA\`.\`VIEWS\` WHERE \`TABLE_SCHEMA\` = '${dbName}'`;
Expand All @@ -1205,11 +1207,13 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
await Promise.all(dropQueries.map(query => this.query(query["query"])));
await this.query(enableForeignKeysCheckQuery);

await this.commitTransaction();
if (!isAnotherTransactionActive)
await this.commitTransaction();

} catch (error) {
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
Expand Down
10 changes: 7 additions & 3 deletions src/driver/oracle/OracleQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,9 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner {
* Removes all tables from the currently connected database.
*/
async clearDatabase(): Promise<void> {
await this.startTransaction();
const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {
// drop views
const dropViewsQuery = `SELECT 'DROP VIEW "' || VIEW_NAME || '"' AS "query" FROM "USER_VIEWS"`;
Expand All @@ -1232,11 +1234,13 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner {
const dropTablesQuery = `SELECT 'DROP TABLE "' || TABLE_NAME || '" CASCADE CONSTRAINTS' AS "query" FROM "USER_TABLES"`;
const dropTableQueries: ObjectLiteral[] = await this.query(dropTablesQuery);
await Promise.all(dropTableQueries.map(query => this.query(query["query"])));
await this.commitTransaction();
if (!isAnotherTransactionActive)
await this.commitTransaction();

} catch (error) {
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
Expand Down
10 changes: 7 additions & 3 deletions src/driver/postgres/PostgresQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1597,7 +1597,9 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner
return name === "current_schema()" ? name : "'" + name + "'";
}).join(", ");

await this.startTransaction();
const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {
const version = await this.getVersion()
// drop views
Expand Down Expand Up @@ -1626,11 +1628,13 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner
// drop enum types
await this.dropEnumTypes(schemaNamesString);

await this.commitTransaction();
if (!isAnotherTransactionActive)
await this.commitTransaction();

} catch (error) {
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
Expand Down
10 changes: 7 additions & 3 deletions src/driver/sap/SapQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,7 +1440,9 @@ export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
return name === "current_schema" ? name : "'" + name + "'";
}).join(", ");

await this.startTransaction();
const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {
// const selectViewDropsQuery = `SELECT 'DROP VIEW IF EXISTS "' || schemaname || '"."' || viewname || '" CASCADE;' as "query" ` +
// `FROM "pg_views" WHERE "schemaname" IN (${schemaNamesString}) AND "viewname" NOT IN ('geography_columns', 'geometry_columns', 'raster_columns', 'raster_overviews')`;
Expand All @@ -1452,11 +1454,13 @@ export class SapQueryRunner extends BaseQueryRunner implements QueryRunner {
const dropTableQueries: ObjectLiteral[] = await this.query(selectTableDropsQuery);
await Promise.all(dropTableQueries.map(q => this.query(q["query"])));

await this.commitTransaction();
if (!isAnotherTransactionActive)
await this.commitTransaction();

} catch (error) {
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
Expand Down
13 changes: 9 additions & 4 deletions src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,10 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
}

await this.query(`PRAGMA foreign_keys = OFF;`);
await this.startTransaction();

const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {
const selectViewDropsQuery = dbPath ? `SELECT 'DROP VIEW "${dbPath}"."' || name || '";' as query FROM "${dbPath}"."sqlite_master" WHERE "type" = 'view'` : `SELECT 'DROP VIEW "' || name || '";' as query FROM "sqlite_master" WHERE "type" = 'view'`;
const dropViewQueries: ObjectLiteral[] = await this.query(selectViewDropsQuery);
Expand All @@ -765,11 +768,13 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
const selectTableDropsQuery = dbPath ? `SELECT 'DROP TABLE "${dbPath}"."' || name || '";' as query FROM "${dbPath}"."sqlite_master" WHERE "type" = 'table' AND "name" != 'sqlite_sequence'` : `SELECT 'DROP TABLE "' || name || '";' as query FROM "sqlite_master" WHERE "type" = 'table' AND "name" != 'sqlite_sequence'`;
const dropTableQueries: ObjectLiteral[] = await this.query(selectTableDropsQuery);
await Promise.all(dropTableQueries.map(q => this.query(q["query"])));
await this.commitTransaction();


if (!isAnotherTransactionActive)
await this.commitTransaction();
} catch (error) {
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;

Expand Down
10 changes: 7 additions & 3 deletions src/driver/sqlserver/SqlServerQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1415,7 +1415,9 @@ export class SqlServerQueryRunner extends BaseQueryRunner implements QueryRunner
return Promise.resolve();
}

await this.startTransaction();
const isAnotherTransactionActive = this.isTransactionActive;
if (!isAnotherTransactionActive)
await this.startTransaction();
try {
let allViewsSql = database
? `SELECT * FROM "${database}"."INFORMATION_SCHEMA"."VIEWS"`
Expand Down Expand Up @@ -1491,11 +1493,13 @@ export class SqlServerQueryRunner extends BaseQueryRunner implements QueryRunner
}));
}

await this.commitTransaction();
if (!isAnotherTransactionActive)
await this.commitTransaction();

} catch (error) {
try { // we throw original error even if rollback thrown an error
await this.rollbackTransaction();
if (!isAnotherTransactionActive)
await this.rollbackTransaction();
} catch (rollbackError) { }
throw error;
}
Expand Down
12 changes: 12 additions & 0 deletions test/github-issues/8527/entity/TestEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Entity} from "../../../../src/decorator/entity/Entity";
import {Column} from "../../../../src/decorator/columns/Column";
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";

@Entity()
export class TestEntity {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;
}
30 changes: 30 additions & 0 deletions test/github-issues/8527/issue-8527.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "reflect-metadata";
import { closeTestingConnections, createTestingConnections, reloadTestingDatabases } from "../../utils/test-utils";
import { Connection } from "../../../src/connection/Connection";
import { expect } from "chai";
import { TestEntity } from "./entity/TestEntity";

describe("github issues > #8527 cannot clear database inside a transaction.", () => {
let connections: Connection[];

before(async () => connections = await createTestingConnections({
entities: [TestEntity],
enabledDrivers: [
"postgres",
"sqlite",
"mysql"
],
dropSchema: true,
schemaCreate: true
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));

it("should not fail when clearing a database inside a transaction", () => Promise.all(connections.map(async (connection) => {
const queryRunner = connection.createQueryRunner();
await queryRunner.startTransaction();
await expect(queryRunner.clearDatabase()).not.to.be.rejected;
await queryRunner.commitTransaction();
await queryRunner.release();
})));
});

0 comments on commit f3cfdd2

Please sign in to comment.