[jOOQ/jOOQ#7291] Restored WHERE clauses

This commit is contained in:
Lukas Eder 2020-04-07 18:12:53 +02:00
parent 76e7b74b0b
commit ade6e27094
6 changed files with 228 additions and 126 deletions

View File

@ -98,21 +98,28 @@ public interface MergeMatchedStep<R extends Record> extends MergeNotMatchedStep<
MergeMatchedSetStep<R> whenMatchedThenUpdate();
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</code> clause to the
* Add the <code>WHEN MATCHED THEN DELETE</code> clause to the
* <code>MERGE</code> statement.
*/
@Support({ DERBY, H2, HSQLDB })
MergeMatchedStep<R> whenMatchedThenDelete();
/**
* Add the <code>WHEN MATCHED AND</code> clause to the
* <code>MERGE</code> statement.
*/
@Support({ DERBY, FIREBIRD, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(Condition condition);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</code> clause to the
* Add the <code>WHEN MATCHED AND</code> clause to the
* <code>MERGE</code> statement.
*/
@Support({ DERBY, H2, HSQLDB })
@Support({ DERBY, FIREBIRD, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(Field<Boolean> condition);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</code> clause to the
* Add the <code>WHEN MATCHED AND</code> clause to the
* <code>MERGE</code> statement.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
@ -124,11 +131,11 @@ public interface MergeMatchedStep<R extends Record> extends MergeNotMatchedStep<
* @see SQL
*/
@PlainSQL
@Support({ DERBY, H2, HSQLDB })
@Support({ DERBY, FIREBIRD, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(SQL sql);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</code> clause to the
* Add the <code>WHEN MATCHED AND</code> clause to the
* <code>MERGE</code> statement.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
@ -140,11 +147,11 @@ public interface MergeMatchedStep<R extends Record> extends MergeNotMatchedStep<
* @see SQL
*/
@PlainSQL
@Support({ DERBY, H2, HSQLDB })
@Support({ DERBY, FIREBIRD, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(String sql);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</code> clause to the
* Add the <code>WHEN MATCHED AND</code> clause to the
* <code>MERGE</code> statement.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
@ -157,12 +164,12 @@ public interface MergeMatchedStep<R extends Record> extends MergeNotMatchedStep<
* @see SQL
*/
@PlainSQL
@Support({ DERBY, H2, HSQLDB })
@Support({ DERBY, FIREBIRD, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(String sql, Object... bindings);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</code> clause to the
* <code>MERGE</code> statement.
* Add the <code>WHEN MATCHED AND</code> clause to the <code>MERGE</code>
* statement.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
@ -174,7 +181,7 @@ public interface MergeMatchedStep<R extends Record> extends MergeNotMatchedStep<
* @see SQL
*/
@PlainSQL
@Support({ DERBY, H2, HSQLDB })
@Support({ DERBY, FIREBIRD, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(String sql, QueryPart... parts);
}

View File

@ -39,11 +39,14 @@ package org.jooq;
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
// ...
// ...
// ...
// ...
// ...
/**
* This type is used for the {@link Merge}'s DSL API.
@ -85,9 +88,16 @@ import static org.jooq.SQLDialect.HSQLDB;
public interface MergeMatchedThenStep<R extends Record> {
/**
* Add the <code>WHEN MATCHED THEN UPDATE</code> clause to the
* Add the <code>THEN UPDATE</code> clause to the
* <code>MERGE</code> statement.
*/
@Support({ DERBY, FIREBIRD, H2, HSQLDB })
MergeMatchedSetStep<R> thenUpdate();
/**
* Add the <code>THEN UPDATE</code> clause to the
* <code>MERGE</code> statement.
*/
@Support({ DERBY, H2, HSQLDB })
MergeMatchedSetStep<R> thenUpdate();
MergeMatchedStep<R> thenDelete();
}

View File

@ -101,7 +101,6 @@ import org.jooq.Field;
import org.jooq.Identity;
import org.jooq.InsertQuery;
import org.jooq.Merge;
import org.jooq.MergeMatchedSetMoreStep;
import org.jooq.MergeNotMatchedStep;
import org.jooq.MergeOnConditionStep;
import org.jooq.Name;
@ -800,16 +799,10 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
// [#1295] Use UPDATE clause only when with ON DUPLICATE KEY UPDATE,
// not with ON DUPLICATE KEY IGNORE
MergeNotMatchedStep<R> notMatched = on;
if (onDuplicateKeyUpdate) {
MergeMatchedSetMoreStep<R> set =
on.whenMatchedThenUpdate()
.set(updateMap);
if (condition.hasWhere())
notMatched = condition != null
? set.where(condition)
: set;
}
if (onDuplicateKeyUpdate)
notMatched = condition.hasWhere()
? on.whenMatchedAnd(condition.getWhere()).thenUpdate().set(updateMap)
: on.whenMatchedThenUpdate().set(updateMap);
return select == null
? notMatched.whenNotMatchedThenInsert(insertMaps.fields())

View File

@ -122,7 +122,6 @@ final class Keywords {
static final Keyword K_DEFAULT = keyword("default");
static final Keyword K_DEFAULT_VALUES = keyword("default values");
static final Keyword K_DELETE = keyword("delete");
static final Keyword K_DELETE_WHERE = keyword("delete where");
static final Keyword K_DENSE_RANK = keyword("dense_rank");
static final Keyword K_DISABLE = keyword("disable");
static final Keyword K_DISTINCT = keyword("distinct");

View File

@ -39,7 +39,6 @@ package org.jooq.impl;
import static java.lang.Boolean.FALSE;
import static org.jooq.Clause.MERGE;
import static org.jooq.Clause.MERGE_DELETE_WHERE;
import static org.jooq.Clause.MERGE_MERGE_INTO;
import static org.jooq.Clause.MERGE_ON;
import static org.jooq.Clause.MERGE_SET;
@ -48,8 +47,8 @@ import static org.jooq.Clause.MERGE_USING;
import static org.jooq.Clause.MERGE_VALUES;
import static org.jooq.Clause.MERGE_WHEN_MATCHED_THEN_UPDATE;
import static org.jooq.Clause.MERGE_WHEN_NOT_MATCHED_THEN_INSERT;
import static org.jooq.Clause.MERGE_WHERE;
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.HSQLDB;
// ...
// ...
// ...
@ -58,9 +57,10 @@ import static org.jooq.impl.DSL.exists;
import static org.jooq.impl.DSL.insertInto;
import static org.jooq.impl.DSL.notExists;
import static org.jooq.impl.DSL.nullSafe;
import static org.jooq.impl.DSL.when;
import static org.jooq.impl.Keywords.K_AND;
import static org.jooq.impl.Keywords.K_AS;
import static org.jooq.impl.Keywords.K_DELETE_WHERE;
import static org.jooq.impl.Keywords.K_DELETE;
import static org.jooq.impl.Keywords.K_INSERT;
import static org.jooq.impl.Keywords.K_KEY;
import static org.jooq.impl.Keywords.K_MATCHED;
@ -80,6 +80,7 @@ import static org.jooq.impl.QueryPartListView.wrap;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -88,6 +89,7 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jooq.Clause;
@ -241,7 +243,8 @@ implements
*/
private static final long serialVersionUID = -8835479296876774391L;
private static final Clause[] CLAUSES = { MERGE };
private static final Set<SQLDialect> NO_SUPPORT_WHERE = SQLDialect.supportedBy(DERBY);
private static final Set<SQLDialect> NO_SUPPORT_AND = SQLDialect.supportedBy(FIREBIRD);
private static final Set<SQLDialect> NO_SUPPORT_MULTI = SQLDialect.supportedBy(FIREBIRD, HSQLDB);
private final WithImpl with;
private final Table<R> table;
@ -249,17 +252,11 @@ implements
private TableLike<?> using;
private boolean usingDual;
// [#998] Oracle extensions to the MERGE statement
private Condition matchedWhere;
private Condition matchedDeleteWhere;
private Condition notMatchedWhere;
// Flags to keep track of DSL object creation state
private boolean matchedClause;
private List<FieldMapForUpdate> matchedUpdate;
private List<Condition> matchedUpdateAnd;
private List<MatchedClause> matched;
private boolean notMatchedClause;
private FieldMapsForInsert notMatchedInsert;
private List<NotMatchedClause> notMatched;
// Objects for the UPSERT syntax (including H2 MERGE, HANA UPSERT, etc.)
private boolean upsertStyle;
@ -278,8 +275,8 @@ implements
this.with = with;
this.table = table;
this.on = new ConditionProviderImpl();
this.matchedUpdate = new ArrayList<>();
this.matchedUpdateAnd = new ArrayList<>();
this.matched = new ArrayList<>();
this.notMatched = new ArrayList<>();
if (fields != null)
columns(fields);
@ -310,8 +307,12 @@ implements
return upsertValues;
}
FieldMapForUpdate getLastMatchedUpdate() {
return matchedUpdate.get(matchedUpdate.size() - 1);
MatchedClause getLastMatched() {
return matched.get(matched.size() - 1);
}
NotMatchedClause getLastNotMatched() {
return notMatched.get(notMatched.size() - 1);
}
@Override
@ -721,8 +722,7 @@ implements
getUpsertValues().addAll(Tools.fields(values, getUpsertFields().toArray(EMPTY_FIELD)));
}
else {
Field<?>[] fields = notMatchedInsert.fields().toArray(EMPTY_FIELD);
notMatchedInsert.set(Tools.fields(values, fields));
getLastNotMatched().insertMap.set(Tools.fields(values, getLastNotMatched().insertMap.fields().toArray(EMPTY_FIELD)));
}
return this;
@ -925,18 +925,20 @@ implements
@Override
public final MergeImpl whenMatchedThenUpdate() {
matchedClause = true;
matchedUpdate.add(new FieldMapForUpdate(table, MERGE_SET_ASSIGNMENT));
matchedUpdateAnd.add(null);
return whenMatchedAnd((Condition) null).thenUpdate();
}
notMatchedClause = false;
return this;
@Override
public final MergeImpl whenMatchedThenDelete() {
return whenMatchedAnd((Condition) null).thenDelete();
}
@Override
public final MergeImpl whenMatchedAnd(Condition condition) {
whenMatchedThenUpdate();
matchedUpdateAnd.set(matchedUpdateAnd.size() - 1, condition);
matchedClause = true;
matched.add(new MatchedClause(condition));
notMatchedClause = false;
return this;
}
@ -970,6 +972,12 @@ implements
return this;
}
@Override
public final MergeImpl thenDelete() {
getLastMatched().delete = true;
return this;
}
@Override
public final <T> MergeImpl set(Field<T> field, T value) {
return set(field, Tools.field(value, field));
@ -978,9 +986,9 @@ implements
@Override
public final <T> MergeImpl set(Field<T> field, Field<T> value) {
if (matchedClause)
getLastMatchedUpdate().put(field, nullSafe(value));
getLastMatched().updateMap.put(field, nullSafe(value));
else if (notMatchedClause)
notMatchedInsert.set(field, nullSafe(value));
getLastNotMatched().insertMap.set(field, nullSafe(value));
else
throw new IllegalStateException("Cannot call where() on the current state of the MERGE statement");
@ -1003,9 +1011,9 @@ implements
@Override
public final MergeImpl set(Map<?, ?> map) {
if (matchedClause)
getLastMatchedUpdate().set(map);
getLastMatched().updateMap.set(map);
else if (notMatchedClause)
notMatchedInsert.set(map);
getLastNotMatched().insertMap.set(map);
else
throw new IllegalStateException("Cannot call where() on the current state of the MERGE statement");
@ -1167,8 +1175,8 @@ implements
@Override
public final MergeImpl whenNotMatchedThenInsert(Collection<? extends Field<?>> fields) {
notMatchedClause = true;
notMatchedInsert = new FieldMapsForInsert(table);
notMatchedInsert.addFields(fields);
notMatched.add(new NotMatchedClause(null));
getLastNotMatched().insertMap.addFields(fields);
matchedClause = false;
return this;
@ -1176,15 +1184,12 @@ implements
@Override
public final MergeImpl where(Condition condition) {
if (matchedClause) {
matchedWhere = condition;
}
else if (notMatchedClause) {
notMatchedWhere = condition;
}
else {
if (matchedClause)
getLastMatched().condition = condition;
else if (notMatchedClause)
getLastNotMatched().condition = condition;
else
throw new IllegalStateException("Cannot call where() on the current state of the MERGE statement");
}
return this;
}
@ -1202,7 +1207,11 @@ implements
@Override
public final MergeImpl deleteWhere(Condition condition) {
matchedDeleteWhere = condition;
if (matchedClause)
matched.add(new MatchedClause(condition, true));
else
throw new IllegalStateException("Cannot call where() on the current state of the MERGE statement");
return this;
}
@ -1274,12 +1283,10 @@ implements
for (int i = 0; i < key.getFields().size(); i++) {
Condition rhs = key.getFields().get(i).equal((Field) srcFields.get(i));
if (condition == null) {
if (condition == null)
condition = rhs;
}
else {
else
condition = condition.and(rhs);
}
}
}
@ -1571,82 +1578,110 @@ implements
.start(MERGE_WHEN_MATCHED_THEN_UPDATE)
.start(MERGE_SET);
// [#999] WHEN MATCHED clause is optional
for (int i = 0; i < matchedUpdate.size(); i++) {
FieldMapForUpdate map = matchedUpdate.get(i);
Condition condition = matchedUpdateAnd.get(i);
// [#7291] Multi MATCHED emulation
boolean emulate = false;
ctx.formatSeparator()
.visit(K_WHEN).sql(' ').visit(K_MATCHED);
emulateCheck:
if (NO_SUPPORT_MULTI.contains(ctx.dialect()) && matched.size() > 1) {
boolean matchUpdate = false;
boolean matchDelete = false;
// [#7291] Standard SQL AND clause in updates
if (condition != null)
ctx.sql(' ').visit(K_AND).sql(' ').visit(condition);
for (MatchedClause m : matched) {
if (m.delete) {
if (emulate |= matchDelete)
break emulateCheck;
// [#5110] Oracle style "WHERE" clause in updates
else if (matchedWhere != null && NO_SUPPORT_WHERE.contains(ctx.family()))
ctx.sql(' ').visit(K_AND).sql(' ').visit(matchedWhere);
matchDelete = true;
}
else {
if (emulate |= matchUpdate)
break emulateCheck;
ctx.sql(' ').visit(K_THEN).sql(' ').visit(K_UPDATE).sql(' ').visit(K_SET)
.formatIndentStart()
.formatSeparator()
.visit(map)
.formatIndentEnd();
matchUpdate = true;
}
}
}
if (emulate) {
MatchedClause update = null;
MatchedClause delete = null;
for (MatchedClause input : matched) {
MatchedClause output;
if (input.delete) {
if (delete == null)
delete = new MatchedClause(null, true);
output = delete;
}
else {
if (update == null)
update = new MatchedClause(null);
output = update;
// TODO: What if there are conflicts between condition-less clauses and pre-existing clauses containing a condition?
// TODO: What happens if both UPDATE and DELETE match?
if (input.condition == null) {
update.updateMap.putAll(input.updateMap);
}
else {
for (Entry<Field<?>, Field<?>> e : input.updateMap.entrySet()) {
Field<?> exp = update.updateMap.get(e.getKey());
if (exp instanceof CaseConditionStepImpl)
((CaseConditionStepImpl) exp).when(input.condition, e.getValue());
else
update.updateMap.put(e.getKey(), when(input.condition, (Field) e.getValue()).else_(e.getKey()));
// TODO: Are there other cases?
}
}
}
if (input.condition != null)
output.condition = output.condition == null ? input.condition : output.condition.or(input.condition);
}
if (update != null)
toSQLMatched(ctx, update);
if (delete != null)
toSQLMatched(ctx, delete);
}
else {
for (MatchedClause m : matched)
toSQLMatched(ctx, m);
}
ctx.end(MERGE_SET)
.start(MERGE_WHERE);
// [#998] Oracle MERGE extension: WHEN MATCHED THEN UPDATE .. WHERE
if (matchedWhere != null)
if (!NO_SUPPORT_WHERE.contains(ctx.family()))
ctx.formatSeparator()
.visit(K_WHERE).sql(' ')
.visit(matchedWhere);
ctx.end(MERGE_WHERE)
.start(MERGE_DELETE_WHERE);
// [#998] Oracle MERGE extension: WHEN MATCHED THEN UPDATE .. DELETE WHERE
if (matchedDeleteWhere != null)
ctx.formatSeparator()
.visit(K_DELETE_WHERE).sql(' ')
.visit(matchedDeleteWhere);
ctx.end(MERGE_DELETE_WHERE)
.end(MERGE_WHEN_MATCHED_THEN_UPDATE)
.start(MERGE_WHEN_NOT_MATCHED_THEN_INSERT);
// [#999] WHEN NOT MATCHED clause is optional
if (notMatchedInsert != null) {
for (NotMatchedClause m : notMatched) {
ctx.formatSeparator()
.visit(K_WHEN).sql(' ')
.visit(K_NOT).sql(' ')
.visit(K_MATCHED).sql(' ');
if (notMatchedWhere != null && NO_SUPPORT_WHERE.contains(ctx.family()))
ctx.visit(K_AND).sql(' ').visit(notMatchedWhere).sql(' ');
if (m.condition != null && !NO_SUPPORT_AND.contains(ctx.dialect()))
ctx.visit(K_AND).sql(' ').visit(m.condition).sql(' ');
ctx.visit(K_THEN).sql(' ')
.visit(K_INSERT);
notMatchedInsert.toSQLReferenceKeys(ctx);
m.insertMap.toSQLReferenceKeys(ctx);
ctx.formatSeparator()
.start(MERGE_VALUES)
.visit(K_VALUES).sql(' ');
notMatchedInsert.toSQL92Values(ctx);
m.insertMap.toSQL92Values(ctx);
ctx.end(MERGE_VALUES);
if (m.condition != null && NO_SUPPORT_AND.contains(ctx.dialect()))
ctx.sql(' ').visit(K_WHERE).sql(' ').visit(m.condition);
}
ctx.start(MERGE_WHERE);
// [#998] Oracle MERGE extension: WHEN NOT MATCHED THEN INSERT .. WHERE
if (notMatchedWhere != null && !NO_SUPPORT_WHERE.contains(ctx.family()))
ctx.formatSeparator()
.visit(K_WHERE).sql(' ')
.visit(notMatchedWhere);
ctx.end(MERGE_WHERE)
.end(MERGE_WHEN_NOT_MATCHED_THEN_INSERT);
ctx.end(MERGE_WHEN_NOT_MATCHED_THEN_INSERT);
@ -1667,11 +1702,68 @@ implements
}
private void toSQLMatched(Context<?> ctx, MatchedClause m) {
ctx.formatSeparator()
.visit(K_WHEN).sql(' ').visit(K_MATCHED);
// [#7291] Standard SQL AND clause in updates
if (m.condition != null)
ctx.sql(' ').visit(K_AND).sql(' ').visit(m.condition);
ctx.sql(' ').visit(K_THEN).sql(' ');
if (m.delete)
ctx.visit(K_DELETE);
else
ctx.visit(K_UPDATE).sql(' ').visit(K_SET)
.formatIndentStart()
.formatSeparator()
.visit(m.updateMap)
.formatIndentEnd();
}
@Override
public final Clause[] clauses(Context<?> ctx) {
return CLAUSES;
}
private final class MatchedClause implements Serializable {
/**
* Generated UID
*/
private static final long serialVersionUID = -7885112480794078253L;
FieldMapForUpdate updateMap;
boolean delete;
Condition condition;
MatchedClause(Condition condition) {
this(condition, false);
}
MatchedClause(Condition condition, boolean delete) {
this.updateMap = new FieldMapForUpdate(table, MERGE_SET_ASSIGNMENT);
this.condition = condition;
this.delete = delete;
}
}
private final class NotMatchedClause implements Serializable {
/**
* Generated UID
*/
private static final long serialVersionUID = -7885112480794078253L;
FieldMapsForInsert insertMap;
Condition condition;
NotMatchedClause(Condition condition) {
this.insertMap = new FieldMapsForInsert(table);
this.condition = condition;
}
}
}

View File

@ -285,9 +285,10 @@ public class UpdatableRecordImpl<R extends UpdatableRecord<R>> extends TableReco
else if (isExecuteWithOptimisticLockingIncludeUnversioned())
checkIfChanged(keys);
// [#1596] Check if the record was really changed in the database
// [#1859] Specify the returning clause if needed
Collection<Field<?>> key = setReturningIfNeeded(query);
// [#1596] Check if the record was really changed in the database
// [#1859] Specify the returning clause if needed
// [#10051] See if we can return keys also on MERGE
Collection<Field<?>> key = merge ? null : setReturningIfNeeded(query);
int result = query.execute();
checkIfChanged(result, version, timestamp);