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 {