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 424f66783c [CALCITE-6735] Type coercion for comparisons does not 
coerce ROW types
424f66783c is described below

commit 424f66783cf7f5aeafed8fca4cfa8c004716385f
Author: Mihai Budiu <[email protected]>
AuthorDate: Tue Dec 17 17:13:53 2024 -0800

    [CALCITE-6735] Type coercion for comparisons does not coerce ROW types
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../adapter/enumerable/RexToLixTranslator.java     | 30 ++++++++++++++++++++++
 .../validate/implicit/AbstractTypeCoercion.java    | 23 +++++++++++++++++
 .../java/org/apache/calcite/test/JdbcTest.java     |  8 +++++-
 .../org/apache/calcite/test/SqlValidatorTest.java  |  2 ++
 .../org/apache/calcite/test/TypeCoercionTest.java  |  2 ++
 core/src/test/resources/sql/misc.iq                | 29 +++++++++++++++++++++
 6 files changed, 93 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index d3411654fe..e38ac3210b 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -32,6 +32,7 @@ import org.apache.calcite.linq4j.tree.ParameterExpression;
 import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.linq4j.tree.Statement;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexCall;
 import org.apache.calcite.rex.RexCallBinding;
@@ -331,6 +332,35 @@ public class RexToLixTranslator implements 
RexVisitor<RexToLixTranslator.Result>
       return Expressions.convert_(cast, 
typeFactory.getJavaClass(nullableTarget));
     }
 
+    if (targetType.getSqlTypeName() == SqlTypeName.ROW) {
+      assert sourceType.getSqlTypeName() == SqlTypeName.ROW;
+      List<RelDataTypeField> targetTypes = targetType.getFieldList();
+      List<RelDataTypeField> sourceTypes = sourceType.getFieldList();
+      assert targetTypes.size() == sourceTypes.size();
+      List<Expression> fields = new ArrayList<>();
+      for (int i = 0; i < targetTypes.size(); i++) {
+        RelDataTypeField targetField = targetTypes.get(i);
+        RelDataTypeField sourceField = sourceTypes.get(i);
+        Expression field = Expressions.arrayIndex(operand, 
Expressions.constant(i));
+        // In the generated Java code 'field' is an Object,
+        // we need to also cast it to the correct type to enable correct 
method dispatch in Java.
+        // We force the type to be nullable; this way, instead of (int) we get 
(Integer).
+        // Casting an object ot an int is not legal.
+        RelDataType nullableSourceFieldType =
+            typeFactory.createTypeWithNullability(sourceField.getType(), true);
+        Type javaType = typeFactory.getJavaClass(nullableSourceFieldType);
+        if (!javaType.getTypeName().equals("java.lang.Void")
+            && !nullableSourceFieldType.isStruct()) {
+          // Cannot cast to Void - this is the type of NULL literals.
+          field = Expressions.convert_(field, javaType);
+        }
+        Expression convert =
+            getConvertExpression(sourceField.getType(), targetField.getType(), 
field, format);
+        fields.add(convert);
+      }
+      return Expressions.call(BuiltInMethod.ARRAY.method, fields);
+    }
+
     switch (targetType.getSqlTypeName()) {
     case VARIANT:
       // Converting any type to a VARIANT invokes the Variant constructor
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java
 
b/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java
index 2d5970440d..8c57c82214 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java
@@ -22,6 +22,7 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
 import org.apache.calcite.runtime.PairList;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCharStringLiteral;
@@ -667,6 +668,28 @@ public abstract class AbstractTypeCoercion implements 
TypeCoercion {
           resultType, type1.isNullable() || type2.isNullable());
     }
 
+    if (typeName1 == SqlTypeName.ROW) {
+      if (typeName2 != typeName1) {
+        return null;
+      }
+      if (type1.getFieldCount() != type2.getFieldCount()) {
+        return null;
+      }
+      List<RelDataTypeField> leftFields = type1.getFieldList();
+      List<RelDataTypeField> rightFields = type2.getFieldList();
+      List<RelDataTypeField> resultFields = new ArrayList<>();
+      for (int i = 0; i < leftFields.size(); i++) {
+        RelDataTypeField leftField = leftFields.get(i);
+        RelDataType type =
+            commonTypeForBinaryComparison(leftField.getType(), 
rightFields.get(i).getType());
+        if (type == null) {
+          return null;
+        }
+        resultFields.add(new RelDataTypeFieldImpl(leftField.getName(), i, 
type));
+      }
+      return factory.createStructType(resultFields);
+    }
+
     return null;
   }
 
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java 
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index f08e99322e..0dcc797d4e 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -6955,7 +6955,13 @@ public class JdbcTest {
   @Test void testRowComparison() {
     CalciteAssert.that()
         .with(CalciteAssert.Config.JDBC_SCOTT)
-        .query("SELECT empno FROM JDBC_SCOTT.emp WHERE (ename, job) < 
('Blake', 'Manager')")
+        // The extra casts are necessary because HSQLDB does not support a ROW 
type,
+        // and in the absence of these explicit casts the code generated 
contains
+        // a cast of a ROW value.  The correct way to fix this would be to 
improve
+        // the code generation for HSQLDB to expand suc casts into constructs
+        // supported by HSQLDB.
+        .query("SELECT empno FROM JDBC_SCOTT.emp WHERE (ename, job) < "
+            + "(CAST('Blake' AS VARCHAR(10)), CAST('Manager' AS VARCHAR(9)))")
         .returnsUnordered("EMPNO=7876", "EMPNO=7499", "EMPNO=7698");
   }
 
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 37a7050701..24951049ef 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1945,6 +1945,8 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
     sql("select row((select deptno from dept where dept.deptno = emp.deptno), 
emp.ename)\n"
         + "from emp")
         .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 void testRowWithValidDot() {
diff --git a/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java 
b/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
index e5375686b3..7377b9cfe1 100644
--- a/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
+++ b/core/src/test/java/org/apache/calcite/test/TypeCoercionTest.java
@@ -404,6 +404,8 @@ class TypeCoercionTest {
         null);
     f.comparisonCommonType(f.recordType("a", f.intType),
         f.recordType("a", f.intType), f.recordType("a", f.intType));
+    f.comparisonCommonType(f.recordType("a", f.intType),
+        f.recordType("a", f.charType), f.recordType("a", f.intType));
     f.comparisonCommonType(f.recordType("a", f.arrayType(f.intType)),
         f.recordType("a", f.arrayType(f.intType)),
         f.recordType("a", f.arrayType(f.intType)));
diff --git a/core/src/test/resources/sql/misc.iq 
b/core/src/test/resources/sql/misc.iq
index 23a602bcbb..8c34645347 100644
--- a/core/src/test/resources/sql/misc.iq
+++ b/core/src/test/resources/sql/misc.iq
@@ -18,6 +18,35 @@
 !use post
 !set outputformat mysql
 
+# Compared by casting varchar to number
+SELECT '1' > 0 AS C;
++------+
+| C    |
++------+
+| true |
++------+
+(1 row)
+
+!ok
+
+# [CALCITE-6735] Type coercion for comparisons does not coerce ROW types
+# These are compared in the same way as '1' > 0
+SELECT ROW('1') > ROW(0) AS C;
++------+
+| C    |
++------+
+| true |
++------+
+(1 row)
+
+!ok
+
+# [CALCITE-6735] Type coercion for comparisons does not coerce ROW types
+# These are compared in the same way as x'10' > 0, which throws
+SELECT ROW(x'10') > ROW(0) AS C;
+java.sql.SQLException: Error while executing SQL "SELECT ROW(x'10') > ROW(0) 
AS C": From line 1, column 11 to line 1, column 26: Cannot apply '>' to 
arguments of type '<RECORDTYPE(BINARY(1) EXPR$0)> > <RECORDTYPE(INTEGER 
EXPR$0)>'.
+!error
+
 # [CALCITE-6733] Type inferred by coercion for comparisons with decimal is too 
narrow
 SELECT ASCII('8') >= ABS(1.1806236821);
 +--------+

Reply via email to