This is an automated email from the ASF dual-hosted git repository.
xuzifu666 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 f229a2af3e [CALCITE-5261] UNION(ALL) inside of the CURSOR throws an
exception while validating the query
f229a2af3e is described below
commit f229a2af3ea1bc622861c9ad132145cc50a99b38
Author: Yu Xu <[email protected]>
AuthorDate: Mon Jun 29 20:45:12 2026 +0800
[CALCITE-5261] UNION(ALL) inside of the CURSOR throws an exception while
validating the query
---
.../calcite/sql/fun/SqlCursorConstructor.java | 8 ++--
.../apache/calcite/sql/validate/SqlValidator.java | 8 ++--
.../calcite/sql/validate/SqlValidatorImpl.java | 50 +++++++++++++++-------
.../apache/calcite/test/SqlToRelConverterTest.java | 14 ++++++
.../org/apache/calcite/test/SqlValidatorTest.java | 36 ++++++++++++++++
.../apache/calcite/test/SqlToRelConverterTest.xml | 32 ++++++++++++++
6 files changed, 125 insertions(+), 23 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlCursorConstructor.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlCursorConstructor.java
index 7d23c4123e..cb6fb9ae14 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCursorConstructor.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCursorConstructor.java
@@ -19,7 +19,7 @@
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlKind;
-import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.type.OperandTypes;
@@ -50,9 +50,9 @@ public SqlCursorConstructor() {
SqlValidator validator,
SqlValidatorScope scope,
SqlCall call) {
- SqlSelect subSelect = call.operand(0);
- validator.declareCursor(subSelect, scope);
- subSelect.validateExpr(validator, scope);
+ final SqlNode query = call.operand(0);
+ validator.declareCursor(query, scope);
+ query.validateExpr(validator, scope);
return super.deriveType(validator, scope, call);
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
index 3846ed5daf..813247376d 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
@@ -635,12 +635,12 @@ SqlNodeList expandStar(SqlNodeList selectList, SqlSelect
query,
SqlValidatorScope getEmptyScope();
/**
- * Declares a SELECT expression as a cursor.
+ * Declares a query expression as a cursor.
*
- * @param select select expression associated with the cursor
- * @param scope scope of the parent query associated with the cursor
+ * @param query query expression associated with the cursor
+ * @param scope scope of the parent query associated with the cursor
*/
- void declareCursor(SqlSelect select, SqlValidatorScope scope);
+ void declareCursor(SqlNode query, SqlValidatorScope scope);
/**
* Pushes a new instance of a function call on to a function call stack.
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index 37f6712e90..26faab77bc 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -415,27 +415,47 @@ public SqlConformance getConformance() {
return new SqlNodeList(list, SqlParserPos.ZERO);
}
- @Override public void declareCursor(SqlSelect select,
+ @Override public void declareCursor(SqlNode query,
SqlValidatorScope parentScope) {
- cursorSet.add(select);
+ cursorSet.add(query);
- // add the cursor to a map that maps the cursor to its select based on
+ // add the cursor to a map that maps the cursor to its query based on
// the position of the cursor relative to other cursors in that call
FunctionParamInfo funcParamInfo =
requireNonNull(functionCallStack.peek(), "functionCall");
- Map<Integer, SqlSelect> cursorMap = funcParamInfo.cursorPosToSelectMap;
+ Map<Integer, SqlNode> cursorMap = funcParamInfo.cursorPosToQueryMap;
final int cursorCount = cursorMap.size();
- cursorMap.put(cursorCount, select);
+ cursorMap.put(cursorCount, query);
- // create a namespace associated with the result of the select
+ // create a namespace associated with the result of the query
// that is the argument to the cursor constructor; register it
// with a scope corresponding to the cursor
- SelectScope cursorScope =
- new SelectScope(parentScope, getEmptyScope(), select);
- clauseScopes.put(IdPair.of(select, Clause.CURSOR), cursorScope);
- final SelectNamespace selectNs = createSelectNamespace(select, select);
- final String alias = SqlValidatorUtil.alias(select, nextGeneratedId++);
- registerNamespace(cursorScope, alias, selectNs, false);
+ final SqlValidatorNamespace ns;
+ final SqlValidatorScope cursorScope;
+ if (query instanceof SqlSelect) {
+ SqlSelect select = (SqlSelect) query;
+ cursorScope = new SelectScope(parentScope, getEmptyScope(), select);
+ clauseScopes.put(IdPair.of(select, Clause.CURSOR), cursorScope);
+ ns = createSelectNamespace(select, select);
+ } else {
+ final SqlCall call = (SqlCall) query;
+ cursorScope = new ListScope(parentScope) {
+ @Override public SqlNode getNode() {
+ return call;
+ }
+ };
+ if (query.isA(SqlKind.SET_QUERY)) {
+ ns = createSetopNamespace(call, call);
+ } else if (query.getKind() == SqlKind.VALUES) {
+ ns = new TableConstructorNamespace(this, call, cursorScope, call);
+ } else if (query.getKind() == SqlKind.WITH) {
+ ns = new WithNamespace(this, (SqlWith) call, call);
+ } else {
+ throw Util.unexpected(query.getKind());
+ }
+ }
+ final String alias = SqlValidatorUtil.alias(query, nextGeneratedId++);
+ registerNamespace(cursorScope, alias, ns, false);
}
@Override public void pushFunctionCall() {
@@ -8469,10 +8489,10 @@ public IdInfo(SqlValidatorScope scope, SqlIdentifier
id) {
protected static class FunctionParamInfo {
/**
* Maps a cursor (based on its position relative to other cursor
- * parameters within a function call) to the SELECT associated with the
+ * parameters within a function call) to the query associated with the
* cursor.
*/
- public final Map<Integer, SqlSelect> cursorPosToSelectMap;
+ public final Map<Integer, SqlNode> cursorPosToQueryMap;
/**
* Maps a column list parameter to the parent cursor parameter it
@@ -8481,7 +8501,7 @@ protected static class FunctionParamInfo {
public final Map<String, String> columnListParamToParentCursorMap;
public FunctionParamInfo() {
- cursorPosToSelectMap = new HashMap<>();
+ cursorPosToQueryMap = new HashMap<>();
columnListParamToParentCursorMap = new HashMap<>();
}
}
diff --git
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 1d5833c516..1016acc410 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -1886,6 +1886,20 @@ public static void checkActualAndReferenceFiles() {
sql(sql).withDecorrelate(false).ok();
}
+ /** Test case for CURSOR containing UNION ALL. */
+ @Test void testCollectionTableWithCursorParamUnion() {
+ final String sql = "select * from table(dedup("
+ + "cursor(select ename from emp union all select ename from emp),
'NAME'))";
+ sql(sql).withDecorrelate(false).ok();
+ }
+
+ /** Test case for CURSOR containing UNION (distinct). */
+ @Test void testCollectionTableWithCursorParamUnionDistinct() {
+ final String sql = "select * from table(dedup("
+ + "cursor(select ename from emp union select ename from emp),
'NAME'))";
+ sql(sql).withDecorrelate(false).ok();
+ }
+
@Test void testUnnest() {
final String sql = "select*from unnest(multiset[1,2])";
sql(sql).ok();
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 0cf1966f99..0fb4fa4055 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -34,6 +34,7 @@
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlOperator;
@@ -47,6 +48,7 @@
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.ArraySqlType;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
@@ -63,6 +65,7 @@
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorCatalogReader;
import org.apache.calcite.sql.validate.SqlValidatorImpl;
+import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.test.catalog.CountingFactory;
import org.apache.calcite.test.catalog.MockCatalogReaderSimple;
@@ -101,12 +104,14 @@
import static org.apache.calcite.test.Matchers.isCharset;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasToString;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -10089,11 +10094,42 @@ void testGroupExpressionEquivalenceParams() {
+ "`CATALOG`.`SALES`.`EMP` AS `EMP`");
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-5261">[CALCITE-5261]
+ * UNION(ALL) inside of the CURSOR throws an exception while validating the
query</a>.
+ * */
@Test void testCollectionTableWithCursorParam() {
sql("select * from table(dedup(cursor(select * from emp),'ename'))")
.type("RecordType(VARCHAR(1024) NOT NULL NAME) NOT NULL");
sql("select * from table(dedup(cursor(select * from ^bloop^),'ename'))")
.fails("Object 'BLOOP' not found");
+ sql("select * from table(dedup(cursor(select ename from emp union all "
+ + "select ename from emp), 'ename'))")
+ .type("RecordType(VARCHAR(1024) NOT NULL NAME) NOT NULL");
+ sql("select * from table(dedup(cursor(select ename from emp union "
+ + "select ename from emp), 'ename'))")
+ .type("RecordType(VARCHAR(1024) NOT NULL NAME) NOT NULL");
+ sql("select * from table(dedup(cursor(values ('a'), ('b')), 'COLUMN0'))")
+ .type("RecordType(VARCHAR(1024) NOT NULL NAME) NOT NULL");
+ sql("select * from table(dedup(cursor(with cte as (select ename from emp) "
+ + "select * from cte), 'ENAME'))")
+ .type("RecordType(VARCHAR(1024) NOT NULL NAME) NOT NULL");
+ }
+
+ @Test void testDeclareCursorUnexpectedKind() {
+ final SqlValidator validator = fixture().factory.createValidator();
+ validator.pushFunctionCall();
+ final SqlCall query =
+ new SqlBasicCall(SqlStdOperatorTable.EXPLICIT_TABLE,
+ new SqlNodeList(
+ ImmutableList.of(SqlLiteral.createNull(SqlParserPos.ZERO)),
+ SqlParserPos.ZERO),
+ SqlParserPos.ZERO);
+ final SqlValidatorScope scope = validator.getEmptyScope();
+ final AssertionError error =
+ assertThrows(AssertionError.class, () ->
+ ((SqlValidatorImpl) validator).declareCursor(query, scope));
+ assertThat(error.getMessage(), containsString("EXPLICIT_TABLE"));
}
@Test void testTemporalTable() {
diff --git
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 90b3c09a24..bd6a2246a1 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -898,6 +898,38 @@ LogicalProject(NAME=[$0])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalProject(NAME=[$1])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testCollectionTableWithCursorParamUnion">
+ <Resource name="sql">
+ <![CDATA[select * from table(dedup(cursor(select ename from emp union
all select ename from emp), 'NAME'))]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalProject(NAME=[$0])
+ LogicalTableFunctionScan(invocation=[DEDUP(CAST($0):CURSOR NOT NULL,
'NAME')], rowType=[RecordType(VARCHAR(1024) NAME)])
+ LogicalUnion(all=[true])
+ LogicalProject(ENAME=[$1])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(ENAME=[$1])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testCollectionTableWithCursorParamUnionDistinct">
+ <Resource name="sql">
+ <![CDATA[select * from table(dedup(cursor(select ename from emp union
select ename from emp), 'NAME'))]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalProject(NAME=[$0])
+ LogicalTableFunctionScan(invocation=[DEDUP(CAST($0):CURSOR NOT NULL,
'NAME')], rowType=[RecordType(VARCHAR(1024) NAME)])
+ LogicalUnion(all=[false])
+ LogicalProject(ENAME=[$1])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(ENAME=[$1])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>