diff --git a/jOOQ/src/main/java/org/jooq/Context.java b/jOOQ/src/main/java/org/jooq/Context.java index f3371ed999..2d4e88890a 100644 --- a/jOOQ/src/main/java/org/jooq/Context.java +++ b/jOOQ/src/main/java/org/jooq/Context.java @@ -428,6 +428,12 @@ public interface Context> extends ExecuteScope { @NotNull C scopeRegister(QueryPart part, boolean forceNew, QueryPart mapped); + /** + * Get all values of a type that are in the current scope or higher. + */ + @NotNull + Iterable scopeParts(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 524abd87f2..f5b400c820 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -870,6 +870,11 @@ abstract class AbstractContext> extends AbstractScope imple return (C) this; } + @Override + public final Iterable scopeParts(Class type) { + return (Iterable) scopeStack.keyIterable(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/ScopeStack.java b/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java index 46f721298e..df79360191 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java +++ b/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java @@ -44,6 +44,8 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -98,7 +100,7 @@ final class ScopeStack implements Iterable { } final Iterable> valueIterable() { - return () -> new ScopeStackIterator>(Value::lastOf, e -> true); + return () -> new ScopeStackIterator>(e -> Value.lastOf(e.getValue()), e -> true); } @Override @@ -107,11 +109,19 @@ final class ScopeStack implements Iterable { } final Iterable iterableAtScopeLevel() { - return () -> new ScopeStackIterator<>(list -> list.size() == scopeLevel + 1 ? list.get(scopeLevel) : null, e -> true); + return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? v.get(scopeLevel) : null, e -> true); } final Iterable iterable(Predicate filter) { - return () -> new ScopeStackIterator<>(list -> list.get(list.size() - 1), filter); + return () -> new ScopeStackIterator<>((k, v) -> v.get(v.size() - 1), filter); + } + + final Iterable keyIterableAtScopeLevel() { + return () -> new ScopeStackIterator<>((k, v) -> v.size() == scopeLevel + 1 ? k : null, e -> true); + } + + final Iterable keyIterable(Predicate filter) { + return () -> new ScopeStackIterator<>((k, v) -> k, filter); } static final record Value(int scopeLevel, V value) { @@ -127,12 +137,16 @@ final class ScopeStack implements Iterable { } private final class ScopeStackIterator implements Iterator { - final Iterator> it = stack().values().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(Function, 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) { this.valueExtractor = valueExtractor; this.filter = filter; } @@ -156,8 +170,8 @@ final class ScopeStack implements Iterable { private U move() { for ( - List list; - it.hasNext() && ((list = it.next()).isEmpty() || ((next = valueExtractor.apply(list)) == null) || !filter.test(next)); + Entry> e; + it.hasNext() && ((e = it.next()).getValue().isEmpty() || ((next = valueExtractor.apply(e)) == null) || !filter.test(next)); next = null ); diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 12349d3067..d0a2cf6f40 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -206,12 +206,15 @@ import static org.jooq.impl.Tools.allMatch; import static org.jooq.impl.Tools.anyMatch; import static org.jooq.impl.Tools.autoAlias; import static org.jooq.impl.Tools.camelCase; +import static org.jooq.impl.Tools.concat; import static org.jooq.impl.Tools.containsUnaliasedTable; import static org.jooq.impl.Tools.fieldArray; import static org.jooq.impl.Tools.hasAmbiguousNames; +import static org.jooq.impl.Tools.hasAmbiguousNamesInTables; import static org.jooq.impl.Tools.isEmpty; import static org.jooq.impl.Tools.isNotEmpty; import static org.jooq.impl.Tools.isWindow; +import static org.jooq.impl.Tools.joinedTables; import static org.jooq.impl.Tools.map; import static org.jooq.impl.Tools.qualify; import static org.jooq.impl.Tools.recordType; @@ -257,6 +260,7 @@ import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -1681,6 +1685,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp + @SuppressWarnings({ "unchecked", "rawtypes" }) final void accept0(Context context) { @@ -1708,16 +1713,25 @@ final class SelectQueryImpl extends AbstractResultQuery imp } case WHEN_MULTIPLE_TABLES: { - int s = getFrom().size(); + if (knownTableSource()) { + Iterator> it = concat( + joinedTables(getFrom()), + context.scopeParts((Class>) (Class) Table.class) + ).iterator(); - if (knownTableSource() && (s == 0 || s == 1 && !(getFrom().get(0) instanceof JoinTable))) - context.data(DATA_RENDER_TABLE, false); + // [#15996] At least 2 tables are in scope + if (!it.hasNext() || it.next() != null && !it.hasNext()) + context.data(DATA_RENDER_TABLE, false); + } break; } case WHEN_AMBIGUOUS_COLUMNS: { - if (knownTableSource() && !hasAmbiguousNames(getSelect())) + if (knownTableSource() && !hasAmbiguousNamesInTables(concat( + joinedTables(getFrom()), + context.scopeParts((Class>) (Class) Table.class) + ))) context.data(DATA_RENDER_TABLE, false); break; diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 4bbcdc54f7..4f874575bb 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -6278,7 +6278,15 @@ final class Tools { return t == Date.class || t == LocalDate.class; } - static final boolean hasAmbiguousNames(Collection> fields) { + static final boolean hasAmbiguousNamesInTables(Iterable> tables) { + if (tables == null) + return false; + + Set names = new HashSet<>(); + return anyMatch(tables, t -> anyMatch(t.fields(), f -> !names.add(f.getName()))); + } + + static final boolean hasAmbiguousNames(Iterable> fields) { if (fields == null) return false; @@ -6659,6 +6667,40 @@ final class Tools { return anyMatch(fields, f -> f.getDataType().isEmbeddable()); } + static final Iterable concat(Iterable i1, Iterable i2) { + return () -> concat(i1.iterator(), i2.iterator()); + } + + static final Iterator concat(Iterator i1, Iterator i2) { + return new Iterator() { + boolean first = true; + + @Override + public boolean hasNext() { + if (first) + if (i1.hasNext()) + return true; + else + first = false; + + return i2.hasNext(); + } + + @Override + public E next() { + return first ? i1.next() : i2.next(); + } + + @Override + public void remove() { + if (first) + i1.remove(); + else + i2.remove(); + } + }; + } + static final List collect(Iterable iterable) { if (iterable instanceof List l) return l; @@ -7360,6 +7402,18 @@ final class Tools { return traverseJoins(in, false, r -> r, search(search, Tools::unwrap)); } + static final List> joinedTables(Iterable> i) { + List> result = new ArrayList<>(); + traverseJoins(i, result::add); + return result; + } + + static final List> joinedTables(Table t) { + List> result = new ArrayList<>(); + traverseJoins(t, result::add); + return result; + } + static final void traverseJoins(Iterable> i, Consumer> consumer) { for (Table t : i) traverseJoins(t, consumer);