diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index bb51b3ea79..5c42d97702 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -49,6 +49,7 @@ import static org.jooq.JoinType.LEFT_OUTER_JOIN; import static org.jooq.conf.InvocationOrder.REVERSE; import static org.jooq.conf.ParamType.INDEXED; import static org.jooq.impl.JoinTable.onKey0; +import static org.jooq.impl.TableFieldImpl.implicitJoinAsScalarSubquery; import static org.jooq.impl.Tools.DATAKEY_RESET_IN_SUBQUERY_SCOPE; import static org.jooq.impl.Tools.EMPTY_CLAUSE; import static org.jooq.impl.Tools.EMPTY_QUERYPART; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java index 2ef875ef40..6841b89543 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java @@ -234,7 +234,7 @@ class DefaultRenderContext extends AbstractContext implements Ren List> tables = new ArrayList<>(); // [#14985] Traverse paths only when referencing paths (!forceNew), - // not when declaring them in FROM (forceNew) + // not when declaring them in FROM or USING (forceNew) if (!forceNew) { while ((child = TableImpl.path(root)) != null) { diff --git a/jOOQ/src/main/java/org/jooq/impl/DeleteQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/DeleteQueryImpl.java index 90be15d6b2..5c3e85b99c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DeleteQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/DeleteQueryImpl.java @@ -87,12 +87,10 @@ 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; @@ -104,6 +102,8 @@ import static org.jooq.impl.Keywords.K_ORDER_BY; 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.SelectQueryImpl.addPathConditions; +import static org.jooq.impl.SelectQueryImpl.prependPathJoins; import static org.jooq.impl.Tools.containsDeclaredTable; import static org.jooq.impl.Tools.fieldName; import static org.jooq.impl.Tools.map; @@ -112,13 +112,11 @@ 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; import org.jooq.Clause; import org.jooq.Condition; @@ -138,7 +136,6 @@ 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; @@ -407,7 +404,7 @@ implements - TableList u0 = u; + TableList u0 = traverseJoinsAndAddPathConditions(ctx, where0, u); ctx.formatSeparator() .visit(K_USING) .sql(' ') @@ -474,6 +471,21 @@ implements ctx.end(DELETE_RETURNING); } + static final TableList traverseJoinsAndAddPathConditions(Context ctx, ConditionProviderImpl where0, TableList u) { + + // [#15636] Allow for explicit path joins in USING + traverseJoins(u, x -> { + if (x instanceof TableImpl) + ctx.scopeRegister(x, true); + }); + + // [#14985] [#15755] Add skipped join segments from path joins + u = prependPathJoins(ctx, where0, u); + addPathConditions(ctx, where0, u); + + return u; + } + private final boolean limitEmulation(Context ctx) { if (limit != null) { if (NO_SUPPORT_LIMIT.contains(ctx.dialect())) @@ -500,7 +512,14 @@ implements SortFieldList orderBy, Field limit ) { - if (orderBy.isEmpty() && limit == null && tables.size() == 1 && tables.get(0) instanceof TableImpl) { + + // [#15636] Cannot use the simplified emulation when there are join paths in the table list (USING or FROM clause) + if (orderBy.isEmpty() + && limit == null + && tables.size() == 1 + && tables.get(0) instanceof TableImpl + && TableImpl.path(tables.get(0)) == null + ) { return new MergeUsing(tables.get(0), emptyMap()); } diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index bf6ee40ff9..24e3bc8b63 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -2896,7 +2896,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp - private static final TableList prependPathJoins(Context ctx, ConditionProviderImpl where, TableList tablelist) { + static final TableList prependPathJoins(Context ctx, ConditionProviderImpl where, TableList tablelist) { TableList result = new TableList(tablelist); for (int i = 0; i < result.size(); i++) { @@ -4375,9 +4375,9 @@ final class SelectQueryImpl extends AbstractResultQuery imp return getWhere(ctx, tablelist, new ConditionProviderImpl()); } - final ConditionProviderImpl getWhere(Context ctx, TableList tablelist, ConditionProviderImpl result) { + final ConditionProviderImpl getWhere(Context ctx, TableList tablelist, ConditionProviderImpl where0) { if (condition.hasWhere()) - result.addConditions(condition.getWhere()); + where0.addConditions(condition.getWhere()); // Apply SEEK predicates in the WHERE clause only if: // - There is an ORDER BY clause (SEEK is non-deterministic) @@ -4386,23 +4386,9 @@ final class SelectQueryImpl extends AbstractResultQuery imp // and SEEK predicate is applied outside). See [#7459] // [#15820] We're not grouping if (!isGrouping() && !getOrderBy().isEmpty() && !getSeek().isEmpty() && unionOp.isEmpty()) - result.addConditions(getSeekCondition(ctx)); + where0.addConditions(getSeekCondition(ctx)); - // [#13639] Add JOIN predicates generated by path expressions in FROM clause. - for (Table t : tablelist) - addPathConditions(ctx, result, t); - - traverseJoins( - tablelist, null, null, - t -> { - if (t instanceof CrossJoin j) { - addPathConditions(ctx, result, j.lhs); - addPathConditions(ctx, result, j.rhs); - } - return true; - }, - null, null, null - ); + addPathConditions(ctx, where0, tablelist); @@ -4411,9 +4397,28 @@ final class SelectQueryImpl extends AbstractResultQuery imp if (NO_SUPPORT_LIMIT_ZERO.contains(ctx.dialect()) && limit.limitZero() && !isGrouping()) - result.addConditions(falseCondition()); + where0.addConditions(falseCondition()); - return result; + return where0; + } + + static final void addPathConditions(Context ctx, ConditionProviderImpl where0, TableList tablelist) { + + // [#13639] Add JOIN predicates generated by path expressions in FROM clause. + for (Table t : tablelist) + addPathConditions(ctx, where0, t); + + traverseJoins( + tablelist, null, null, + t -> { + if (t instanceof CrossJoin j) { + addPathConditions(ctx, where0, j.lhs); + addPathConditions(ctx, where0, j.rhs); + } + return true; + }, + null, null, null + ); } private static final void addPathConditions(Context ctx, ConditionProviderImpl result, Table t) { diff --git a/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java b/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java index 8d296f1ce9..3154e9b7ec 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java @@ -206,24 +206,20 @@ implements accept1(ctx); } - private final boolean implicitJoinAsScalarSubquery(Context ctx, TableImpl t, Table root) { + static final boolean implicitJoinAsScalarSubquery(Context ctx, TableImpl t, Table root) { return // [#15755] Never apply the scalar subquery rendering when we're already rendering an implicit // join tree. Otherwise, we'd get useless subqueries in the ON clauses. !TRUE.equals(ctx.data(DATA_RENDER_IMPLICIT_JOIN)) - + && !ctx.inScope(t) && ( // [#15754] Explicit scalar subqueries can be configured for implicit to-one paths - (t.childPath != null - && ctx.settings().getRenderImplicitJoinType() == SCALAR_SUBQUERY - && !ctx.inScope(t)) + (t.childPath != null && ctx.settings().getRenderImplicitJoinType() == SCALAR_SUBQUERY) // [#15755] The default is to render scalar subqueries for implicit to-many paths - || (t.parentPath != null - && (ctx.settings().getRenderImplicitJoinToManyType() == SCALAR_SUBQUERY) - && !ctx.inScope(t)) + || (t.parentPath != null && (ctx.settings().getRenderImplicitJoinToManyType() == SCALAR_SUBQUERY)) // [#7508] Implicit join path references inside of DML queries have to // be emulated differently diff --git a/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java index 263bdee9dc..933d3089e3 100644 --- a/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java @@ -93,6 +93,7 @@ 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.DeleteQueryImpl.traverseJoinsAndAddPathConditions; import static org.jooq.impl.InlineDerivedTable.hasInlineDerivedTables; import static org.jooq.impl.InlineDerivedTable.transformInlineDerivedTables; import static org.jooq.impl.InlineDerivedTable.transformInlineDerivedTables0; @@ -103,9 +104,12 @@ import static org.jooq.impl.Keywords.K_SET; import static org.jooq.impl.Keywords.K_UPDATE; import static org.jooq.impl.Keywords.K_WHERE; import static org.jooq.impl.SQLDataType.INTEGER; +import static org.jooq.impl.SelectQueryImpl.addPathConditions; +import static org.jooq.impl.SelectQueryImpl.prependPathJoins; import static org.jooq.impl.Tools.anyMatch; import static org.jooq.impl.Tools.containsDeclaredTable; import static org.jooq.impl.Tools.findAny; +import static org.jooq.impl.Tools.traverseJoins; import static org.jooq.impl.Tools.BooleanDataKey.DATA_UNQUALIFY_LOCAL_SCOPE; import java.util.Arrays; @@ -749,6 +753,9 @@ implements .declareTables(declareTables) .end(UPDATE_UPDATE); + ConditionProviderImpl where0 = new ConditionProviderImpl(); + TableList f = getFrom(ctx, where0); + @@ -771,9 +778,8 @@ implements - acceptFrom(ctx); + acceptFrom(ctx, where0, f); - ConditionProviderImpl where0 = new ConditionProviderImpl(); if (limitEmulation(ctx)) { // [#16632] Push down USING table list here @@ -839,12 +845,11 @@ implements return false; } - private final void acceptFrom(Context ctx) { - ctx.start(UPDATE_FROM); + private final TableList getFrom(Context ctx, ConditionProviderImpl where0) { + TableList f = new TableList(); // [#16634] Prevent unnecessary FROM clause in some dialects, e.g. HANA if (!NO_SUPPORT_FROM.contains(ctx.dialect())) { - TableList f = new TableList(); @@ -865,11 +870,21 @@ implements if (!f.isEmpty()) - ctx.formatSeparator() - .visit(K_FROM).sql(' ') - .declareTables(true, c -> c.visit(f)); + f = traverseJoinsAndAddPathConditions(ctx, where0, f); } + return f; + } + + private final void acceptFrom(Context ctx, ConditionProviderImpl where0, TableList f) { + ctx.start(UPDATE_FROM); + + // [#16634] Prevent unnecessary FROM clause in some dialects, e.g. HANA + if (!f.isEmpty()) + ctx.formatSeparator() + .visit(K_FROM).sql(' ') + .declareTables(true, c -> c.visit(f)); + ctx.end(UPDATE_FROM); }