This is an automated email from the ASF dual-hosted git repository.

adarshsanjeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 3ad90699d0c SQL: Stricter validation for GREATEST/LEAST. (#18562)
3ad90699d0c is described below

commit 3ad90699d0ca2351df272ae2c63263d8913e708a
Author: Gian Merlino <[email protected]>
AuthorDate: Mon Sep 22 23:22:36 2025 -0700

    SQL: Stricter validation for GREATEST/LEAST. (#18562)
    
    At the execution layer, the greatest and least functions only accept
    numbers and strings. They throw errors when encountering other types.
    To line up with this, the SQL bindings should verify that the SQL
    type is a type that is represented by a string or number.
---
 .../qaArray/ops_funcs_scalar_funcs.38.all.iq       |  88 ++++++++---------
 .../builtin/GreatestOperatorConversion.java        |   3 +-
 .../builtin/LeastOperatorConversion.java           |   3 +-
 .../builtin/ReductionOperatorConversionHelper.java |  86 ++++++++++++++---
 .../apache/druid/sql/calcite/CalciteQueryTest.java | 104 +++++++++++++++++++++
 5 files changed, 222 insertions(+), 62 deletions(-)

diff --git 
a/quidem-ut/src/test/quidem/org.apache.druid.quidem.QTest/qaArray/ops_funcs_scalar_funcs.38.all.iq
 
b/quidem-ut/src/test/quidem/org.apache.druid.quidem.QTest/qaArray/ops_funcs_scalar_funcs.38.all.iq
index fa8df716435..dff176b90eb 100644
--- 
a/quidem-ut/src/test/quidem/org.apache.druid.quidem.QTest/qaArray/ops_funcs_scalar_funcs.38.all.iq
+++ 
b/quidem-ut/src/test/quidem/org.apache.druid.quidem.QTest/qaArray/ops_funcs_scalar_funcs.38.all.iq
@@ -22,7 +22,7 @@ Cannot apply 'TIMESTAMPDIFF' to arguments of type 
'TIMESTAMPDIFF(
 #-------------------------------------------------------------------------
 SELECT greatest(a_bool, a_bool) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -30,7 +30,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[0]' RETURNING boolean array), 
json_value(a_nested, '$[0]' RETURNING boolean array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -38,7 +38,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[7][0]' RETURNING boolean array), 
json_value(a_nested, '$[7][0]' RETURNING boolean array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -46,7 +46,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(a_int, a_int) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -54,7 +54,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[1]' RETURNING bigint array), 
json_value(a_nested, '$[1]' RETURNING bigint array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -62,7 +62,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[7][1]' RETURNING bigint array), 
json_value(a_nested, '$[7][1]' RETURNING bigint array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -70,7 +70,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(a_float, a_float) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -78,7 +78,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[2]' RETURNING decimal array), 
json_value(a_nested, '$[2]' RETURNING decimal array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -86,7 +86,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[7][2]' RETURNING decimal array), 
json_value(a_nested, '$[7][2]' RETURNING decimal array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -94,7 +94,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(a_str, a_str) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -102,7 +102,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[3]' RETURNING varchar array), 
json_value(a_nested, '$[3]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -110,7 +110,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[7][3]' RETURNING varchar array), 
json_value(a_nested, '$[7][3]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -118,7 +118,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(a_null, a_null) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -126,7 +126,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[4]' RETURNING varchar array), 
json_value(a_nested, '$[4]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -134,7 +134,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[7][4]' RETURNING varchar array), 
json_value(a_nested, '$[7][4]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -142,7 +142,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(a_empty, a_empty) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -150,7 +150,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[5]' RETURNING varchar array), 
json_value(a_nested, '$[5]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -158,7 +158,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[7][5]' RETURNING varchar array), 
json_value(a_nested, '$[7][5]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -166,7 +166,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(a_mixed, a_mixed) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -174,7 +174,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[6]' RETURNING varchar array), 
json_value(a_nested, '$[6]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -182,7 +182,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(json_value(a_nested, '$[7][6]' RETURNING varchar array), 
json_value(a_nested, '$[7][6]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -190,7 +190,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT greatest(a_nested, a_nested) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'GREATEST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -198,7 +198,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(a_bool, a_bool) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -206,7 +206,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[0]' RETURNING boolean array), 
json_value(a_nested, '$[0]' RETURNING boolean array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -214,7 +214,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[7][0]' RETURNING boolean array), 
json_value(a_nested, '$[7][0]' RETURNING boolean array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -222,7 +222,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(a_int, a_int) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -230,7 +230,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[1]' RETURNING bigint array), 
json_value(a_nested, '$[1]' RETURNING bigint array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -238,7 +238,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[7][1]' RETURNING bigint array), 
json_value(a_nested, '$[7][1]' RETURNING bigint array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -246,7 +246,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(a_float, a_float) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -254,7 +254,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[2]' RETURNING decimal array), 
json_value(a_nested, '$[2]' RETURNING decimal array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -262,7 +262,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[7][2]' RETURNING decimal array), 
json_value(a_nested, '$[7][2]' RETURNING decimal array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -270,7 +270,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(a_str, a_str) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -278,7 +278,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[3]' RETURNING varchar array), 
json_value(a_nested, '$[3]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -286,7 +286,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[7][3]' RETURNING varchar array), 
json_value(a_nested, '$[7][3]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -294,7 +294,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(a_null, a_null) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -302,7 +302,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[4]' RETURNING varchar array), 
json_value(a_nested, '$[4]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -310,7 +310,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[7][4]' RETURNING varchar array), 
json_value(a_nested, '$[7][4]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -318,7 +318,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(a_empty, a_empty) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -326,7 +326,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[5]' RETURNING varchar array), 
json_value(a_nested, '$[5]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -334,7 +334,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[7][5]' RETURNING varchar array), 
json_value(a_nested, '$[7][5]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -342,7 +342,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(a_mixed, a_mixed) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -350,7 +350,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[6]' RETURNING varchar array), 
json_value(a_nested, '$[6]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -358,7 +358,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(json_value(a_nested, '$[7][6]' RETURNING varchar array), 
json_value(a_nested, '$[7][6]' RETURNING varchar array)) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
@@ -366,7 +366,7 @@ AvaticaSqlException
 #-------------------------------------------------------------------------
 SELECT least(a_nested, a_nested) AS col
 FROM test_array;
-AvaticaSqlException
+Cannot apply 'LEAST' to arguments of type
 !error
 
 #-------------------------------------------------------------------------
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/GreatestOperatorConversion.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/GreatestOperatorConversion.java
index a3038e2550a..8177951851a 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/GreatestOperatorConversion.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/GreatestOperatorConversion.java
@@ -20,7 +20,6 @@
 package org.apache.druid.sql.calcite.expression.builtin;
 
 import org.apache.calcite.sql.SqlFunction;
-import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.math.expr.Function;
 import org.apache.druid.sql.calcite.expression.DirectOperatorConversion;
@@ -30,7 +29,7 @@ public class GreatestOperatorConversion extends 
DirectOperatorConversion
 {
   private static final SqlFunction SQL_FUNCTION = OperatorConversions
       .operatorBuilder(StringUtils.toUpperCase(Function.GreatestFunc.NAME))
-      .operandTypeChecker(OperandTypes.VARIADIC)
+      
.operandTypeChecker(ReductionOperatorConversionHelper.OPERAND_TYPE_CHECKER)
       .returnTypeInference(ReductionOperatorConversionHelper.TYPE_INFERENCE)
       .build();
 
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/LeastOperatorConversion.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/LeastOperatorConversion.java
index 9745dfb3c1d..52d1348605b 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/LeastOperatorConversion.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/LeastOperatorConversion.java
@@ -20,7 +20,6 @@
 package org.apache.druid.sql.calcite.expression.builtin;
 
 import org.apache.calcite.sql.SqlFunction;
-import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.math.expr.Function;
 import org.apache.druid.sql.calcite.expression.DirectOperatorConversion;
@@ -30,7 +29,7 @@ public class LeastOperatorConversion extends 
DirectOperatorConversion
 {
   private static final SqlFunction SQL_FUNCTION = OperatorConversions
       .operatorBuilder(StringUtils.toUpperCase(Function.LeastFunc.NAME))
-      .operandTypeChecker(OperandTypes.VARIADIC)
+      
.operandTypeChecker(ReductionOperatorConversionHelper.OPERAND_TYPE_CHECKER)
       .returnTypeInference(ReductionOperatorConversionHelper.TYPE_INFERENCE)
       .build();
 
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ReductionOperatorConversionHelper.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ReductionOperatorConversionHelper.java
index 8cd1a781993..5a472cd5042 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ReductionOperatorConversionHelper.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ReductionOperatorConversionHelper.java
@@ -21,12 +21,21 @@ package org.apache.druid.sql.calcite.expression.builtin;
 
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlCallBinding;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperandCountRange;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.type.SqlOperandCountRanges;
+import org.apache.calcite.sql.type.SqlOperandTypeChecker;
 import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.druid.java.util.common.IAE;
+import org.apache.druid.error.DruidException;
 import org.apache.druid.math.expr.ExpressionTypeConversion;
+import org.apache.druid.math.expr.Function;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.ValueType;
+import org.apache.druid.sql.calcite.expression.OperatorConversions;
 import org.apache.druid.sql.calcite.planner.Calcites;
 
 class ReductionOperatorConversionHelper
@@ -39,7 +48,7 @@ class ReductionOperatorConversionHelper
    * Implements type precedence rules similar to:
    * 
https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#function_least
    *
-   * @see org.apache.druid.math.expr.Function.ReduceFunction#apply
+   * @see Function.ReduceFunction#apply
    * @see ExpressionTypeConversion#function
    */
   static final SqlReturnTypeInference TYPE_INFERENCE =
@@ -53,7 +62,6 @@ class ReductionOperatorConversionHelper
 
         SqlTypeName returnSqlTypeName = SqlTypeName.NULL;
         boolean hasDouble = false;
-        boolean isString = false;
         for (int i = 0; i < n; i++) {
           final RelDataType type = opBinding.getOperandType(i);
           final SqlTypeName sqlTypeName = type.getSqlTypeName();
@@ -62,28 +70,78 @@ class ReductionOperatorConversionHelper
           if (SqlTypeName.INTERVAL_TYPES.contains(type.getSqlTypeName())) {
             // handle intervals as a LONG type even though it is a string
             valueType = ColumnType.LONG;
+          } else if (SqlTypeName.NULL == type.getSqlTypeName()) {
+            valueType = null;
           } else {
             valueType = Calcites.getColumnTypeForRelDataType(type);
           }
 
           // Return types are listed in order of preference:
-          if (valueType != null) {
-            if (valueType.is(ValueType.STRING)) {
-              returnSqlTypeName = sqlTypeName;
-              isString = true;
-              break;
-            } else if (valueType.anyOf(ValueType.DOUBLE, ValueType.FLOAT)) {
-              returnSqlTypeName = SqlTypeName.DOUBLE;
-              hasDouble = true;
-            } else if (valueType.is(ValueType.LONG) && !hasDouble) {
+          if (valueType == null) {
+            continue;
+          } else if (valueType.is(ValueType.STRING)) {
+            returnSqlTypeName = sqlTypeName;
+            break;
+          } else if (valueType.anyOf(ValueType.DOUBLE, ValueType.FLOAT)) {
+            returnSqlTypeName = SqlTypeName.DOUBLE;
+            hasDouble = true;
+          } else if (valueType.is(ValueType.LONG)) {
+            if (!hasDouble) {
               returnSqlTypeName = SqlTypeName.BIGINT;
             }
-          } else if (sqlTypeName != SqlTypeName.NULL) {
-            throw new IAE("Argument %d has invalid type: %s", i, sqlTypeName);
+          } else {
+            // The operand checker of the function should prevent other types 
from reaching us.
+            // Throw a defensive exception if we encounter one.
+            throw DruidException.defensive("Got type[%s], which should have 
been a validation error.", type);
           }
         }
 
         return 
typeFactory.createTypeWithNullability(typeFactory.createSqlType(returnSqlTypeName),
 true);
       };
+
+  /**
+   * Type checker that matches the implementation of {@link 
Function.ReduceFunction}: only accept SQL types that
+   * are represented by string or number at the execution layer.
+   */
+  static final SqlOperandTypeChecker OPERAND_TYPE_CHECKER = new 
SqlOperandTypeChecker()
+  {
+    @Override
+    public boolean checkOperandTypes(
+        final SqlCallBinding callBinding,
+        final boolean throwOnFailure
+    )
+    {
+      for (SqlNode operand : callBinding.operands()) {
+        final RelDataType type = 
callBinding.getValidator().deriveType(callBinding.getScope(), operand);
+        final boolean validType =
+            SqlTypeFamily.STRING.contains(type)
+            || SqlTypeFamily.NUMERIC.contains(type)
+            || SqlTypeFamily.TIMESTAMP.contains(type)
+            || SqlTypeFamily.NULL.contains(type);
+
+        if (!validType) {
+          return OperatorConversions.throwOrReturn(
+              throwOnFailure,
+              callBinding,
+              SqlCallBinding::newValidationSignatureError
+          );
+        }
+      }
+
+      return true;
+    }
+
+    @Override
+    public SqlOperandCountRange getOperandCountRange()
+    {
+      return SqlOperandCountRanges.any();
+    }
+
+    @Override
+    public String getAllowedSignatures(SqlOperator op, String opName)
+    {
+      return opName + "(<STRING | NUMERIC | DATETIME | BOOLEAN | NULL>, ...)";
+    }
+  };
 }
 
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
index 593d8535663..edd78ae2007 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
@@ -14740,6 +14740,110 @@ public class CalciteQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
+  @Test
+  public void testGreatestLeastTypes()
+  {
+    cannotVectorizeUnlessFallback();
+    String query = "SELECT\n"
+                   + "__time,\n"
+                   + "l1,\n"
+                   + "m1,\n"
+                   + "l2,\n"
+                   + "GREATEST(l1, l2),\n"
+                   + "GREATEST(l1, dim1),\n"
+                   + "GREATEST(dim1, l1),\n"
+                   + "GREATEST(l1, m1),\n"
+                   + "GREATEST(m1, l1),\n"
+                   + "GREATEST(l1, CAST(l2 AS VARCHAR)),\n"
+                   + "GREATEST(__time, __time + INTERVAL '1' HOUR),\n"
+                   + "GREATEST(l1, NULL),\n"
+                   + "GREATEST(NULL, NULL)\n"
+                   + "FROM druid.numfoo";
+
+    ScanQuery expectedQuery =
+        Druids.newScanQueryBuilder()
+              .dataSource(CalciteTests.DATASOURCE3)
+              .intervals(querySegmentSpec(Filtration.eternity()))
+              .columns("__time", "l1", "m1", "l2", "v0", "v1", "v2", "v3", 
"v4", "v5", "v6", "v7", "v8")
+              .columnTypes(
+                  ColumnType.LONG,
+                  ColumnType.LONG,
+                  ColumnType.FLOAT,
+                  ColumnType.LONG,
+                  ColumnType.LONG,
+                  ColumnType.STRING,
+                  ColumnType.STRING,
+                  ColumnType.DOUBLE,
+                  ColumnType.DOUBLE,
+                  ColumnType.STRING,
+                  ColumnType.LONG,
+                  ColumnType.LONG,
+                  ColumnType.STRING
+              )
+              .virtualColumns(
+                  expressionVirtualColumn(
+                      "v0",
+                      "greatest(\"l1\",\"l2\")",
+                      ColumnType.LONG
+                  ),
+                  expressionVirtualColumn(
+                      "v1",
+                      "greatest(\"l1\",\"dim1\")",
+                      ColumnType.STRING
+                  ),
+                  expressionVirtualColumn(
+                      "v2",
+                      "greatest(\"dim1\",\"l1\")",
+                      ColumnType.STRING
+                  ),
+                  expressionVirtualColumn(
+                      "v3",
+                      "greatest(\"l1\",\"m1\")",
+                      ColumnType.DOUBLE
+                  ),
+                  expressionVirtualColumn(
+                      "v4",
+                      "greatest(\"m1\",\"l1\")",
+                      ColumnType.DOUBLE
+                  ),
+                  expressionVirtualColumn(
+                      "v5",
+                      "greatest(\"l1\",CAST(\"l2\", 'STRING'))",
+                      ColumnType.STRING
+                  ),
+                  expressionVirtualColumn(
+                      "v6",
+                      "greatest(\"__time\",(\"__time\" + 3600000))",
+                      ColumnType.LONG
+                  ),
+                  expressionVirtualColumn(
+                      "v7",
+                      "greatest(\"l1\",null)",
+                      ColumnType.LONG
+                  ),
+                  expressionVirtualColumn(
+                      "v8",
+                      "null",
+                      ColumnType.STRING
+                  )
+              )
+              .resultFormat(ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+              .build();
+
+    testQuery(
+        query,
+        List.of(expectedQuery),
+        List.of(
+            new Object[]{946684800000L, 7L, 1.0F, null, 7L, "7", "7", 7.0D, 
7.0D, "7", 946688400000L, 7L, null},
+            new Object[]{946771200000L, 325323L, 2.0F, 325323L, 325323L, 
"325323", "325323", 325323.0D, 325323.0D, "325323", 946774800000L, 325323L, 
null},
+            new Object[]{946857600000L, 0L, 3.0F, 0L, 0L, "2", "2", 3.0D, 
3.0D, "0", 946861200000L, 0L, null},
+            new Object[]{978307200000L, null, 4.0F, null, null, "1", "1", 
4.0D, 4.0D, null, 978310800000L, null, null},
+            new Object[]{978393600000L, null, 5.0F, null, null, "def", "def", 
5.0D, 5.0D, null, 978397200000L, null, null},
+            new Object[]{978480000000L, null, 6.0F, null, null, "abc", "abc", 
6.0D, 6.0D, null, 978483600000L, null, null}
+        )
+    );
+  }
+
   @Test
   public void testSubqueryTypeMismatchWithLiterals()
   {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to