[jOOQ/jOOQ#15637] Emulate DELETE .. USING with MERGE, where available

This commit is contained in:
Lukas Eder 2024-04-30 16:36:46 +02:00
parent 310f3cb266
commit d0bf1d180f
4 changed files with 112 additions and 33 deletions

View File

@ -44,9 +44,14 @@ package org.jooq;
// ...
// ...
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.DUCKDB;
// ...
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
// ...
import static org.jooq.SQLDialect.HSQLDB;
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
// ...
@ -84,19 +89,19 @@ public interface DeleteQuery<R extends Record> extends ConditionProvider, Delete
/**
* Add tables to the <code>USING</code> clause.
*/
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
void addUsing(TableLike<?> table);
/**
* Add tables to the <code>USING</code> clause.
*/
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
void addUsing(TableLike<?>... tables);
/**
* Add tables to the <code>USING</code> clause.
*/
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
void addUsing(Collection<? extends TableLike<?>> tables);
// ------------------------------------------------------------------------

View File

@ -41,7 +41,14 @@ package org.jooq;
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.DUCKDB;
// ...
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
@ -96,21 +103,21 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
* Add a <code>USING</code> clause to the query.
*/
@NotNull @CheckReturnValue
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
DeleteWhereStep<R> using(TableLike<?> table);
/**
* Add a <code>USING</code> clause to the query.
*/
@NotNull @CheckReturnValue
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
DeleteWhereStep<R> using(TableLike<?>... tables);
/**
* Add a <code>USING</code> clause to the query.
*/
@NotNull @CheckReturnValue
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
DeleteWhereStep<R> using(Collection<? extends TableLike<?>> tables);
/**
@ -125,7 +132,7 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
* @see SQL
*/
@NotNull @CheckReturnValue
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
@PlainSQL
DeleteWhereStep<R> using(SQL sql);
@ -141,7 +148,7 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
* @see SQL
*/
@NotNull @CheckReturnValue
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
@PlainSQL
DeleteWhereStep<R> using(String sql);
@ -158,7 +165,7 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
* @see SQL
*/
@NotNull @CheckReturnValue
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
@PlainSQL
DeleteWhereStep<R> using(String sql, Object... bindings);
@ -175,7 +182,7 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
* @see SQL
*/
@NotNull @CheckReturnValue
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
@PlainSQL
DeleteWhereStep<R> using(String sql, QueryPart... parts);
@ -185,6 +192,6 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
* @see DSL#table(Name)
*/
@NotNull @CheckReturnValue
@Support({ DUCKDB, MARIADB, MYSQL, POSTGRES })
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
DeleteWhereStep<R> using(Name name);
}

View File

@ -51,6 +51,7 @@ import static org.jooq.SQLDialect.CLICKHOUSE;
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.DUCKDB;
// ...
@ -80,6 +81,7 @@ import static org.jooq.SQLDialect.YUGABYTEDB;
import static org.jooq.conf.SettingsTools.getExecuteDeleteWithoutWhere;
import static org.jooq.impl.ConditionProviderImpl.extractCondition;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.mergeInto;
import static org.jooq.impl.DSL.noCondition;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
@ -105,6 +107,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jooq.Clause;
import org.jooq.Condition;
@ -123,6 +126,7 @@ import org.jooq.SQLDialect;
import org.jooq.Scope;
import org.jooq.SortField;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.TableLike;
// ...
import org.jooq.conf.ParamType;
@ -152,6 +156,8 @@ implements
static final Set<SQLDialect> REQUIRE_REPEAT_FROM_IN_USING = SQLDialect.supportedBy(MARIADB, MYSQL);
static final Set<SQLDialect> NO_SUPPORT_REPEAT_FROM_IN_USING = SQLDialect.supportedBy(DUCKDB, POSTGRES, YUGABYTEDB);
static final Set<SQLDialect> REQUIRES_WHERE = SQLDialect.supportedBy(CLICKHOUSE);
static final Set<SQLDialect> EMULATE_USING_WITH_MERGE = SQLDialect.supportedBy(DERBY, FIREBIRD, H2, HSQLDB);
@ -303,6 +309,11 @@ implements
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
final void accept1(Context<?> ctx) {
if (!using.isEmpty() && EMULATE_USING_WITH_MERGE.contains(ctx.dialect())) {
acceptUsingAsMerge(ctx);
return;
}
ctx.start(DELETE_DELETE)
.visit(K_DELETE).sql(' ');
@ -439,6 +450,78 @@ implements
ctx.end(DELETE_RETURNING);
}
static final record MergeUsing(Table<?> table, boolean patchSource) {
}
static final MergeUsing mergeUsing(
TableList tables,
Table<?> table,
Condition condition,
SortFieldList orderBy,
Field<? extends Number> limit
) {
boolean patchSource = true;
if (orderBy.isEmpty() && limit == null) {
if (tables.size() == 1 && tables.get(0) instanceof TableImpl && !(patchSource = false))
return new MergeUsing(tables.get(0), patchSource);
else
return new MergeUsing(select().from(tables).asTable("s"), patchSource);
}
// TODO [#13326]: Avoid the JOIN if it isn't strictly necessary
// (i.e. if ORDER BY references only from, not table)
else
return new MergeUsing(
select(tables.fields())
.from(tables)
.join(table).on(condition)
.orderBy(orderBy)
.limit(limit)
.asTable("s"),
patchSource
);
}
private final void acceptUsingAsMerge(Context<?> ctx) {
// TODO: What about RETURNING?
// TODO: What if there are multiple FROM tables?
// TODO: What if there are SET ROW = ROW assignment(s)?
// TODO: What if there are SET ROW = (SELECT ..) assignment(s)?
Condition c = condition;
Table<?> t = table(ctx);
TableList u;
// [#15637] Same semantics as NO_SUPPORT_REPEAT_FROM_IN_USING
if (containsDeclaredTable(using, t)) {
u = new TableList(using);
u.remove(t);
}
else
u = using;
MergeUsing mu = mergeUsing(u, t, c, orderBy, limit);
if (mu.patchSource() && ctx.configuration().requireCommercial(() -> "The DELETE .. USING to MERGE transformation requires commercial only logic for non-trivial USING clauses. Please upgrade to the jOOQ Professional Edition or jOOQ Enterprise Edition")) {
}
ctx.visit(mergeInto(table).using(mu.table()).on(c).whenMatchedThenDelete());
}
static final void acceptLimit(Context<?> ctx, Field<? extends Number> limit) {
if (limit != null)
if (ctx.family() == FIREBIRD)

View File

@ -90,6 +90,7 @@ import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.DSL.selectFrom;
import static org.jooq.impl.DSL.trueCondition;
import static org.jooq.impl.DeleteQueryImpl.mergeUsing;
import static org.jooq.impl.InlineDerivedTable.hasInlineDerivedTables;
import static org.jooq.impl.InlineDerivedTable.transformInlineDerivedTables;
import static org.jooq.impl.InlineDerivedTable.transformInlineDerivedTables0;
@ -180,11 +181,11 @@ import org.jooq.TableField;
import org.jooq.TableLike;
// ...
import org.jooq.UpdateQuery;
import org.jooq.impl.DeleteQueryImpl.MergeUsing;
import org.jooq.impl.FieldMapForUpdate.SetClause;
import org.jooq.impl.QOM.UnmodifiableList;
import org.jooq.impl.QOM.UnmodifiableMap;
import org.jooq.impl.QOM.Update;
import org.jooq.impl.Tools.BooleanDataKey;
/**
* @author Lukas Eder
@ -658,29 +659,12 @@ implements
// TODO: What if there are SET ROW = ROW assignment(s)?
// TODO: What if there are SET ROW = (SELECT ..) assignment(s)?
Table<?> s;
boolean patchSource = true;
Condition c = condition;
Table<?> t = table(ctx);
FieldMapForUpdate um = updateMap;
MergeUsing mu = mergeUsing(from, t, c, orderBy, limit);
if (orderBy.isEmpty() && limit == null) {
if (from.size() == 1 && from.get(0) instanceof TableImpl && !(patchSource = false))
s = from.get(0);
else
s = select().from(from).asTable("s");
}
// TODO [#13326]: Avoid the JOIN if it isn't strictly necessary
// (i.e. if ORDER BY references only from, not table)
else
s = select(from.fields())
.from(from)
.join(table).on(condition)
.orderBy(orderBy)
.limit(limit)
.asTable("s");
if (patchSource && ctx.configuration().requireCommercial(() -> "The UPDATE .. FROM to MERGE transformation requires commercial only logic for non-trivial FROM clauses. Please upgrade to the jOOQ Professional Edition or jOOQ Enterprise Edition")) {
if (mu.patchSource() && ctx.configuration().requireCommercial(() -> "The UPDATE .. FROM to MERGE transformation requires commercial only logic for non-trivial FROM clauses. Please upgrade to the jOOQ Professional Edition or jOOQ Enterprise Edition")) {
@ -698,7 +682,7 @@ implements
}
ctx.visit(mergeInto(table).using(s).on(c).whenMatchedThenUpdate().set(um));
ctx.visit(mergeInto(table).using(mu.table()).on(c).whenMatchedThenUpdate().set(um));
}
final boolean updatesField(Field<?> field) {