[jOOQ/jOOQ#16633] Add broader DELETE .. USING and UPDATE .. FROM support, using the same emulation as that for DML ORDER BY .. LIMIT
This includes: - [jOOQ/jOOQ#13412] Emulate UPDATE .. FROM with multiple tables or joins - [jOOQ/jOOQ#13425] Correctly emulate UPDATE .. FROM .. ORDER BY - [jOOQ/jOOQ#16631] Error when running DELETE .. USING .. LIMIT in MySQL - [jOOQ/jOOQ#16632] Wrong emulation of DELETE .. USING .. LIMIT where DELETE .. LIMIT is not natively supported - [jOOQ/jOOQ#16634] HANA UPDATE statement always repeats target table, even if unnecessary This doesn't include: - [jOOQ/jOOQ#16635] Support references to UPDATE .. FROM tables from SET clause in dialects that don't support MERGE
This commit is contained in:
parent
e2e7db2f81
commit
3a63e58699
@ -89,19 +89,19 @@ public interface DeleteQuery<R extends Record> extends ConditionProvider, Delete
|
||||
/**
|
||||
* Add tables to the <code>USING</code> clause.
|
||||
*/
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
void addUsing(TableLike<?> table);
|
||||
|
||||
/**
|
||||
* Add tables to the <code>USING</code> clause.
|
||||
*/
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
void addUsing(TableLike<?>... tables);
|
||||
|
||||
/**
|
||||
* Add tables to the <code>USING</code> clause.
|
||||
*/
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
void addUsing(Collection<? extends TableLike<?>> tables);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@ -103,21 +103,21 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
|
||||
* Add a <code>USING</code> clause to the query.
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
DeleteWhereStep<R> using(TableLike<?> table);
|
||||
|
||||
/**
|
||||
* Add a <code>USING</code> clause to the query.
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
DeleteWhereStep<R> using(TableLike<?>... tables);
|
||||
|
||||
/**
|
||||
* Add a <code>USING</code> clause to the query.
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
DeleteWhereStep<R> using(Collection<? extends TableLike<?>> tables);
|
||||
|
||||
/**
|
||||
@ -132,7 +132,7 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
|
||||
* @see SQL
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
@PlainSQL
|
||||
DeleteWhereStep<R> using(SQL sql);
|
||||
|
||||
@ -148,7 +148,7 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
|
||||
* @see SQL
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
@PlainSQL
|
||||
DeleteWhereStep<R> using(String sql);
|
||||
|
||||
@ -165,7 +165,7 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
|
||||
* @see SQL
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
@PlainSQL
|
||||
DeleteWhereStep<R> using(String sql, Object... bindings);
|
||||
|
||||
@ -182,7 +182,7 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
|
||||
* @see SQL
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
@PlainSQL
|
||||
DeleteWhereStep<R> using(String sql, QueryPart... parts);
|
||||
|
||||
@ -192,6 +192,6 @@ public interface DeleteUsingStep<R extends Record> extends DeleteWhereStep<R> {
|
||||
* @see DSL#table(Name)
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
|
||||
@Support
|
||||
DeleteWhereStep<R> using(Name name);
|
||||
}
|
||||
|
||||
@ -106,21 +106,21 @@ public interface UpdateFromStep<R extends Record> extends UpdateWhereStep<R> {
|
||||
* Add a <code>FROM</code> clause to the query.
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
UpdateWhereStep<R> from(TableLike<?> table);
|
||||
|
||||
/**
|
||||
* Add a <code>FROM</code> clause to the query.
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
UpdateWhereStep<R> from(TableLike<?>... table);
|
||||
|
||||
/**
|
||||
* Add a <code>FROM</code> clause to the query.
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
UpdateWhereStep<R> from(Collection<? extends TableLike<?>> tables);
|
||||
|
||||
/**
|
||||
@ -135,7 +135,7 @@ public interface UpdateFromStep<R extends Record> extends UpdateWhereStep<R> {
|
||||
* @see SQL
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
@PlainSQL
|
||||
UpdateWhereStep<R> from(SQL sql);
|
||||
|
||||
@ -151,7 +151,7 @@ public interface UpdateFromStep<R extends Record> extends UpdateWhereStep<R> {
|
||||
* @see SQL
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
@PlainSQL
|
||||
UpdateWhereStep<R> from(String sql);
|
||||
|
||||
@ -168,7 +168,7 @@ public interface UpdateFromStep<R extends Record> extends UpdateWhereStep<R> {
|
||||
* @see SQL
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
@PlainSQL
|
||||
UpdateWhereStep<R> from(String sql, Object... bindings);
|
||||
|
||||
@ -185,7 +185,7 @@ public interface UpdateFromStep<R extends Record> extends UpdateWhereStep<R> {
|
||||
* @see SQL
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
@PlainSQL
|
||||
UpdateWhereStep<R> from(String sql, QueryPart... parts);
|
||||
|
||||
@ -195,6 +195,6 @@ public interface UpdateFromStep<R extends Record> extends UpdateWhereStep<R> {
|
||||
* @see DSL#table(Name)
|
||||
*/
|
||||
@NotNull @CheckReturnValue
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
UpdateWhereStep<R> from(Name name);
|
||||
}
|
||||
|
||||
@ -373,7 +373,7 @@ public interface UpdateQuery<R extends Record> extends StoreQuery<R>, ConditionP
|
||||
*
|
||||
* @param from The added tables
|
||||
*/
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
void addFrom(TableLike<?> from);
|
||||
|
||||
/**
|
||||
@ -381,7 +381,7 @@ public interface UpdateQuery<R extends Record> extends StoreQuery<R>, ConditionP
|
||||
*
|
||||
* @param from The added tables
|
||||
*/
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
void addFrom(TableLike<?>... from);
|
||||
|
||||
/**
|
||||
@ -389,7 +389,7 @@ public interface UpdateQuery<R extends Record> extends StoreQuery<R>, ConditionP
|
||||
*
|
||||
* @param from The added tables
|
||||
*/
|
||||
@Support({ CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE })
|
||||
@Support
|
||||
void addFrom(Collection<? extends TableLike<?>> from);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
|
||||
package org.jooq.impl;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static org.jooq.Clause.DELETE;
|
||||
import static org.jooq.Clause.DELETE_DELETE;
|
||||
import static org.jooq.Clause.DELETE_RETURNING;
|
||||
@ -49,6 +50,7 @@ import static org.jooq.Clause.DELETE_WHERE;
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.CLICKHOUSE;
|
||||
// ...
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.CUBRID;
|
||||
// ...
|
||||
// ...
|
||||
@ -67,6 +69,7 @@ import static org.jooq.SQLDialect.MARIADB;
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.MYSQL;
|
||||
// ...
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.POSTGRES;
|
||||
// ...
|
||||
// ...
|
||||
@ -75,6 +78,7 @@ import static org.jooq.SQLDialect.SQLITE;
|
||||
// ...
|
||||
// ...
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.TRINO;
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.YUGABYTEDB;
|
||||
// ...
|
||||
@ -82,11 +86,13 @@ 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.name;
|
||||
import static org.jooq.impl.DSL.noCondition;
|
||||
import static org.jooq.impl.DSL.row;
|
||||
import static org.jooq.impl.DSL.select;
|
||||
import static org.jooq.impl.DSL.systemName;
|
||||
import static org.jooq.impl.DSL.trueCondition;
|
||||
import static org.jooq.impl.DeleteQueryImpl.keyFieldsCondition;
|
||||
import static org.jooq.impl.InlineDerivedTable.hasInlineDerivedTables;
|
||||
import static org.jooq.impl.InlineDerivedTable.transformInlineDerivedTables;
|
||||
import static org.jooq.impl.InlineDerivedTable.transformInlineDerivedTables0;
|
||||
@ -99,12 +105,17 @@ import static org.jooq.impl.Keywords.K_ROWS;
|
||||
import static org.jooq.impl.Keywords.K_USING;
|
||||
import static org.jooq.impl.Keywords.K_WHERE;
|
||||
import static org.jooq.impl.Tools.containsDeclaredTable;
|
||||
import static org.jooq.impl.Tools.fieldName;
|
||||
import static org.jooq.impl.Tools.map;
|
||||
import static org.jooq.impl.Tools.traverseJoins;
|
||||
import static org.jooq.impl.Tools.BooleanDataKey.DATA_UNQUALIFY_LOCAL_SCOPE;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@ -115,6 +126,7 @@ import org.jooq.Configuration;
|
||||
import org.jooq.Context;
|
||||
import org.jooq.DeleteQuery;
|
||||
import org.jooq.Field;
|
||||
import org.jooq.Name;
|
||||
import org.jooq.Operator;
|
||||
import org.jooq.OrderField;
|
||||
// ...
|
||||
@ -152,11 +164,16 @@ implements
|
||||
|
||||
// LIMIT is supported but not ORDER BY
|
||||
static final Set<SQLDialect> NO_SUPPORT_ORDER_BY_LIMIT = SQLDialect.supportedBy(IGNITE);
|
||||
|
||||
// LIMIT is supported only in the absence of USING
|
||||
static final Set<SQLDialect> NO_SUPPORT_LIMIT_WITH_USING = SQLDialect.supportedUntil(MARIADB, MYSQL);
|
||||
|
||||
static final Set<SQLDialect> SUPPORT_MULTITABLE_DELETE = SQLDialect.supportedBy(MARIADB, MYSQL);
|
||||
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> NO_SUPPORT_REPEAT_FROM_IN_USING = SQLDialect.supportedBy(DUCKDB, POSTGRES, SQLITE, YUGABYTEDB);
|
||||
static final Set<SQLDialect> REQUIRES_WHERE = SQLDialect.supportedBy(CLICKHOUSE);
|
||||
static final Set<SQLDialect> EMULATE_USING_WITH_MERGE = SQLDialect.supportedBy(DERBY, FIREBIRD, H2, HSQLDB);
|
||||
static final Set<SQLDialect> NO_SUPPORT_USING = SQLDialect.supportedUntil(CLICKHOUSE, CUBRID, IGNITE, SQLITE, TRINO, YUGABYTEDB);
|
||||
|
||||
|
||||
|
||||
@ -294,6 +311,16 @@ implements
|
||||
ctx.scopeEnd();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
static final Condition keyFieldsCondition(Context<?> ctx, Table<?> t1, MergeUsing mu) {
|
||||
Field<?>[] keyFields = keyFields(ctx, t1);
|
||||
|
||||
if (keyFields.length == 1)
|
||||
return keyFields[0].eq((Field) mu.table().field((Field<?>) (mu.lookup().isEmpty() ? keyFields[0] : mu.lookup().get(keyFields[0]))));
|
||||
else
|
||||
return row(keyFields).eq(row(mu.table().fields(mu.lookup().isEmpty() ? keyFields : map(keyFields, f -> mu.lookup().get(f), Field<?>[]::new))));
|
||||
}
|
||||
|
||||
static final Field<?>[] keyFields(Context<?> ctx, Table<?> table) {
|
||||
|
||||
// [#16569] [#16571] The PostgreSQL ctid is not unique in a logically partitioned table
|
||||
@ -301,9 +328,9 @@ implements
|
||||
? ctx.family() == POSTGRES
|
||||
? new Field[] { field(systemName("tableoid")), table.rowid() }
|
||||
: new Field[] { table.rowid() }
|
||||
: (table.getPrimaryKey() != null
|
||||
: table.fields((table.getPrimaryKey() != null
|
||||
? table.getPrimaryKey()
|
||||
: table.getKeys().get(0)).getFieldsArray();
|
||||
: table.getKeys().get(0)).getFieldsArray());
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@ -325,6 +352,10 @@ implements
|
||||
// [#11925] In MySQL, the tables in FROM must be repeated in USING
|
||||
boolean hasUsing = !using.isEmpty() || multiTableJoin || specialDeleteAsSyntax && Tools.alias(t) != null;
|
||||
|
||||
// [#16632] If LIMIT is emulated in a self-joined subquery, then USING must be pushed down into the subquery as well
|
||||
if (hasUsing && limitEmulation(ctx))
|
||||
hasUsing = false;
|
||||
|
||||
// [#11924] Multiple tables listed in the FROM clause mean this is a
|
||||
// MySQL style multi table DELETE
|
||||
if (multiTableJoin)
|
||||
@ -348,22 +379,21 @@ implements
|
||||
ctx.visit(K_FROM).sql(' ').declareTables(!specialDeleteAsSyntax, c -> c.visit(t));
|
||||
|
||||
// [#14011] Additional predicates that are added for various reasons
|
||||
Condition moreWhere = noCondition();
|
||||
ConditionProviderImpl where0 = new ConditionProviderImpl();
|
||||
TableList u;
|
||||
|
||||
if (REQUIRE_REPEAT_FROM_IN_USING.contains(ctx.dialect()) && !containsDeclaredTable(using, t)) {
|
||||
u = new TableList(t);
|
||||
u.addAll(using);
|
||||
}
|
||||
else if (NO_SUPPORT_REPEAT_FROM_IN_USING.contains(ctx.dialect()) && containsDeclaredTable(using, t)) {
|
||||
u = new TableList(using);
|
||||
u.remove(t);
|
||||
}
|
||||
else
|
||||
u = using;
|
||||
|
||||
if (hasUsing) {
|
||||
TableList u;
|
||||
|
||||
if (REQUIRE_REPEAT_FROM_IN_USING.contains(ctx.dialect()) && !containsDeclaredTable(using, t)) {
|
||||
u = new TableList(t);
|
||||
u.addAll(using);
|
||||
}
|
||||
else if (NO_SUPPORT_REPEAT_FROM_IN_USING.contains(ctx.dialect()) && containsDeclaredTable(using, t)) {
|
||||
u = new TableList(using);
|
||||
u.remove(t);
|
||||
}
|
||||
else
|
||||
u = using;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -391,52 +421,46 @@ implements
|
||||
|
||||
|
||||
boolean noSupportParametersInWhere = false;
|
||||
if (moreWhere instanceof NoCondition && REQUIRES_WHERE.contains(ctx.dialect()))
|
||||
moreWhere = trueCondition();
|
||||
|
||||
Condition where = DSL.and(getWhere(), moreWhere);
|
||||
boolean noQualifyInWhere = NO_SUPPORT_QUALIFY_IN_WHERE.contains(ctx.dialect());
|
||||
|
||||
if (limit != null && NO_SUPPORT_LIMIT.contains(ctx.dialect()) || !orderBy.isEmpty() && NO_SUPPORT_ORDER_BY_LIMIT.contains(ctx.dialect())) {
|
||||
Field<?>[] keyFields = keyFields(ctx, table());
|
||||
if (limitEmulation(ctx)) {
|
||||
|
||||
ctx.start(DELETE_WHERE)
|
||||
.formatSeparator()
|
||||
.visit(K_WHERE).sql(' ');
|
||||
// [#16632] Push down USING table list here
|
||||
TableList t0 = new TableList();
|
||||
if (!containsDeclaredTable(u, t))
|
||||
t0.add(t);
|
||||
t0.addAll(u);
|
||||
|
||||
Field<?>[] keyFields = keyFields(ctx, t);
|
||||
|
||||
if (keyFields.length == 1)
|
||||
where0.addConditions(keyFields[0].in(select((Field) keyFields[0]).from(t0).where(getWhere()).orderBy(orderBy).limit(limit)));
|
||||
else
|
||||
where0.addConditions(row(keyFields).in(select(keyFields).from(t0).where(getWhere()).orderBy(orderBy).limit(limit)));
|
||||
}
|
||||
else if (hasWhere())
|
||||
where0.addConditions(getWhere());
|
||||
else if (!where0.hasWhere() && REQUIRES_WHERE.contains(ctx.dialect()))
|
||||
where0.addConditions(trueCondition());
|
||||
|
||||
ctx.start(DELETE_WHERE);
|
||||
|
||||
if (where0.hasWhere()) {
|
||||
ctx.paramTypeIf(ParamType.INLINED, noSupportParametersInWhere, c -> {
|
||||
if (noQualifyInWhere)
|
||||
ctx.data(DATA_UNQUALIFY_LOCAL_SCOPE, true);
|
||||
|
||||
if (keyFields.length == 1)
|
||||
c.visit(keyFields[0].in(select((Field) keyFields[0]).from(table()).where(where).orderBy(orderBy).limit(limit)));
|
||||
else
|
||||
c.visit(row(keyFields).in(select(keyFields).from(table()).where(where).orderBy(orderBy).limit(limit)));
|
||||
ctx.formatSeparator()
|
||||
.visit(K_WHERE).sql(' ').visit(where0);
|
||||
|
||||
if (noQualifyInWhere)
|
||||
ctx.data(DATA_UNQUALIFY_LOCAL_SCOPE, false);
|
||||
});
|
||||
|
||||
ctx.end(DELETE_WHERE);
|
||||
}
|
||||
else {
|
||||
ctx.start(DELETE_WHERE);
|
||||
|
||||
if (!(where instanceof NoCondition))
|
||||
ctx.paramTypeIf(ParamType.INLINED, noSupportParametersInWhere, c -> {
|
||||
c.formatSeparator().visit(K_WHERE).sql(' ');
|
||||
|
||||
if (noQualifyInWhere)
|
||||
ctx.data(DATA_UNQUALIFY_LOCAL_SCOPE, true);
|
||||
|
||||
ctx.visit(where);
|
||||
|
||||
if (noQualifyInWhere)
|
||||
ctx.data(DATA_UNQUALIFY_LOCAL_SCOPE, false);
|
||||
});
|
||||
|
||||
ctx.end(DELETE_WHERE);
|
||||
ctx.end(DELETE_WHERE);
|
||||
|
||||
if (!limitEmulation(ctx)) {
|
||||
if (!orderBy.isEmpty())
|
||||
ctx.formatSeparator()
|
||||
.visit(K_ORDER_BY).sql(' ')
|
||||
@ -450,10 +474,25 @@ implements
|
||||
ctx.end(DELETE_RETURNING);
|
||||
}
|
||||
|
||||
static final record MergeUsing(Table<?> table, boolean patchSource) {
|
||||
private final boolean limitEmulation(Context<?> ctx) {
|
||||
if (limit != null) {
|
||||
if (NO_SUPPORT_LIMIT.contains(ctx.dialect()))
|
||||
return true;
|
||||
if (NO_SUPPORT_LIMIT_WITH_USING.contains(ctx.dialect()) && !using.isEmpty())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!orderBy.isEmpty() && NO_SUPPORT_ORDER_BY_LIMIT.contains(ctx.dialect()))
|
||||
return true;
|
||||
|
||||
if (!using.isEmpty() && NO_SUPPORT_USING.contains(ctx.dialect()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static final record MergeUsing(Table<?> table, Map<Field<?>, Field<?>> lookup) {}
|
||||
|
||||
static final MergeUsing mergeUsing(
|
||||
TableList tables,
|
||||
Table<?> table,
|
||||
@ -461,28 +500,32 @@ implements
|
||||
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);
|
||||
if (orderBy.isEmpty() && limit == null && tables.size() == 1 && tables.get(0) instanceof TableImpl) {
|
||||
return new MergeUsing(tables.get(0), emptyMap());
|
||||
}
|
||||
|
||||
// TODO [#13326]: Avoid the JOIN if it isn't strictly necessary
|
||||
// (i.e. if ORDER BY references only from, not table)
|
||||
else
|
||||
else {
|
||||
Map<Field<?>, Field<?>> lookup = new LinkedHashMap<>();
|
||||
Name s = name("s");
|
||||
|
||||
int i = 0;
|
||||
for (Field<?> f : table.fields())
|
||||
lookup.put(f, field(s.append(fieldName(++i)), f.getDataType()));
|
||||
for (Field<?> f : tables.fields())
|
||||
lookup.put(f, field(s.append(fieldName(++i)), f.getDataType()));
|
||||
|
||||
return new MergeUsing(
|
||||
select(tables.fields())
|
||||
select(map(lookup.entrySet(), e -> e.getKey().as(e.getValue())))
|
||||
.from(tables)
|
||||
.join(table).on(condition)
|
||||
.orderBy(orderBy)
|
||||
.limit(limit)
|
||||
.asTable("s"),
|
||||
patchSource
|
||||
.asTable(s),
|
||||
lookup
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private final void acceptUsingAsMerge(Context<?> ctx) {
|
||||
@ -504,22 +547,7 @@ implements
|
||||
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());
|
||||
ctx.visit(mergeInto(table).using(mu.table()).on(mu.lookup().isEmpty() ? c : keyFieldsCondition(ctx, t, mu)).whenMatchedThenDelete());
|
||||
}
|
||||
|
||||
static final void acceptLimit(Context<?> ctx, Field<? extends Number> limit) {
|
||||
|
||||
@ -51,7 +51,7 @@ import static org.jooq.Clause.UPDATE_WHERE;
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.CLICKHOUSE;
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.CUBRID;
|
||||
import static org.jooq.SQLDialect.*;
|
||||
// ...
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.DERBY;
|
||||
@ -59,6 +59,7 @@ import static org.jooq.SQLDialect.DUCKDB;
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.FIREBIRD;
|
||||
// ...
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.H2;
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.HSQLDB;
|
||||
@ -90,10 +91,12 @@ 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.keyFieldsCondition;
|
||||
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;
|
||||
import static org.jooq.impl.Keywords.K_AND;
|
||||
import static org.jooq.impl.Keywords.K_FROM;
|
||||
import static org.jooq.impl.Keywords.K_ORDER_BY;
|
||||
import static org.jooq.impl.Keywords.K_SET;
|
||||
@ -208,6 +211,7 @@ implements
|
||||
|
||||
static final Set<SQLDialect> REQUIRES_WHERE = SQLDialect.supportedBy(CLICKHOUSE);
|
||||
static final Set<SQLDialect> EMULATE_FROM_WITH_MERGE = SQLDialect.supportedUntil(CUBRID, DERBY, FIREBIRD, H2, HSQLDB);
|
||||
static final Set<SQLDialect> NO_SUPPORT_FROM = SQLDialect.supportedUntil(CLICKHOUSE, IGNITE, MARIADB, MYSQL, TRINO, YUGABYTEDB);
|
||||
static final Set<SQLDialect> EMULATE_RETURNING_WITH_UPSERT = SQLDialect.supportedBy(MARIADB);
|
||||
|
||||
// LIMIT is not supported at all
|
||||
@ -216,7 +220,6 @@ implements
|
||||
// LIMIT is supported but not ORDER BY
|
||||
static final Set<SQLDialect> NO_SUPPORT_ORDER_BY_LIMIT = SQLDialect.supportedBy(IGNITE);
|
||||
static final Set<SQLDialect> NO_SUPPORT_UPDATE_JOIN = SQLDialect.supportedBy(CLICKHOUSE, CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, IGNITE, POSTGRES, SQLITE, YUGABYTEDB);
|
||||
|
||||
// https://github.com/ClickHouse/ClickHouse/issues/61020
|
||||
static final Set<SQLDialect> NO_SUPPORT_QUALIFY_IN_WHERE = SQLDialect.supportedBy(CLICKHOUSE);
|
||||
|
||||
@ -664,8 +667,7 @@ implements
|
||||
FieldMapForUpdate um = updateMap;
|
||||
MergeUsing mu = mergeUsing(from, t, c, orderBy, limit);
|
||||
|
||||
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")) {
|
||||
|
||||
if (!mu.lookup().isEmpty() && 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")) {
|
||||
|
||||
|
||||
|
||||
@ -682,7 +684,7 @@ implements
|
||||
|
||||
}
|
||||
|
||||
ctx.visit(mergeInto(table).using(mu.table()).on(c).whenMatchedThenUpdate().set(um));
|
||||
ctx.visit(mergeInto(table).using(mu.table()).on(mu.lookup().isEmpty() ? c : keyFieldsCondition(ctx, t, mu)).whenMatchedThenUpdate().set(um));
|
||||
}
|
||||
|
||||
final boolean updatesField(Field<?> field) {
|
||||
@ -746,6 +748,9 @@ implements
|
||||
.declareTables(declareTables)
|
||||
.end(UPDATE_UPDATE);
|
||||
|
||||
// [#16634] Prevent unnecessary FROM clause in some dialects, e.g. HANA
|
||||
boolean hasFrom = !from.isEmpty() && !NO_SUPPORT_FROM.contains(ctx.dialect());
|
||||
|
||||
|
||||
|
||||
|
||||
@ -768,51 +773,49 @@ implements
|
||||
|
||||
|
||||
|
||||
acceptFrom(ctx);
|
||||
boolean noQualifyInWhere = NO_SUPPORT_QUALIFY_IN_WHERE.contains(ctx.dialect());
|
||||
if (hasFrom)
|
||||
acceptFrom(ctx);
|
||||
|
||||
if (limit != null && NO_SUPPORT_LIMIT.contains(ctx.dialect()) || !orderBy.isEmpty() && NO_SUPPORT_ORDER_BY_LIMIT.contains(ctx.dialect())) {
|
||||
Field<?>[] keyFields = DeleteQueryImpl.keyFields(ctx, table());
|
||||
ConditionProviderImpl where0 = new ConditionProviderImpl();
|
||||
if (limitEmulation(ctx)) {
|
||||
|
||||
ctx.start(UPDATE_WHERE)
|
||||
.formatSeparator()
|
||||
.visit(K_WHERE).sql(' ');
|
||||
// [#16632] Push down USING table list here
|
||||
TableList t0 = new TableList();
|
||||
if (!containsDeclaredTable(from, t))
|
||||
t0.add(t);
|
||||
t0.addAll(from);
|
||||
|
||||
Field<?>[] keyFields = DeleteQueryImpl.keyFields(ctx, t);
|
||||
|
||||
if (keyFields.length == 1)
|
||||
where0.addConditions(keyFields[0].in(select((Field) keyFields[0]).from(t0).where(getWhere()).orderBy(orderBy).limit(limit)));
|
||||
else
|
||||
where0.addConditions(row(keyFields).in(select(keyFields).from(t0).where(getWhere()).orderBy(orderBy).limit(limit)));
|
||||
}
|
||||
|
||||
if (hasWhere() && (from.isEmpty() || !NO_SUPPORT_FROM.contains(ctx.dialect())))
|
||||
where0.addConditions(getWhere());
|
||||
else if (!where0.hasWhere() && REQUIRES_WHERE.contains(ctx.dialect()))
|
||||
where0.addConditions(trueCondition());
|
||||
|
||||
ctx.start(UPDATE_WHERE);
|
||||
|
||||
if (where0.hasWhere()) {
|
||||
boolean noQualifyInWhere = NO_SUPPORT_QUALIFY_IN_WHERE.contains(ctx.dialect());
|
||||
|
||||
if (noQualifyInWhere)
|
||||
ctx.data(DATA_UNQUALIFY_LOCAL_SCOPE, true);
|
||||
|
||||
if (keyFields.length == 1)
|
||||
ctx.visit(keyFields[0].in(select((Field) keyFields[0]).from(table()).where(getWhere()).orderBy(orderBy).limit(limit)));
|
||||
else
|
||||
ctx.visit(row(keyFields).in(select(keyFields).from(table()).where(getWhere()).orderBy(orderBy).limit(limit)));
|
||||
ctx.formatSeparator()
|
||||
.visit(K_WHERE).sql(' ').visit(where0);
|
||||
|
||||
if (noQualifyInWhere)
|
||||
ctx.data(DATA_UNQUALIFY_LOCAL_SCOPE, false);
|
||||
|
||||
ctx.end(UPDATE_WHERE);
|
||||
}
|
||||
else {
|
||||
ctx.start(UPDATE_WHERE);
|
||||
|
||||
if (hasWhere()) {
|
||||
ctx.formatSeparator()
|
||||
.visit(K_WHERE).sql(' ');
|
||||
|
||||
if (noQualifyInWhere)
|
||||
ctx.data(DATA_UNQUALIFY_LOCAL_SCOPE, true);
|
||||
|
||||
ctx.visit(getWhere());
|
||||
|
||||
if (noQualifyInWhere)
|
||||
ctx.data(DATA_UNQUALIFY_LOCAL_SCOPE, false);
|
||||
}
|
||||
else if (REQUIRES_WHERE.contains(ctx.dialect()))
|
||||
ctx.formatSeparator()
|
||||
.visit(K_WHERE).sql(' ')
|
||||
.visit(trueCondition());
|
||||
|
||||
ctx.end(UPDATE_WHERE);
|
||||
ctx.end(UPDATE_WHERE);
|
||||
|
||||
if (!limitEmulation(ctx)) {
|
||||
if (!orderBy.isEmpty())
|
||||
ctx.formatSeparator()
|
||||
.visit(K_ORDER_BY).sql(' ')
|
||||
@ -826,6 +829,19 @@ implements
|
||||
ctx.end(UPDATE_RETURNING);
|
||||
}
|
||||
|
||||
private final boolean limitEmulation(Context<?> ctx) {
|
||||
if (limit != null && NO_SUPPORT_LIMIT.contains(ctx.dialect()))
|
||||
return true;
|
||||
|
||||
if (!orderBy.isEmpty() && NO_SUPPORT_ORDER_BY_LIMIT.contains(ctx.dialect()))
|
||||
return true;
|
||||
|
||||
if (!from.isEmpty() && NO_SUPPORT_FROM.contains(ctx.dialect()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private final void acceptFrom(Context<?> ctx) {
|
||||
ctx.start(UPDATE_FROM);
|
||||
TableList f = new TableList();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user