[jOOQ/jOOQ#7291] Add support for multiple MERGE .. WHEN [ NOT ] MATCHED AND { condition }

This commit is contained in:
Lukas Eder 2020-04-06 17:18:15 +02:00
parent e62581488c
commit 7b443dc2f1
3 changed files with 147 additions and 10 deletions

View File

@ -81,7 +81,7 @@ import org.jooq.impl.DSL;
*
* @author Lukas Eder
*/
public interface MergeMatchedDeleteStep<R extends Record> extends MergeNotMatchedStep<R> {
public interface MergeMatchedDeleteStep<R extends Record> extends MergeMatchedStep<R> {
/**
* Add an additional <code>DELETE WHERE</code> clause to the preceding

View File

@ -49,6 +49,8 @@ import static org.jooq.SQLDialect.HSQLDB;
// ...
// ...
import org.jooq.impl.DSL;
/**
* This type is used for the {@link Merge}'s DSL API.
* <p>
@ -90,8 +92,89 @@ public interface MergeMatchedStep<R extends Record> extends MergeNotMatchedStep<
/**
* Add the <code>WHEN MATCHED THEN UPDATE</code> clause to the
* <code>MERGE</code> statement
* <code>MERGE</code> statement.
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB })
MergeMatchedSetStep<R> whenMatchedThenUpdate();
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</code> clause to the
* <code>MERGE</code> statement.
*/
@Support({ DERBY, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(Condition condition);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</code> clause to the
* <code>MERGE</code> statement.
*/
@Support({ DERBY, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(Field<Boolean> condition);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</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
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#condition(SQL)
* @see SQL
*/
@PlainSQL
@Support({ DERBY, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(SQL sql);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</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
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#condition(String)
* @see SQL
*/
@PlainSQL
@Support({ DERBY, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(String sql);
/**
* Add the <code>WHEN MATCHED AND .. THEN UPDATE</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
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#condition(String, Object...)
* @see DSL#sql(String, Object...)
* @see SQL
*/
@PlainSQL
@Support({ DERBY, 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.
* <p>
* <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
* guarantee syntax integrity. You may also create the possibility of
* malicious SQL injection. Be sure to properly use bind variables and/or
* escape literals when concatenated into SQL clauses!
*
* @see DSL#condition(String, QueryPart...)
* @see DSL#sql(String, QueryPart...)
* @see SQL
*/
@PlainSQL
@Support({ DERBY, H2, HSQLDB })
MergeMatchedThenStep<R> whenMatchedAnd(String sql, QueryPart... parts);
}

View File

@ -119,6 +119,7 @@ import org.jooq.MergeKeyStep8;
import org.jooq.MergeKeyStep9;
import org.jooq.MergeMatchedDeleteStep;
import org.jooq.MergeMatchedSetMoreStep;
import org.jooq.MergeMatchedThenStep;
import org.jooq.MergeNotMatchedSetMoreStep;
import org.jooq.MergeNotMatchedValuesStep1;
import org.jooq.MergeNotMatchedValuesStep10;
@ -203,6 +204,7 @@ implements
MergeOnStep<R>,
MergeOnConditionStep<R>,
MergeMatchedSetMoreStep<R>,
MergeMatchedThenStep<R>,
MergeNotMatchedSetMoreStep<R>,
@ -254,7 +256,8 @@ implements
// Flags to keep track of DSL object creation state
private boolean matchedClause;
private FieldMapForUpdate matchedUpdate;
private List<FieldMapForUpdate> matchedUpdate;
private List<Condition> matchedUpdateAnd;
private boolean notMatchedClause;
private FieldMapsForInsert notMatchedInsert;
@ -275,6 +278,8 @@ implements
this.with = with;
this.table = table;
this.on = new ConditionProviderImpl();
this.matchedUpdate = new ArrayList<>();
this.matchedUpdateAnd = new ArrayList<>();
if (fields != null)
columns(fields);
@ -305,6 +310,10 @@ implements
return upsertValues;
}
FieldMapForUpdate getLastMatchedUpdate() {
return matchedUpdate.get(matchedUpdate.size() - 1);
}
@Override
public final MergeImpl columns(Field<?>... fields) {
return columns(Arrays.asList(fields));
@ -917,12 +926,50 @@ implements
@Override
public final MergeImpl whenMatchedThenUpdate() {
matchedClause = true;
matchedUpdate = new FieldMapForUpdate(table, MERGE_SET_ASSIGNMENT);
matchedUpdate.add(new FieldMapForUpdate(table, MERGE_SET_ASSIGNMENT));
matchedUpdateAnd.add(null);
notMatchedClause = false;
return this;
}
@Override
public final MergeImpl whenMatchedAnd(Condition condition) {
whenMatchedThenUpdate();
matchedUpdateAnd.set(matchedUpdateAnd.size() - 1, condition);
return this;
}
@Override
public final MergeImpl whenMatchedAnd(Field<Boolean> condition) {
return whenMatchedAnd(condition(condition));
}
@Override
public final MergeImpl whenMatchedAnd(SQL sql) {
return whenMatchedAnd(condition(sql));
}
@Override
public final MergeImpl whenMatchedAnd(String sql) {
return whenMatchedAnd(condition(sql));
}
@Override
public final MergeImpl whenMatchedAnd(String sql, Object... bindings) {
return whenMatchedAnd(condition(sql, bindings));
}
@Override
public final MergeImpl whenMatchedAnd(String sql, QueryPart... parts) {
return whenMatchedAnd(condition(sql, parts));
}
@Override
public final MergeImpl thenUpdate() {
return this;
}
@Override
public final <T> MergeImpl set(Field<T> field, T value) {
return set(field, Tools.field(value, field));
@ -931,7 +978,7 @@ implements
@Override
public final <T> MergeImpl set(Field<T> field, Field<T> value) {
if (matchedClause)
matchedUpdate.put(field, nullSafe(value));
getLastMatchedUpdate().put(field, nullSafe(value));
else if (notMatchedClause)
notMatchedInsert.set(field, nullSafe(value));
else
@ -956,7 +1003,7 @@ implements
@Override
public final MergeImpl set(Map<?, ?> map) {
if (matchedClause)
matchedUpdate.set(map);
getLastMatchedUpdate().set(map);
else if (notMatchedClause)
notMatchedInsert.set(map);
else
@ -1525,18 +1572,25 @@ implements
.start(MERGE_SET);
// [#999] WHEN MATCHED clause is optional
if (matchedUpdate != null) {
for (int i = 0; i < matchedUpdate.size(); i++) {
FieldMapForUpdate map = matchedUpdate.get(i);
Condition condition = matchedUpdateAnd.get(i);
ctx.formatSeparator()
.visit(K_WHEN).sql(' ').visit(K_MATCHED);
// [#5110] Standard SQL "WHERE" clause in updates
if (matchedWhere != null && NO_SUPPORT_WHERE.contains(ctx.family()))
// [#7291] Standard SQL AND clause in updates
if (condition != null)
ctx.sql(' ').visit(K_AND).sql(' ').visit(condition);
// [#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);
ctx.sql(' ').visit(K_THEN).sql(' ').visit(K_UPDATE).sql(' ').visit(K_SET)
.formatIndentStart()
.formatSeparator()
.visit(matchedUpdate)
.visit(map)
.formatIndentEnd();
}