[#7705] Add support for PostgreSQL FOR NO KEY UPDATE and FOR KEY SHARE

This commit is contained in:
lukaseder 2018-07-30 13:06:49 +02:00
parent 245a4f19c9
commit 5c77c2c031
6 changed files with 160 additions and 63 deletions

View File

@ -435,6 +435,8 @@ offsetFetch =
forUpdate =
'FOR SHARE'
| 'FOR KEY SHARE'
| 'FOR NO KEY UPDATE'
| 'FOR UPDATE' [ 'OF' fields ] [ 'NOWAIT' | 'WAIT' unsignedInteger | 'SKIP LOCKED' ]
;
@ -806,7 +808,8 @@ castDataType =
dataType =
(
'BIGINT' [ 'UNSIGNED' ]
'ARRAY'
| 'BIGINT' [ 'UNSIGNED' ]
| 'BINARY' [ '(' unsignedInteger ')' ]
| 'BIT' [ '(' unsignedInteger ')' ]
| 'BLOB' [ '(' unsignedInteger ')' ]
@ -834,6 +837,7 @@ dataType =
| 'NUMBER' [ '(' unsignedInteger [ ',' unsignedInteger ] ')' ]
| 'NUMERIC' [ '(' unsignedInteger [ ',' unsignedInteger ] ')' ]
| 'NVARCHAR' [ '(' unsignedInteger ')' ] [ 'COLLATE' collationName ]
| 'OTHER'
| 'REAL' [ '(' unsignedInteger [ ',' unsignedInteger ] ')' ]
| 'SERIAL'
| 'SERIAL4'

View File

@ -133,6 +133,15 @@ public interface SelectForUpdateStep<R extends Record> extends SelectOptionStep<
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
SelectForUpdateOfStep<R> forUpdate();
/**
* Add a <code>FOR NO KEY UPDATE</code> clause to the end of the query.
*
* @see SelectQuery#setForShare(boolean) see LockProvider for more
* details
*/
@Support({ POSTGRES })
SelectForUpdateOfStep<R> forNoKeyUpdate();
/**
* Add a <code>FOR SHARE</code> clause to the end of the query.
*
@ -142,6 +151,15 @@ public interface SelectForUpdateStep<R extends Record> extends SelectOptionStep<
@Support({ MARIADB, MYSQL, POSTGRES })
SelectOptionStep<R> forShare();
/**
* Add a <code>FOR KEY SHARE</code> clause to the end of the query.
*
* @see SelectQuery#setForShare(boolean) see LockProvider for more
* details
*/
@Support({ POSTGRES })
SelectForUpdateOfStep<R> forKeyShare();

View File

@ -871,6 +871,12 @@ public interface SelectQuery<R extends Record> extends Select<R>, ConditionProvi
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
void setForUpdate(boolean forUpdate);
/**
* Sets the "FOR NO KEY UPDATE" flag onto the query.
*/
@Support({ POSTGRES })
void setForNoKeyUpdate(boolean forNoKeyUpdate);
/**
* Some RDBMS allow for specifying the fields that should be locked by the
* <code>FOR UPDATE</code> clause, instead of the full row.
@ -1014,6 +1020,12 @@ public interface SelectQuery<R extends Record> extends Select<R>, ConditionProvi
@Support({ MARIADB, MYSQL, POSTGRES })
void setForShare(boolean forShare);
/**
* Sets the "FOR KEY SHARE" flag onto the query.
*/
@Support({ POSTGRES })
void setForKeyShare(boolean forKeyShare);

View File

@ -891,27 +891,29 @@ final class ParserImpl implements Parser {
}
if (parseKeywordIf(ctx, "FOR")) {
if (parseKeywordIf(ctx, "SHARE")) {
if (parseKeywordIf(ctx, "KEY SHARE"))
result.setForKeyShare(true);
else if (parseKeywordIf(ctx, "NO KEY UPDATE"))
result.setForNoKeyUpdate(true);
else if (parseKeywordIf(ctx, "SHARE"))
result.setForShare(true);
}
else if (parseKeywordIf(ctx, "UPDATE")) {
else if (parseKeywordIf(ctx, "UPDATE"))
result.setForUpdate(true);
if (parseKeywordIf(ctx, "OF"))
result.setForUpdateOf(parseFields(ctx));
if (parseKeywordIf(ctx, "NOWAIT"))
result.setForUpdateNoWait();
else if (parseKeywordIf(ctx, "WAIT") && ctx.requireProEdition())
;
else if (parseKeywordIf(ctx, "SKIP LOCKED"))
result.setForUpdateSkipLocked();
}
else
throw ctx.expected("SHARE", "UPDATE");
throw ctx.expected("UPDATE", "NO KEY UPDATE", "SHARE", "KEY SHARE");
if (parseKeywordIf(ctx, "OF"))
result.setForUpdateOf(parseFields(ctx));
if (parseKeywordIf(ctx, "NOWAIT"))
result.setForUpdateNoWait();
else if (parseKeywordIf(ctx, "WAIT") && ctx.requireProEdition())
;
else if (parseKeywordIf(ctx, "SKIP LOCKED"))
result.setForUpdateSkipLocked();
}
return result;
@ -8394,6 +8396,9 @@ final class ParserImpl implements Parser {
"EXCEPT",
"FETCH FIRST",
"FETCH NEXT",
"FOR KEY SHARE",
"FOR NO KEY UPDATE",
"FOR SHARE",
"FOR UPDATE",
"FROM",
"GO", // The T-SQL statement batch delimiter, not a SELECT keyword
@ -8421,6 +8426,8 @@ final class ParserImpl implements Parser {
"EXCEPT",
"FETCH FIRST",
"FETCH NEXT",
"FOR KEY SHARE",
"FOR NO KEY UPDATE",
"FOR SHARE",
"FOR UPDATE",
"FULL JOIN",

View File

@ -1734,6 +1734,12 @@ final class SelectImpl<R extends Record, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
return this;
}
@Override
public final SelectImpl forNoKeyUpdate() {
getQuery().setForNoKeyUpdate(true);
return this;
}
@Override
public final SelectImpl of(Field<?>... fields) {
getQuery().setForUpdateOf(fields);
@ -1778,6 +1784,12 @@ final class SelectImpl<R extends Record, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
return this;
}
@Override
public final SelectImpl forKeyShare() {
getQuery().setForKeyShare(true);
return this;
}

View File

@ -103,8 +103,7 @@ import static org.jooq.impl.Keywords.K_BY;
import static org.jooq.impl.Keywords.K_CONNECT_BY;
import static org.jooq.impl.Keywords.K_DISTINCT;
import static org.jooq.impl.Keywords.K_DISTINCT_ON;
import static org.jooq.impl.Keywords.K_FOR_SHARE;
import static org.jooq.impl.Keywords.K_FOR_UPDATE;
import static org.jooq.impl.Keywords.K_FOR;
import static org.jooq.impl.Keywords.K_FROM;
import static org.jooq.impl.Keywords.K_GROUP_BY;
import static org.jooq.impl.Keywords.K_HAVING;
@ -226,12 +225,11 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
private String option;
private boolean distinct;
private QueryPartList<SelectFieldOrAsterisk> distinctOn;
private boolean forUpdate;
private QueryPartList<Field<?>> forUpdateOf;
private TableList forUpdateOfTables;
private ForUpdateMode forUpdateMode;
private ForUpdateLockMode forUpdateLockMode;
private ForUpdateWaitMode forUpdateWaitMode;
private int forUpdateWait;
private boolean forShare;
@ -653,9 +651,47 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
}
// [#1296] FOR UPDATE is emulated in some dialects using ResultSet.CONCUR_UPDATABLE
if (forUpdate && !NO_SUPPORT_FOR_UPDATE.contains(family)) {
context.formatSeparator()
.visit(K_FOR_UPDATE);
if (forUpdateLockMode != null && !NO_SUPPORT_FOR_UPDATE.contains(family)) {
switch (forUpdateLockMode) {
case UPDATE:
context.formatSeparator()
.visit(K_FOR)
.sql(' ')
.visit(forUpdateLockMode.toKeyword());
break;
case SHARE:
switch (family) {
// MySQL has a non-standard implementation for the "FOR SHARE" clause
case MARIADB:
case MYSQL:
context.formatSeparator()
.visit(K_LOCK_IN_SHARE_MODE);
break;
// Postgres is known to implement the "FOR SHARE" clause like this
default:
context.visit(K_FOR)
.sql(' ')
.visit(forUpdateLockMode.toKeyword());
break;
}
break;
case KEY_SHARE:
case NO_KEY_UPDATE:
default:
context.visit(K_FOR)
.sql(' ')
.visit(forUpdateLockMode.toKeyword());
break;
}
if (Tools.isNotEmpty(forUpdateOf)) {
@ -701,41 +737,19 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
// [#3186] Firebird's FOR UPDATE clause has a different semantics. To achieve "regular"
// FOR UPDATE semantics, we should use FOR UPDATE WITH LOCK
if (family == FIREBIRD) {
if (family == FIREBIRD)
context.sql(' ').visit(K_WITH_LOCK);
}
if (forUpdateMode != null) {
if (forUpdateWaitMode != null) {
context.sql(' ');
context.visit(forUpdateMode.toKeyword());
context.visit(forUpdateWaitMode.toKeyword());
if (forUpdateMode == ForUpdateMode.WAIT) {
if (forUpdateWaitMode == ForUpdateWaitMode.WAIT) {
context.sql(' ');
context.sql(forUpdateWait);
}
}
}
else if (forShare) {
switch (family) {
// MySQL has a non-standard implementation for the "FOR SHARE" clause
case MARIADB:
case MYSQL:
context.formatSeparator()
.visit(K_LOCK_IN_SHARE_MODE);
break;
// Postgres is known to implement the "FOR SHARE" clause like this
default:
context.formatSeparator()
.visit(K_FOR_SHARE);
break;
}
}
@ -1857,8 +1871,17 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
@Override
public final void setForUpdate(boolean forUpdate) {
this.forUpdate = forUpdate;
this.forShare = false;
this.forUpdateLockMode = ForUpdateLockMode.UPDATE;
}
@Override
public final void setForNoKeyUpdate(boolean forNoKeyUpdate) {
this.forUpdateLockMode = ForUpdateLockMode.NO_KEY_UPDATE;
}
@Override
public final void setForKeyShare(boolean forKeyShare) {
this.forUpdateLockMode = ForUpdateLockMode.KEY_SHARE;
}
@Override
@ -1892,24 +1915,23 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
@Override
public final void setForUpdateNoWait() {
setForUpdate(true);
forUpdateMode = ForUpdateMode.NOWAIT;
forUpdateWaitMode = ForUpdateWaitMode.NOWAIT;
forUpdateWait = 0;
}
@Override
public final void setForUpdateSkipLocked() {
setForUpdate(true);
forUpdateMode = ForUpdateMode.SKIP_LOCKED;
forUpdateWaitMode = ForUpdateWaitMode.SKIP_LOCKED;
forUpdateWait = 0;
}
@Override
public final void setForShare(boolean forShare) {
this.forUpdate = false;
this.forShare = forShare;
this.forUpdateLockMode = ForUpdateLockMode.SHARE;
this.forUpdateOf = null;
this.forUpdateOfTables = null;
this.forUpdateMode = null;
this.forUpdateWaitMode = null;
this.forUpdateWait = 0;
}
@ -2246,7 +2268,7 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
@Override
final boolean isForUpdate() {
return forUpdate;
return forUpdateLockMode == ForUpdateLockMode.UPDATE;
}
@Override
@ -2590,7 +2612,29 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
/**
* The lock mode for the <code>FOR UPDATE</code> clause, if set.
*/
private static enum ForUpdateMode {
private static enum ForUpdateLockMode {
UPDATE("update"),
NO_KEY_UPDATE("no key update"),
SHARE("share"),
KEY_SHARE("key share"),
;
private final Keyword keyword;
private ForUpdateLockMode(String sql) {
this.keyword = DSL.keyword(sql);
}
public final Keyword toKeyword() {
return keyword;
}
}
/**
* The wait mode for the <code>FOR UPDATE</code> clause, if set.
*/
private static enum ForUpdateWaitMode {
WAIT("wait"),
NOWAIT("nowait"),
SKIP_LOCKED("skip locked"),
@ -2599,7 +2643,7 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
private final Keyword keyword;
private ForUpdateMode(String sql) {
private ForUpdateWaitMode(String sql) {
this.keyword = DSL.keyword(sql);
}