Skip to content

Commit

Permalink
feat: add FOR NO KEY UPDATE lock mode for postgresql (#5971)
Browse files Browse the repository at this point in the history
* [Add] FOR NO KEY UPDATE lock mode for postgresql

* [Add] for no key update lock test

* [Fix] lint

* [Fix] test

Co-authored-by: JeyongOh <jeyong.oh@gogo-ssing.com>
  • Loading branch information
ohjeyong and JeyongOh committed May 16, 2020
1 parent 37fd946 commit 360122f
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/query-builder/QueryExpressionMap.ts
Expand Up @@ -150,7 +150,7 @@ export class QueryExpressionMap {
/**
* Locking mode.
*/
lockMode?: "optimistic"|"pessimistic_read"|"pessimistic_write"|"dirty_read";
lockMode?: "optimistic"|"pessimistic_read"|"pessimistic_write"|"dirty_read"|"for_no_key_update";

/**
* Current version of the entity, used for locking.
Expand Down
12 changes: 9 additions & 3 deletions src/query-builder/SelectQueryBuilder.ts
Expand Up @@ -967,12 +967,12 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
/**
* Sets locking mode.
*/
setLock(lockMode: "pessimistic_read"|"pessimistic_write"|"dirty_read"): this;
setLock(lockMode: "pessimistic_read"|"pessimistic_write"|"dirty_read"|"for_no_key_update"): this;

/**
* Sets locking mode.
*/
setLock(lockMode: "optimistic"|"pessimistic_read"|"pessimistic_write"|"dirty_read", lockVersion?: number|Date): this {
setLock(lockMode: "optimistic"|"pessimistic_read"|"pessimistic_write"|"dirty_read"|"for_no_key_update", lockVersion?: number|Date): this {
this.expressionMap.lockMode = lockMode;
this.expressionMap.lockVersion = lockVersion;
return this;
Expand Down Expand Up @@ -1672,6 +1672,12 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
} else {
throw new LockNotSupportedOnGivenDriverError();
}
case "for_no_key_update":
if (driver instanceof PostgresDriver) {
return " FOR NO KEY UPDATE";
} else {
throw new LockNotSupportedOnGivenDriverError();
}
default:
return "";
}
Expand Down Expand Up @@ -1806,7 +1812,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
if (!this.expressionMap.mainAlias)
throw new Error(`Alias is not set. Use "from" method to set an alias.`);

if ((this.expressionMap.lockMode === "pessimistic_read" || this.expressionMap.lockMode === "pessimistic_write") && !queryRunner.isTransactionActive)
if ((this.expressionMap.lockMode === "pessimistic_read" || this.expressionMap.lockMode === "pessimistic_write" || this.expressionMap.lockMode === "for_no_key_update") && !queryRunner.isTransactionActive)
throw new PessimisticLockTransactionRequiredError();

if (this.expressionMap.lockMode === "optimistic") {
Expand Down
61 changes: 61 additions & 0 deletions test/functional/query-builder/locking/query-builder-locking.ts
Expand Up @@ -77,6 +77,28 @@ 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) {
return connection.createQueryBuilder(PostWithVersion, "post")
.setLock("for_no_key_update")
.where("post.id = :id", { id: 1 })
.getOne().should.be.rejectedWith(PessimisticLockTransactionRequiredError);
}
return;
})));

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) {
return connection.manager.transaction(entityManager => {
return Promise.all([entityManager.createQueryBuilder(PostWithVersion, "post")
.setLock("for_no_key_update")
.where("post.id = :id", { id: 1})
.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)
return;
Expand Down Expand Up @@ -141,6 +163,30 @@ 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) {
const sql = connection.createQueryBuilder(PostWithVersion, "post")
.where("post.id = :id", { id: 1 })
.getSql();

expect(sql.indexOf("FOR NO KEY UPDATE") === -1).to.be.true;
}
return;
})));

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) {
const sql = connection.createQueryBuilder(PostWithVersion, "post")
.setLock("for_no_key_update")
.where("post.id = :id", { id: 1 })
.getSql();

expect(sql.indexOf("FOR NO KEY UPDATE") !== -1).to.be.true;
}
return;

})));

it("should throw error if optimistic lock used with getMany method", () => Promise.all(connections.map(async connection => {

return connection.createQueryBuilder(PostWithVersion, "post")
Expand Down Expand Up @@ -291,4 +337,19 @@ describe("query builder > locking", () => {
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)) {
return connection.manager.transaction(entityManager => {
return Promise.all([
entityManager.createQueryBuilder(PostWithVersion, "post")
.setLock("for_no_key_update")
.where("post.id = :id", { id: 1 })
.getOne().should.be.rejectedWith(LockNotSupportedOnGivenDriverError),
]);
});
}

return;
})));

});

0 comments on commit 360122f

Please sign in to comment.