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