From f479ee2a8803f98ab001ecaa569fae8174e0918b Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 22 Oct 2013 18:09:02 +0200 Subject: [PATCH] [#531] [#2790] Add a Context data map scoped to the current subquery --- .../java/org/jooq/impl/AbstractContext.java | 231 ++++++++++++++++-- .../org/jooq/impl/DefaultRenderContext.java | 203 --------------- .../src/main/java/org/jooq/impl/Function.java | 49 +++- .../org/jooq/impl/InternalVisitListener.java | 86 +++++++ .../java/org/jooq/impl/SelectQueryImpl.java | 17 ++ jOOQ/src/main/java/org/jooq/impl/Utils.java | 19 ++ .../org/jooq/impl/WindowDefinitionImpl.java | 6 +- 7 files changed, 376 insertions(+), 235 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/InternalVisitListener.java diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index 00d48d51e5..a231352a12 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -40,14 +40,24 @@ */ package org.jooq.impl; +import static org.jooq.impl.Utils.DATA_OMIT_CLAUSE_EVENT_EMISSION; + +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; import java.util.Map; +import org.jooq.BindContext; import org.jooq.Clause; import org.jooq.Configuration; import org.jooq.Context; import org.jooq.QueryPart; import org.jooq.QueryPartInternal; +import org.jooq.RenderContext; +import org.jooq.Table; +import org.jooq.VisitContext; +import org.jooq.VisitListener; +import org.jooq.VisitListenerProvider; /** * @author Lukas Eder @@ -55,18 +65,212 @@ import org.jooq.QueryPartInternal; @SuppressWarnings("unchecked") abstract class AbstractContext> implements Context { - final Configuration configuration; - final Map data; + final Configuration configuration; + final Map data; - boolean declareFields; - boolean declareTables; - boolean declareWindows; - boolean subquery; - int index; + boolean declareFields; + boolean declareTables; + boolean declareWindows; + boolean subquery; + int index; + + // [#2665] VisitListener API + final VisitListener[] visitListeners; + private final Deque visitClauses; + private final DefaultVisitContext visitContext; + private final Deque visitParts; AbstractContext(Configuration configuration) { this.configuration = configuration; this.data = new HashMap(); + this.visitClauses = new ArrayDeque(); + + VisitListenerProvider[] providers = configuration.visitListenerProviders(); + + this.visitListeners = new VisitListener[providers.length + 1]; + this.visitContext = new DefaultVisitContext(); + this.visitParts = new ArrayDeque(); + + for (int i = 0; i < providers.length; i++) { + this.visitListeners[i] = providers[i].provide(); + } + + this.visitListeners[providers.length] = new InternalVisitListener(); + } + + // ------------------------------------------------------------------------ + // VisitListener API + // ------------------------------------------------------------------------ + + @Override + public final C visit(QueryPart part) { + if (part != null) { + + // Issue start clause events + // ----------------------------------------------------------------- + Clause[] clauses = visitListeners.length > 0 ? clause(part) : null; + if (clauses != null) + for (int i = 0; i < clauses.length; i++) + start(clauses[i]); + + // Perform the actual visiting, or recurse into the replacement + // ----------------------------------------------------------------- + QueryPart original = part; + QueryPart replacement = start(part); + + if (original == replacement) + visit0(original); + else + visit0(replacement); + + end(replacement); + + // Issue end clause events + // ----------------------------------------------------------------- + if (clauses != null) + for (int i = clauses.length - 1; i >= 0; i--) + end(clauses[i]); + } + + return (C) this; + } + + /** + * Emit a clause from a query part being visited. + *

+ * This method returns a clause to emit as a surrounding event before / + * after visiting a query part. This is needed for all reusable query parts, + * whose clause type is ambiguous at the container site. An example: + *

+ *

SELECT * FROM [A CROSS JOIN B]
+ *

+ * The type of the above JoinTable modelling + * A CROSS JOIN B is not known to the surrounding + * SELECT statement, which only knows {@link Table} types. The + * {@link Clause#TABLE_JOIN} event that is required to be emitted around the + * {@link Context#visit(QueryPart)} event has to be issued here in + * AbstractContext. + */ + private final Clause[] clause(QueryPart part) { + if (part instanceof QueryPartInternal && data(DATA_OMIT_CLAUSE_EVENT_EMISSION) == null) { + return ((QueryPartInternal) part).clauses(this); + } + + return null; + } + + @Override + public final C start(Clause clause) { + if (clause != null) { + visitClauses.addLast(clause); + + for (VisitListener listener : visitListeners) { + listener.clauseStart(visitContext); + } + } + + return (C) this; + } + + @Override + public final C end(Clause clause) { + if (clause != null) { + for (VisitListener listener : visitListeners) { + listener.clauseEnd(visitContext); + } + + if (visitClauses.removeLast() != clause) + throw new IllegalStateException("Mismatch between visited clauses!"); + } + + return (C) this; + } + + private final QueryPart start(QueryPart part) { + visitParts.addLast(part); + + for (VisitListener listener : visitListeners) { + listener.visitStart(visitContext); + } + + return visitParts.peekLast(); + } + + private final void end(QueryPart part) { + for (VisitListener listener : visitListeners) { + listener.visitEnd(visitContext); + } + + if (visitParts.removeLast() != part) + throw new RuntimeException("Mismatch between visited query parts"); + } + + /** + * A {@link VisitContext} is always in the scope of the current + * {@link RenderContext}. + */ + private class DefaultVisitContext implements VisitContext { + + @Override + public final Map data() { + return AbstractContext.this.data(); + } + + @Override + public final Object data(Object key) { + return AbstractContext.this.data(key); + } + + @Override + public final Object data(Object key, Object value) { + return AbstractContext.this.data(key, value); + } + + @Override + public final Configuration configuration() { + return AbstractContext.this.configuration(); + } + + @Override + public final Clause clause() { + return visitClauses.peekLast(); + } + + @Override + public final Clause[] clauses() { + return visitClauses.toArray(new Clause[visitClauses.size()]); + } + + @Override + public final QueryPart queryPart() { + return visitParts.peekLast(); + } + + @Override + public final void queryPart(QueryPart part) { + visitParts.pollLast(); + visitParts.addLast(part); + } + + @Override + public final QueryPart[] queryParts() { + return visitParts.toArray(new QueryPart[visitParts.size()]); + } + + @Override + public final Context context() { + return AbstractContext.this; + } + + @Override + public final RenderContext renderContext() { + return context() instanceof RenderContext ? (RenderContext) context() : null; + } + + @Override + public final BindContext bindContext() { + return context() instanceof BindContext ? (BindContext) context() : null; + } } // ------------------------------------------------------------------------ @@ -93,18 +297,7 @@ abstract class AbstractContext> implements Context { return data.put(key, value); } - @Override - public /* non-final */ C start(Clause clause) { - return (C) this; - } - - @Override - public /* non-final */ C end(Clause clause) { - return (C) this; - } - - @Override - public /* non-final */ C visit(QueryPart part) { + private final C visit0(QueryPart part) { if (part != null) { QueryPartInternal internal = (QueryPartInternal) part; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java index 662661cfb6..2dc78992d3 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java @@ -46,32 +46,21 @@ import static org.jooq.conf.ParamType.INDEXED; import static org.jooq.conf.ParamType.INLINED; import static org.jooq.conf.ParamType.NAMED; import static org.jooq.impl.Utils.DATA_COUNT_BIND_VALUES; -import static org.jooq.impl.Utils.DATA_OMIT_CLAUSE_EVENT_EMISSION; -import java.util.ArrayDeque; import java.util.Arrays; -import java.util.Deque; import java.util.HashSet; -import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.regex.Pattern; -import org.jooq.BindContext; -import org.jooq.Clause; import org.jooq.Configuration; import org.jooq.Constants; -import org.jooq.Context; import org.jooq.Param; import org.jooq.QueryPart; import org.jooq.QueryPartInternal; import org.jooq.RenderContext; import org.jooq.SQLDialect; import org.jooq.Select; -import org.jooq.Table; -import org.jooq.VisitContext; -import org.jooq.VisitListener; -import org.jooq.VisitListenerProvider; import org.jooq.conf.ParamType; import org.jooq.conf.RenderKeywordStyle; import org.jooq.conf.RenderNameStyle; @@ -107,12 +96,6 @@ class DefaultRenderContext extends AbstractContext implements Ren private RenderNameStyle cachedRenderNameStyle; private boolean cachedRenderFormatted; - // [#2665] VisitListener API - final VisitListener[] visitListeners; - private final DefaultVisitContext visitContext; - private final Deque visitClauses; - private final Deque visitParts; - DefaultRenderContext(Configuration configuration) { super(configuration); @@ -122,17 +105,6 @@ class DefaultRenderContext extends AbstractContext implements Ren this.cachedRenderKeywordStyle = settings.getRenderKeywordStyle(); this.cachedRenderFormatted = Boolean.TRUE.equals(settings.isRenderFormatted()); this.cachedRenderNameStyle = settings.getRenderNameStyle(); - - VisitListenerProvider[] providers = configuration.visitListenerProviders(); - - this.visitListeners = new VisitListener[providers.length]; - this.visitContext = new DefaultVisitContext(); - this.visitClauses = new ArrayDeque(); - this.visitParts = new ArrayDeque(); - - for (int i = 0; i < providers.length; i++) { - this.visitListeners[i] = providers[i].provide(); - } } DefaultRenderContext(RenderContext context) { @@ -146,181 +118,6 @@ class DefaultRenderContext extends AbstractContext implements Ren data().putAll(context.data()); } - // ------------------------------------------------------------------------ - // VisitListener API - // ------------------------------------------------------------------------ - - @Override - public final RenderContext start(Clause clause) { - if (clause != null) { - visitClauses.addLast(clause); - - for (VisitListener listener : visitListeners) { - listener.clauseStart(visitContext); - } - } - - return this; - } - - @Override - public final RenderContext end(Clause clause) { - if (clause != null) { - for (VisitListener listener : visitListeners) { - listener.clauseEnd(visitContext); - } - - if (visitClauses.removeLast() != clause) - throw new IllegalStateException("Mismatch between visited clauses!"); - } - - return this; - } - - @Override - public final RenderContext visit(QueryPart part) { - if (part != null) { - - // Issue start clause events - // ----------------------------------------------------------------- - Clause[] clauses = visitListeners.length > 0 ? clause(part) : null; - if (clauses != null) - for (int i = 0; i < clauses.length; i++) - start(clauses[i]); - - // Perform the actual visiting, or recurse into the replacement - // ----------------------------------------------------------------- - QueryPart original = part; - QueryPart replacement = start(part); - - if (original == replacement) - super.visit(original); - else - visit(replacement); - - end(replacement); - - // Issue end clause events - // ----------------------------------------------------------------- - if (clauses != null) - for (int i = clauses.length - 1; i >= 0; i--) - end(clauses[i]); - } - - return this; - } - - private final QueryPart start(QueryPart part) { - visitParts.addLast(part); - - for (VisitListener listener : visitListeners) { - listener.visitStart(visitContext); - } - - return visitParts.peekLast(); - } - - private final void end(QueryPart part) { - for (VisitListener listener : visitListeners) { - listener.visitEnd(visitContext); - } - - if (visitParts.removeLast() != part) - throw new RuntimeException("Mismatch between visited query parts"); - } - - /** - * Emit a clause from a query part being visited. - *

- * This method returns a clause to emit as a surrounding event before / - * after visiting a query part. This is needed for all reusable query parts, - * whose clause type is ambiguous at the container site. An example: - *

- *

SELECT * FROM [A CROSS JOIN B]
- *

- * The type of the above JoinTable modelling - * A CROSS JOIN B is not known to the surrounding - * SELECT statement, which only knows {@link Table} types. The - * {@link Clause#TABLE_JOIN} event that is required to be emitted around the - * {@link Context#visit(QueryPart)} event has to be issued here in - * AbstractContext. - */ - private final Clause[] clause(QueryPart part) { - if (part instanceof QueryPartInternal && data(DATA_OMIT_CLAUSE_EVENT_EMISSION) == null) { - return ((QueryPartInternal) part).clauses(this); - } - - return null; - } - - /** - * A {@link VisitContext} is always in the scope of the current - * {@link RenderContext}. - */ - private class DefaultVisitContext implements VisitContext { - - @Override - public final Map data() { - return DefaultRenderContext.this.data(); - } - - @Override - public final Object data(Object key) { - return DefaultRenderContext.this.data(key); - } - - @Override - public final Object data(Object key, Object value) { - return DefaultRenderContext.this.data(key, value); - } - - @Override - public final Configuration configuration() { - return DefaultRenderContext.this.configuration(); - } - - @Override - public final Clause clause() { - return visitClauses.peekLast(); - } - - @Override - public final Clause[] clauses() { - return visitClauses.toArray(new Clause[visitClauses.size()]); - } - - @Override - public final QueryPart queryPart() { - return visitParts.peekLast(); - } - - @Override - public final void queryPart(QueryPart part) { - visitParts.pollLast(); - visitParts.addLast(part); - } - - @Override - public final QueryPart[] queryParts() { - return visitParts.toArray(new QueryPart[visitParts.size()]); - } - - @Override - public final Context context() { - return DefaultRenderContext.this; - } - - @Override - public final RenderContext renderContext() { - return DefaultRenderContext.this; - } - - @Override - public final BindContext bindContext() { - throw new UnsupportedOperationException("QueryPart traversal listening is currently only supported for RenderContext"); - } - } - // ------------------------------------------------------------------------ // RenderContext API // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/impl/Function.java b/jOOQ/src/main/java/org/jooq/impl/Function.java index 2ce41d54ea..f5cf1e2508 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Function.java +++ b/jOOQ/src/main/java/org/jooq/impl/Function.java @@ -53,12 +53,16 @@ import static org.jooq.SQLDialect.POSTGRES; import static org.jooq.impl.DSL.name; import static org.jooq.impl.Term.LIST_AGG; import static org.jooq.impl.Term.ROW_NUMBER; +import static org.jooq.impl.Utils.DATA_LOCALLY_SCOPED_DATA_MAP; +import static org.jooq.impl.Utils.DATA_WINDOW_DEFINITIONS; import java.util.Arrays; import java.util.Collection; +import java.util.Map; import org.jooq.AggregateFunction; import org.jooq.BindContext; +import org.jooq.Context; import org.jooq.DataType; import org.jooq.Field; import org.jooq.Name; @@ -194,12 +198,9 @@ class Function extends AbstractField implements .visit(keepDenseRankOrderBy) .visit(withinGroupOrderBy); - if (windowSpecification != null) - ctx.visit(windowSpecification); - else if (windowDefinition != null) - ctx.visit(windowDefinition); - else if (windowName != null) - ctx.visit(windowName); + QueryPart window = window(ctx); + if (window != null) + ctx.visit(window); } } @@ -331,9 +332,10 @@ class Function extends AbstractField implements } private final void toSQLOverClause(RenderContext ctx) { + QueryPart window = window(ctx); // Render this clause only if needed - if (windowSpecification == null && windowDefinition == null && windowName == null) + if (window == null) return; // [#1524] Don't render this clause where it is not supported @@ -344,14 +346,37 @@ class Function extends AbstractField implements ctx.sql(" ") .keyword("over") .sql(" (") - .visit(windowSpecification != null - ? windowSpecification - : windowDefinition != null - ? windowDefinition - : windowName) + .visit(window) .sql(")"); } + @SuppressWarnings("unchecked") + private final QueryPart window(Context ctx) { + if (windowSpecification != null) + return windowSpecification; + + if (windowDefinition != null) + return windowDefinition; + + // [#531] Inline window specifications if the WINDOW clause is not supported + if (windowName != null) { + if (asList(POSTGRES).contains(ctx.configuration().dialect().family())) { + return windowName; + } + + Map map = (Map) ctx.data(DATA_LOCALLY_SCOPED_DATA_MAP); + QueryPartList windows = (QueryPartList) map.get(DATA_WINDOW_DEFINITIONS); + + for (WindowDefinition window : windows) { + if (((WindowDefinitionImpl) window).getName().equals(windowName)) { + return window; + } + } + } + + return null; + } + /** * Render KEEP (DENSE_RANK [FIRST | LAST] ORDER BY {...}) clause */ diff --git a/jOOQ/src/main/java/org/jooq/impl/InternalVisitListener.java b/jOOQ/src/main/java/org/jooq/impl/InternalVisitListener.java new file mode 100644 index 0000000000..490d4b0838 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/InternalVisitListener.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2009-2013, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.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 + * + * http://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. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq.impl; + +import static org.jooq.Clause.SELECT; +import static org.jooq.impl.Utils.DATA_LOCALLY_SCOPED_DATA_MAP; + +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; + +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 + */ +class InternalVisitListener extends DefaultVisitListener { + + private Deque stack = new LinkedList(); + + @Override + public void clauseStart(VisitContext ctx) { + if (ctx.clause() == SELECT) { + stack.push(ctx.context().data(DATA_LOCALLY_SCOPED_DATA_MAP)); + ctx.context().data(DATA_LOCALLY_SCOPED_DATA_MAP, new HashMap()); + } + } + + @Override + public void clauseEnd(VisitContext ctx) { + if (ctx.clause() == SELECT) { + ctx.context().data(DATA_LOCALLY_SCOPED_DATA_MAP, stack.pop()); + } + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 4b35b718bd..f567b2d4dd 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -76,13 +76,16 @@ import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.one; import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.rowNumber; +import static org.jooq.impl.Utils.DATA_LOCALLY_SCOPED_DATA_MAP; import static org.jooq.impl.Utils.DATA_ROW_VALUE_EXPRESSION_PREDICATE_SUBQUERY; +import static org.jooq.impl.Utils.DATA_WINDOW_DEFINITIONS; import static org.jooq.impl.Utils.DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import org.jooq.BindContext; import org.jooq.Clause; @@ -193,6 +196,8 @@ class SelectQueryImpl extends AbstractSelect implements Sel @Override public final void bind(BindContext context) { + pushWindow(context); + context.declareFields(true) .visit(getSelect0()) .declareFields(false) @@ -225,6 +230,8 @@ class SelectQueryImpl extends AbstractSelect implements Sel @Override public final void toSQL(RenderContext context) { + pushWindow(context); + Boolean wrapDerivedTables = (Boolean) context.data(DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES); if (TRUE.equals(wrapDerivedTables)) { context.sql("(") @@ -386,6 +393,16 @@ class SelectQueryImpl extends AbstractSelect implements Sel } } + @SuppressWarnings("unchecked") + private final void pushWindow(Context context) { + // [#531] [#2790] Make the WINDOW clause available to the SELECT clause + // to be able to inline window definitions if the WINDOW clause is not + // supported. + if (!getWindow().isEmpty()) { + ((Map) context.data(DATA_LOCALLY_SCOPED_DATA_MAP)).put(DATA_WINDOW_DEFINITIONS, getWindow()); + } + } + /** * The default LIMIT / OFFSET clause in most dialects */ diff --git a/jOOQ/src/main/java/org/jooq/impl/Utils.java b/jOOQ/src/main/java/org/jooq/impl/Utils.java index 69bb4ecb3f..322f9043e1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Utils.java +++ b/jOOQ/src/main/java/org/jooq/impl/Utils.java @@ -211,6 +211,25 @@ final class Utils { */ static final String DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES = "org.jooq.configuration.wrap-derived-tables-in-parentheses"; + /** + * [#2790] A locally scoped data map. + *

+ * 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 clauses, as is required to + * emulate #531. + */ + static final String DATA_LOCALLY_SCOPED_DATA_MAP = "org.jooq.configuration.locally-scoped-data-map"; + + /** + * [#531] The local window definitions. + *

+ * The window definitions declared in the WINDOW clause are + * needed in the SELECT clause when emulating them by inlining + * window specifications. + */ + static final String DATA_WINDOW_DEFINITIONS = "org.jooq.configuration.local-window-definitions"; + // ------------------------------------------------------------------------ // Other constants // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/impl/WindowDefinitionImpl.java b/jOOQ/src/main/java/org/jooq/impl/WindowDefinitionImpl.java index 90a002e352..8ffbcde542 100644 --- a/jOOQ/src/main/java/org/jooq/impl/WindowDefinitionImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/WindowDefinitionImpl.java @@ -69,6 +69,10 @@ class WindowDefinitionImpl extends AbstractQueryPart implements WindowDefinition this.window = window; } + final Name getName() { + return name; + } + @Override public final void toSQL(RenderContext ctx) { @@ -108,7 +112,7 @@ class WindowDefinitionImpl extends AbstractQueryPart implements WindowDefinition } @Override - public boolean declaresWindows() { + public final boolean declaresWindows() { return true; }