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 (

Reply via email to