[jOOQ/jOOQ#9814] MappedSchema.withInput().withOutput(abc) prepends schema qualification to derived table references

This includes:

- [jOOQ/jOOQ#17420] Add Context.scopeHide(QueryPart) and Context.scopeShow(QueryPart)
- [jOOQ/jOOQ#17421] Add Context.currentScopeParts(Class<Q>): Iterable<Q> to allow for iterating over the values that are only in the current scope
This commit is contained in:
Lukas Eder 2024-10-08 15:01:06 +02:00
parent 9f035246db
commit ddf271ec6a
7 changed files with 157 additions and 30 deletions

View File

@ -391,6 +391,20 @@ public interface Context<C extends Context<C>> 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<C extends Context<C>> extends ExecuteScope {
@NotNull
<Q extends QueryPart> Iterable<Q> scopeParts(Class<? extends Q> type);
/**
* Get all values of a type that are in the current scope.
*/
@NotNull
<Q extends QueryPart> Iterable<Q> currentScopeParts(Class<? extends Q> type);
/**
* Check whether a query part is registered in the current scope or higher.
*/

View File

@ -909,6 +909,16 @@ abstract class AbstractContext<C extends Context<C>> 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<C extends Context<C>> extends AbstractScope imple
return (Iterable<Q>) scopeStack.keyIterable(k -> type.isInstance(k));
}
@Override
public final <Q extends QueryPart> Iterable<Q> currentScopeParts(Class<? extends Q> type) {
return (Iterable<Q>) scopeStack.keyIterableAtScopeLevel(k -> type.isInstance(k));
}
@Override
public final boolean inScope(QueryPart part) {
return scopeStack.get(part) != null;

View File

@ -223,6 +223,22 @@ class DefaultRenderContext extends AbstractContext<RenderContext> 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()) {

View File

@ -58,7 +58,7 @@ import java.util.function.Supplier;
final class ScopeStack<K, V> implements Iterable<V> {
private int scopeLevel = -1;
private Map<K, List<V>> stack;
private Map<K, List<Element<V>>> stack;
private final ObjIntFunction<K, V> constructor;
ScopeStack() {
@ -73,7 +73,7 @@ final class ScopeStack<K, V> implements Iterable<V> {
this.constructor = constructor;
}
private final Map<K, List<V>> stack() {
private final Map<K, List<Element<V>>> stack() {
if (stack == null)
stack = new LinkedHashMap<>();
@ -84,9 +84,9 @@ final class ScopeStack<K, V> implements Iterable<V> {
int l = scopeLevel + 1;
if (l >= 0) {
int size;
for (Iterator<Map.Entry<K, List<V>>> it = stack().entrySet().iterator(); it.hasNext(); ) {
Map.Entry<K, List<V>> entry = it.next();
List<V> list = entry.getValue();
for (Iterator<Map.Entry<K, List<Element<V>>>> it = stack().entrySet().iterator(); it.hasNext(); ) {
Map.Entry<K, List<Element<V>>> entry = it.next();
List<Element<V>> 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<K, V> implements Iterable<V> {
}
final Iterable<V> iterableAtScopeLevel() {
return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? v.get(scopeLevel) : null, e -> true);
return iterableAtScopeLevel(e -> true);
}
final Iterable<V> iterableAtScopeLevel(Predicate<? super V> filter) {
return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? getCurrentScope0(v) : null, filter);
}
final Iterable<V> iterable(Predicate<? super V> filter) {
return () -> new ScopeStackIterator<>((k, v) -> v.get(v.size() - 1), filter);
return () -> new ScopeStackIterator<>((k, v) -> get0(v), filter);
}
final Iterable<K> keyIterableAtScopeLevel() {
return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? k : null, e -> true);
return keyIterableAtScopeLevel(e -> true);
}
final Iterable<K> keyIterableAtScopeLevel(Predicate<? super K> filter) {
return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? k : null, filter);
}
final Iterable<K> keyIterable(Predicate<? super K> filter) {
return () -> new ScopeStackIterator<>((k, v) -> k, filter);
}
static final class Element<V> {
final V value;
boolean hidden;
Element(V value) {
this.value = value;
}
@Override
public String toString() {
return "E[" + value + (hidden ? ", hidden" : "") + "]";
}
}
static final record Value<V>(int scopeLevel, V value) {
static <V> Value<V> of(int scopeLevel, V value) {
return value == null ? null : new Value<>(scopeLevel, value);
}
static <V> Value<V> lastOf(List<V> list) {
static <V> Value<V> lastOf(List<Element<V>> 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<U> implements Iterator<U> {
final Iterator<Entry<K, List<V>>> it = stack().entrySet().iterator();
final Function<Entry<K, List<V>>, U> valueExtractor;
final Predicate<? super U> filter;
U next;
final Iterator<Entry<K, List<Element<V>>>> it = stack().entrySet().iterator();
final Function<Entry<K, List<Element<V>>>, U> valueExtractor;
final Predicate<? super U> filter;
U next;
ScopeStackIterator(BiFunction<K, List<V>, U> valueExtractor, Predicate<? super U> filter) {
ScopeStackIterator(BiFunction<K, List<Element<V>>, U> valueExtractor, Predicate<? super U> filter) {
this(e -> valueExtractor.apply(e.getKey(), e.getValue()), filter);
}
ScopeStackIterator(Function<Entry<K, List<V>>, U> valueExtractor, Predicate<? super U> filter) {
ScopeStackIterator(Function<Entry<K, List<Element<V>>>, U> valueExtractor, Predicate<? super U> filter) {
this.valueExtractor = valueExtractor;
this.filter = filter;
}
@ -170,8 +192,8 @@ final class ScopeStack<K, V> implements Iterable<V> {
private U move() {
for (
Entry<K, List<V>> e;
it.hasNext() && ((e = it.next()).getValue().isEmpty() || ((next = valueExtractor.apply(e)) == null) || !filter.test(next));
Entry<K, List<Element<V>>> 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<K, V> implements Iterable<V> {
set0(list(key), value);
}
private final V get0(List<V> list) {
private static final <V> V get0(List<Element<V>> list) {
return getIfNotHidden(getElement0(list));
}
private static final <V> Element<V> getElement0(List<Element<V>> list) {
int i;
if (list == null)
@ -204,7 +230,16 @@ final class ScopeStack<K, V> implements Iterable<V> {
return list.get(i - 1);
}
private final V getCurrentScope0(List<V> list) {
private static final <V> V getIfNotHidden(Element<V> element) {
if (element == null)
return null;
else if (element.hidden)
return null;
else
return element.value;
}
private final V getCurrentScope0(List<Element<V>> list) {
int i;
if (list == null)
@ -214,7 +249,25 @@ final class ScopeStack<K, V> implements Iterable<V> {
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<V> 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<K, V> implements Iterable<V> {
}
final V getOrCreate(K key) {
List<V> list = list(key);
List<Element<V>> list = list(key);
V result = get0(list);
return result != null ? result : create0(key, list);
}
@ -242,13 +295,13 @@ final class ScopeStack<K, V> implements Iterable<V> {
return create0(key, list(key));
}
private final V create0(K key, List<V> list) {
private final V create0(K key, List<Element<V>> list) {
V result = constructor.apply(key, scopeLevel);
set0(list, result);
return result;
}
private final void set0(List<V> list, V value) {
private final void set0(List<Element<V>> list, V value) {
int l = scopeLevel + 1;
int size = list.size();
if (size < l) {
@ -256,14 +309,14 @@ final class ScopeStack<K, V> implements Iterable<V> {
for (int i = 0; i < nulls; i++)
list.add(null);
}
list.set(scopeLevel, value);
list.set(scopeLevel, new Element<>(value));
}
private final List<V> listOrNull(K key) {
private final List<Element<V>> listOrNull(K key) {
return stack().get(key);
}
private final List<V> list(K key) {
private final List<Element<V>> list(K key) {
return stack().computeIfAbsent(key, k -> new ArrayList<>());
}

View File

@ -2452,7 +2452,7 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> 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();

View File

@ -67,6 +67,8 @@ extends
AbstractTable<R>
implements
QOM.TableAlias<R>,
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<Table<?>> tables = collect(ctx.currentScopeParts((Class<Table<?>>) (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

View File

@ -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)