From 4df05b13a7de6b6dd19e52f9966e9d850aa7baa1 Mon Sep 17 00:00:00 2001 From: Mihai Budiu Date: Thu, 13 Aug 2026 21:58:48 -0700 Subject: [PATCH] [CALCITE-7719] Field access on an element of a ROW array built by a sub-query raises IllegalArgumentException Signed-off-by: Mihai Budiu --- .../org/apache/calcite/rex/LogicVisitor.java | 28 +++++++++++++++++-- core/src/test/resources/sql/sub-query.iq | 13 +++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java b/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java index 68a0d0aa9638..b18613b104aa 100644 --- a/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java +++ b/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java @@ -31,13 +31,20 @@ import static java.util.Objects.requireNonNull; /** - * Visitor pattern for traversing a tree of {@link RexNode} objects. + * Visitor that, given the {@link Logic} in force at the root of an + * expression, computes the Logic in force at every occurrence of a sought + * sub-expression {@code seek}. Results are collected in {@code logicCollection}. + * + *

This value is meaningful only for expressions that evaluate to Boolean values. */ public class LogicVisitor extends RexUnaryBiVisitor<@Nullable Logic> { private final RexNode seek; private final Collection logicCollection; - /** Creates a LogicVisitor. */ + /** Creates a LogicVisitor. + * + * @param seek Expression whose occurrences to find + * @param logicCollection Receives the Logic in force for each occurrence of {@code seek} */ private LogicVisitor(RexNode seek, Collection logicCollection) { super(true); this.seek = seek; @@ -51,6 +58,14 @@ private LogicVisitor(RexNode seek, Collection logicCollection) { * answer) with the fewest possibilities (that is, we prefer one that * returns [true as true, false as false, unknown as false] over one that * distinguishes false from unknown). + * + *

If {@code seek} occurs multiple times, the result is + * a single Logic that is safe for every one of them. If the occurrences + * are evaluated under different Logic values, the result is + * {@link Logic#TRUE_FALSE_UNKNOWN}, which is safe for any occurrence. + * + * @throws IllegalArgumentException if {@code seek} does not occur in + * {@code nodes} */ public static Logic find(Logic logic, List nodes, RexNode seek) { @@ -74,6 +89,9 @@ public static Logic find(Logic logic, List nodes, } } + /** Appends to {@code logicList}, for each occurrence of {@code seek} + * within {@code node} in depth-first order, the Logic in force at that + * occurrence. */ public static void collect(RexNode node, RexNode seek, Logic logic, List logicList) { node.accept(new LogicVisitor(seek, logicList), logic); @@ -137,6 +155,12 @@ public static void collect(RexNode node, RexNode seek, Logic logic, @Override public @Nullable Logic visitFieldAccess(RexFieldAccess fieldAccess, @Nullable Logic arg) { + // Not a Boolean value + Logic logic = requireNonNull(arg, "arg"); + if (logic == Logic.TRUE) { + logic = Logic.TRUE_FALSE_UNKNOWN; + } + super.visitFieldAccess(fieldAccess, logic); return end(fieldAccess, arg); } diff --git a/core/src/test/resources/sql/sub-query.iq b/core/src/test/resources/sql/sub-query.iq index 5d1bbb738675..08c5bcf6e4b1 100644 --- a/core/src/test/resources/sql/sub-query.iq +++ b/core/src/test/resources/sql/sub-query.iq @@ -10152,3 +10152,16 @@ ORDER BY emp.ename; !ok # End sub-query.iq + +# [CALCITE-7719] Field access on an element of a ROW array built by a +# sub-query raises IllegalArgumentException. +select t.a[1]."EXPR$0"."EXPR$1" as v +from (select array(select ROW(ROW(1, 2), 3) from (values (0))) as a) as t; ++---+ +| V | ++---+ +| 2 | ++---+ +(1 row) + +!ok