Add support for Postgres lock types (no key, share, key share) with FOR UPDATE

This commit is contained in:
rob bygrave
2020-11-24 23:36:54 +13:00
parent f4ea674d60
commit 5c34b0daa5
16 changed files with 207 additions and 41 deletions
@@ -174,6 +174,11 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdate();
/**
* Execute using "for update" with given lock type (currently Postgres only).
*/
Query<T> forUpdate(Query.LockType lockType);
/**
* Execute using "for update" clause with No Wait option.
* <p>
@@ -182,6 +187,11 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdateNoWait();
/**
* Execute using "for update nowait" with given lock type (currently Postgres only).
*/
Query<T> forUpdateNoWait(Query.LockType lockType);
/**
* Execute using "for update" clause with Skip Locked option.
* <p>
@@ -190,6 +200,11 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdateSkipLocked();
/**
* Execute using "for update skip locked" with given lock type (currently Postgres only).
*/
Query<T> forUpdateSkipLocked(Query.LockType lockType);
/**
* Execute the query including soft deleted rows.
*/
@@ -179,6 +179,36 @@ import java.util.stream.Stream;
*/
public interface Query<T> {
/**
* The lock type (strength) to use with query FOR UPDATE row locking.
*/
enum LockType {
/**
* The default lock type - See PlatformConfig.lockWithKey option.
*/
Default,
/**
* FOR UPDATE.
*/
Update,
/**
* FOR NO KEY UPDATE.
*/
NoKeyUpdate,
/**
* FOR SHARE.
*/
Share,
/**
* FOR KEY SHARE.
*/
KeyShare
}
/**
* For update mode.
*/
@@ -1618,6 +1648,11 @@ public interface Query<T> {
*/
Query<T> forUpdate();
/**
* Execute using "for update" with given lock type (currently Postgres only).
*/
Query<T> forUpdate(LockType lockType);
/**
* Execute using "for update" clause with "no wait" option.
* <p>
@@ -1626,6 +1661,11 @@ public interface Query<T> {
*/
Query<T> forUpdateNoWait();
/**
* Execute using "for update nowait" with given lock type (currently Postgres only).
*/
Query<T> forUpdateNoWait(LockType lockType);
/**
* Execute using "for update" clause with "skip locked" option.
* <p>
@@ -1634,6 +1674,11 @@ public interface Query<T> {
*/
Query<T> forUpdateSkipLocked();
/**
* Execute using "for update skip locked" with given lock type (currently Postgres only).
*/
Query<T> forUpdateSkipLocked(LockType lockType);
/**
* Return true if this query has forUpdate set.
*/
@@ -1644,6 +1689,11 @@ public interface Query<T> {
*/
ForUpdate getForUpdateMode();
/**
* Return the lock type (strength) to use with "for update".
*/
LockType getForUpdateLockType();
/**
* Set root table alias.
*/
@@ -679,9 +679,8 @@ public class DatabasePlatform {
public String completeSql(String sql, Query<?> query) {
if (query.isForUpdate()) {
sql = withForUpdate(sql, query.getForUpdateMode());
sql = withForUpdate(sql, query.getForUpdateMode(), query.getForUpdateLockType());
}
return sql;
}
@@ -693,7 +692,7 @@ public class DatabasePlatform {
return null;
}
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode, Query.LockType lockType) {
// silently assume the database does not support the "for update" clause.
logger.info("it seems your database does not support the 'for update' clause");
return sql;
@@ -51,7 +51,7 @@ public class H2Platform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode, Query.LockType lockType) {
// NOWAIT and SKIP LOCKED currently not supported with H2
return sql + " for update";
}
@@ -1,5 +1,6 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.Query;
import io.ebean.Query.ForUpdate;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
@@ -68,7 +69,7 @@ public class HanaPlatform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql, ForUpdate forUpdateMode) {
protected String withForUpdate(String sql, ForUpdate forUpdateMode, Query.LockType lockType) {
switch (forUpdateMode) {
case BASE:
return sql + " for update";
@@ -60,7 +60,7 @@ public abstract class BaseMySqlPlatform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode, Query.LockType lockType) {
// NOWAIT and SKIP LOCKED currently not supported with MySQL
return sql + " for update";
}
@@ -48,7 +48,7 @@ public class NuoDbPlatform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode, Query.LockType lockType) {
switch (forUpdateMode) {
case NOWAIT:
return sql + " for update nowait";
@@ -79,7 +79,7 @@ public class OraclePlatform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode, Query.LockType lockType) {
switch (forUpdateMode) {
case SKIPLOCKED:
return sql + " for update skip locked";
@@ -26,10 +26,14 @@ import java.sql.Types;
*/
public class PostgresPlatform extends DatabasePlatform {
// by default using NO KEY option with FOR UPDATE clauses
private String forUpdateSkipLocked = " for no key update skip locked";
private String forUpdateNowait = " for no key update nowait";
private String forUpdate = " for no key update";
private static final String SKIP_LOCKED = " skip locked";
private static final String NO_WAIT = " nowait";
private static final String FOR_UPDATE = " for update";
private static final String FOR_NO_KEY_UPDATE = " for no key update";
private static final String FOR_SHARE = " for share";
private static final String FOR_KEY_SHARE = " for key share";
private boolean defaultLockWithKey = true;
public PostgresPlatform() {
super();
@@ -90,11 +94,7 @@ public class PostgresPlatform extends DatabasePlatform {
@Override
public void configure(PlatformConfig config) {
super.configure(config);
if (config.isLockWithKey()) {
this.forUpdateSkipLocked = " for update skip locked";
this.forUpdateNowait = " for update nowait";
this.forUpdate = " for update";
}
defaultLockWithKey = config.isLockWithKey();
}
@Override
@@ -124,22 +124,32 @@ public class PostgresPlatform extends DatabasePlatform {
*/
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new PostgresSequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode, Query.LockType lockType) {
switch (forUpdateMode) {
case SKIPLOCKED:
return sql + forUpdateSkipLocked;
return sql + lock(lockType) + SKIP_LOCKED;
case NOWAIT:
return sql + forUpdateNowait;
return sql + lock(lockType) + NO_WAIT;
default:
return sql + forUpdate;
return sql + lock(lockType);
}
}
private String lock(Query.LockType lockType) {
switch (lockType) {
case Update: return FOR_UPDATE;
case NoKeyUpdate: return FOR_NO_KEY_UPDATE;
case Share: return FOR_SHARE;
case KeyShare: return FOR_KEY_SHARE;
case Default: return defaultLockWithKey ? FOR_UPDATE : FOR_NO_KEY_UPDATE;
}
return FOR_UPDATE;
}
@Override
public boolean tablePartitionsExist(Connection connection, String table) throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("select count(*) from pg_inherits i WHERE i.inhparent = ?::regclass")) {
@@ -117,7 +117,7 @@ abstract class SqlServerBasePlatform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode, Query.LockType lockType) {
// for update are hints on from clause of base table
return sql;
}