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 7ac3ac8725 [CALCITE-6911] SqlItemOperator.inferReturnType throws 
AssertionError for out of bounds accesses
7ac3ac8725 is described below

commit 7ac3ac8725e2577e217fdd21bac828d554ee6d7b
Author: Mihai Budiu <[email protected]>
AuthorDate: Tue Mar 25 14:36:15 2025 -0700

    [CALCITE-6911] SqlItemOperator.inferReturnType throws AssertionError for 
out of bounds accesses
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../apache/calcite/runtime/CalciteResource.java    |  9 +++++
 .../apache/calcite/sql/fun/SqlItemOperator.java    | 45 +++++++++++++++++++++-
 .../calcite/runtime/CalciteResource.properties     |  3 ++
 .../org/apache/calcite/test/SqlValidatorTest.java  | 10 +++++
 core/src/test/resources/sql/operator.iq            |  2 +-
 site/_docs/reference.md                            | 18 +++++----
 .../apache/calcite/test/MockSqlOperatorTable.java  |  2 +-
 .../org/apache/calcite/test/SqlOperatorTest.java   |  5 +--
 8 files changed, 79 insertions(+), 15 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java 
b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index d927ab0b3f..ae75eace9d 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -1138,4 +1138,13 @@ ExInst<RuntimeException> 
multipleCapturingGroupsForRegexpFunctions(String value,
 
   @BaseMessage("ASOF JOIN does not support correlated subqueries")
   ExInst<CalciteException> asofCannotBeCorrelated();
+
+  @BaseMessage("ROW type does not have a field named ''{0}'': {1}")
+  ExInst<SqlValidatorException> unknownRowField(String field, String type);
+
+  @BaseMessage("ROW type does not have a field with index {0,number}; legal 
range is 1 to {1,number}")
+  ExInst<SqlValidatorException> illegalRowIndexValue(int field, int max);
+
+  @BaseMessage("Index in ROW type does not have a constant integer or string 
value")
+  ExInst<SqlValidatorException> illegalRowIndex();
 }
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
index dffaa91389..9709f7359b 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
@@ -39,6 +39,7 @@
 
 import static 
org.apache.calcite.sql.type.NonNullableAccessors.getComponentTypeOrThrow;
 import static 
org.apache.calcite.sql.validate.SqlNonNullableAccessors.getOperandLiteralValueOrThrow;
+import static org.apache.calcite.util.Static.RESOURCE;
 
 import static java.util.Objects.requireNonNull;
 
@@ -101,8 +102,48 @@ public SqlItemOperator(String name,
       return false;
     }
     final SqlSingleOperandTypeChecker checker = getChecker(callBinding);
-    return checker.checkSingleOperandType(callBinding, right, 0,
-        throwOnFailure);
+    if (!checker.checkSingleOperandType(callBinding, right, 0, 
throwOnFailure)) {
+      return false;
+    }
+
+    final RelDataType operandType = callBinding.getOperandType(0);
+    if (operandType.getSqlTypeName() != SqlTypeName.ROW) {
+      return true;
+    }
+
+    // For ROW types validate the index value (must be a constant).
+    RelDataType indexType = callBinding.getOperandType(1);
+    if (SqlTypeUtil.isString(indexType)) {
+      final String fieldName = getOperandLiteralValueOrThrow(callBinding, 1, 
String.class);
+      RelDataTypeField field = operandType.getField(fieldName, false, false);
+      if (field == null) {
+        if (throwOnFailure) {
+          throw callBinding.newValidationError(
+              RESOURCE.unknownRowField(fieldName, operandType.toString()));
+        } else {
+          return false;
+        }
+      }
+    } else if (SqlTypeUtil.isIntType(indexType)) {
+      Integer index = callBinding.getOperandLiteralValue(1, Integer.class);
+      if (index == null) {
+        if (throwOnFailure) {
+          throw callBinding.newValidationError(RESOURCE.illegalRowIndex());
+        } else {
+          return false;
+        }
+      }
+      if (index < 1 || index > operandType.getFieldCount()) {
+        if (throwOnFailure) {
+          throw callBinding.newValidationError(
+              RESOURCE.illegalRowIndexValue(index, 
operandType.getFieldCount()));
+        } else {
+          return false;
+        }
+      }
+    }
+
+    return true;
   }
 
   @Override public SqlSingleOperandTypeChecker getOperandTypeChecker() {
diff --git 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 462af08ac1..495c273169 100644
--- 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -372,4 +372,7 @@ AsofRequiresMatchCondition=ASOF JOIN missing MATCH_CONDITION
 AsofMatchMustBeComparison=ASOF JOIN MATCH_CONDITION must be a comparison 
between columns from the two inputs
 AsofConditionMustBeComparison=ASOF JOIN condition must be a conjunction of 
equality comparisons
 AsofCannotBeCorrelated=ASOF JOIN does not support correlated subqueries
+UnknownRowField=ROW type does not have a field named ''{0}'': {1}
+IllegalRowIndexValue=ROW type does not have a field with index {0,number}; 
legal range is 1 to {1,number}
+IllegalRowIndex=Index in ROW type does not have a constant integer or string 
value
 # End CalciteResource.properties
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 8f96fcec2e..ea5485b0b8 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -2029,6 +2029,16 @@ void testLikeAndSimilarFails() {
         .columnType("RecordType(INTEGER EXPR$0, VARCHAR(20) NOT NULL EXPR$1) 
NOT NULL");
     sql("select ROW^(x'12') <> ROW(0.01)^")
         .fails("Cannot apply '<>' to arguments of type.*");
+    // Test cases for [CALCITE-6911] 
https://issues.apache.org/jira/browse/CALCITE-6911
+    // SqlItemOperator.inferReturnType throws AssertionError for out of bounds 
accesses
+    sql("select ^x[2]^ from (select ROW(1) as x)")
+        .fails("ROW type does not have a field with index 2; legal range is 1 
to 1");
+    sql("select ^x[0]^ from (select ROW(1) as x)")
+        .fails("ROW type does not have a field with index 0; legal range is 1 
to 1");
+    sql("select ^T.x['g']^ from (SELECT CAST(ROW(1) AS ROW(f INT)) AS x) AS T")
+        .fails("ROW type does not have a field named 'g': RecordType\\(INTEGER 
F\\)");
+    sql("select T.x['f'] from (SELECT CAST(ROW(1) AS ROW(f INT)) AS x) AS T")
+        .columnType("INTEGER NOT NULL");
   }
 
   @Test void testRowWithValidDot() {
diff --git a/core/src/test/resources/sql/operator.iq 
b/core/src/test/resources/sql/operator.iq
index 9e74030c35..caa6377ad4 100644
--- a/core/src/test/resources/sql/operator.iq
+++ b/core/src/test/resources/sql/operator.iq
@@ -358,7 +358,7 @@ CITY VARCHAR
 !ok
 
 select au."birthPlace"[CAST(NULL AS INTEGER)] as city from 
"bookstore"."authors" au;
-Cannot infer type of field at position null within ROW type: 
RecordType(RecordType(JavaType(class java.math.BigDecimal) latitude, 
JavaType(class java.math.BigDecimal) longtitude) coords, JavaType(class 
java.lang.String) city, JavaType(class java.lang.String) country)
+Index in ROW type does not have a constant integer or string value
 !error
 
 select au."birthPlace"[2] as city from "bookstore"."authors" au;
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 5891735399..8d5f20bc1c 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -418,21 +418,23 @@ ## Grammar
 timestamp value. The matched row on the right side is the closest
 match whose timestamp column is compared using one of the operations
 &lt;, &le;, &gt;, or &ge;, as specified by the comparison operator in
-the `MATCH_CONDITION` clause.  The comparison is performed using SQL
-semantics, which returns 'false' when comparing NULL values with any
+the MATCH_CONDITION clause.  The comparison is performed using SQL
+semantics, which returns 'false' when comparing 'NULL' values with any
 other values.  Thus a 'NULL' timestamp in the left table will not
 match any timestamps in the right table.
 
-ASOF JOIN statements can also be LEFT ASOF JOIN.  In this case, when there
-is no match for a row in the left table, the columns from the right table
-are null-padded.
+ASOF JOIN can be used in an OUTER JOIN form as LEFT ASOF JOIN.  In this case,
+when there is no match for a row in the left table, the columns from
+the right table are null-padded.  There are no RIGHT ASOF joins.
 
-There are no RIGHT ASOF joins.
+Example:
 
+```SQL
 SELECT *
-FROM left_table [ LEFT ] ASOF JOIN right_table
-MATCH_CONDITION ( left_table.timecol &leq; right_table.timecol )
+FROM left_table LEFT ASOF JOIN right_table
+MATCH_CONDITION left_table.timecol <= right_table.timecol
 ON left_table.col = right_table.col
+```
 
 ## Keywords
 
diff --git 
a/testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java 
b/testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java
index 2b65cde83d..242bcf8b42 100644
--- a/testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java
+++ b/testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java
@@ -275,7 +275,7 @@ private static class OperandMetadataImpl implements 
SqlOperandMetadata {
 
       @Override public String getAllowedSignatures(SqlOperator op, String 
opName) {
         return "Score(TABLE table_name)";
-      };
+      }
     }
   }
 
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java 
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 09e3ce7790..b02a4d5d35 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -13289,10 +13289,9 @@ private static void 
checkArrayConcatAggFuncFails(SqlOperatorFixture t) {
     f.check("select \"T\".\"X\"[2] "
             + "from (VALUES (ROW(ROW(3, CAST(NULL AS INTEGER)), ROW(4, 8)))) 
as T(x, y)",
         SqlTests.ANY_TYPE_CHECKER, isNullValue());
-    f.checkFails("select \"T\".\"X\"[1 + CAST(NULL AS INTEGER)] "
+    f.checkFails("select ^\"T\".\"X\"[1 + CAST(NULL AS INTEGER)]^ "
             + "from (VALUES (ROW(ROW(3, CAST(NULL AS INTEGER)), ROW(4, 8)))) 
as T(x, y)",
-        "Cannot infer type of field at position null within ROW type: "
-            + "RecordType\\(INTEGER EXPR\\$0, INTEGER EXPR\\$1\\)", false);
+        "Index in ROW type does not have a constant integer or string value", 
false);
   }
 
   @Test void testOffsetOperator() {

Reply via email to