mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
Merge pull request #11264 from alumni/fix-transaction-issues
This commit is contained in:
commit
673b6ceb88
@ -2,7 +2,6 @@ version: "3"
|
||||
services:
|
||||
# mysql
|
||||
mysql:
|
||||
platform: linux/amd64
|
||||
image: "mysql:5.7.37"
|
||||
container_name: "typeorm-mysql"
|
||||
ports:
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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,20 +217,18 @@ 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
|
||||
this.transactionDepth -= 1
|
||||
// This was disabled because it failed tests after update to CRDB 24.2
|
||||
// https://github.com/typeorm/typeorm/pull/11190
|
||||
// await this.query("RELEASE SAVEPOINT cockroach_restart")
|
||||
await this.query("RELEASE SAVEPOINT cockroach_restart")
|
||||
await this.query("COMMIT")
|
||||
this.queries = []
|
||||
this.isTransactionActive = false
|
||||
this.transactionRetries = 0
|
||||
this.transactionDepth -= 1
|
||||
}
|
||||
|
||||
await this.broadcaster.broadcast("AfterTransactionCommit")
|
||||
@ -247,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")
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -581,6 +581,7 @@ export class RelationIdLoader {
|
||||
entities: ObjectLiteral[],
|
||||
relatedEntities?: ObjectLiteral[],
|
||||
) {
|
||||
const originalRelation = relation
|
||||
relation = relation.inverseRelation!
|
||||
|
||||
if (
|
||||
@ -611,7 +612,7 @@ export class RelationIdLoader {
|
||||
const primaryColumnName =
|
||||
joinColumn.entityMetadata.name +
|
||||
"_" +
|
||||
relation.inverseRelation!.propertyPath.replace(
|
||||
originalRelation.propertyPath.replace(
|
||||
".",
|
||||
"_",
|
||||
) +
|
||||
@ -636,7 +637,7 @@ export class RelationIdLoader {
|
||||
undefined,
|
||||
primaryColumn.entityMetadata.name +
|
||||
"_" +
|
||||
relation.inverseRelation!.propertyPath.replace(".", "_") +
|
||||
originalRelation.propertyPath.replace(".", "_") +
|
||||
"_" +
|
||||
primaryColumn.propertyPath.replace(".", "_"),
|
||||
)
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import "reflect-metadata"
|
||||
import { expect } from "chai"
|
||||
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
|
||||
import { Company } from "./entity/Company"
|
||||
import { Office } from "./entity/Office"
|
||||
import { expect } from "chai"
|
||||
|
||||
describe("deferrable uq constraints should be check at the end of transaction", () => {
|
||||
describe("deferrable unique constraint", () => {
|
||||
let connections: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
@ -21,7 +23,7 @@ describe("deferrable uq constraints should be check at the end of transaction",
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
it("use initially deferred deferrable uq constraints", () =>
|
||||
it("initially deferred unique should be validated at the end of transaction", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await connection.manager.transaction(async (entityManager) => {
|
||||
@ -63,7 +65,7 @@ describe("deferrable uq constraints should be check at the end of transaction",
|
||||
}),
|
||||
))
|
||||
|
||||
it("use initially immediated deferrable uq constraints", () =>
|
||||
it("initially immediate unique should be validated at the end at transaction with deferred check time", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await connection.manager.transaction(async (entityManager) => {
|
||||
|
||||
@ -1,28 +1,30 @@
|
||||
import "reflect-metadata"
|
||||
import { expect } from "chai"
|
||||
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
|
||||
import { Company } from "./entity/Company"
|
||||
import { Office } from "./entity/Office"
|
||||
import { User } from "./entity/User"
|
||||
import { expect } from "chai"
|
||||
|
||||
describe("deferrable fk constraints should be check at the end of transaction (#2191)", () => {
|
||||
describe("deferrable foreign key constraint", () => {
|
||||
let connections: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
entities: [__dirname + "/entity/*{.js,.ts}"],
|
||||
enabledDrivers: ["postgres"],
|
||||
enabledDrivers: ["better-sqlite3", "postgres", "sap", "sqlite"],
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
it("use initially deferred deferrable fk constraints", () =>
|
||||
it("initially deferred fk should be validated at the end of transaction", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
await connection.manager.transaction(async (entityManager) => {
|
||||
@ -40,7 +42,7 @@ describe("deferrable fk constraints should be check at the end of transaction (#
|
||||
company.name = "Acme"
|
||||
|
||||
await entityManager.save(company)
|
||||
})
|
||||
}).should.not.be.rejected
|
||||
|
||||
// now check
|
||||
const user = await connection.manager.findOne(User, {
|
||||
@ -48,9 +50,7 @@ describe("deferrable fk constraints should be check at the end of transaction (#
|
||||
where: { id: 1 },
|
||||
})
|
||||
|
||||
expect(user).not.to.be.null
|
||||
|
||||
user!.should.be.eql({
|
||||
expect(user).to.deep.equal({
|
||||
id: 1,
|
||||
name: "Bob",
|
||||
company: {
|
||||
@ -61,9 +61,12 @@ describe("deferrable fk constraints should be check at the end of transaction (#
|
||||
}),
|
||||
))
|
||||
|
||||
it("use initially immediated deferrable fk constraints", () =>
|
||||
it("initially immediate fk should be validated at the end at transaction with deferred check time", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
// changing the constraint check time is only supported on postgres
|
||||
if (connection.driver.options.type !== "postgres") return
|
||||
|
||||
await connection.manager.transaction(async (entityManager) => {
|
||||
// first set constraints deferred manually
|
||||
await entityManager.query("SET CONSTRAINTS ALL DEFERRED")
|
||||
@ -82,7 +85,7 @@ describe("deferrable fk constraints should be check at the end of transaction (#
|
||||
company.name = "Emca"
|
||||
|
||||
await entityManager.save(company)
|
||||
})
|
||||
}).should.not.be.rejected
|
||||
|
||||
// now check
|
||||
const office = await connection.manager.findOne(Office, {
|
||||
@ -90,9 +93,7 @@ describe("deferrable fk constraints should be check at the end of transaction (#
|
||||
where: { id: 2 },
|
||||
})
|
||||
|
||||
expect(office).not.to.be.null
|
||||
|
||||
office!.should.be.eql({
|
||||
expect(office).to.deep.equal({
|
||||
id: 2,
|
||||
name: "Barcelona",
|
||||
company: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { Column } from "../../../../src/decorator/columns/Column"
|
||||
import { PrimaryColumn } from "../../../../src/decorator/columns/PrimaryColumn"
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { Unique } from "../../../../src/decorator/Unique"
|
||||
|
||||
@Entity()
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { Column } from "../../../../src/decorator/columns/Column"
|
||||
import { ManyToOne } from "../../../../src/decorator/relations/ManyToOne"
|
||||
import { PrimaryColumn } from "../../../../src/decorator/columns/PrimaryColumn"
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { ManyToOne } from "../../../../src/decorator/relations/ManyToOne"
|
||||
import { Unique } from "../../../../src/decorator/Unique"
|
||||
|
||||
import { Company } from "./Company"
|
||||
|
||||
@Entity()
|
||||
@ -14,8 +15,6 @@ export class Office {
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@ManyToOne((type) => Company, (company) => company.id, {
|
||||
deferrable: "INITIALLY IMMEDIATE",
|
||||
})
|
||||
@ManyToOne(() => Company, { deferrable: "INITIALLY IMMEDIATE" })
|
||||
company: Company
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { Column } from "../../../../src/decorator/columns/Column"
|
||||
import { ManyToOne } from "../../../../src/decorator/relations/ManyToOne"
|
||||
import { PrimaryColumn } from "../../../../src/decorator/columns/PrimaryColumn"
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { ManyToOne } from "../../../../src/decorator/relations/ManyToOne"
|
||||
|
||||
import { Company } from "./Company"
|
||||
|
||||
@Entity()
|
||||
@ -12,8 +13,6 @@ export class User {
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@ManyToOne((type) => Company, (company) => company.id, {
|
||||
deferrable: "INITIALLY DEFERRED",
|
||||
})
|
||||
@ManyToOne(() => Company, { deferrable: "INITIALLY DEFERRED" })
|
||||
company: Company
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -39,21 +39,22 @@ describe("repository > find options > locking", () => {
|
||||
DriverUtils.isSQLiteFamily(connection.driver) ||
|
||||
connection.driver.options.type === "sap" ||
|
||||
connection.driver.options.type === "spanner"
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (connection.driver.options.type === "cockroachdb") {
|
||||
return Promise.all([
|
||||
connection
|
||||
.getRepository(PostWithVersion)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
})
|
||||
.should.be.rejectedWith(
|
||||
PessimisticLockTransactionRequiredError,
|
||||
),
|
||||
])
|
||||
await connection
|
||||
.getRepository(PostWithVersion)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
})
|
||||
.should.be.rejectedWith(
|
||||
PessimisticLockTransactionRequiredError,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
@ -91,20 +92,16 @@ describe("repository > find options > locking", () => {
|
||||
return
|
||||
|
||||
if (connection.driver.options.type === "cockroachdb") {
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
entityManager
|
||||
.getRepository(PostWithVersion)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
}).should.not.be.rejected,
|
||||
])
|
||||
})
|
||||
return connection.manager.transaction((entityManager) =>
|
||||
entityManager.getRepository(PostWithVersion).findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
}),
|
||||
).should.not.be.rejected
|
||||
}
|
||||
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
return connection.manager.transaction((entityManager) =>
|
||||
Promise.all([
|
||||
entityManager.getRepository(PostWithVersion).find({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_read" },
|
||||
@ -114,8 +111,8 @@ describe("repository > find options > locking", () => {
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
}).should.not.be.rejected,
|
||||
])
|
||||
})
|
||||
]),
|
||||
)
|
||||
}),
|
||||
))
|
||||
|
||||
@ -150,19 +147,15 @@ describe("repository > find options > locking", () => {
|
||||
})
|
||||
|
||||
if (DriverUtils.isMySQLFamily(connection.driver)) {
|
||||
expect(executedSql[0].indexOf("LOCK IN SHARE MODE") !== -1)
|
||||
.to.be.true
|
||||
expect(executedSql[0]).to.contain("LOCK IN SHARE MODE")
|
||||
} else if (connection.driver.options.type === "postgres") {
|
||||
expect(executedSql[0].indexOf("FOR SHARE") !== -1).to.be
|
||||
.true
|
||||
expect(executedSql[0]).to.contain("FOR SHARE")
|
||||
} else if (connection.driver.options.type === "oracle") {
|
||||
expect(executedSql[0].indexOf("FOR UPDATE") !== -1).to.be
|
||||
.true
|
||||
expect(executedSql[0]).to.contain("FOR UPDATE")
|
||||
} else if (connection.driver.options.type === "mssql") {
|
||||
expect(
|
||||
executedSql[0].indexOf("WITH (HOLDLOCK, ROWLOCK)") !==
|
||||
-1,
|
||||
).to.be.true
|
||||
expect(executedSql[0]).to.contain(
|
||||
"WITH (HOLDLOCK, ROWLOCK)",
|
||||
)
|
||||
}
|
||||
}),
|
||||
))
|
||||
@ -170,7 +163,9 @@ describe("repository > find options > locking", () => {
|
||||
it("should attach for no key update lock statement on query if locking enabled", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
if (!(connection.driver.options.type === "postgres")) return
|
||||
if (connection.driver.options.type !== "postgres") {
|
||||
return
|
||||
}
|
||||
|
||||
const executedSql: string[] = []
|
||||
|
||||
@ -193,8 +188,7 @@ describe("repository > find options > locking", () => {
|
||||
})
|
||||
})
|
||||
|
||||
expect(executedSql.join(" ").includes("FOR NO KEY UPDATE")).to
|
||||
.be.true
|
||||
expect(executedSql.join(" ")).to.contain("FOR NO KEY UPDATE")
|
||||
}),
|
||||
))
|
||||
|
||||
@ -224,8 +218,7 @@ describe("repository > find options > locking", () => {
|
||||
})
|
||||
})
|
||||
|
||||
expect(executedSql.join(" ").includes("FOR KEY SHARE")).to.be
|
||||
.true
|
||||
expect(executedSql.join(" ")).to.contain("FOR KEY SHARE")
|
||||
}),
|
||||
))
|
||||
|
||||
@ -241,8 +234,9 @@ describe("repository > find options > locking", () => {
|
||||
"8.0.0",
|
||||
))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const executedSql: string[] = []
|
||||
|
||||
@ -268,8 +262,9 @@ describe("repository > find options > locking", () => {
|
||||
})
|
||||
})
|
||||
|
||||
expect(executedSql.join(" ").includes("FOR SHARE SKIP LOCKED"))
|
||||
.to.be.true
|
||||
expect(executedSql.join(" ")).to.contain(
|
||||
"FOR SHARE SKIP LOCKED",
|
||||
)
|
||||
}),
|
||||
))
|
||||
|
||||
@ -285,8 +280,9 @@ describe("repository > find options > locking", () => {
|
||||
"8.0.0",
|
||||
))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const executedSql: string[] = []
|
||||
|
||||
@ -312,8 +308,7 @@ describe("repository > find options > locking", () => {
|
||||
})
|
||||
})
|
||||
|
||||
expect(executedSql.join(" ").includes("FOR UPDATE NOWAIT")).to
|
||||
.be.true
|
||||
expect(executedSql.join(" ")).to.contain("FOR UPDATE NOWAIT")
|
||||
}),
|
||||
))
|
||||
|
||||
@ -324,8 +319,9 @@ describe("repository > find options > locking", () => {
|
||||
DriverUtils.isSQLiteFamily(connection.driver) ||
|
||||
connection.driver.options.type === "sap" ||
|
||||
connection.driver.options.type === "spanner"
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const executedSql: string[] = []
|
||||
|
||||
@ -351,13 +347,9 @@ describe("repository > find options > locking", () => {
|
||||
connection.driver.options.type === "postgres" ||
|
||||
connection.driver.options.type === "oracle"
|
||||
) {
|
||||
expect(executedSql[0].indexOf("FOR UPDATE") !== -1).to.be
|
||||
.true
|
||||
expect(executedSql[0]).to.contain("FOR UPDATE")
|
||||
} else if (connection.driver.options.type === "mssql") {
|
||||
expect(
|
||||
executedSql[0].indexOf("WITH (UPDLOCK, ROWLOCK)") !==
|
||||
-1,
|
||||
).to.be.true
|
||||
expect(executedSql[0]).to.contain("WITH (UPDLOCK, ROWLOCK)")
|
||||
}
|
||||
}),
|
||||
))
|
||||
@ -388,15 +380,14 @@ describe("repository > find options > locking", () => {
|
||||
})
|
||||
})
|
||||
|
||||
expect(executedSql[0].indexOf("WITH (NOLOCK)") !== -1).to.be
|
||||
.true
|
||||
expect(executedSql[0]).to.contain("WITH (NOLOCK)")
|
||||
}),
|
||||
))
|
||||
|
||||
it("should throw error if optimistic lock used with `find` method", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
return connection
|
||||
await connection
|
||||
.getRepository(PostWithVersion)
|
||||
.find({ lock: { mode: "optimistic", version: 1 } })
|
||||
.should.be.rejectedWith(OptimisticLockCanNotBeUsedError)
|
||||
@ -406,7 +397,7 @@ describe("repository > find options > locking", () => {
|
||||
it("should not throw error if optimistic lock used with `findOne` method", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
return connection.getRepository(PostWithVersion).findOne({
|
||||
await connection.getRepository(PostWithVersion).findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "optimistic", version: 1 },
|
||||
}).should.not.be.rejected
|
||||
@ -420,7 +411,7 @@ describe("repository > find options > locking", () => {
|
||||
post.title = "New post"
|
||||
await connection.manager.save(post)
|
||||
|
||||
return connection
|
||||
await connection
|
||||
.getRepository(PostWithoutVersionAndUpdateDate)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
@ -437,7 +428,7 @@ describe("repository > find options > locking", () => {
|
||||
post.title = "New post"
|
||||
await connection.manager.save(post)
|
||||
|
||||
return connection
|
||||
await connection
|
||||
.getRepository(PostWithVersion)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
@ -454,7 +445,7 @@ describe("repository > find options > locking", () => {
|
||||
post.title = "New post"
|
||||
await connection.manager.save(post)
|
||||
|
||||
return connection.getRepository(PostWithVersion).findOne({
|
||||
await connection.getRepository(PostWithVersion).findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "optimistic", version: 1 },
|
||||
}).should.not.be.rejected
|
||||
@ -465,13 +456,15 @@ describe("repository > find options > locking", () => {
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
// skipped because inserted milliseconds are not always equal to what we say it to insert, unskip when needed
|
||||
if (connection.driver.options.type === "mssql") return
|
||||
if (connection.driver.options.type === "mssql") {
|
||||
return
|
||||
}
|
||||
|
||||
const post = new PostWithUpdateDate()
|
||||
post.title = "New post"
|
||||
await connection.manager.save(post)
|
||||
|
||||
return connection
|
||||
await connection
|
||||
.getRepository(PostWithUpdateDate)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
@ -488,13 +481,15 @@ describe("repository > find options > locking", () => {
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
// skipped because inserted milliseconds are not always equal to what we say it to insert, unskip when needed
|
||||
if (connection.driver.options.type === "mssql") return
|
||||
if (connection.driver.options.type === "mssql") {
|
||||
return
|
||||
}
|
||||
|
||||
const post = new PostWithUpdateDate()
|
||||
post.title = "New post"
|
||||
await connection.manager.save(post)
|
||||
|
||||
return connection.getRepository(PostWithUpdateDate).findOne({
|
||||
await connection.getRepository(PostWithUpdateDate).findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "optimistic", version: post.updateDate },
|
||||
}).should.not.be.rejected
|
||||
@ -505,13 +500,15 @@ describe("repository > find options > locking", () => {
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
// skipped because inserted milliseconds are not always equal to what we say it to insert, unskip when needed
|
||||
if (connection.driver.options.type === "mssql") return
|
||||
if (connection.driver.options.type === "mssql") {
|
||||
return
|
||||
}
|
||||
|
||||
const post = new PostWithVersionAndUpdatedDate()
|
||||
post.title = "New post"
|
||||
await connection.manager.save(post)
|
||||
|
||||
return Promise.all([
|
||||
await Promise.all([
|
||||
connection
|
||||
.getRepository(PostWithVersionAndUpdatedDate)
|
||||
.findOne({
|
||||
@ -538,29 +535,26 @@ describe("repository > find options > locking", () => {
|
||||
DriverUtils.isSQLiteFamily(connection.driver) ||
|
||||
connection.driver.options.type === "sap"
|
||||
)
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
entityManager
|
||||
.getRepository(PostWithVersion)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_read" },
|
||||
})
|
||||
.should.be.rejectedWith(
|
||||
LockNotSupportedOnGivenDriverError,
|
||||
),
|
||||
|
||||
entityManager
|
||||
.getRepository(PostWithVersion)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
})
|
||||
.should.be.rejectedWith(
|
||||
LockNotSupportedOnGivenDriverError,
|
||||
),
|
||||
])
|
||||
})
|
||||
await connection.manager
|
||||
.transaction((entityManager) =>
|
||||
Promise.all([
|
||||
entityManager
|
||||
.getRepository(PostWithVersion)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_read" },
|
||||
}),
|
||||
entityManager
|
||||
.getRepository(PostWithVersion)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
}),
|
||||
]),
|
||||
)
|
||||
.should.be.rejectedWith(
|
||||
LockNotSupportedOnGivenDriverError,
|
||||
)
|
||||
|
||||
return
|
||||
}),
|
||||
@ -570,26 +564,22 @@ describe("repository > find options > locking", () => {
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
if (
|
||||
!(
|
||||
connection.driver.options.type === "postgres" ||
|
||||
connection.driver.options.type === "cockroachdb"
|
||||
)
|
||||
)
|
||||
connection.driver.options.type !== "postgres" &&
|
||||
connection.driver.options.type !== "cockroachdb"
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
entityManager
|
||||
.getRepository(Post)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write", tables: [] },
|
||||
})
|
||||
.should.be.rejectedWith(
|
||||
"lockTables cannot be an empty array",
|
||||
),
|
||||
])
|
||||
})
|
||||
await connection.manager
|
||||
.transaction((entityManager) =>
|
||||
entityManager.getRepository(Post).findOne({
|
||||
where: { id: 1 },
|
||||
lock: { mode: "pessimistic_write", tables: [] },
|
||||
}),
|
||||
)
|
||||
.should.be.rejectedWith(
|
||||
"lockTables cannot be an empty array",
|
||||
)
|
||||
}),
|
||||
))
|
||||
|
||||
@ -597,39 +587,40 @@ describe("repository > find options > locking", () => {
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
if (
|
||||
!(
|
||||
connection.driver.options.type === "postgres" ||
|
||||
connection.driver.options.type === "cockroachdb"
|
||||
)
|
||||
)
|
||||
connection.driver.options.type !== "postgres" &&
|
||||
connection.driver.options.type !== "cockroachdb"
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
entityManager
|
||||
.getRepository(Post)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
relations: { author: true },
|
||||
lock: {
|
||||
mode: "pessimistic_write",
|
||||
tables: ["img"],
|
||||
},
|
||||
})
|
||||
.should.be.rejectedWith(
|
||||
'"img" is not part of this query',
|
||||
),
|
||||
])
|
||||
})
|
||||
await connection.manager
|
||||
.transaction((entityManager) =>
|
||||
entityManager.getRepository(Post).findOne({
|
||||
where: { id: 1 },
|
||||
relations: { author: true },
|
||||
lock: {
|
||||
mode: "pessimistic_write",
|
||||
tables: ["img"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
.should.be.rejectedWith('"img" is not part of this query')
|
||||
}),
|
||||
))
|
||||
|
||||
it("should allow on a left join", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
if (connection.driver.options.type === "cockroachdb") {
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
if (
|
||||
connection.driver.options.type !== "postgres" &&
|
||||
connection.driver.options.type !== "cockroachdb"
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
await connection.manager
|
||||
.transaction((entityManager) =>
|
||||
Promise.all([
|
||||
entityManager.getRepository(Post).findOne({
|
||||
where: { id: 1 },
|
||||
relations: { author: true },
|
||||
@ -638,56 +629,28 @@ describe("repository > find options > locking", () => {
|
||||
tables: ["post"],
|
||||
},
|
||||
}),
|
||||
entityManager
|
||||
.getRepository(Post)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
relations: { author: true },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
})
|
||||
.should.be.rejectedWith(
|
||||
"FOR UPDATE cannot be applied to the nullable side of an outer join",
|
||||
),
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
if (connection.driver.options.type === "postgres") {
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
entityManager.getRepository(Post).findOne({
|
||||
where: { id: 1 },
|
||||
relations: { author: true },
|
||||
lock: {
|
||||
mode: "pessimistic_write",
|
||||
tables: ["post"],
|
||||
},
|
||||
lock: { mode: "pessimistic_write" },
|
||||
}),
|
||||
entityManager
|
||||
.getRepository(Post)
|
||||
.findOne({
|
||||
where: { id: 1 },
|
||||
relations: { author: true },
|
||||
lock: { mode: "pessimistic_write" },
|
||||
})
|
||||
.should.be.rejectedWith(
|
||||
"FOR UPDATE cannot be applied to the nullable side of an outer join",
|
||||
),
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
]),
|
||||
)
|
||||
.should.be.rejectedWith(
|
||||
"FOR UPDATE cannot be applied to the nullable side of an outer join",
|
||||
)
|
||||
}),
|
||||
))
|
||||
|
||||
it("should allow using lockTables on all types of locking", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
if (!(connection.driver.options.type === "postgres")) return
|
||||
if (connection.driver.options.type !== "postgres") {
|
||||
return
|
||||
}
|
||||
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
await connection.manager.transaction((entityManager) =>
|
||||
Promise.all([
|
||||
entityManager.getRepository(Post).findOne({
|
||||
where: { id: 1 },
|
||||
relations: { author: true },
|
||||
@ -736,8 +699,8 @@ describe("repository > find options > locking", () => {
|
||||
tables: ["post"],
|
||||
},
|
||||
}),
|
||||
])
|
||||
})
|
||||
]),
|
||||
)
|
||||
}),
|
||||
))
|
||||
|
||||
@ -745,31 +708,28 @@ describe("repository > find options > locking", () => {
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
if (
|
||||
!(
|
||||
connection.driver.options.type === "postgres" ||
|
||||
connection.driver.options.type === "cockroachdb"
|
||||
)
|
||||
)
|
||||
connection.driver.options.type !== "postgres" &&
|
||||
connection.driver.options.type !== "cockroachdb"
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
return connection.manager.transaction((entityManager) => {
|
||||
return Promise.all([
|
||||
entityManager.getRepository(Post).findOne({
|
||||
where: { id: 1 },
|
||||
join: {
|
||||
alias: "post",
|
||||
innerJoinAndSelect: {
|
||||
categorys: "post.categories",
|
||||
images: "categorys.images",
|
||||
},
|
||||
await connection.manager.transaction((entityManager) =>
|
||||
entityManager.getRepository(Post).findOne({
|
||||
where: { id: 1 },
|
||||
join: {
|
||||
alias: "post",
|
||||
innerJoinAndSelect: {
|
||||
categorys: "post.categories",
|
||||
images: "categorys.images",
|
||||
},
|
||||
lock: {
|
||||
mode: "pessimistic_write",
|
||||
tables: ["image"],
|
||||
},
|
||||
}),
|
||||
])
|
||||
})
|
||||
},
|
||||
lock: {
|
||||
mode: "pessimistic_write",
|
||||
tables: ["image"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
}),
|
||||
))
|
||||
})
|
||||
|
||||
@ -28,8 +28,8 @@ describe("transaction > nested transaction", () => {
|
||||
shouldExist: boolean
|
||||
}[] = []
|
||||
|
||||
// Spanner does not support nested transactions
|
||||
if (connection.driver.options.type === "spanner") return
|
||||
// SAP HANA, Spanner etc. do not support nested transactions
|
||||
if (connection.driver.transactionSupport !== "nested") return
|
||||
|
||||
await connection.manager.transaction(async (em0) => {
|
||||
const post = new Post()
|
||||
@ -37,29 +37,28 @@ describe("transaction > nested transaction", () => {
|
||||
await em0.save(post)
|
||||
conditions.push({ ...post, shouldExist: true })
|
||||
|
||||
try {
|
||||
await em0.transaction(async (em1) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #2"
|
||||
await em1.save(post)
|
||||
conditions.push({ ...post, shouldExist: false })
|
||||
await em0.transaction(async (em1) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #2"
|
||||
await em1.save(post)
|
||||
conditions.push({ ...post, shouldExist: false })
|
||||
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #3"
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post, shouldExist: false })
|
||||
})
|
||||
throw new Error("")
|
||||
})
|
||||
} catch (_) {}
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #3"
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post, shouldExist: false })
|
||||
}).should.not.be.rejected
|
||||
|
||||
throw new Error("")
|
||||
}).should.be.rejected
|
||||
|
||||
await em0.transaction(async (em1) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #4"
|
||||
await em1.save(post)
|
||||
conditions.push({ ...post, shouldExist: true })
|
||||
})
|
||||
}).should.not.be.rejected
|
||||
|
||||
await em0.transaction(async (em1) => {
|
||||
const post = new Post()
|
||||
@ -67,32 +66,31 @@ describe("transaction > nested transaction", () => {
|
||||
await em1.save(post)
|
||||
conditions.push({ ...post, shouldExist: true })
|
||||
|
||||
try {
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #6"
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post, shouldExist: false })
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #6"
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post, shouldExist: false })
|
||||
|
||||
await em2.transaction(async (em3) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #7"
|
||||
await em3.save(post)
|
||||
conditions.push({
|
||||
...post,
|
||||
shouldExist: false,
|
||||
})
|
||||
await em2.transaction(async (em3) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #7"
|
||||
await em3.save(post)
|
||||
conditions.push({
|
||||
...post,
|
||||
shouldExist: false,
|
||||
})
|
||||
throw new Error("")
|
||||
})
|
||||
} catch (_) {}
|
||||
}).should.not.be.rejected
|
||||
|
||||
throw new Error("")
|
||||
}).should.be.rejected
|
||||
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #8"
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post, shouldExist: true })
|
||||
})
|
||||
}).should.not.be.rejected
|
||||
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
@ -100,38 +98,37 @@ describe("transaction > nested transaction", () => {
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post, shouldExist: true })
|
||||
|
||||
try {
|
||||
await em2.transaction(async (em3) => {
|
||||
await em2.transaction(async (em3) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #10"
|
||||
await em3.save(post)
|
||||
conditions.push({
|
||||
...post,
|
||||
shouldExist: false,
|
||||
})
|
||||
|
||||
await em3.transaction(async (em4) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #10"
|
||||
await em3.save(post)
|
||||
post.title = "Post #11"
|
||||
await em4.save(post)
|
||||
conditions.push({
|
||||
...post,
|
||||
shouldExist: false,
|
||||
})
|
||||
}).should.not.be.rejected
|
||||
|
||||
await em3.transaction(async (em4) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #11"
|
||||
await em4.save(post)
|
||||
conditions.push({
|
||||
...post,
|
||||
shouldExist: false,
|
||||
})
|
||||
})
|
||||
throw new Error("")
|
||||
})
|
||||
} catch (_) {}
|
||||
throw new Error("")
|
||||
}).should.be.rejected
|
||||
|
||||
await em2.transaction(async (em3) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #12"
|
||||
await em3.save(post)
|
||||
conditions.push({ ...post, shouldExist: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}).should.not.be.rejected
|
||||
}).should.not.be.rejected
|
||||
}).should.not.be.rejected
|
||||
}).should.not.be.rejected
|
||||
|
||||
for (const condition of conditions) {
|
||||
const post = await connection.manager.findOne(Post, {
|
||||
@ -155,59 +152,55 @@ describe("transaction > nested transaction", () => {
|
||||
connections.map(async (connection) => {
|
||||
const conditions: { id: number; title: string }[] = []
|
||||
|
||||
try {
|
||||
await connection.manager.transaction(async (em0) => {
|
||||
await connection.manager.transaction(async (em0) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #1"
|
||||
await em0.save(post)
|
||||
conditions.push({ ...post })
|
||||
|
||||
await em0.transaction(async (em1) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #1"
|
||||
await em0.save(post)
|
||||
post.title = "Post #2"
|
||||
await em1.save(post)
|
||||
conditions.push({ ...post })
|
||||
|
||||
try {
|
||||
await em0.transaction(async (em1) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #2"
|
||||
await em1.save(post)
|
||||
conditions.push({ ...post })
|
||||
throw new Error("")
|
||||
})
|
||||
} catch (_) {}
|
||||
throw new Error("")
|
||||
}).should.be.rejected
|
||||
|
||||
await em0.transaction(async (em1) => {
|
||||
await em0.transaction(async (em1) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #3"
|
||||
await em1.save(post)
|
||||
conditions.push({ ...post })
|
||||
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #3"
|
||||
await em1.save(post)
|
||||
post.title = "Post #4"
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post })
|
||||
|
||||
try {
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #4"
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post })
|
||||
throw new Error("")
|
||||
})
|
||||
} catch (_) {}
|
||||
throw new Error("")
|
||||
}).should.be.rejected
|
||||
|
||||
await em1.transaction(async (em2) => {
|
||||
await em1.transaction(async (em2) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #5"
|
||||
await em2.save(post)
|
||||
conditions.push({ ...post })
|
||||
|
||||
await em2.transaction(async (em3) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #5"
|
||||
await em2.save(post)
|
||||
post.title = "Post #6"
|
||||
await em3.save(post)
|
||||
conditions.push({ ...post })
|
||||
|
||||
try {
|
||||
await em2.transaction(async (em3) => {
|
||||
const post = new Post()
|
||||
post.title = "Post #6"
|
||||
await em3.save(post)
|
||||
conditions.push({ ...post })
|
||||
throw new Error("")
|
||||
})
|
||||
} catch (_) {}
|
||||
})
|
||||
})
|
||||
throw new Error("")
|
||||
})
|
||||
} catch (_) {}
|
||||
throw new Error("")
|
||||
}).should.be.rejected
|
||||
}).should.not.be.rejected
|
||||
}).should.not.be.rejected
|
||||
|
||||
throw new Error("")
|
||||
}).should.be.rejected
|
||||
|
||||
for (const condition of conditions) {
|
||||
const post = await connection.manager.findOne(Post, {
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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[]
|
||||
}
|
||||
@ -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
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
14
test/github-issues/11260/entity/Company.ts
Normal file
14
test/github-issues/11260/entity/Company.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Column } from "../../../../src/decorator/columns/Column"
|
||||
import { PrimaryColumn } from "../../../../src/decorator/columns/PrimaryColumn"
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { Unique } from "../../../../src/decorator/Unique"
|
||||
|
||||
@Entity()
|
||||
@Unique(["name"], { deferrable: "INITIALLY DEFERRED" })
|
||||
export class Company {
|
||||
@PrimaryColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
name?: string
|
||||
}
|
||||
18
test/github-issues/11260/entity/User.ts
Normal file
18
test/github-issues/11260/entity/User.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Column } from "../../../../src/decorator/columns/Column"
|
||||
import { PrimaryColumn } from "../../../../src/decorator/columns/PrimaryColumn"
|
||||
import { Entity } from "../../../../src/decorator/entity/Entity"
|
||||
import { ManyToOne } from "../../../../src/decorator/relations/ManyToOne"
|
||||
|
||||
import { Company } from "./Company"
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@ManyToOne(() => Company, { deferrable: "INITIALLY DEFERRED" })
|
||||
company: Company
|
||||
}
|
||||
93
test/github-issues/11260/issue-11260.ts
Normal file
93
test/github-issues/11260/issue-11260.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import "reflect-metadata"
|
||||
import { expect } from "chai"
|
||||
|
||||
import { DataSource } from "../../../src/data-source/DataSource"
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases,
|
||||
} from "../../utils/test-utils"
|
||||
|
||||
import { EntityManager } from "../../../src"
|
||||
import { BaseQueryRunner } from "../../../src/query-runner/BaseQueryRunner"
|
||||
import { Company } from "./entity/Company"
|
||||
import { User } from "./entity/User"
|
||||
|
||||
describe("github issues > #10626 Regression in transactionDepth handling", () => {
|
||||
let connections: DataSource[]
|
||||
before(
|
||||
async () =>
|
||||
(connections = await createTestingConnections({
|
||||
entities: [__dirname + "/entity/*{.js,.ts}"],
|
||||
enabledDrivers: ["better-sqlite3", "postgres", "sqlite"],
|
||||
})),
|
||||
)
|
||||
beforeEach(() => reloadTestingDatabases(connections))
|
||||
after(() => closeTestingConnections(connections))
|
||||
|
||||
it("transactionDepth should be updated correctly when commit fails", () =>
|
||||
Promise.all(
|
||||
connections.map(async (connection) => {
|
||||
const queryRunner = connection.createQueryRunner()
|
||||
const transactionDepths: Record<string, number> = {}
|
||||
const recordDepth = (mark: string) => {
|
||||
transactionDepths[mark] = (
|
||||
queryRunner as unknown as BaseQueryRunner
|
||||
)["transactionDepth"]
|
||||
}
|
||||
|
||||
recordDepth("initial")
|
||||
|
||||
await queryRunner.startTransaction()
|
||||
recordDepth("startTransaction")
|
||||
|
||||
const runInTransaction = async (
|
||||
entityManager: EntityManager,
|
||||
) => {
|
||||
// first save user
|
||||
const user = new User()
|
||||
user.id = 1
|
||||
user.company = { id: 100 }
|
||||
user.name = "Bob"
|
||||
|
||||
await entityManager.save(user)
|
||||
|
||||
// then save company
|
||||
const company = new Company()
|
||||
company.id = 200
|
||||
company.name = "Acme"
|
||||
|
||||
await entityManager.save(company)
|
||||
}
|
||||
await runInTransaction(queryRunner.manager).should.not.rejected
|
||||
recordDepth("afterStatements")
|
||||
|
||||
await queryRunner.commitTransaction().should.be.rejected
|
||||
recordDepth("afterCommit")
|
||||
|
||||
await queryRunner.rollbackTransaction().should.not.be.rejected
|
||||
recordDepth("afterRollback")
|
||||
|
||||
await queryRunner.release()
|
||||
recordDepth("afterRelease")
|
||||
|
||||
expect(transactionDepths).to.deep.equal({
|
||||
initial: 0,
|
||||
startTransaction: 1,
|
||||
afterStatements: 1,
|
||||
afterCommit: 1,
|
||||
afterRollback: 0,
|
||||
afterRelease: 0,
|
||||
})
|
||||
|
||||
// check data
|
||||
const user = await connection.manager.findOneBy(User, { id: 1 })
|
||||
expect(user).to.equal(null)
|
||||
|
||||
const company = await connection.manager.findOneBy(Company, {
|
||||
id: 200,
|
||||
})
|
||||
expect(company).to.equal(null)
|
||||
}),
|
||||
))
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user