Revert "fix: nested transactions issues (#10210)"

This reverts commit 25e6ecdfd23569b4b6ba15b845b4444927386f42.
This commit is contained in:
Lucian Mocanu 2025-01-28 01:09:25 +01:00
parent 3044e7a233
commit 7aa4f3c3e0
13 changed files with 47 additions and 285 deletions

View File

@ -2,7 +2,6 @@ version: "3"
services:
# mysql
mysql:
platform: linux/amd64
image: "mysql:5.7.37"
container_name: "typeorm-mysql"
ports:

View File

@ -99,12 +99,11 @@ export class AuroraMysqlQueryRunner
}
if (this.transactionDepth === 0) {
this.transactionDepth += 1
await this.client.startTransaction()
} else {
this.transactionDepth += 1
await this.query(`SAVEPOINT typeorm_${this.transactionDepth - 1}`)
await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
}
this.transactionDepth += 1
await this.broadcaster.broadcast("AfterTransactionStart")
}
@ -119,15 +118,14 @@ export class AuroraMysqlQueryRunner
await this.broadcaster.broadcast("BeforeTransactionCommit")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`RELEASE SAVEPOINT typeorm_${this.transactionDepth}`,
`RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.client.commitTransaction()
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionCommit")
}
@ -142,15 +140,14 @@ export class AuroraMysqlQueryRunner
await this.broadcaster.broadcast("BeforeTransactionRollback")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`,
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.client.rollbackTransaction()
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionRollback")
}

View File

@ -110,12 +110,11 @@ export class AuroraPostgresQueryRunner
}
if (this.transactionDepth === 0) {
this.transactionDepth += 1
await this.client.startTransaction()
} else {
this.transactionDepth += 1
await this.query(`SAVEPOINT typeorm_${this.transactionDepth} - 1`)
await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
}
this.transactionDepth += 1
await this.broadcaster.broadcast("AfterTransactionStart")
}
@ -130,15 +129,14 @@ export class AuroraPostgresQueryRunner
await this.broadcaster.broadcast("BeforeTransactionCommit")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`RELEASE SAVEPOINT typeorm_${this.transactionDepth}`,
`RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.client.commitTransaction()
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionCommit")
}
@ -153,15 +151,14 @@ export class AuroraPostgresQueryRunner
await this.broadcaster.broadcast("BeforeTransactionRollback")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`,
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.client.rollbackTransaction()
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionRollback")
}

View File

@ -190,7 +190,6 @@ export class CockroachQueryRunner
}
if (this.transactionDepth === 0) {
this.transactionDepth += 1
await this.query("START TRANSACTION")
await this.query("SAVEPOINT cockroach_restart")
if (isolationLevel) {
@ -199,10 +198,10 @@ export class CockroachQueryRunner
)
}
} else {
this.transactionDepth += 1
await this.query(`SAVEPOINT typeorm_${this.transactionDepth - 1}`)
await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
}
this.transactionDepth += 1
this.storeQueries = true
await this.broadcaster.broadcast("AfterTransactionStart")
@ -218,10 +217,10 @@ export class CockroachQueryRunner
await this.broadcaster.broadcast("BeforeTransactionCommit")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`RELEASE SAVEPOINT typeorm_${this.transactionDepth}`,
`RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
this.transactionDepth -= 1
} else {
this.storeQueries = false
await this.query("RELEASE SAVEPOINT cockroach_restart")
@ -229,6 +228,7 @@ export class CockroachQueryRunner
this.queries = []
this.isTransactionActive = false
this.transactionRetries = 0
this.transactionDepth -= 1
}
await this.broadcaster.broadcast("AfterTransactionCommit")
@ -244,18 +244,17 @@ export class CockroachQueryRunner
await this.broadcaster.broadcast("BeforeTransactionRollback")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`,
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.storeQueries = false
this.transactionDepth -= 1
await this.query("ROLLBACK")
this.queries = []
this.isTransactionActive = false
this.transactionRetries = 0
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionRollback")
}

View File

@ -119,7 +119,6 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
throw err
}
if (this.transactionDepth === 0) {
this.transactionDepth += 1
if (isolationLevel) {
await this.query(
"SET TRANSACTION ISOLATION LEVEL " + isolationLevel,
@ -127,9 +126,9 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
}
await this.query("START TRANSACTION")
} else {
this.transactionDepth += 1
await this.query(`SAVEPOINT typeorm_${this.transactionDepth - 1}`)
await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
}
this.transactionDepth += 1
await this.broadcaster.broadcast("AfterTransactionStart")
}
@ -144,15 +143,14 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
await this.broadcaster.broadcast("BeforeTransactionCommit")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`RELEASE SAVEPOINT typeorm_${this.transactionDepth}`,
`RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.query("COMMIT")
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionCommit")
}
@ -167,15 +165,14 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
await this.broadcaster.broadcast("BeforeTransactionRollback")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`,
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.query("ROLLBACK")
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionRollback")
}

View File

@ -136,14 +136,13 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner {
}
if (this.transactionDepth === 0) {
this.transactionDepth += 1
await this.query(
"SET TRANSACTION ISOLATION LEVEL " + isolationLevel,
)
} else {
this.transactionDepth += 1
await this.query(`SAVEPOINT typeorm_${this.transactionDepth - 1}`)
await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
}
this.transactionDepth += 1
await this.broadcaster.broadcast("AfterTransactionStart")
}
@ -176,15 +175,14 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner {
await this.broadcaster.broadcast("BeforeTransactionRollback")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`,
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.query("ROLLBACK")
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionRollback")
}

View File

@ -174,7 +174,6 @@ export class PostgresQueryRunner
}
if (this.transactionDepth === 0) {
this.transactionDepth += 1
await this.query("START TRANSACTION")
if (isolationLevel) {
await this.query(
@ -182,9 +181,9 @@ export class PostgresQueryRunner
)
}
} else {
this.transactionDepth += 1
await this.query(`SAVEPOINT typeorm_${this.transactionDepth - 1}`)
await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
}
this.transactionDepth += 1
await this.broadcaster.broadcast("AfterTransactionStart")
}
@ -199,15 +198,14 @@ export class PostgresQueryRunner
await this.broadcaster.broadcast("BeforeTransactionCommit")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`RELEASE SAVEPOINT typeorm_${this.transactionDepth}`,
`RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.query("COMMIT")
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionCommit")
}
@ -222,15 +220,14 @@ export class PostgresQueryRunner
await this.broadcaster.broadcast("BeforeTransactionRollback")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`,
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.query("ROLLBACK")
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionRollback")
}

View File

@ -101,7 +101,6 @@ export abstract class AbstractSqliteQueryRunner
}
if (this.transactionDepth === 0) {
this.transactionDepth += 1
if (isolationLevel) {
if (isolationLevel === "READ UNCOMMITTED") {
await this.query("PRAGMA read_uncommitted = true")
@ -111,9 +110,9 @@ export abstract class AbstractSqliteQueryRunner
}
await this.query("BEGIN TRANSACTION")
} else {
this.transactionDepth += 1
await this.query(`SAVEPOINT typeorm_${this.transactionDepth - 1}`)
await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
}
this.transactionDepth += 1
await this.broadcaster.broadcast("AfterTransactionStart")
}
@ -128,15 +127,14 @@ export abstract class AbstractSqliteQueryRunner
await this.broadcaster.broadcast("BeforeTransactionCommit")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`RELEASE SAVEPOINT typeorm_${this.transactionDepth}`,
`RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.query("COMMIT")
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionCommit")
}
@ -151,15 +149,14 @@ export abstract class AbstractSqliteQueryRunner
await this.broadcaster.broadcast("BeforeTransactionRollback")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`,
`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth - 1}`,
)
} else {
this.transactionDepth -= 1
await this.query("ROLLBACK")
this.isTransactionActive = false
}
this.transactionDepth -= 1
await this.broadcaster.broadcast("AfterTransactionRollback")
}

View File

@ -107,7 +107,6 @@ export class SqlServerQueryRunner
}
if (this.transactionDepth === 0) {
this.transactionDepth += 1
const pool = await (this.mode === "slave"
? this.driver.obtainSlaveConnection()
: this.driver.obtainMasterConnection())
@ -125,12 +124,12 @@ export class SqlServerQueryRunner
this.databaseConnection.begin(transactionCallback)
}
} else {
this.transactionDepth += 1
await this.query(
`SAVE TRANSACTION typeorm_${this.transactionDepth - 1}`,
`SAVE TRANSACTION typeorm_${this.transactionDepth}`,
)
ok()
}
this.transactionDepth += 1
})
await this.broadcaster.broadcast("AfterTransactionStart")
@ -149,7 +148,6 @@ export class SqlServerQueryRunner
if (this.transactionDepth === 1) {
return new Promise<void>((ok, fail) => {
this.transactionDepth -= 1
this.databaseConnection.commit(async (err: any) => {
if (err) return fail(err)
this.isTransactionActive = false
@ -159,6 +157,7 @@ export class SqlServerQueryRunner
ok()
this.connection.logger.logQuery("COMMIT")
this.transactionDepth -= 1
})
})
}
@ -177,13 +176,12 @@ export class SqlServerQueryRunner
await this.broadcaster.broadcast("BeforeTransactionRollback")
if (this.transactionDepth > 1) {
this.transactionDepth -= 1
await this.query(
`ROLLBACK TRANSACTION typeorm_${this.transactionDepth}`,
`ROLLBACK TRANSACTION typeorm_${this.transactionDepth - 1}`,
)
this.transactionDepth -= 1
} else {
return new Promise<void>((ok, fail) => {
this.transactionDepth -= 1
this.databaseConnection.rollback(async (err: any) => {
if (err) return fail(err)
this.isTransactionActive = false
@ -193,6 +191,7 @@ export class SqlServerQueryRunner
ok()
this.connection.logger.logQuery("ROLLBACK")
this.transactionDepth -= 1
})
})
}

View File

@ -1,44 +0,0 @@
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "../../../../src"
import { ConfigurationEntity } from "./configuration"
export enum AssetStatus {
new = 0,
deleted = -999,
}
@Entity("assets")
export class AssetEntity {
@PrimaryGeneratedColumn("uuid")
id!: string
@Column({ length: 255 })
name!: string
@Column({ type: "uuid" })
configuration_id!: string
@Column()
status!: AssetStatus
@CreateDateColumn()
created_at!: Date
@UpdateDateColumn()
updated_at!: Date
@DeleteDateColumn()
deleted_at!: Date | null
@ManyToOne(() => ConfigurationEntity, { nullable: false })
@JoinColumn({ name: "configuration_id" })
configuration!: ConfigurationEntity
}

View File

@ -1,50 +0,0 @@
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "../../../../src"
import { AssetEntity } from "./asset"
import { LocationEntity } from "./location"
export enum ConfigurationStatus {
deleted = -999,
new = 0,
}
@Entity("configurations")
export class ConfigurationEntity {
@PrimaryGeneratedColumn("uuid")
id!: string
@Column({ length: 255 })
name!: string
@Column()
status!: ConfigurationStatus
@Column({ type: "uuid", nullable: false })
location_id!: string
@ManyToOne(() => LocationEntity, { nullable: false })
@JoinColumn({ name: "location_id" })
location!: LocationEntity
@Column({ default: true })
active!: boolean
@OneToMany(() => AssetEntity, (asset) => asset.configuration, {
cascade: true,
})
assets!: AssetEntity[]
@CreateDateColumn()
created_at!: Date
@UpdateDateColumn()
updated_at!: Date
}

View File

@ -1,34 +0,0 @@
import {
Column,
CreateDateColumn,
Entity,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "../../../../src"
import { ConfigurationEntity } from "./configuration"
@Entity("locations")
export class LocationEntity {
@PrimaryGeneratedColumn("uuid")
id!: string
@Column({ length: 255 })
name!: string
@Column({ default: true })
active!: boolean
@CreateDateColumn()
created_at!: Date
@UpdateDateColumn()
updated_at!: Date
@OneToMany(
() => ConfigurationEntity,
(configuration) => configuration.location,
{ cascade: true },
)
configurations!: ConfigurationEntity[]
}

View File

@ -1,90 +0,0 @@
import "reflect-metadata"
import {
createTestingConnections,
closeTestingConnections,
reloadTestingDatabases,
} from "../../utils/test-utils"
import { DataSource } from "../../../src/data-source/DataSource"
import { expect } from "chai"
import { LocationEntity } from "./entity/location"
import {
ConfigurationEntity,
ConfigurationStatus,
} from "./entity/configuration"
import { AssetEntity, AssetStatus } from "./entity/asset"
describe("github issues > #10209", () => {
let dataSources: DataSource[]
before(
async () =>
(dataSources = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchema: true,
})),
)
beforeEach(() => reloadTestingDatabases(dataSources))
after(() => closeTestingConnections(dataSources))
it("should not fail to run multiple nested transactions in parallel", function () {
this.retries(3) // Fix for SQLite
return Promise.all(
dataSources.map(async (dataSource) => {
const manager = dataSource.createEntityManager()
await manager.transaction(async (txManager) => {
const location = txManager.create(LocationEntity)
location.name = "location-0"
location.configurations = []
for (let c = 0; c < 3; c++) {
const config = txManager.create(ConfigurationEntity)
config.name = `config-${c}`
config.status = ConfigurationStatus.new
config.assets = []
for (let a = 0; a < 5; a++) {
const asset = txManager.create(AssetEntity)
asset.name = `asset-${c}-${a}`
asset.status = AssetStatus.new
config.assets.push(asset)
}
location.configurations.push(config)
}
await txManager.save(location)
})
const location =
(await manager.findOne(LocationEntity, {
where: {
name: "location-0",
},
relations: ["configurations", "configurations.assets"],
})) || ({} as LocationEntity)
await manager.transaction(async (txManager) => {
return Promise.all(
location.configurations.map(async (config) => {
await txManager.transaction(async (txManager2) => {
await Promise.all(
config.assets.map(async (asset) => {
asset.status = AssetStatus.deleted
await txManager2.save(asset)
await txManager2.softDelete(
AssetEntity,
asset,
)
}),
)
})
config.status = ConfigurationStatus.deleted
return await txManager.save(config)
}),
)
})
// We only care that the transaction above didn't fail
expect(true).to.be.true
}),
)
})
})