diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index 5c42d97702..94e751a11e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -40,27 +40,23 @@ package org.jooq.impl; import static java.lang.Boolean.TRUE; import static org.jooq.JoinType.JOIN; 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.DSL.field; +import static org.jooq.impl.DSL.name; 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; import static org.jooq.impl.Tools.lazy; import static org.jooq.impl.Tools.traverseJoins; -import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONDITION; import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONTENT; import static org.jooq.impl.Tools.BooleanDataKey.DATA_NESTED_SET_OPERATIONS; import static org.jooq.impl.Tools.BooleanDataKey.DATA_OMIT_CLAUSE_EVENT_EMISSION; -import static org.jooq.impl.Tools.BooleanDataKey.DATA_UNQUALIFY_LOCAL_SCOPE; import static org.jooq.impl.Tools.BooleanDataKey.DATA_RENDER_IMPLICIT_JOIN; +import static org.jooq.impl.Tools.BooleanDataKey.DATA_UNALIAS_ALIASED_EXPRESSIONS; +import static org.jooq.impl.Tools.BooleanDataKey.DATA_UNQUALIFY_LOCAL_SCOPE; +import static org.jooq.impl.Tools.SimpleDataKey.DATA_OVERRIDE_ALIASES_IN_ORDER_BY; import static org.jooq.tools.StringUtils.defaultIfNull; import java.sql.PreparedStatement; @@ -76,7 +72,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.function.Consumer; import org.jooq.BindContext; @@ -110,7 +105,6 @@ import org.jooq.conf.SettingsTools; import org.jooq.conf.StatementType; import org.jooq.exception.DataAccessException; import org.jooq.impl.QOM.UEmpty; -import org.jooq.impl.Tools.BooleanDataKey; import org.jooq.impl.Tools.DataKey; @@ -120,11 +114,6 @@ import org.jooq.impl.Tools.DataKey; @SuppressWarnings("unchecked") abstract class AbstractContext> extends AbstractScope implements Context { - - - - - final ExecuteContext ctx; final PreparedStatement stmt; @@ -181,28 +170,15 @@ abstract class AbstractContext> extends AbstractScope imple VisitListenerProvider[] providers = configuration.visitListenerProviders(); - // [#2080] [#3935] Currently, the InternalVisitListener is not used everywhere - boolean useInternalVisitListener = - false - - - - ; - // [#6758] Avoid this allocation if unneeded - VisitListener[] visitListeners = providers.length > 0 || useInternalVisitListener - ? new VisitListener[providers.length + (useInternalVisitListener ? 1 : 0)] + VisitListener[] visitListeners = providers.length > 0 + ? new VisitListener[providers.length] : null; if (visitListeners != null) { for (int i = 0; i < providers.length; i++) visitListeners[i] = providers[i].provide(); - - - - - this.visitContext = new DefaultVisitContext(); this.visitParts = new ArrayDeque<>(); this.visitClauses = new ArrayDeque<>(); @@ -310,6 +286,10 @@ abstract class AbstractContext> extends AbstractScope imple } } + // [#16928] Apply type specific replacements that can't be implemented in individual types, + // and for which an internal VisitListener is overkill + part = typeSpecificReplacements(part); + // Issue start clause events // ----------------------------------------------------------------- Clause[] clauses = Tools.isNotEmpty(visitListenersStart) ? clause(part) : null; @@ -396,6 +376,26 @@ abstract class AbstractContext> extends AbstractScope imple return (C) this; } + static final record AliasOverride(List> originalFields, List> aliasedFields) {} + + private final QueryPart typeSpecificReplacements(QueryPart part) { + if (!declareFields() && part instanceof Field) { + + // [#2080] Override the actual alias in case a synthetic alias is generated + // in the SELECT clause + AliasOverride override = (AliasOverride) data(DATA_OVERRIDE_ALIASES_IN_ORDER_BY); + + // Don't combine the effects of DATA_OVERRIDE_ALIASES_IN_ORDER_BY with DATA_UNALIAS_ALIASES_IN_ORDER_BY + if (override != null && !TRUE.equals(data(DATA_UNALIAS_ALIASED_EXPRESSIONS))) { + for (int i = 0; i < override.originalFields().size(); i++) + if (part.equals(override.originalFields().get(i))) + part = field(name(override.aliasedFields().get(i).getName())); + } + } + + return part; + } + @Override public final C visitSubquery(QueryPart part) { Tools.visitSubquery(this, part); diff --git a/jOOQ/src/main/java/org/jooq/impl/InternalVisitListener.java b/jOOQ/src/main/java/org/jooq/impl/InternalVisitListener.java deleted file mode 100644 index 47bfe15e0c..0000000000 --- a/jOOQ/src/main/java/org/jooq/impl/InternalVisitListener.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Other licenses: - * ----------------------------------------------------------------------------- - * Commercial licenses for this work are available. These replace the above - * ASL 2.0 and offer limited warranties, support, maintenance, and commercial - * database integrations. - * - * For more information, please visit: https://www.jooq.org/legal/licensing - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ -package org.jooq.impl; - -import static java.lang.Boolean.TRUE; -import static org.jooq.impl.DSL.field; -import static org.jooq.impl.DSL.name; -import static org.jooq.impl.Tools.BooleanDataKey.DATA_UNALIAS_ALIASED_EXPRESSIONS; -import static org.jooq.impl.Tools.SimpleDataKey.DATA_OVERRIDE_ALIASES_IN_ORDER_BY; - -import java.util.List; - -import org.jooq.Field; -// ... -import org.jooq.QueryPart; -import org.jooq.VisitContext; -import org.jooq.VisitListener; - -/** - * A {@link VisitListener} used by jOOQ internally, to implement some useful - * features. - *

- * Features implemented are: - *

[#2790] Keep a locally scoped data map, scoped for the current subquery

- *

- * Sometimes, it is useful to have some information only available while - * visiting QueryParts in the same context of the current subquery, e.g. when - * communicating between SELECT and WINDOW clause, as is required to emulate - * [#531]. - *

- * - * @author Lukas Eder - */ -final class InternalVisitListener implements VisitListener { - - - - - - - - - - - - - - - - - - - - - - - - - - - -} diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index d4f249c9c6..316458457e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -324,6 +324,7 @@ import org.jooq.WindowDefinition; import org.jooq.XML; import org.jooq.conf.AutoAliasExpressions; import org.jooq.exception.DataAccessException; +import org.jooq.impl.AbstractContext.AliasOverride; import org.jooq.impl.ForLock.ForLockMode; import org.jooq.impl.ForLock.ForLockWaitMode; import org.jooq.impl.QOM.CompareCondition; @@ -1798,10 +1799,12 @@ final class SelectQueryImpl extends AbstractResultQuery imp try { - List> originalFields = null; - List> alternativeFields = null; + AliasOverride aliasOverride = null; if (selectAliases != null) { + List> originalFields = null; + List> alternativeFields = null; + context.data().remove(DATA_SELECT_ALIASES); @@ -1810,6 +1813,8 @@ final class SelectQueryImpl extends AbstractResultQuery imp alternativeFields = map(originalFields = getSelect(), (f, i) -> i < a.length && a[i] != null ? f.as(a[i]) : f ); + + aliasOverride = new AliasOverride(originalFields, alternativeFields); } if (TRUE.equals(renderTrailingLimit)) @@ -1950,13 +1955,13 @@ final class SelectQueryImpl extends AbstractResultQuery imp case MARIADB: { if (getLimit().isApplicable() && getLimit().isExpression()) - toSQLReferenceLimitWithWindowFunctions(context, originalFields, alternativeFields); + toSQLReferenceLimitWithWindowFunctions(context, aliasOverride); else - toSQLReferenceLimitDefault(context, originalFields, alternativeFields); + toSQLReferenceLimitDefault(context, aliasOverride); break; } @@ -1966,7 +1971,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp - toSQLReferenceLimitDefault(context, originalFields, alternativeFields); + toSQLReferenceLimitDefault(context, aliasOverride); break; } @@ -1977,18 +1982,18 @@ final class SelectQueryImpl extends AbstractResultQuery imp case FIREBIRD: case MYSQL: { if (getLimit().isApplicable() && (getLimit().withTies() || getLimit().isExpression())) - toSQLReferenceLimitWithWindowFunctions(context, originalFields, alternativeFields); + toSQLReferenceLimitWithWindowFunctions(context, aliasOverride); else - toSQLReferenceLimitDefault(context, originalFields, alternativeFields); + toSQLReferenceLimitDefault(context, aliasOverride); break; } case TRINO: { if (getLimit().isApplicable() && getLimit().isExpression()) - toSQLReferenceLimitWithWindowFunctions(context, originalFields, alternativeFields); + toSQLReferenceLimitWithWindowFunctions(context, aliasOverride); else - toSQLReferenceLimitDefault(context, originalFields, alternativeFields); + toSQLReferenceLimitDefault(context, aliasOverride); break; } @@ -2000,16 +2005,16 @@ final class SelectQueryImpl extends AbstractResultQuery imp case DUCKDB: case YUGABYTEDB: { if (getLimit().isApplicable() && getLimit().withTies()) - toSQLReferenceLimitWithWindowFunctions(context, originalFields, alternativeFields); + toSQLReferenceLimitWithWindowFunctions(context, aliasOverride); else - toSQLReferenceLimitDefault(context, originalFields, alternativeFields); + toSQLReferenceLimitDefault(context, aliasOverride); break; } // By default, render the dialect's limit clause default: { - toSQLReferenceLimitDefault(context, originalFields, alternativeFields); + toSQLReferenceLimitDefault(context, aliasOverride); break; } } @@ -2113,26 +2118,26 @@ final class SelectQueryImpl extends AbstractResultQuery imp /** * The default LIMIT / OFFSET clause in most dialects */ - private final void toSQLReferenceLimitDefault(Context context, List> originalFields, List> alternativeFields) { - context.data(DATA_RENDER_TRAILING_LIMIT_IF_APPLICABLE, true, c -> toSQLReference0(context, originalFields, alternativeFields, null)); + private final void toSQLReferenceLimitDefault(Context context, AliasOverride aliasOverride) { + context.data(DATA_RENDER_TRAILING_LIMIT_IF_APPLICABLE, true, c -> toSQLReference0(context, aliasOverride, null)); } /** * Omit rendering any limit clause */ - private final void toSQLReferenceQualifyInsteadOfLimit(Context context, List> originalFields, List> alternativeFields) { - context.data(DATA_RENDER_TRAILING_LIMIT_IF_APPLICABLE, false, c -> toSQLReference0(context, originalFields, alternativeFields, limitWindowFunctionCondition(limitWindowFunction(context)))); + private final void toSQLReferenceQualifyInsteadOfLimit(Context context, AliasOverride aliasOverride) { + context.data(DATA_RENDER_TRAILING_LIMIT_IF_APPLICABLE, false, c -> toSQLReference0(context, aliasOverride, limitWindowFunctionCondition(limitWindowFunction(context)))); } /** * Emulate the LIMIT / OFFSET clause using window functions, specifically * when the WITH TIES clause is specified. */ - private final void toSQLReferenceLimitWithWindowFunctions(Context ctx, List> originalFields, List> alternativeFields) { + private final void toSQLReferenceLimitWithWindowFunctions(Context ctx, AliasOverride aliasOverride) { if (Transformations.EMULATE_QUALIFY.contains(ctx.dialect()) || getQualify().hasWhere()) toSQLReferenceLimitWithWindowFunctions0(ctx); else - toSQLReferenceQualifyInsteadOfLimit(ctx, originalFields, alternativeFields); + toSQLReferenceQualifyInsteadOfLimit(ctx, aliasOverride); } @@ -2143,15 +2148,15 @@ final class SelectQueryImpl extends AbstractResultQuery imp // AUTHOR.ID as v1, BOOK.ID as v2, BOOK.TITLE as v3 // Enforce x.* or just * if we have no known field names (e.g. when plain SQL tables are involved) - final List> alternativeFields = new ArrayList<>(originalFields.size()); + final List> aliasedFields = new ArrayList<>(originalFields.size()); if (originalFields.isEmpty()) - alternativeFields.add(DSL.field("*")); + aliasedFields.add(DSL.field("*")); else - alternativeFields.addAll(aliasedFields(originalFields)); + aliasedFields.addAll(aliasedFields(originalFields)); - alternativeFields.add(CustomField.of("rn", SQLDataType.INTEGER, c -> { - boolean wrapQueryExpressionBodyInDerivedTable = wrapQueryExpressionBodyInDerivedTable(c); + aliasedFields.add(CustomField.of("rn", SQLDataType.INTEGER, c -> { + boolean wrapQueryExpressionBodyInDerivedTable = wrapQueryExpressionBodyInDerivedTable(c, true); // [#3575] Ensure that no column aliases from the surrounding SELECT clause // are referenced from the below ranking functions' ORDER BY clause. @@ -2159,7 +2164,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp boolean q = c.qualify(); - c.data(DATA_OVERRIDE_ALIASES_IN_ORDER_BY, new Object[] { originalFields, alternativeFields }); + c.data(DATA_OVERRIDE_ALIASES_IN_ORDER_BY, new AliasOverride(originalFields, aliasedFields)); if (wrapQueryExpressionBodyInDerivedTable) c.qualify(false); @@ -2190,7 +2195,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp .visit(K_FROM).sqlIndentStart(" (") .subquery(true); - toSQLReference0(ctx, originalFields, alternativeFields, null); + toSQLReference0(ctx, new AliasOverride(originalFields, aliasedFields), null); ctx.subquery(false) .sqlIndentEnd(") ") @@ -2307,8 +2312,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp @SuppressWarnings("unchecked") private final void toSQLReference0( Context context, - List> originalFields, - List> alternativeFields, + AliasOverride aliasOverride, Condition additionalQualify ) { SQLDialect family = context.family(); @@ -2362,7 +2366,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp - + || wrapQueryExpressionBodyInDerivedTable(context, aliasOverride != null) // [#7459] In the presence of UNIONs and other set operations, the SEEK // predicate must be applied on a derived table, not on the individual subqueries @@ -2379,10 +2383,10 @@ final class SelectQueryImpl extends AbstractResultQuery imp .formatNewLine() .sql("t.*"); - if (alternativeFields != null && originalFields.size() < alternativeFields.size()) + if (aliasOverride != null && aliasOverride.originalFields().size() < aliasOverride.aliasedFields().size()) context.sql(", ") .formatSeparator() - .declareFields(true, c -> c.visit(alternativeFields.get(alternativeFields.size() - 1))); + .declareFields(true, c -> c.visit(aliasOverride.aliasedFields().get(aliasOverride.aliasedFields().size() - 1))); context.formatIndentEnd() .formatSeparator() @@ -2413,7 +2417,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp unionParenthesis( context, '(', - alternativeFields != null ? alternativeFields : getSelect(), + aliasOverride != null ? aliasOverride.aliasedFields() : getSelect(), derivedTableRequired(context, this), unionParensRequired = unionOpNesting || unionParensRequired(context), null @@ -2488,11 +2492,11 @@ final class SelectQueryImpl extends AbstractResultQuery imp // [#2335] When emulating LIMIT .. OFFSET, the SELECT clause needs to generate // non-ambiguous column names as ambiguous column names are not allowed in subqueries - if (alternativeFields != null) - if (wrapQueryExpressionBodyInDerivedTable && originalFields.size() < alternativeFields.size()) - context.visit(new SelectFieldList<>(alternativeFields.subList(0, originalFields.size()))); + if (aliasOverride != null) + if (wrapQueryExpressionBodyInDerivedTable && aliasOverride.originalFields().size() < aliasOverride.aliasedFields().size()) + context.visit(new SelectFieldList<>(aliasOverride.aliasedFields().subList(0, aliasOverride.originalFields().size()))); else - context.visit(new SelectFieldList<>(alternativeFields)); + context.visit(new SelectFieldList<>(aliasOverride.aliasedFields())); // The default behaviour else @@ -2762,8 +2766,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp // ORDER BY clause for local subselect // ----------------------------------- toSQLOrderBy( - context, - originalFields, alternativeFields, + context, aliasOverride, false, wrapQueryExpressionBodyInDerivedTable, false, orderBy, limit ); @@ -2827,8 +2830,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp // ORDER BY clause for UNION // ------------------------- context.qualify(false, c -> toSQLOrderBy( - context, - originalFields, alternativeFields, + context, aliasOverride, wrapQueryExpressionInDerivedTable, wrapQueryExpressionBodyInDerivedTable, true, unionOrderBy, unionLimit )); @@ -3425,8 +3427,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp @SuppressWarnings("unchecked") private final void toSQLOrderBy( final Context ctx, - final List> originalFields, - final List> alternativeFields, + final AliasOverride aliasOverride, final boolean wrapQueryExpressionInDerivedTable, final boolean wrapQueryExpressionBodyInDerivedTable, final boolean isUnionOrderBy, @@ -3608,11 +3609,16 @@ final class SelectQueryImpl extends AbstractResultQuery imp return !getSeek().isEmpty() && !getOrderBy().isEmpty() && !unionOp.isEmpty(); } - private final boolean wrapQueryExpressionBodyInDerivedTable(Context ctx) { + private final boolean wrapQueryExpressionBodyInDerivedTable(Context ctx, boolean hasAlternativeFields) { // [#2059] [#7539] Some dialects require query in derived table when using ORDER BY return !unionOp.isEmpty() && ( - WRAP_EXP_BODY_IN_DERIVED_TABLE_LIMIT.contains(ctx.dialect()) && getLimit().isApplicable() + + // [#16928] "Alternative fields" such as ROW_NUMBER() calculations only work with UNIONs when the + // UNION is nested. + hasAlternativeFields + || WRAP_EXP_BODY_IN_DERIVED_TABLE_LIMIT.contains(ctx.dialect()) && getLimit().isApplicable() +