[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)
This commit is contained in:
Lukas Eder 2023-09-25 16:38:32 +02:00
parent 0255ad3199
commit 6ef4abdbbb
4 changed files with 97 additions and 52 deletions

View File

@ -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<C extends Context<C>> extends AbstractScope imple
? CastMode.NEVER
: CastMode.DEFAULT;
this.languageContext = LanguageContext.QUERY;
this.scopeStack = new ScopeStack<QueryPart, ScopeStackElement>(ScopeStackElement::new);
this.scopeStack = new ScopeStack<QueryPart, ScopeStackElement>((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<C extends Context<C>> 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<C extends Context<C>> 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<C extends Context<C>> 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<C extends Context<C>> 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<C extends Context<C>> 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<Object> 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<Object> 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<C extends Context<C>> 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();
}

View File

@ -86,6 +86,11 @@ package org.jooq.impl;

View File

@ -2335,7 +2335,8 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
'(',
alternativeFields != null ? alternativeFields : getSelect(),
derivedTableRequired(context, this),
unionParensRequired = unionOpNesting || unionParensRequired(context)
unionParensRequired = unionOpNesting || unionParensRequired(context),
null
);
}
}
@ -2678,7 +2679,7 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> 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<R extends Record> extends AbstractResultQuery<R> 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<R extends Record> extends AbstractResultQuery<R> imp
char parenthesis,
List<Field<?>> 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);

View File

@ -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}
*/