This is an automated email from the ASF dual-hosted git repository.

yashmayya pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new a1aab649f8d Refactor set operators to allow multiple children for 
UNION / UNION ALL (#16990)
a1aab649f8d is described below

commit a1aab649f8d126bf55ee81f596b1a6eaaa223826
Author: Yash Mayya <[email protected]>
AuthorDate: Fri Oct 10 15:53:22 2025 -0700

    Refactor set operators to allow multiple children for UNION / UNION ALL 
(#16990)
---
 ...asedSetOperator.java => BinarySetOperator.java} | 49 ++++++++++++--
 .../runtime/operator/set/IntersectAllOperator.java |  2 +-
 .../runtime/operator/set/IntersectOperator.java    |  2 +-
 .../runtime/operator/set/MinusAllOperator.java     |  2 +-
 .../query/runtime/operator/set/MinusOperator.java  |  2 +-
 .../query/runtime/operator/set/SetOperator.java    | 76 ++--------------------
 .../runtime/operator/set/UnionAllOperator.java     | 36 ++++++++--
 .../query/runtime/operator/set/UnionOperator.java  | 73 +++++++++++----------
 .../runtime/operator/set/UnionAllOperatorTest.java |  7 +-
 .../runtime/operator/set/UnionOperatorTest.java    |  4 +-
 .../query/runtime/queries/QueryRunnerTestBase.java |  3 +
 .../src/test/resources/queries/SetOpsNonH2.json    |  9 +++
 12 files changed, 136 insertions(+), 129 deletions(-)

diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/RightRowSetBasedSetOperator.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/BinarySetOperator.java
similarity index 70%
rename from 
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/RightRowSetBasedSetOperator.java
rename to 
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/BinarySetOperator.java
index 0a2ff8e513b..7b9a5938772 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/RightRowSetBasedSetOperator.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/BinarySetOperator.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.query.runtime.operator.set;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.HashMultiset;
 import com.google.common.collect.Multiset;
 import java.util.ArrayList;
@@ -31,25 +32,31 @@ import 
org.apache.pinot.query.runtime.plan.OpChainExecutionContext;
 
 
 /**
- * Abstract base class for set operators that process the right child operator 
first in order to build a set of rows
- * that are then used to filter rows from the left child operator.
+ * Base class for set operators like INTERSECT and EXCEPT / MINUS that always 
have two children.
  */
-public abstract class RightRowSetBasedSetOperator extends SetOperator {
+public abstract class BinarySetOperator extends SetOperator {
+
+  protected final MultiStageOperator _leftChildOperator;
+  protected final MultiStageOperator _rightChildOperator;
   protected final Multiset<Record> _rightRowSet;
+  private MseBlock.Eos _eos;
+  private boolean _isRightChildOperatorProcessed;
 
-  public RightRowSetBasedSetOperator(OpChainExecutionContext 
opChainExecutionContext,
+  public BinarySetOperator(OpChainExecutionContext opChainExecutionContext,
       List<MultiStageOperator> inputOperators,
       DataSchema dataSchema) {
     super(opChainExecutionContext, inputOperators, dataSchema);
+    Preconditions.checkArgument(inputOperators.size() == 2, "Binary set 
operator should have 2 inputs");
+    _leftChildOperator = inputOperators.get(0);
+    _rightChildOperator = inputOperators.get(1);
     _rightRowSet = HashMultiset.create();
   }
 
   /**
    * Processes the right child operator and builds the set of rows that can be 
used to filter the left child.
    *
-   * @return either a data block containing rows or an EoS block, never {@code 
null}.
+   * @return EoS block after processing the right child completely.
    */
-  @Override
   protected MseBlock processRightOperator() {
     MseBlock block = _rightChildOperator.nextBlock();
     while (block.isData()) {
@@ -69,7 +76,6 @@ public abstract class RightRowSetBasedSetOperator extends 
SetOperator {
    *
    * @return block containing matched rows or EoS, never {@code null}.
    */
-  @Override
   protected MseBlock processLeftOperator() {
     // Keep reading the input blocks until we find a match row or all blocks 
are processed.
     // TODO: Consider batching the rows to improve performance.
@@ -92,6 +98,35 @@ public abstract class RightRowSetBasedSetOperator extends 
SetOperator {
     }
   }
 
+  @Override
+  protected MseBlock getNextBlock() {
+    if (_eos != null) {
+      return _eos;
+    }
+
+    if (!_isRightChildOperatorProcessed) {
+      MseBlock mseBlock = processRightOperator();
+
+      if (mseBlock.isData()) {
+        return mseBlock;
+      } else if (mseBlock.isError()) {
+        _eos = (MseBlock.Eos) mseBlock;
+        return _eos;
+      } else if (mseBlock.isSuccess()) {
+        // If it's a regular EOS block, we continue to process the left child 
operator.
+        _isRightChildOperatorProcessed = true;
+      }
+    }
+
+    MseBlock mseBlock = processLeftOperator();
+    if (mseBlock.isEos()) {
+      _eos = (MseBlock.Eos) mseBlock;
+      return _eos;
+    } else {
+      return mseBlock;
+    }
+  }
+
   /**
    * Returns true if the row matches the criteria defined by the set operation.
    * <p>
diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/IntersectAllOperator.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/IntersectAllOperator.java
index 7b0ad8962f3..e5129edb992 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/IntersectAllOperator.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/IntersectAllOperator.java
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
 /**
  * INTERSECT ALL operator.
  */
-public class IntersectAllOperator extends RightRowSetBasedSetOperator {
+public class IntersectAllOperator extends BinarySetOperator {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(IntersectAllOperator.class);
   private static final String EXPLAIN_NAME = "INTERSECT_ALL";
 
diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/IntersectOperator.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/IntersectOperator.java
index 96ef78082b2..981a821cccf 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/IntersectOperator.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/IntersectOperator.java
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Intersect operator.
  */
-public class IntersectOperator extends RightRowSetBasedSetOperator {
+public class IntersectOperator extends BinarySetOperator {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(IntersectOperator.class);
   private static final String EXPLAIN_NAME = "INTERSECT";
 
diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/MinusAllOperator.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/MinusAllOperator.java
index d500f096d5f..e81aa345bf4 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/MinusAllOperator.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/MinusAllOperator.java
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
 /**
  * EXCEPT ALL operator.
  */
-public class MinusAllOperator extends RightRowSetBasedSetOperator {
+public class MinusAllOperator extends BinarySetOperator {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(MinusAllOperator.class);
   private static final String EXPLAIN_NAME = "MINUS_ALL";
 
diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/MinusOperator.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/MinusOperator.java
index 180101b3f80..7cb2857feb7 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/MinusOperator.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/MinusOperator.java
@@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Minus/Except operator.
  */
-public class MinusOperator extends RightRowSetBasedSetOperator {
+public class MinusOperator extends BinarySetOperator {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(MinusOperator.class);
   private static final String EXPLAIN_NAME = "MINUS";
 
diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/SetOperator.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/SetOperator.java
index dcfeb9397cc..6e870835ef2 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/SetOperator.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/SetOperator.java
@@ -18,43 +18,28 @@
  */
 package org.apache.pinot.query.runtime.operator.set;
 
-import com.google.common.base.Preconditions;
 import java.util.List;
 import org.apache.pinot.common.datatable.StatMap;
 import org.apache.pinot.common.utils.DataSchema;
-import org.apache.pinot.core.common.ExplainPlanRows;
-import org.apache.pinot.core.operator.ExecutionStatistics;
-import org.apache.pinot.query.runtime.blocks.MseBlock;
 import org.apache.pinot.query.runtime.operator.MultiStageOperator;
 import org.apache.pinot.query.runtime.plan.OpChainExecutionContext;
-import org.apache.pinot.segment.spi.IndexSegment;
 
 
 /**
- * Set operator, which supports UNION, INTERSECT and EXCEPT.
- * This has two child operators, and the left child operator is the one that 
is used to construct the result.
- * The right child operator is used to construct a set of rows that are used 
to filter the left child operator.
- * The right child operator is consumed in a blocking manner, and the left 
child operator is consumed in a non-blocking
- * UnionOperator: The right child operator is consumed in a blocking manner.
+ * Set operator, which supports UNION (ALL), INTERSECT (ALL) and EXCEPT / 
MINUS (ALL).
  */
 public abstract class SetOperator extends MultiStageOperator {
 
-  protected final MultiStageOperator _leftChildOperator;
-  protected final MultiStageOperator _rightChildOperator;
+  protected final List<MultiStageOperator> _inputOperators;
   protected final DataSchema _dataSchema;
 
-  private boolean _isRightChildOperatorProcessed;
-  private MseBlock.Eos _eos;
   private final StatMap<StatKey> _statMap = new StatMap<>(StatKey.class);
 
   public SetOperator(OpChainExecutionContext opChainExecutionContext, 
List<MultiStageOperator> inputOperators,
       DataSchema dataSchema) {
     super(opChainExecutionContext);
     _dataSchema = dataSchema;
-    Preconditions.checkState(inputOperators.size() == 2, "Set operator should 
have 2 child operators");
-    _leftChildOperator = inputOperators.get(0);
-    _rightChildOperator = inputOperators.get(1);
-    _isRightChildOperatorProcessed = false;
+    _inputOperators = inputOperators;
   }
 
   @Override
@@ -67,62 +52,9 @@ public abstract class SetOperator extends MultiStageOperator 
{
 
   @Override
   public List<MultiStageOperator> getChildOperators() {
-    return List.of(_leftChildOperator, _rightChildOperator);
+    return _inputOperators;
   }
 
-  @Override
-  public void prepareForExplainPlan(ExplainPlanRows explainPlanRows) {
-    super.prepareForExplainPlan(explainPlanRows);
-  }
-
-  @Override
-  public void explainPlan(ExplainPlanRows explainPlanRows, int[] globalId, int 
parentId) {
-    super.explainPlan(explainPlanRows, globalId, parentId);
-  }
-
-  @Override
-  public IndexSegment getIndexSegment() {
-    return super.getIndexSegment();
-  }
-
-  @Override
-  public ExecutionStatistics getExecutionStatistics() {
-    return super.getExecutionStatistics();
-  }
-
-  @Override
-  protected MseBlock getNextBlock() {
-    if (_eos != null) {
-      return _eos;
-    }
-
-    if (!_isRightChildOperatorProcessed) {
-      MseBlock mseBlock = processRightOperator();
-
-      if (mseBlock.isData()) {
-        return mseBlock;
-      } else if (mseBlock.isError()) {
-        _eos = (MseBlock.Eos) mseBlock;
-        return _eos;
-      } else if (mseBlock.isSuccess()) {
-        // If it's a regular EOS block, we continue to process the left child 
operator.
-        _isRightChildOperatorProcessed = true;
-      }
-    }
-
-    MseBlock mseBlock = processLeftOperator();
-    if (mseBlock.isEos()) {
-      _eos = (MseBlock.Eos) mseBlock;
-      return _eos;
-    } else {
-      return mseBlock;
-    }
-  }
-
-  protected abstract MseBlock processLeftOperator();
-
-  protected abstract MseBlock processRightOperator();
-
   @Override
   protected StatMap<?> copyStatMaps() {
     return new StatMap<>(_statMap);
diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/UnionAllOperator.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/UnionAllOperator.java
index 8cd2f07a9cb..f858a27547f 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/UnionAllOperator.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/UnionAllOperator.java
@@ -28,12 +28,15 @@ import org.slf4j.LoggerFactory;
 
 
 /**
- * Union operator for UNION ALL queries.
+ * Union operator for UNION ALL queries. Each child operator is fully drained 
sequentially and all rows are returned.
  */
 public class UnionAllOperator extends SetOperator {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(UnionAllOperator.class);
   private static final String EXPLAIN_NAME = "UNION_ALL";
 
+  private MseBlock _eosBlock = null;
+  private int _currentOperatorIndex = 0;
+
   public UnionAllOperator(OpChainExecutionContext opChainExecutionContext, 
List<MultiStageOperator> inputOperators,
       DataSchema dataSchema) {
     super(opChainExecutionContext, inputOperators, dataSchema);
@@ -55,12 +58,31 @@ public class UnionAllOperator extends SetOperator {
   }
 
   @Override
-  protected MseBlock processRightOperator() {
-    return _rightChildOperator.nextBlock();
-  }
+  protected MseBlock getNextBlock()
+      throws Exception {
+    if (_eosBlock != null) {
+      return _eosBlock;
+    }
 
-  @Override
-  protected MseBlock processLeftOperator() {
-    return _leftChildOperator.nextBlock();
+    while (_currentOperatorIndex < _inputOperators.size()) {
+      MultiStageOperator currentOperator = 
_inputOperators.get(_currentOperatorIndex);
+      MseBlock block = currentOperator.nextBlock();
+      if (block.isError()) {
+        _eosBlock = block;
+        return block;
+      } else if (block.isSuccess()) {
+        _currentOperatorIndex++;
+        if (_currentOperatorIndex == _inputOperators.size()) {
+          _eosBlock = block;
+          return block;
+        }
+      } else if (block.isData()) {
+        return block;
+      }
+    }
+
+    // All input operators are exhausted, return EoS block.
+    assert _eosBlock != null;
+    return _eosBlock;
   }
 }
diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/UnionOperator.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/UnionOperator.java
index ff724f22bea..2d51edcdbc9 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/UnionOperator.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/set/UnionOperator.java
@@ -18,8 +18,10 @@
  */
 package org.apache.pinot.query.runtime.operator.set;
 
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import javax.annotation.Nullable;
 import org.apache.pinot.common.utils.DataSchema;
 import org.apache.pinot.core.data.table.Record;
@@ -33,53 +35,58 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Union operator for UNION queries. Unlike {@link UnionAllOperator}, this 
operator removes duplicate rows and only
- * returns distinct rows.
+ * returns distinct rows. Each child operator is fully drained sequentially 
and distinct rows are returned.
  */
-public class UnionOperator extends RightRowSetBasedSetOperator {
+public class UnionOperator extends SetOperator {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(UnionOperator.class);
   private static final String EXPLAIN_NAME = "UNION";
 
+  private MseBlock _eosBlock = null;
+  private int _currentOperatorIndex = 0;
+  private final Set<Record> _seenRecords = new ObjectOpenHashSet<>();
+
   public UnionOperator(OpChainExecutionContext opChainExecutionContext,
       List<MultiStageOperator> inputOperators, DataSchema dataSchema) {
     super(opChainExecutionContext, inputOperators, dataSchema);
   }
 
   @Override
-  protected MseBlock processRightOperator() {
-    MseBlock block = _rightChildOperator.nextBlock();
-    while (block.isData()) {
-      MseBlock.Data dataBlock = (MseBlock.Data) block;
-      List<Object[]> rows = new ArrayList<>();
-      for (Object[] row : dataBlock.asRowHeap().getRows()) {
-        Record record = new Record(row);
-        if (!_rightRowSet.contains(record)) {
-          // Add a new unique row.
-          rows.add(row);
-          _rightRowSet.add(record);
+  protected MseBlock getNextBlock()
+      throws Exception {
+    if (_eosBlock != null) {
+      return _eosBlock;
+    }
+
+    while (_currentOperatorIndex < _inputOperators.size()) {
+      MultiStageOperator currentOperator = 
_inputOperators.get(_currentOperatorIndex);
+      MseBlock block = currentOperator.nextBlock();
+      if (block.isError()) {
+        _eosBlock = block;
+        return block;
+      } else if (block.isSuccess()) {
+        _currentOperatorIndex++;
+        if (_currentOperatorIndex == _inputOperators.size()) {
+          _eosBlock = block;
+          return block;
+        }
+      } else if (block.isData()) {
+        List<Object[]> rows = new ArrayList<>();
+        for (Object[] row : ((MseBlock.Data) block).asRowHeap().getRows()) {
+          Record record = new Record(row);
+          // TODO: Use a more memory efficient way to track seen rows.
+          if (_seenRecords.add(record)) {
+            rows.add(row);
+          }
+        }
+        if (!rows.isEmpty()) {
+          return new RowHeapDataBlock(rows, _dataSchema);
         }
-      }
-      checkTerminationAndSampleUsage();
-      // If we have collected some rows, return them as a new block.
-      if (!rows.isEmpty()) {
-        return new RowHeapDataBlock(rows, _dataSchema);
-      } else {
-        block = _rightChildOperator.nextBlock();
       }
     }
-    assert block.isEos();
-    return block;
-  }
 
-  @Override
-  protected boolean handleRowMatched(Object[] row) {
-    if (!_rightRowSet.contains(new Record(row))) {
-      // Row is unique, add it to the result and also to the row set to skip 
later duplicates.
-      _rightRowSet.add(new Record(row));
-      return true;
-    } else {
-      // Row is a duplicate, skip it.
-      return false;
-    }
+    // All input operators are exhausted, return EoS block.
+    assert _eosBlock != null;
+    return _eosBlock;
   }
 
   @Override
diff --git 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/set/UnionAllOperatorTest.java
 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/set/UnionAllOperatorTest.java
index fdfc494c1a9..79ecbbe8b93 100644
--- 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/set/UnionAllOperatorTest.java
+++ 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/set/UnionAllOperatorTest.java
@@ -42,6 +42,7 @@ public class UnionAllOperatorTest {
     MultiStageOperator leftOperator = new 
BlockListMultiStageOperator.Builder(schema)
         .addRow(1, "AA")
         .addRow(2, "BB")
+        .addRow(3, "aa")
         .buildWithEos();
     MultiStageOperator rightOperator = new 
BlockListMultiStageOperator.Builder(schema)
         .addRow(3, "aa")
@@ -58,11 +59,9 @@ public class UnionAllOperatorTest {
       resultRows.addAll(((MseBlock.Data) result).asRowHeap().getRows());
       result = unionAllOperator.nextBlock();
     }
-    // Note that UNION ALL does not guarantee the order of rows, and our 
implementation adds rows from the right child
-    // first
     List<Object[]> expectedRows =
-        Arrays.asList(new Object[]{3, "aa"}, new Object[]{4, "bb"}, new 
Object[]{5, "cc"}, new Object[]{1, "AA"},
-            new Object[]{2, "BB"});
+        Arrays.asList(new Object[]{1, "AA"}, new Object[]{2, "BB"}, new 
Object[]{3, "aa"}, new Object[]{3, "aa"},
+            new Object[]{4, "bb"}, new Object[]{5, "cc"});
     Assert.assertEquals(resultRows.size(), expectedRows.size());
     for (int i = 0; i < resultRows.size(); i++) {
       Assert.assertEquals(resultRows.get(i), expectedRows.get(i));
diff --git 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/set/UnionOperatorTest.java
 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/set/UnionOperatorTest.java
index 971bbb34062..a69b8f0febd 100644
--- 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/set/UnionOperatorTest.java
+++ 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/operator/set/UnionOperatorTest.java
@@ -60,8 +60,8 @@ public class UnionOperatorTest {
       result = unionOperator.nextBlock();
     }
     List<Object[]> expectedRows =
-        Arrays.asList(new Object[]{3, "aa"}, new Object[]{4, "bb"}, new 
Object[]{5, "cc"}, new Object[]{2, "BB"},
-            new Object[]{1, "AA"});
+        Arrays.asList(new Object[]{1, "AA"}, new Object[]{2, "BB"}, new 
Object[]{3, "aa"}, new Object[]{4, "bb"},
+            new Object[]{5, "cc"});
     Assert.assertEquals(resultRows.size(), expectedRows.size());
     for (int i = 0; i < resultRows.size(); i++) {
       Assert.assertEquals(resultRows.get(i), expectedRows.get(i));
diff --git 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java
 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java
index 35cacc6ce20..44a54c76a3a 100644
--- 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java
+++ 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryRunnerTestBase.java
@@ -306,6 +306,9 @@ public abstract class QueryRunnerTestBase extends 
QueryTestSet {
             "Got unexpected value type: " + value.getClass() + " for BYTES 
column, expected: String or byte[]");
         return value;
       case INT_ARRAY:
+        if (value instanceof List) {
+          return ((List) value).stream().mapToInt(i -> (int) i).toArray();
+        }
         if (value instanceof JdbcArray) {
           try {
             Object[] array = (Object[]) ((JdbcArray) value).getArray();
diff --git a/pinot-query-runtime/src/test/resources/queries/SetOpsNonH2.json 
b/pinot-query-runtime/src/test/resources/queries/SetOpsNonH2.json
index 2a0e8de5697..df5eeeb8aff 100644
--- a/pinot-query-runtime/src/test/resources/queries/SetOpsNonH2.json
+++ b/pinot-query-runtime/src/test/resources/queries/SetOpsNonH2.json
@@ -166,6 +166,15 @@
           [4],
           [2]
         ]
+      },
+      {
+        "description": "UNION with three children",
+        "sql": "WITH data AS (SELECT a FROM (VALUES(array [1, 2]), (array [3, 
4]), (array [5, 6])) \"data\" (\"a\")) SELECT * FROM data",
+        "outputs": [
+          [[1, 2]],
+          [[3, 4]],
+          [[5, 6]]
+        ]
       }
     ]
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to