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 4194916e8b [CALCITE-7228] Validator rejects legal ASOF JOIN program
4194916e8b is described below

commit 4194916e8b2e7e61e4c7eea67621f53704343e56
Author: Mihai Budiu <[email protected]>
AuthorDate: Sat Oct 18 23:13:02 2025 -0700

    [CALCITE-7228] Validator rejects legal ASOF JOIN program
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../calcite/sql/validate/SqlValidatorImpl.java     | 35 ++++++++++++----
 core/src/test/resources/sql/asof.iq                | 49 ++++++++++++++++++++++
 2 files changed, 75 insertions(+), 9 deletions(-)

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 512898f36d..71e76be78a 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
@@ -3699,6 +3699,22 @@ 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);
+    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;
+      return join.children.size();
+    }
+    return 1;
+  }
+
   protected void validateJoin(SqlJoin join, SqlValidatorScope scope) {
     final SqlNode left = join.getLeft();
     final SqlNode right = join.getRight();
@@ -3798,8 +3814,9 @@ protected void validateJoin(SqlJoin join, 
SqlValidatorScope scope) {
         throw newValidationError(condition, 
RESOURCE.asofConditionMustBeComparison());
       }
 
+      int leftScopeCount = getScopeCount(left);
       CompareFromBothSides validateCompare =
-          new CompareFromBothSides(joinScope,
+          new CompareFromBothSides(joinScope, leftScopeCount,
               catalogReader, RESOURCE.asofConditionMustBeComparison());
       condition.accept(validateCompare);
 
@@ -3815,7 +3832,7 @@ protected void validateJoin(SqlJoin join, 
SqlValidatorScope scope) {
 
       // Change the exception in validateCompare when we validate the match 
condition
       validateCompare =
-          new CompareFromBothSides(joinScope,
+          new CompareFromBothSides(joinScope, leftScopeCount,
               catalogReader, RESOURCE.asofMatchMustBeComparison());
       matchCondition.accept(validateCompare);
       break;
@@ -3833,16 +3850,21 @@ protected void validateJoin(SqlJoin join, 
SqlValidatorScope scope) {
    */
   private class CompareFromBothSides extends SqlShuttle {
     final SqlValidatorScope scope;
+    // Number of children scopes on the left side of the join.
+    // Used to determine whether an identifier is from the left input or the 
right input.
+    final int leftScopeCount;
     final SqlValidatorCatalogReader catalogReader;
     final Resources.ExInst<SqlValidatorException> exception;
 
     private CompareFromBothSides(
         SqlValidatorScope scope,
+        int leftScopeCount,
         SqlValidatorCatalogReader catalogReader,
         Resources.ExInst<SqlValidatorException> exception) {
       this.scope = scope;
       this.catalogReader = catalogReader;
       this.exception = exception;
+      this.leftScopeCount = leftScopeCount;
     }
 
     @Override public @Nullable SqlNode visit(final SqlCall call) {
@@ -3873,16 +3895,11 @@ private CompareFromBothSides(
           scope.resolve(id.names.subList(0, id.names.size() - 1), nameMatcher, 
false, resolved);
           SqlValidatorScope.Resolve resolve = resolved.only();
           int index = resolve.path.steps().get(0).i;
-          if (index == 0) {
+          if (index < leftScopeCount) {
             leftFound = true;
-          }
-          if (index == 1) {
+          } else {
             rightFound = true;
           }
-
-          if (!leftFound && !rightFound) {
-            throw newValidationError(call, this.exception);
-          }
         }
         if (!leftFound || !rightFound) {
           // The comparison does not look at both tables
diff --git a/core/src/test/resources/sql/asof.iq 
b/core/src/test/resources/sql/asof.iq
index c0484d94f4..abefdf7746 100644
--- a/core/src/test/resources/sql/asof.iq
+++ b/core/src/test/resources/sql/asof.iq
@@ -219,4 +219,53 @@ ON t1.k = t2.k;
 
 !ok
 
+!use blank
+# The following 3 tables and ASOF JOIN comprise the test case for 
[CALCITE-7228]
+# https://issues.apache.org/jira/browse/CALCITE-7228 Validator rejects legal 
ASOF JOIN program
+CREATE TABLE source_stream_a (
+    common_join_attribute SMALLINT,
+    join_key_a2 VARCHAR,
+    join_key_a1 VARCHAR,
+    event_timestamp TIMESTAMP
+);
+(0 rows modified)
+
+!update
+
+CREATE TABLE reference_table_b (
+    join_key_b1 VARCHAR NOT NULL,
+    common_join_attribute SMALLINT,
+    event_timestamp TIMESTAMP
+);
+(0 rows modified)
+
+!update
+
+CREATE TABLE source_stream_c (
+    join_key_b1 VARCHAR NOT NULL,
+    join_key_c2 VARCHAR NOT NULL,
+    common_join_attribute SMALLINT,
+    epoch_ts_c BIGINT,
+    timestamp_c TIMESTAMP,
+    event_timestamp TIMESTAMP
+);
+(0 rows modified)
+
+!update
+
+SELECT t1.* FROM source_stream_a AS t1
+LEFT JOIN reference_table_b AS t2
+ON t1.common_join_attribute = t2.common_join_attribute AND t1.join_key_a1 = 
t2.join_key_b1
+LEFT ASOF JOIN source_stream_c AS t3
+MATCH_CONDITION ( t1.event_timestamp >= t3.event_timestamp )
+ON t1.common_join_attribute = t3.common_join_attribute AND t1.join_key_a2 = 
t3.join_key_c2
+ AND t1.join_key_a1 = t3.join_key_b1;
++-----------------------+-------------+-------------+-----------------+
+| COMMON_JOIN_ATTRIBUTE | JOIN_KEY_A2 | JOIN_KEY_A1 | EVENT_TIMESTAMP |
++-----------------------+-------------+-------------+-----------------+
++-----------------------+-------------+-------------+-----------------+
+(0 rows)
+
+!ok
+
 # End asof.iq

Reply via email to