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 5b3cf92265 [CALCITE-7231] Validator crashes with AssertionFailure on
query with ROW and IN
5b3cf92265 is described below
commit 5b3cf922657dcdd72eed7d45093db82cc80d0efc
Author: Mihai Budiu <[email protected]>
AuthorDate: Wed Oct 22 14:29:44 2025 -0700
[CALCITE-7231] Validator crashes with AssertionFailure on query with ROW
and IN
Signed-off-by: Mihai Budiu <[email protected]>
---
.../sql/validate/implicit/TypeCoercionImpl.java | 39 +++++++++++++++++-----
core/src/test/resources/sql/conditions.iq | 31 +++++++++++++++++
2 files changed, 61 insertions(+), 9 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java
b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java
index 4db38a144f..762d0498ff 100644
---
a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java
+++
b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java
@@ -516,6 +516,8 @@ private boolean coalesceCoercion(SqlCallBinding
callBinding) {
argTypes[0] = type1;
argTypes[1] = type2;
boolean coerced = false;
+
+ // Find the common types for RSH and LSH columns.
List<RelDataType> widenTypes = new ArrayList<>();
for (int i = 0; i < colCount; i++) {
final int i2 = i;
@@ -545,12 +547,20 @@ private boolean coalesceCoercion(SqlCallBinding
callBinding) {
}
widenTypes.add(widenType);
}
- // Find all the common type for RSH and LSH columns.
+
assert widenTypes.size() == colCount;
+ if (!type1.isStruct()) {
+ // A "scalar" (non-ROW) type is in the LHS of the IN
+ coerced = coerceOperandType(scope, binding.getCall(), 0,
widenTypes.get(0))
+ || coerced;
+ }
+
for (int i = 0; i < widenTypes.size(); i++) {
RelDataType desired = widenTypes.get(i);
// LSH maybe a row values or single node.
if (node1.getKind() == SqlKind.ROW) {
+ // E.g., SELECT ROW('a', 'b') IN (...)
+ // Coerce every field of the ROW to the desired type
assert node1 instanceof SqlCall;
if (coerceOperandType(scope, (SqlCall) node1, i, desired)) {
updateInferredColumnType(
@@ -558,18 +568,28 @@ private boolean coalesceCoercion(SqlCallBinding
callBinding) {
node1, i, widenTypes.get(i));
coerced = true;
}
- } else {
- coerced = coerceOperandType(scope, binding.getCall(), 0, desired)
- || coerced;
}
- // RHS may be a row values expression or sub-query.
+
+ // RHS may be a list of expressions or a sub-query.
if (node2 instanceof SqlNodeList) {
+ // A list of expressions, e.g., SELECT x IN (a, b, c)
+ // ^^^^^^^^^
+ // In this case we try to coerce every expression in the list to the
same type
+ // (this may not be possible)
final SqlNodeList node3 = (SqlNodeList) node2;
boolean listCoerced = false;
if (type2.isStruct()) {
- for (SqlNode node : (SqlNodeList) node2) {
- assert node instanceof SqlCall;
- listCoerced = coerceOperandType(scope, (SqlCall) node, i,
desired) || listCoerced;
+ for (SqlNode node : node3) {
+ if (node instanceof SqlCall) {
+ SqlCall call = (SqlCall) node;
+ if (call.getKind() == SqlKind.ROW) {
+ // An expression in the RHS list is a ROW expression.
+ // e.g., SELECT x IN (ROW(1, 2), ...)
+ // ^^^^^^^^^
+ // In this case cast every field of the ROW to the
corresponding desired type
+ listCoerced = coerceOperandType(scope, (SqlCall) node, i,
desired) || listCoerced;
+ }
+ }
}
if (listCoerced) {
updateInferredColumnType(
@@ -586,7 +606,8 @@ private boolean coalesceCoercion(SqlCallBinding
callBinding) {
}
coerced = coerced || listCoerced;
} else {
- // Another sub-query.
+ // Another sub-query, e.t. SELECT x IN (SELECT a, b FROM ...)
+ // x must have the type ROW(typeof(a), typeof(b))
SqlValidatorScope scope1 = node2 instanceof SqlSelect
? validator.getSelectScope((SqlSelect) node2)
: scope;
diff --git a/core/src/test/resources/sql/conditions.iq
b/core/src/test/resources/sql/conditions.iq
index a9fa54b529..5fa94d0889 100644
--- a/core/src/test/resources/sql/conditions.iq
+++ b/core/src/test/resources/sql/conditions.iq
@@ -18,6 +18,37 @@
!use catchall
!set outputformat mysql
+# Test case for [CALCITE-7231] Validator crashes with AssertionFailure on
query with ROW and IN
+WITH t(x) AS (VALUES(ROW(ROW(1)))) SELECT x IN (x) AS e FROM t;
++------+
+| E |
++------+
+| true |
++------+
+(1 row)
+
+!ok
+
+WITH t(x) AS (VALUES(ROW(ROW(1)))) SELECT x NOT IN (x) AS e FROM t;
++-------+
+| E |
++-------+
+| false |
++-------+
+(1 row)
+
+!ok
+
+WITH t(x) as (VALUES(ROW(ROW(4, 'cat')))) SELECT x IN (ROW(4, 'cat')) AS e
FROM t;
++------+
+| E |
++------+
+| true |
++------+
+(1 row)
+
+!ok
+
# OR test
with tmp(a, b) as (