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

mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new bbe763444a [CALCITE-7846] Set operations on one-column table functions 
can throw or return incorrect rows
bbe763444a is described below

commit bbe763444a9cbd4b4cedac116ee89196e5bec93c
Author: Rodrigo Setti <[email protected]>
AuthorDate: Fri Oct 9 13:15:56 2026 -0500

    [CALCITE-7846] Set operations on one-column table functions can throw or 
return incorrect rows
---
 .../enumerable/EnumerableTableFunctionScan.java    | 16 ++++--
 .../org/apache/calcite/test/TableFunctionTest.java | 62 ++++++++++++++++++++++
 2 files changed, 74 insertions(+), 4 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableTableFunctionScan.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableTableFunctionScan.java
index c9a74e8ae6..09ec574069 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableTableFunctionScan.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableTableFunctionScan.java
@@ -35,6 +35,7 @@
 import org.apache.calcite.sql.validate.SqlConformance;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
+import org.apache.calcite.util.BuiltInMethod;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -109,12 +110,15 @@ private Result defaultTableFunctionImplement(
     // Non-array user-specified types are not supported yet
     final JavaRowFormat format;
     Type elementType = getElementType();
-    if (elementType == null) {
+    final boolean arrayRows = elementType instanceof Class
+        && Object[].class.isAssignableFrom((Class<?>) elementType);
+    if (arrayRows && getRowType().getFieldCount() == 1) {
+      format = JavaRowFormat.SCALAR;
+    } else if (elementType == null) {
       format = JavaRowFormat.ARRAY;
     } else if (getRowType().getFieldCount() == 1 && isQueryable()) {
       format = JavaRowFormat.SCALAR;
-    } else if (elementType instanceof Class
-        && Object[].class.isAssignableFrom((Class<?>) elementType)) {
+    } else if (arrayRows) {
       format = JavaRowFormat.ARRAY;
     } else {
       format = JavaRowFormat.CUSTOM;
@@ -127,7 +131,11 @@ private Result defaultTableFunctionImplement(
             (JavaTypeFactory) getCluster().getTypeFactory(),
             bb, null, implementor.getConformance());
     t = t.setCorrelates(implementor.allCorrelateVariables);
-    bb.add(Expressions.return_(null, t.translate(getCall())));
+    Expression expression = t.translate(getCall());
+    if (arrayRows && format == JavaRowFormat.SCALAR) {
+      expression = Expressions.call(BuiltInMethod.SLICE0.method, expression);
+    }
+    bb.add(Expressions.return_(null, expression));
     return implementor.result(physType, bb.toBlock());
   }
 
diff --git a/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java 
b/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java
index 71aed9c3b9..57a96477b9 100644
--- a/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java
+++ b/core/src/test/java/org/apache/calcite/test/TableFunctionTest.java
@@ -16,19 +16,27 @@
  */
 package org.apache.calcite.test;
 
+import org.apache.calcite.DataContext;
 import org.apache.calcite.config.CalciteConnectionProperty;
 import org.apache.calcite.jdbc.CalciteConnection;
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.linq4j.Linq4j;
 import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.schema.ScannableTable;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.Table;
 import org.apache.calcite.schema.TableFunction;
 import org.apache.calcite.schema.impl.AbstractSchema;
+import org.apache.calcite.schema.impl.AbstractTable;
 import org.apache.calcite.schema.impl.TableFunctionImpl;
+import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.calcite.util.Smalls;
 import org.apache.calcite.util.TestUtil;
 
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
@@ -195,6 +203,60 @@ private CalciteAssert.AssertThat with() {
     assertThat(CalciteAssert.toString(resultSet), is(result));
   }
 
+  /** Tests set operations between one-column table functions and tables. */
+  @Test void testOneColumnTableFunctionSetOperations() throws SQLException {
+    try (Connection connection = DriverManager.getConnection("jdbc:calcite:");
+         Statement statement = connection.createStatement()) {
+      final SchemaPlus schema = connection.unwrap(CalciteConnection.class)
+          .getRootSchema().add("s", new AbstractSchema());
+      schema.add("numbers_fn", TableFunctionImpl.create(NumbersTable.class, 
"numbers"));
+      schema.add("numbers_table", new NumbersTable(2, 3));
+
+      final String function = "SELECT n FROM TABLE(\"s\".\"numbers_fn\"())";
+      final String table = "SELECT n FROM \"s\".\"numbers_table\"";
+      assertThat(CalciteAssert.toString(statement.executeQuery(function + " 
EXCEPT " + table)),
+          is("N=1\n"));
+      assertThat(CalciteAssert.toString(statement.executeQuery(table + " 
EXCEPT " + function)),
+          is("N=3\n"));
+      assertThat(
+          CalciteAssert.toString(
+              statement.executeQuery(function + " UNION " + table + " ORDER BY 
n")),
+          is("N=1\nN=2\nN=3\n"));
+      assertThat(
+          CalciteAssert.toString(
+              statement.executeQuery(table + " UNION " + function + " ORDER BY 
n")),
+          is("N=1\nN=2\nN=3\n"));
+      assertThat(CalciteAssert.toString(statement.executeQuery(function + " 
INTERSECT " + table)),
+          is("N=2\n"));
+      assertThat(CalciteAssert.toString(statement.executeQuery(table + " 
INTERSECT " + function)),
+          is("N=2\n"));
+    }
+  }
+
+  /** A one-column table whose rows are arrays, used as a table and a 
function. */
+  public static class NumbersTable extends AbstractTable implements 
ScannableTable {
+    private final Object[][] rows;
+
+    NumbersTable(int... values) {
+      rows = new Object[values.length][1];
+      for (int i = 0; i < values.length; i++) {
+        rows[i][0] = values[i];
+      }
+    }
+
+    public static ScannableTable numbers() {
+      return new NumbersTable(1, 2);
+    }
+
+    @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+      return typeFactory.builder().add("N", SqlTypeName.INTEGER).build();
+    }
+
+    @Override public Enumerable<@Nullable Object[]> scan(DataContext root) {
+      return Linq4j.asEnumerable(rows);
+    }
+  }
+
   /** As {@link #testScannableTableFunction()} but with named parameters. */
   @Test void testScannableTableFunctionWithNamedParameters()
       throws SQLException {

Reply via email to