diff --git a/jOOQ/src/main/java/org/jooq/Context.java b/jOOQ/src/main/java/org/jooq/Context.java index e70e1dca38..0f2334e3d0 100644 --- a/jOOQ/src/main/java/org/jooq/Context.java +++ b/jOOQ/src/main/java/org/jooq/Context.java @@ -391,6 +391,20 @@ public interface Context> extends ExecuteScope { @NotNull C scopeMarkStart(QueryPart part); + /** + * Hide the argument query part from child scopes, if it has been registered + * previously. + */ + @NotNull + C scopeHide(QueryPart part); + + /** + * Show the argument query part in child scopes, if it has been registered + * previously. + */ + @NotNull + C scopeShow(QueryPart part); + /** * Register a "special" query part in the scope, reusing the object from a * higher scope, if available. @@ -434,6 +448,12 @@ public interface Context> extends ExecuteScope { @NotNull Iterable scopeParts(Class type); + /** + * Get all values of a type that are in the current scope. + */ + @NotNull + Iterable currentScopeParts(Class type); + /** * Check whether a query part is registered in the current scope or higher. */ diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index b0ccf99d31..d86da48f21 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -909,6 +909,16 @@ abstract class AbstractContext> extends AbstractScope imple return e != null ? e.scopeDefiner : null; } + @Override + public /* non-final */ C scopeHide(QueryPart part) { + return (C) this; + } + + @Override + public /* non-final */ C scopeShow(QueryPart part) { + return (C) this; + } + @Override public final C scopeRegister(QueryPart part) { return scopeRegister(part, false); @@ -929,6 +939,11 @@ abstract class AbstractContext> extends AbstractScope imple return (Iterable) scopeStack.keyIterable(k -> type.isInstance(k)); } + @Override + public final Iterable currentScopeParts(Class type) { + return (Iterable) scopeStack.keyIterableAtScopeLevel(k -> type.isInstance(k)); + } + @Override public final boolean inScope(QueryPart part) { return scopeStack.get(part) != null; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java index f7173860c9..5c2d2a26d9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java @@ -223,6 +223,22 @@ class DefaultRenderContext extends AbstractContext implements Ren return part; } + @Override + public RenderContext scopeHide(QueryPart part) { + if (scopeStack.inScope()) + scopeStack.hide(part); + + return this; + } + + @Override + public RenderContext scopeShow(QueryPart part) { + if (scopeStack.inScope()) + scopeStack.show(part); + + return this; + } + @Override public RenderContext scopeRegister(QueryPart part, boolean forceNew, QueryPart mapped) { if (scopeStack.inScope()) { diff --git a/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java b/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java index 43e700f553..e8f39cc9c1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java +++ b/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java @@ -58,7 +58,7 @@ import java.util.function.Supplier; final class ScopeStack implements Iterable { private int scopeLevel = -1; - private Map> stack; + private Map>> stack; private final ObjIntFunction constructor; ScopeStack() { @@ -73,7 +73,7 @@ final class ScopeStack implements Iterable { this.constructor = constructor; } - private final Map> stack() { + private final Map>> stack() { if (stack == null) stack = new LinkedHashMap<>(); @@ -84,9 +84,9 @@ final class ScopeStack implements Iterable { int l = scopeLevel + 1; if (l >= 0) { int size; - for (Iterator>> it = stack().entrySet().iterator(); it.hasNext(); ) { - Map.Entry> entry = it.next(); - List list = entry.getValue(); + for (Iterator>>> it = stack().entrySet().iterator(); it.hasNext(); ) { + Map.Entry>> entry = it.next(); + List> list = entry.getValue(); while ((size = list.size()) > l || size > 0 && list.get(size - 1) == null) list.remove(size - 1); if (list.isEmpty()) @@ -109,44 +109,66 @@ final class ScopeStack implements Iterable { } final Iterable iterableAtScopeLevel() { - return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? v.get(scopeLevel) : null, e -> true); + return iterableAtScopeLevel(e -> true); + } + + final Iterable iterableAtScopeLevel(Predicate filter) { + return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? getCurrentScope0(v) : null, filter); } final Iterable iterable(Predicate filter) { - return () -> new ScopeStackIterator<>((k, v) -> v.get(v.size() - 1), filter); + return () -> new ScopeStackIterator<>((k, v) -> get0(v), filter); } final Iterable keyIterableAtScopeLevel() { - return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? k : null, e -> true); + return keyIterableAtScopeLevel(e -> true); + } + + final Iterable keyIterableAtScopeLevel(Predicate filter) { + return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? k : null, filter); } final Iterable keyIterable(Predicate filter) { return () -> new ScopeStackIterator<>((k, v) -> k, filter); } + static final class Element { + final V value; + boolean hidden; + + Element(V value) { + this.value = value; + } + + @Override + public String toString() { + return "E[" + value + (hidden ? ", hidden" : "") + "]"; + } + } + static final record Value(int scopeLevel, V value) { static Value of(int scopeLevel, V value) { return value == null ? null : new Value<>(scopeLevel, value); } - static Value lastOf(List list) { + static Value lastOf(List> list) { int size = list.size(); - V value = list.get(size - 1); + V value = getIfNotHidden(list.get(size - 1)); return of(size - 1, value); } } private final class ScopeStackIterator implements Iterator { - final Iterator>> it = stack().entrySet().iterator(); - final Function>, U> valueExtractor; - final Predicate filter; - U next; + final Iterator>>> it = stack().entrySet().iterator(); + final Function>>, U> valueExtractor; + final Predicate filter; + U next; - ScopeStackIterator(BiFunction, U> valueExtractor, Predicate filter) { + ScopeStackIterator(BiFunction>, U> valueExtractor, Predicate filter) { this(e -> valueExtractor.apply(e.getKey(), e.getValue()), filter); } - ScopeStackIterator(Function>, U> valueExtractor, Predicate filter) { + ScopeStackIterator(Function>>, U> valueExtractor, Predicate filter) { this.valueExtractor = valueExtractor; this.filter = filter; } @@ -170,8 +192,8 @@ final class ScopeStack implements Iterable { private U move() { for ( - Entry> e; - it.hasNext() && ((e = it.next()).getValue().isEmpty() || ((next = valueExtractor.apply(e)) == null) || !filter.test(next)); + Entry>> e; + it.hasNext() && ((e = it.next()).getValue().isEmpty() || get0(e.getValue()) == null || ((next = valueExtractor.apply(e)) == null) || !filter.test(next)); next = null ); @@ -193,7 +215,11 @@ final class ScopeStack implements Iterable { set0(list(key), value); } - private final V get0(List list) { + private static final V get0(List> list) { + return getIfNotHidden(getElement0(list)); + } + + private static final Element getElement0(List> list) { int i; if (list == null) @@ -204,7 +230,16 @@ final class ScopeStack implements Iterable { return list.get(i - 1); } - private final V getCurrentScope0(List list) { + private static final V getIfNotHidden(Element element) { + if (element == null) + return null; + else if (element.hidden) + return null; + else + return element.value; + } + + private final V getCurrentScope0(List> list) { int i; if (list == null) @@ -214,7 +249,25 @@ final class ScopeStack implements Iterable { else if (scopeLevel >= i) return null; else - return list.get(i - 1); + return getIfNotHidden(list.get(i - 1)); + } + + final V hide(K key) { + return hide0(key, true); + } + + final V show(K key) { + return hide0(key, false); + } + + private final V hide0(K key, boolean hidden) { + Element e = getElement0(listOrNull(key)); + + if (e == null) + return null; + + e.hidden = hidden; + return e.value; } final V get(K key) { @@ -233,7 +286,7 @@ final class ScopeStack implements Iterable { } final V getOrCreate(K key) { - List list = list(key); + List> list = list(key); V result = get0(list); return result != null ? result : create0(key, list); } @@ -242,13 +295,13 @@ final class ScopeStack implements Iterable { return create0(key, list(key)); } - private final V create0(K key, List list) { + private final V create0(K key, List> list) { V result = constructor.apply(key, scopeLevel); set0(list, result); return result; } - private final void set0(List list, V value) { + private final void set0(List> list, V value) { int l = scopeLevel + 1; int size = list.size(); if (size < l) { @@ -256,14 +309,14 @@ final class ScopeStack implements Iterable { for (int i = 0; i < nulls; i++) list.add(null); } - list.set(scopeLevel, value); + list.set(scopeLevel, new Element<>(value)); } - private final List listOrNull(K key) { + private final List> listOrNull(K key) { return stack().get(key); } - private final List list(K key) { + private final List> list(K key) { return stack().computeIfAbsent(key, k -> new ArrayList<>()); } diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 1208948f0f..4aa028188f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -2452,7 +2452,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp TableList tablelist = getFrom(); traverseJoins(tablelist, t -> { - if (t instanceof TableImpl) + if (t instanceof TableImpl || t instanceof TableAlias) context.scopeRegister(t, true); }); ConditionProviderImpl where = new ConditionProviderImpl(); diff --git a/jOOQ/src/main/java/org/jooq/impl/TableAlias.java b/jOOQ/src/main/java/org/jooq/impl/TableAlias.java index 92839d2d0c..05ee3ec5de 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableAlias.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableAlias.java @@ -67,6 +67,8 @@ extends AbstractTable implements QOM.TableAlias, + ScopeMappable, + ScopeNestable, SimpleCheckQueryPart { @@ -149,7 +151,26 @@ implements @Override public final void accept(Context ctx) { - ctx.visit(alias); + + // [#9814] [#12579] Derived tables and similar can't see the current scope + if (ctx.declareTables() && !(alias.wrapped instanceof TableImpl)) { + + // [#9814] TODO: Implement LATERAL semantics (without it, lateral join paths will break!) + // [#9814] TODO: Once this is implemented, move the logic to Tools.visitSubquery() to be more generic + // [#9814] TOOD: Avoid this logic if unnecessary (e.g. RenderTable makes it necessary) + // List> tables = collect(ctx.currentScopeParts((Class>) (Class) Table.class)); + // + // for (Table t : tables) + // ctx.scopeHide(t); + // + // ctx.visit(alias); + // + // for (Table t : tables) + // ctx.scopeShow(t); + ctx.scopeHide(this).visit(alias).scopeShow(this); + } + else + ctx.visit(alias); } @Override // Avoid AbstractTable implementation diff --git a/jOOQ/src/main/java/org/jooq/impl/TableImpl.java b/jOOQ/src/main/java/org/jooq/impl/TableImpl.java index 32995f65db..4bb3b947ee 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableImpl.java @@ -483,7 +483,9 @@ implements if (ctx.declareTables()) ctx.scopeMarkStart(this); - if (ctx.qualifySchema() && (ctx.declareTables() + boolean noSchemaMapping = !ctx.declareTables() && getSchema() == null && ctx.inScope(this); + + if (!noSchemaMapping && ctx.qualifySchema() && (ctx.declareTables() || (!NO_SUPPORT_QUALIFIED_TVF_CALLS.contains(ctx.dialect()) || parameters == null)