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
<, ≤, >, or ≥, 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 ≤ 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() {