From 6ef4abdbbbf0cfae752b764a075b79b278c9afa0 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Mon, 25 Sep 2023 16:38:32 +0200 Subject: [PATCH] [jOOQ/jOOQ#2682] [jOOQ/jOOQ#15632] Support set operations (UNION etc.) This includes a refactoring where ScopeStackElement is subclassed for specialisation. We need one key per type of ScopeStackElement, such that values can be null for certain keys. That way, the new ScopeDefinerScopeStack element can be absent even if the DataKeyScopeStackElement is present on all scopes. Future refactors might further specialise the DefaultScopeStackElement, which contains the query part specific contents (e.g. join tree) --- .../java/org/jooq/impl/AbstractContext.java | 103 ++++++++++++++---- .../org/jooq/impl/DefaultPolicyProvider.java | 5 + .../java/org/jooq/impl/SelectQueryImpl.java | 16 +-- jOOQ/src/main/java/org/jooq/impl/Tools.java | 25 ----- 4 files changed, 97 insertions(+), 52 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index 6adc08b04c..f731ceb212 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -105,8 +105,8 @@ import org.jooq.conf.RenderImplicitJoinType; import org.jooq.conf.Settings; import org.jooq.conf.SettingsTools; import org.jooq.conf.StatementType; +import org.jooq.impl.QOM.UEmpty; import org.jooq.impl.Tools.DataKey; -import org.jooq.impl.Tools.ScopeStackPart; /** @@ -233,7 +233,14 @@ abstract class AbstractContext> extends AbstractScope imple ? CastMode.NEVER : CastMode.DEFAULT; this.languageContext = LanguageContext.QUERY; - this.scopeStack = new ScopeStack(ScopeStackElement::new); + this.scopeStack = new ScopeStack((k, v) -> { + if (k == DataKeyScopeStackPart.INSTANCE) + return new DataKeyScopeStackElement(k, v); + else if (k == ScopeDefinerScopeStackPart.INSTANCE) + return new ScopeDefinerScopeStackElement(k, v); + else + return new DefaultScopeStackElement(k, v); + }); } // ------------------------------------------------------------------------ @@ -824,17 +831,17 @@ abstract class AbstractContext> extends AbstractScope imple @Override public final C scopeStart(QueryPart part) { scopeStack.scopeStart(); - ScopeStackElement e = scopeStack.getOrCreate(ScopeStackPart.INSTANCE); - e.scopeDefiner = part; + if (part != null) + ((ScopeDefinerScopeStackElement) scopeStack.getOrCreate(ScopeDefinerScopeStackPart.INSTANCE)).scopeDefiner = part; scopeStart0(); - resetDataKeys(e); + resetDataKeys((DataKeyScopeStackElement) scopeStack.getOrCreate(DataKeyScopeStackPart.INSTANCE)); return (C) this; } @Override public final QueryPart scopePart() { - ScopeStackElement e = scopeStack.get(ScopeStackPart.INSTANCE); + ScopeDefinerScopeStackElement e = ((ScopeDefinerScopeStackElement) scopeStack.get(ScopeDefinerScopeStackPart.INSTANCE)); return e != null ? e.scopeDefiner : null; } @@ -886,7 +893,7 @@ abstract class AbstractContext> extends AbstractScope imple @Override public final C scopeEnd() { - restoreDataKeys(scopeStack.getOrCreate(ScopeStackPart.INSTANCE)); + restoreDataKeys((DataKeyScopeStackElement) scopeStack.getOrCreate(DataKeyScopeStackPart.INSTANCE)); scopeEnd0(); scopeStack.scopeEnd(); @@ -899,7 +906,7 @@ abstract class AbstractContext> extends AbstractScope imple void scopeMarkEnd0(@SuppressWarnings("unused") QueryPart part) {} void scopeEnd0() {} - final void resetDataKeys(ScopeStackElement e) { + final void resetDataKeys(DataKeyScopeStackElement e) { for (int i = 0; i < DATAKEY_RESET_IN_SUBQUERY_SCOPE.length; i++) { DataKey key = DATAKEY_RESET_IN_SUBQUERY_SCOPE[i]; @@ -908,7 +915,7 @@ abstract class AbstractContext> extends AbstractScope imple } } - final void restoreDataKeys(ScopeStackElement e) { + final void restoreDataKeys(DataKeyScopeStackElement e) { for (int i = 0; i < DATAKEY_RESET_IN_SUBQUERY_SCOPE.length; i++) { DataKey key = DATAKEY_RESET_IN_SUBQUERY_SCOPE[i]; @@ -1302,22 +1309,83 @@ abstract class AbstractContext> extends AbstractScope imple } } - static class ScopeStackElement { + abstract static class ScopeStackElement { final int scopeLevel; final QueryPart part; - QueryPart scopeDefiner; QueryPart mapped; int[] positions; int bindIndex; int indent; JoinNode joinNode; - List restoreDataKeys; ScopeStackElement(QueryPart part, int scopeLevel) { this.part = part; this.mapped = part; this.scopeLevel = scopeLevel; } + } + + static abstract class AbstractScopeStackPart extends AbstractQueryPart implements UEmpty { + + @Override + public final void accept(Context ctx) {} + + @Override + public boolean equals(Object that) { + return this == that; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + } + + static final class DataKeyScopeStackPart extends AbstractScopeStackPart { + static final DataKeyScopeStackPart INSTANCE = new DataKeyScopeStackPart(); + private DataKeyScopeStackPart() {} + } + + static final class ScopeDefinerScopeStackPart extends AbstractScopeStackPart { + static final ScopeDefinerScopeStackPart INSTANCE = new ScopeDefinerScopeStackPart(); + private ScopeDefinerScopeStackPart() {} + } + + static final class DataKeyScopeStackElement extends ScopeStackElement { + List restoreDataKeys; + + DataKeyScopeStackElement(QueryPart part, int scopeLevel) { + super(part, scopeLevel); + } + + @Override + public String toString() { + return "RestoreDataKeys [" + restoreDataKeys + "]"; + } + } + + static final class ScopeDefinerScopeStackElement extends ScopeStackElement { + QueryPart scopeDefiner; + + ScopeDefinerScopeStackElement(QueryPart part, int scopeLevel) { + super(part, scopeLevel); + } + + @Override + public String toString() { + return "" + scopeDefiner; + } + } + + static final class DefaultScopeStackElement extends ScopeStackElement { + DefaultScopeStackElement(QueryPart part, int scopeLevel) { + super(part, scopeLevel); + } @Override public String toString() { @@ -1326,15 +1394,10 @@ abstract class AbstractContext> extends AbstractScope imple if (positions != null) sb.append(Arrays.toString(positions)); - if (part instanceof ScopeStackPart) { - sb.append(scopeDefiner); - } - else { - sb.append(part); + sb.append(part); - if (mapped != null) - sb.append(" (" + mapped + ")"); - } + if (mapped != null) + sb.append(" (" + mapped + ")"); return sb.toString(); } diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultPolicyProvider.java b/jOOQ/src/main/java/org/jooq/impl/DefaultPolicyProvider.java index 1891fef607..6e4c7e6117 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultPolicyProvider.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultPolicyProvider.java @@ -86,6 +86,11 @@ package org.jooq.impl; + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 1244d20e88..94369fbaba 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -2335,7 +2335,8 @@ final class SelectQueryImpl extends AbstractResultQuery imp '(', alternativeFields != null ? alternativeFields : getSelect(), derivedTableRequired(context, this), - unionParensRequired = unionOpNesting || unionParensRequired(context) + unionParensRequired = unionOpNesting || unionParensRequired(context), + null ); } } @@ -2678,7 +2679,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp // SET operations like UNION, EXCEPT, INTERSECT // -------------------------------------------- if (unionOpSize > 0) { - unionParenthesis(context, ')', null, derivedTableRequired(context, this), unionParensRequired); + unionParenthesis(context, ')', null, derivedTableRequired(context, this), unionParensRequired, null); for (int i = 0; i < unionOpSize; i++) { CombineOperator op = unionOp.get(i); @@ -2695,14 +2696,14 @@ final class SelectQueryImpl extends AbstractResultQuery imp else context.formatSeparator(); - unionParenthesis(context, '(', other.getSelect(), derivedTableRequired, otherUnionParensRequired); + unionParenthesis(context, '(', other.getSelect(), derivedTableRequired, otherUnionParensRequired, other); context.visit(other); - unionParenthesis(context, ')', null, derivedTableRequired, otherUnionParensRequired); + unionParenthesis(context, ')', null, derivedTableRequired, otherUnionParensRequired, null); } // [#1658] Close parentheses opened previously if (i < unionOpSize - 1) - unionParenthesis(context, ')', null, derivedTableRequired(context, this), unionParensRequired); + unionParenthesis(context, ')', null, derivedTableRequired(context, this), unionParensRequired, null); switch (unionOp.get(i)) { case EXCEPT: context.end(SELECT_EXCEPT); break; @@ -3563,10 +3564,11 @@ final class SelectQueryImpl extends AbstractResultQuery imp char parenthesis, List> fields, boolean derivedTableRequired, - boolean parensRequired + boolean parensRequired, + QueryPart subquery ) { if ('(' == parenthesis) - ((AbstractContext) ctx).subquery0(true, true, null); + ((AbstractContext) ctx).subquery0(true, true, subquery); else if (')' == parenthesis) ((AbstractContext) ctx).subquery0(false, true, null); diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 418cb68309..1631b3544c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -421,31 +421,6 @@ final class Tools { // Some constants for use with Context.data() // ------------------------------------------------------------------------ - static final class ScopeStackPart extends AbstractQueryPart implements UEmpty { - - static final ScopeStackPart INSTANCE = new ScopeStackPart(); - - private ScopeStackPart() {} - - @Override - public final void accept(Context ctx) {} - - @Override - public boolean equals(Object that) { - return this == that; - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public String toString() { - return "SCOPE_STACK_PART"; - } - } - /** * A common super types for {@link BooleanDataKey}, {@link SimpleDataKey} and {@link ExtendedDataKey} */