diff --git a/docs/find-options.md b/docs/find-options.md index a6741538455..79ff1e7411c 100644 --- a/docs/find-options.md +++ b/docs/find-options.md @@ -239,6 +239,7 @@ Support of lock modes, and SQL statements they translate to, are listed in the t | Oracle | FOR UPDATE | FOR UPDATE | (nothing) | | | | | SQL Server | WITH (HOLDLOCK, ROWLOCK) | WITH (UPDLOCK, ROWLOCK) | WITH (NOLOCK) | | | | | AuroraDataApi | LOCK IN SHARE MODE | FOR UPDATE | (nothing) | | | | +| CockroachDB | | FOR UPDATE | (nothing) | | FOR UPDATE NOWAIT | FOR NO KEY UPDATE | ``` diff --git a/src/query-builder/SelectQueryBuilder.ts b/src/query-builder/SelectQueryBuilder.ts index 275d7b9c0a3..2508a0c90a6 100644 --- a/src/query-builder/SelectQueryBuilder.ts +++ b/src/query-builder/SelectQueryBuilder.ts @@ -1739,7 +1739,7 @@ export class SelectQueryBuilder extends QueryBuilder implements let lockTablesClause = ""; if (this.expressionMap.lockTables) { - if (!(driver instanceof PostgresDriver)) { + if (!(driver instanceof PostgresDriver || driver instanceof CockroachDriver)) { throw new TypeORMError("Lock tables not supported in selected driver"); } if (this.expressionMap.lockTables.length < 1) { @@ -1770,7 +1770,7 @@ export class SelectQueryBuilder extends QueryBuilder implements return " FOR UPDATE"; } - else if (driver instanceof PostgresDriver ) { + else if (driver instanceof PostgresDriver || driver instanceof CockroachDriver) { return " FOR UPDATE" + lockTablesClause; } else if (driver instanceof SqlServerDriver) { @@ -1790,7 +1790,7 @@ export class SelectQueryBuilder extends QueryBuilder implements throw new LockNotSupportedOnGivenDriverError(); } case "pessimistic_write_or_fail": - if (driver instanceof PostgresDriver) { + if (driver instanceof PostgresDriver || driver instanceof CockroachDriver) { return " FOR UPDATE" + lockTablesClause + " NOWAIT"; } else if (driver instanceof MysqlDriver) { @@ -1801,7 +1801,7 @@ export class SelectQueryBuilder extends QueryBuilder implements } case "for_no_key_update": - if (driver instanceof PostgresDriver) { + if (driver instanceof PostgresDriver || driver instanceof CockroachDriver) { return " FOR NO KEY UPDATE" + lockTablesClause; } else { throw new LockNotSupportedOnGivenDriverError(); diff --git a/test/functional/query-builder/locking/query-builder-locking.ts b/test/functional/query-builder/locking/query-builder-locking.ts index 18e49ea4c98..4ba7ccf27f0 100644 --- a/test/functional/query-builder/locking/query-builder-locking.ts +++ b/test/functional/query-builder/locking/query-builder-locking.ts @@ -4,7 +4,7 @@ import {SapDriver} from "../../../../src/driver/sap/SapDriver"; import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils"; import {Connection} from "../../../../src/connection/Connection"; import {PostWithVersion} from "./entity/PostWithVersion"; -import {Post} from './entity/Post'; +import {Post} from "./entity/Post"; import {expect} from "chai"; import {PostWithoutVersionAndUpdateDate} from "./entity/PostWithoutVersionAndUpdateDate"; import {PostWithUpdateDate} from "./entity/PostWithUpdateDate"; @@ -61,9 +61,20 @@ describe("query builder > locking", () => { }))); it("should not throw error if pessimistic lock used with transaction", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof SapDriver) + if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof SapDriver) return; + if (connection.driver instanceof CockroachDriver) { + return connection.manager.transaction(entityManager => { + return Promise.all([ + entityManager.createQueryBuilder(PostWithVersion, "post") + .setLock("pessimistic_write") + .where("post.id = :id", { id: 1 }) + .getOne().should.not.be.rejected + ]); + }); + } + return connection.manager.transaction(entityManager => { return Promise.all([ entityManager.createQueryBuilder(PostWithVersion, "post") @@ -80,7 +91,7 @@ describe("query builder > locking", () => { }))); it("should throw error if for no key update lock used without transaction", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver) { + if (connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver) { return connection.createQueryBuilder(PostWithVersion, "post") .setLock("for_no_key_update") .where("post.id = :id", { id: 1 }) @@ -90,7 +101,7 @@ describe("query builder > locking", () => { }))); it("should not throw error if for no key update lock used with transaction", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver) { + if (connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver) { return connection.manager.transaction(entityManager => { return Promise.all([entityManager.createQueryBuilder(PostWithVersion, "post") .setLock("for_no_key_update") @@ -114,19 +125,19 @@ describe("query builder > locking", () => { "SELECT VERSION() as version;" ); version = version.toLowerCase(); - if (version.includes('maria')) return; // not supported in mariadb - if (VersionUtils.isGreaterOrEqual(version, '8.0.0')) { + if (version.includes("maria")) return; // not supported in mariadb + if (VersionUtils.isGreaterOrEqual(version, "8.0.0")) { return connection.createQueryBuilder(PostWithVersion, "post") .setLock("pessimistic_partial_write") .where("post.id = :id", { id: 1 }) - .getOne().should.be.rejectedWith(PessimisticLockTransactionRequiredError); + .getOne().should.be.rejectedWith(PessimisticLockTransactionRequiredError); } } return; - }))); + }))); it("should not throw error if pessimistic_partial_write lock used with transaction", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver) { + if (connection.driver instanceof PostgresDriver) { return connection.manager.transaction(entityManager => { return Promise.all([entityManager.createQueryBuilder(PostWithVersion, "post") .setLock("pessimistic_partial_write") @@ -140,8 +151,8 @@ describe("query builder > locking", () => { "SELECT VERSION() as version;" ); version = version.toLowerCase(); - if (version.includes('maria')) return; // not supported in mariadb - if (VersionUtils.isGreaterOrEqual(version, '8.0.0')) { + if (version.includes("maria")) return; // not supported in mariadb + if (VersionUtils.isGreaterOrEqual(version, "8.0.0")) { return connection.manager.transaction(entityManager => { return Promise.all([entityManager.createQueryBuilder(PostWithVersion, "post") .setLock("pessimistic_partial_write") @@ -149,12 +160,12 @@ describe("query builder > locking", () => { .getOne().should.not.be.rejected]); }); } - } + } return; }))); - + it("should throw error if pessimistic_write_or_fail lock used without transaction", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver) { + if (connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver) { return connection.createQueryBuilder(PostWithVersion, "post") .setLock("pessimistic_write_or_fail") .where("post.id = :id", { id: 1 }) @@ -166,18 +177,18 @@ describe("query builder > locking", () => { "SELECT VERSION() as version;" ); version = version.toLowerCase(); - if ((version.includes('maria') && VersionUtils.isGreaterOrEqual(version, "10.3.0")) || !version.includes('maria') && VersionUtils.isGreaterOrEqual(version, '8.0.0')) { + if ((version.includes("maria") && VersionUtils.isGreaterOrEqual(version, "10.3.0")) || !version.includes("maria") && VersionUtils.isGreaterOrEqual(version, "8.0.0")) { return connection.createQueryBuilder(PostWithVersion, "post") .setLock("pessimistic_write_or_fail") .where("post.id = :id", { id: 1 }) .getOne().should.be.rejectedWith(PessimisticLockTransactionRequiredError); } } - return; - }))); + return; + }))); it("should not throw error if pessimistic_write_or_fail lock used with transaction", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver) { + if (connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver) { return connection.manager.transaction(entityManager => { return Promise.all([entityManager.createQueryBuilder(PostWithVersion, "post") .setLock("pessimistic_write_or_fail") @@ -191,7 +202,7 @@ describe("query builder > locking", () => { "SELECT VERSION() as version;" ); version = version.toLowerCase(); - if ((version.includes('maria') && VersionUtils.isGreaterOrEqual(version, "10.3.0")) || !version.includes('maria') && VersionUtils.isGreaterOrEqual(version, '8.0.0')) { + if ((version.includes("maria") && VersionUtils.isGreaterOrEqual(version, "10.3.0")) || !version.includes("maria") && VersionUtils.isGreaterOrEqual(version, "8.0.0")) { return connection.manager.transaction(entityManager => { return Promise.all([entityManager.createQueryBuilder(PostWithVersion, "post") .setLock("pessimistic_write_or_fail") @@ -199,9 +210,9 @@ describe("query builder > locking", () => { .getOne().should.not.be.rejected]); }); } - } + } return; - }))); + }))); it("should attach pessimistic read lock statement on query if locking enabled", () => Promise.all(connections.map(async connection => { if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof SapDriver) @@ -250,7 +261,7 @@ describe("query builder > locking", () => { }))); it("should attach pessimistic write lock statement on query if locking enabled", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof SapDriver) + if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof SapDriver) return; const sql = connection.createQueryBuilder(PostWithVersion, "post") @@ -258,7 +269,7 @@ describe("query builder > locking", () => { .where("post.id = :id", { id: 1 }) .getSql(); - if (connection.driver instanceof MysqlDriver || connection.driver instanceof PostgresDriver || connection.driver instanceof OracleDriver) { + if (connection.driver instanceof MysqlDriver || connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof OracleDriver) { expect(sql.indexOf("FOR UPDATE") !== -1).to.be.true; } else if (connection.driver instanceof SqlServerDriver) { @@ -268,7 +279,7 @@ describe("query builder > locking", () => { }))); it("should not attach for no key update lock statement on query if locking is not used", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver) { + if (connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver) { const sql = connection.createQueryBuilder(PostWithVersion, "post") .where("post.id = :id", { id: 1 }) .getSql(); @@ -279,7 +290,7 @@ describe("query builder > locking", () => { }))); it("should attach for no key update lock statement on query if locking enabled", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver) { + if (connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver) { const sql = connection.createQueryBuilder(PostWithVersion, "post") .setLock("for_no_key_update") .where("post.id = :id", { id: 1 }) @@ -300,7 +311,7 @@ describe("query builder > locking", () => { expect(sql.indexOf("FOR UPDATE SKIP LOCKED") === -1).to.be.true; } return; - }))); + }))); it("should attach pessimistic_partial_write lock statement on query if locking enabled", () => Promise.all(connections.map(async connection => { if (connection.driver instanceof PostgresDriver || connection.driver instanceof MysqlDriver) { @@ -313,10 +324,10 @@ describe("query builder > locking", () => { } return; - }))); + }))); it("should not attach pessimistic_write_or_fail lock statement on query if locking is not used", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver || connection.driver instanceof MysqlDriver) { + if (connection.driver instanceof PostgresDriver || connection.driver instanceof MysqlDriver || connection.driver instanceof CockroachDriver) { const sql = connection.createQueryBuilder(PostWithVersion, "post") .where("post.id = :id", { id: 1 }) .getSql(); @@ -324,10 +335,10 @@ describe("query builder > locking", () => { expect(sql.indexOf("FOR UPDATE NOWAIT") === -1).to.be.true; } return; - }))); + }))); it("should attach pessimistic_write_or_fail lock statement on query if locking enabled", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof PostgresDriver || connection.driver instanceof MysqlDriver) { + if (connection.driver instanceof PostgresDriver || connection.driver instanceof MysqlDriver || connection.driver instanceof CockroachDriver) { const sql = connection.createQueryBuilder(PostWithVersion, "post") .setLock("pessimistic_write_or_fail") .where("post.id = :id", { id: 1 }) @@ -337,7 +348,7 @@ describe("query builder > locking", () => { } return; - }))); + }))); it("should throw error if optimistic lock used with getMany method", () => Promise.all(connections.map(async connection => { @@ -471,7 +482,7 @@ describe("query builder > locking", () => { }))); it("should throw error if pessimistic locking not supported by given driver", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof SapDriver) + if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof SapDriver) return connection.manager.transaction(entityManager => { return Promise.all([ entityManager.createQueryBuilder(PostWithVersion, "post") @@ -486,11 +497,21 @@ describe("query builder > locking", () => { ]); }); + if (connection.driver instanceof CockroachDriver) + return connection.manager.transaction(entityManager => { + return Promise.all([ + entityManager.createQueryBuilder(PostWithVersion, "post") + .setLock("pessimistic_read") + .where("post.id = :id", { id: 1 }) + .getOne().should.be.rejectedWith(LockNotSupportedOnGivenDriverError), + ]); + }); + return; }))); it("should throw error if for no key update locking not supported by given driver", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) { + if (!(connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver)) { return connection.manager.transaction(entityManager => { return Promise.all([ entityManager.createQueryBuilder(PostWithVersion, "post") @@ -505,69 +526,86 @@ describe("query builder > locking", () => { }))); it("should only specify locked tables in FOR UPDATE OF clause if argument is given", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) + if (!(connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver)) return; const sql = connection.createQueryBuilder(Post, "post") .innerJoin("post.author", "user") - .setLock('pessimistic_write', undefined, ["user"]) + .setLock("pessimistic_write", undefined, ["user"]) .getSql(); expect(sql).to.match(/FOR UPDATE OF user$/); const sql2 = connection.createQueryBuilder(Post, "post") .innerJoin("post.author", "user") - .setLock('pessimistic_write', undefined, ["post","user"]) + .setLock("pessimistic_write", undefined, ["post","user"]) .getSql(); expect(sql2).to.match(/FOR UPDATE OF post, user$/); }))); it("should not allow empty array for lockTables", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) + if (!(connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver)) return; return connection.manager.transaction(entityManager => { return Promise.all([ entityManager.createQueryBuilder(Post, "post") .innerJoin("post.author", "user") - .setLock('pessimistic_write', undefined, []) - .getOne().should.be.rejectedWith('lockTables cannot be an empty array'), + .setLock("pessimistic_write", undefined, []) + .getOne().should.be.rejectedWith("lockTables cannot be an empty array"), ]); }); }))); it("should throw error when specifying a table that is not part of the query", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) + if (!(connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver)) return; return connection.manager.transaction(entityManager => { return Promise.all([ entityManager.createQueryBuilder(Post, "post") .innerJoin("post.author", "user") - .setLock('pessimistic_write', undefined, ["img"]) - .getOne().should.be.rejectedWith('relation "img" in FOR UPDATE clause not found in FROM clause'), + .setLock("pessimistic_write", undefined, ["img"]) + .getOne(), ]); - }); + // With the exception being thrown the transaction is not closed. if ".should.be.rejectedWith" is added to the inner promise + }).should.be.rejectedWith('relation "img" in FOR UPDATE clause not found in FROM clause'); }))); it("should allow on a left join", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) - return; + if (connection.driver instanceof CockroachDriver) { + return connection.manager.transaction(entityManager => { + return Promise.all([ + entityManager.createQueryBuilder(Post, "post") + .leftJoin("post.author", "user") + .setLock("pessimistic_write", undefined, ["post"]) + .getOne(), + entityManager.createQueryBuilder(Post, "post") + .leftJoin("post.author", "user") + .setLock("pessimistic_write") + .getOne(), + ]); + }); + } - return connection.manager.transaction(entityManager => { + if (connection.driver instanceof PostgresDriver) { + return connection.manager.transaction(entityManager => { - return Promise.all([ - entityManager.createQueryBuilder(Post, "post") - .leftJoin("post.author", "user") - .setLock('pessimistic_write', undefined, ["post"]) - .getOne(), - entityManager.createQueryBuilder(Post, "post") - .leftJoin("post.author", "user") - .setLock('pessimistic_write') - .getOne().should.be.rejectedWith('FOR UPDATE cannot be applied to the nullable side of an outer join'), - ]); - }); + return Promise.all([ + entityManager.createQueryBuilder(Post, "post") + .leftJoin("post.author", "user") + .setLock("pessimistic_write", undefined, ["post"]) + .getOne(), + entityManager.createQueryBuilder(Post, "post") + .leftJoin("post.author", "user") + .setLock("pessimistic_write") + .getOne().should.be.rejectedWith("FOR UPDATE cannot be applied to the nullable side of an outer join"), + ]); + }); + } + + return; }))); it("should allow using lockTables on all types of locking", () => Promise.all(connections.map(async connection => { @@ -579,30 +617,30 @@ describe("query builder > locking", () => { return Promise.all([ entityManager.createQueryBuilder(Post, "post") .leftJoin("post.author", "user") - .setLock('pessimistic_read', undefined, ["post"]) + .setLock("pessimistic_read", undefined, ["post"]) .getOne(), entityManager.createQueryBuilder(Post, "post") .leftJoin("post.author", "user") - .setLock('pessimistic_write', undefined, ["post"]) + .setLock("pessimistic_write", undefined, ["post"]) .getOne(), entityManager.createQueryBuilder(Post, "post") .leftJoin("post.author", "user") - .setLock('pessimistic_partial_write', undefined, ["post"]) + .setLock("pessimistic_partial_write", undefined, ["post"]) .getOne(), entityManager.createQueryBuilder(Post, "post") .leftJoin("post.author", "user") - .setLock('pessimistic_write_or_fail', undefined, ["post"]) + .setLock("pessimistic_write_or_fail", undefined, ["post"]) .getOne(), entityManager.createQueryBuilder(Post, "post") .leftJoin("post.author", "user") - .setLock('for_no_key_update', undefined, ["post"]) + .setLock("for_no_key_update", undefined, ["post"]) .getOne(), ]); }); }))); it("should allow locking a relation of a relation", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) + if (!(connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver)) return; return connection.manager.transaction(entityManager => { @@ -611,7 +649,7 @@ describe("query builder > locking", () => { entityManager.createQueryBuilder(Post, "post") .innerJoin("post.categories", "cat") .innerJoin("cat.images", "img") - .setLock('pessimistic_write', undefined, ["img"]) + .setLock("pessimistic_write", undefined, ["img"]) .getOne() ]); }); diff --git a/test/functional/repository/find-options-locking/find-options-locking.ts b/test/functional/repository/find-options-locking/find-options-locking.ts index 42f7cf1bbd4..c5b594f039c 100644 --- a/test/functional/repository/find-options-locking/find-options-locking.ts +++ b/test/functional/repository/find-options-locking/find-options-locking.ts @@ -8,7 +8,7 @@ import {expect} from "chai"; import {PostWithoutVersionAndUpdateDate} from "./entity/PostWithoutVersionAndUpdateDate"; import {PostWithUpdateDate} from "./entity/PostWithUpdateDate"; import {PostWithVersionAndUpdatedDate} from "./entity/PostWithVersionAndUpdatedDate"; -import {Post} from './entity/Post'; +import {Post} from "./entity/Post"; import {OptimisticLockVersionMismatchError} from "../../../../src/error/OptimisticLockVersionMismatchError"; import {OptimisticLockCanNotBeUsedError} from "../../../../src/error/OptimisticLockCanNotBeUsedError"; import {NoVersionOrUpdateDateColumnError} from "../../../../src/error/NoVersionOrUpdateDateColumnError"; @@ -30,9 +30,18 @@ describe("repository > find options > locking", () => { after(() => closeTestingConnections(connections)); it("should throw error if pessimistic lock used without transaction", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof SapDriver) + if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof SapDriver) return; + if (connection.driver instanceof CockroachDriver) { + return Promise.all([ + connection + .getRepository(PostWithVersion) + .findOne(1, { lock: { mode: "pessimistic_write" } }) + .should.be.rejectedWith(PessimisticLockTransactionRequiredError), + ]); + } + return Promise.all([ connection .getRepository(PostWithVersion) @@ -47,9 +56,20 @@ describe("repository > find options > locking", () => { }))); it("should not throw error if pessimistic lock used with transaction", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof SapDriver) + if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof SapDriver) return; + if (connection.driver instanceof CockroachDriver) { + return connection.manager.transaction(entityManager => { + return Promise.all([ + entityManager + .getRepository(PostWithVersion) + .findOne(1, { lock: { mode: "pessimistic_write" } }) + .should.not.be.rejected + ]); + }); + } + return connection.manager.transaction(entityManager => { return Promise.all([ entityManager @@ -120,7 +140,7 @@ describe("repository > find options > locking", () => { }))); it("should attach pessimistic write lock statement on query if locking enabled", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof SapDriver) + if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof SapDriver) return; const executedSql: string[] = []; @@ -272,7 +292,7 @@ describe("repository > find options > locking", () => { }))); it("should throw error if pessimistic locking not supported by given driver", () => Promise.all(connections.map(async connection => { - if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof CockroachDriver || connection.driver instanceof SapDriver) + if (connection.driver instanceof AbstractSqliteDriver || connection.driver instanceof SapDriver) return connection.manager.transaction(entityManager => { return Promise.all([ entityManager @@ -287,54 +307,80 @@ describe("repository > find options > locking", () => { ]); }); + if (connection.driver instanceof CockroachDriver) + return connection.manager.transaction(entityManager => { + return Promise.all([ + entityManager + .getRepository(PostWithVersion) + .findOne(1, { lock: { mode: "pessimistic_read" } }) + .should.be.rejectedWith(LockNotSupportedOnGivenDriverError), + ]); + }); + return; }))); it("should not allow empty array for lockTables", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) + if (!(connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver)) return; return connection.manager.transaction(entityManager => { return Promise.all([ entityManager.getRepository(Post) .findOne({ - lock: {mode: 'pessimistic_write', tables: []} - }).should.be.rejectedWith('lockTables cannot be an empty array'), + lock: {mode: "pessimistic_write", tables: []} + }).should.be.rejectedWith("lockTables cannot be an empty array"), ]); }); }))); it("should throw error when specifying a table that is not part of the query", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) + if (!(connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver)) return; return connection.manager.transaction(entityManager => { return Promise.all([ entityManager.getRepository(Post) .findOne({ - relations: ['author'], - lock: {mode: 'pessimistic_write', tables: ['img']} + relations: ["author"], + 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 instanceof PostgresDriver)) - return; + if (connection.driver instanceof CockroachDriver) { + return connection.manager.transaction(entityManager => { + return Promise.all([ + entityManager.getRepository(Post).findOne({ + relations: ["author"], + lock: {mode: "pessimistic_write", tables: ["post"]} + }), + entityManager.getRepository(Post).findOne({ + relations: ["author"], + lock: {mode: "pessimistic_write"} + }) + ]); + }); + } - return connection.manager.transaction(entityManager => { - return Promise.all([ - entityManager.getRepository(Post).findOne({ - relations: ['author'], - lock: {mode: 'pessimistic_write', tables: ['post']} - }), - entityManager.getRepository(Post).findOne({ - relations: ['author'], - lock: {mode: 'pessimistic_write'} - }).should.be.rejectedWith('FOR UPDATE cannot be applied to the nullable side of an outer join') - ]); - }); + if (connection.driver instanceof PostgresDriver) { + return connection.manager.transaction(entityManager => { + return Promise.all([ + entityManager.getRepository(Post).findOne({ + relations: ["author"], + lock: {mode: "pessimistic_write", tables: ["post"]} + }), + entityManager.getRepository(Post).findOne({ + relations: ["author"], + lock: {mode: "pessimistic_write"} + }).should.be.rejectedWith("FOR UPDATE cannot be applied to the nullable side of an outer join") + ]); + }); + } + + return; }))); it("should allow using lockTables on all types of locking", () => Promise.all(connections.map(async connection => { @@ -345,31 +391,31 @@ describe("repository > find options > locking", () => { return Promise.all([ entityManager.getRepository(Post).findOne({ - relations: ['author'], - lock: {mode: 'pessimistic_read', tables: ['post']} + relations: ["author"], + lock: {mode: "pessimistic_read", tables: ["post"]} }), entityManager.getRepository(Post).findOne({ - relations: ['author'], - lock: {mode: 'pessimistic_write', tables: ['post']} + relations: ["author"], + lock: {mode: "pessimistic_write", tables: ["post"]} }), entityManager.getRepository(Post).findOne({ - relations: ['author'], - lock: {mode: 'pessimistic_partial_write', tables: ['post']} + relations: ["author"], + lock: {mode: "pessimistic_partial_write", tables: ["post"]} }), entityManager.getRepository(Post).findOne({ - relations: ['author'], - lock: {mode: 'pessimistic_write_or_fail', tables: ['post']} + relations: ["author"], + lock: {mode: "pessimistic_write_or_fail", tables: ["post"]} }), entityManager.getRepository(Post).findOne({ - relations: ['author'], - lock: {mode: 'for_no_key_update', tables: ['post']} + relations: ["author"], + lock: {mode: "for_no_key_update", tables: ["post"]} }), ]); }); }))); it("should allow locking a relation of a relation", () => Promise.all(connections.map(async connection => { - if (!(connection.driver instanceof PostgresDriver)) + if (!(connection.driver instanceof PostgresDriver || connection.driver instanceof CockroachDriver)) return; return connection.manager.transaction(entityManager => { @@ -377,13 +423,13 @@ describe("repository > find options > locking", () => { return Promise.all([ entityManager.getRepository(Post).findOne({ join: { - alias: 'post', + alias: "post", innerJoinAndSelect: { - categorys: 'post.categories', - images: 'categorys.images' + categorys: "post.categories", + images: "categorys.images" } }, - lock: {mode: 'pessimistic_write', tables: ['image']} + lock: {mode: "pessimistic_write", tables: ["image"]} }), ]); });