This is an automated email from the ASF dual-hosted git repository.
mbudiu 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 8f2d350670 [CALCITE-7305] Subqueries in ASOF JOIN MATCH_CONDITION
cause an assertion failure
8f2d350670 is described below
commit 8f2d350670420ae4f5b0dc46b826cbf4f8e671eb
Author: Mihai Budiu <[email protected]>
AuthorDate: Fri Nov 28 14:32:40 2025 -0800
[CALCITE-7305] Subqueries in ASOF JOIN MATCH_CONDITION cause an assertion
failure
Signed-off-by: Mihai Budiu <[email protected]>
---
.../calcite/sql/validate/IdentifierNamespace.java | 1 +
.../calcite/sql/validate/SqlValidatorImpl.java | 18 ++++++++++++------
.../org/apache/calcite/test/SqlValidatorTest.java | 22 ++++++++++++++++++++++
3 files changed, 35 insertions(+), 6 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
b/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
index 9291752b90..f23ddec087 100644
---
a/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
+++
b/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java
@@ -69,6 +69,7 @@ public class IdentifierNamespace extends AbstractNamespace {
* @param extendList Extension columns, or null
* @param enclosingNode Enclosing node
* @param parentScope Parent scope which this namespace turns to in order
to
+ * resolve objects
*/
IdentifierNamespace(SqlValidatorImpl validator, SqlIdentifier id,
@Nullable SqlNodeList extendList, @Nullable SqlNode enclosingNode,
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 00a02a3cc4..96d46174d4 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
@@ -2600,6 +2600,9 @@ private SqlNode registerFrom(
scopes.putIfAbsent(stripAs(join.getRight()), parentScope);
scopes.putIfAbsent(stripAs(join.getLeft()), parentScope);
registerSubQueries(joinScope, join.getCondition());
+ if (join.getJoinType() == JoinType.ASOF || join.getJoinType() ==
JoinType.LEFT_ASOF) {
+ registerSubQueries(joinScope, ((SqlAsofJoin)
join).getMatchCondition());
+ }
final JoinNamespace joinNamespace = new JoinNamespace(this, join);
registerNamespace(null, null, joinNamespace, forceNullable);
return join;
@@ -3702,17 +3705,20 @@ private void checkRollUpInUsing(SqlIdentifier
identifier,
/** Get the number of scopes referenced by the specified node; the node
* represents a computation that will be converted to a Rel node eventually.
*/
- private int getScopeCount(SqlNode node) {
- SqlValidatorScope scope = scopes.get(node);
+ private int getScopeCount(@Nullable SqlValidatorScope scope) {
if (scope == null) {
// Not all nodes have an associated scope; count these as "1".
// For example, a VALUES node.
return 1;
- }
- if (scope instanceof ListScope) {
- ListScope join = (ListScope) scope;
+ } else if (scope instanceof JoinScope) {
+ JoinScope join = (JoinScope) scope;
return join.children.size();
+ } else if (scope instanceof WithScope) {
+ return getScopeCount(((WithScope) scope).getParent());
}
+ // We don't need to handle arbitrary scopes here, because the argument
scope
+ // is always from the left side of a join, and the SQL syntax constrains
the
+ // kinds of subqueries that can appear in the left side of a join.
return 1;
}
@@ -3815,7 +3821,7 @@ protected void validateJoin(SqlJoin join,
SqlValidatorScope scope) {
throw newValidationError(condition,
RESOURCE.asofConditionMustBeComparison());
}
- int leftScopeCount = getScopeCount(left);
+ int leftScopeCount = getScopeCount(scopes.get(left));
CompareFromBothSides validateCompare =
new CompareFromBothSides(joinScope, leftScopeCount,
catalogReader, RESOURCE.asofConditionMustBeComparison());
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 105e428efe..2de4ae6704 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -3507,6 +3507,28 @@ void testWinPartClause() {
}
@Test void testAsOfJoin() {
+ sql("WITH "
+ + " T2(id, intt) AS (VALUES(1, 0)),\n"
+ + " T1(id, intt) as (VALUES(1, 0)),\n"
+ + " T3(id) AS (VALUES(1))\n"
+ + "SELECT t1.id, t2.intt\n"
+ + "FROM T1 LEFT ASOF JOIN T2\n"
+ + " MATCH_CONDITION t2.intt < t1.intt\n"
+ + " ON t1.id = t2.id")
+ .ok();
+
+ // Test case for [CALCITE-7305]
+ // Subqueries in ASOF JOIN MATCH_CONDITION cause an assertion failure
+ sql("WITH T1(id, intt) as (VALUES(1, 0)),\n"
+ + " T2(id, intt) AS (VALUES(1, 0)),\n"
+ + " T3(id) AS (VALUES(1))\n"
+ + "SELECT t1.id, t2.intt\n"
+ + "FROM T1 LEFT ASOF JOIN T2\n"
+ + " MATCH_CONDITION ^(t2.intt IN (SELECT id FROM T3))^\n"
+ + " ON t1.id = t2.id")
+ .fails("ASOF JOIN MATCH_CONDITION must be a comparison between columns
"
+ + "from the two inputs");
+
final String type0 = "RecordType(INTEGER NOT NULL EMPNO, INTEGER NOT NULL
DEPTNO) NOT NULL";
final String sql0 = "select emp.empno, dept.deptno from emp asof join
dept\n"
+ "match_condition emp.deptno <= dept.deptno\n"