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

cwylie 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 e373f626925 fix expression post aggregator array handling when 
grouping wrapper types leak (#15543)
e373f626925 is described below

commit e373f6269251655f5be93ce895aee8dee8cc67dd
Author: Clint Wylie <[email protected]>
AuthorDate: Fri Dec 15 21:43:27 2023 -0800

    fix expression post aggregator array handling when grouping wrapper types 
leak (#15543)
    
    * fix expression post aggregator array handling when grouping wrapper types 
leak
    * more consistent expression function error messaging
---
 .../druid/math/expr/BinaryEvalOpExprBase.java      |   2 +-
 .../java/org/apache/druid/math/expr/ExprEval.java  |  20 +-
 .../org/apache/druid/math/expr/FunctionalExpr.java |  16 +-
 .../apache/druid/math/expr/UnaryOperatorExpr.java  |   2 +-
 .../org/apache/druid/segment/column/Types.java     |  16 ++
 .../java/org/apache/druid/math/expr/EvalTest.java  |  14 +
 .../org/apache/druid/math/expr/FunctionTest.java   | 271 +++++++++----------
 .../druid/sql/calcite/CalciteArraysQueryTest.java  |  93 +++++++
 .../sql/calcite/expression/ExpressionTestBase.java |  36 ---
 .../sql/calcite/expression/ExpressionsTest.java    | 290 +++++++++++----------
 .../calcite/expression/GreatestExpressionTest.java |   3 +-
 .../expression/IPv4AddressMatchExpressionTest.java |  80 +++---
 .../expression/IPv4AddressParseExpressionTest.java |  38 +--
 .../IPv4AddressStringifyExpressionTest.java        |  38 +--
 .../calcite/expression/LeastExpressionTest.java    |   3 +-
 .../TimeFormatOperatorConversionTest.java          |   3 +-
 16 files changed, 521 insertions(+), 404 deletions(-)

diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/BinaryEvalOpExprBase.java 
b/processing/src/main/java/org/apache/druid/math/expr/BinaryEvalOpExprBase.java
index 8dd4b960251..2104dcc45db 100644
--- 
a/processing/src/main/java/org/apache/druid/math/expr/BinaryEvalOpExprBase.java
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/BinaryEvalOpExprBase.java
@@ -207,7 +207,7 @@ abstract class BinaryBooleanOpExprBase extends 
BinaryOpExprBase
         break;
     }
     if (!ExpressionProcessing.useStrictBooleans() && !type.is(ExprType.STRING) 
&& !type.isArray()) {
-      return ExprEval.ofBoolean(result, type.getType());
+      return ExprEval.ofBoolean(result, type);
     }
     return ExprEval.ofLongBoolean(result);
   }
diff --git a/processing/src/main/java/org/apache/druid/math/expr/ExprEval.java 
b/processing/src/main/java/org/apache/druid/math/expr/ExprEval.java
index e62021623bd..9c0f5e2736a 100644
--- a/processing/src/main/java/org/apache/druid/math/expr/ExprEval.java
+++ b/processing/src/main/java/org/apache/druid/math/expr/ExprEval.java
@@ -30,6 +30,9 @@ import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.segment.column.NullableTypeStrategy;
 import org.apache.druid.segment.column.TypeStrategies;
 import org.apache.druid.segment.column.TypeStrategy;
+import org.apache.druid.segment.column.Types;
+import org.apache.druid.segment.data.ComparableList;
+import org.apache.druid.segment.data.ComparableStringArray;
 import org.apache.druid.segment.nested.StructuredData;
 
 import javax.annotation.Nullable;
@@ -357,9 +360,9 @@ public abstract class ExprEval<T>
    * instead.
    */
   @Deprecated
-  public static ExprEval ofBoolean(boolean value, ExprType type)
+  public static ExprEval ofBoolean(boolean value, ExpressionType type)
   {
-    switch (type) {
+    switch (type.getType()) {
       case DOUBLE:
         return ExprEval.of(Evals.asDouble(value));
       case LONG:
@@ -367,7 +370,7 @@ public abstract class ExprEval<T>
       case STRING:
         return ExprEval.of(String.valueOf(value));
       default:
-        throw new IllegalArgumentException("Invalid type, cannot coerce [" + 
type + "] to boolean");
+        throw new Types.InvalidCastBooleanException(type);
     }
   }
 
@@ -502,6 +505,13 @@ public abstract class ExprEval<T>
       final List<?> theList = val instanceof List ? ((List<?>) val) : 
Arrays.asList((Object[]) val);
       return bestEffortArray(theList);
     }
+    // handle leaky group by array types
+    if (val instanceof ComparableStringArray) {
+      return new ArrayExprEval(ExpressionType.STRING_ARRAY, 
((ComparableStringArray) val).getDelegate());
+    }
+    if (val instanceof ComparableList) {
+      return bestEffortArray(((ComparableList) val).getDelegate());
+    }
 
     // in 'best effort' mode, we couldn't possibly use byte[] as a complex or 
anything else useful without type
     // knowledge, so lets turn it into a base64 encoded string so at least 
something downstream can use it by decoding
@@ -1569,8 +1579,8 @@ public abstract class ExprEval<T>
     }
   }
 
-  public static IAE invalidCast(ExpressionType fromType, ExpressionType toType)
+  public static Types.InvalidCastException invalidCast(ExpressionType 
fromType, ExpressionType toType)
   {
-    return new IAE("Invalid type, cannot cast [" + fromType + "] to [" + 
toType + "]");
+    return new Types.InvalidCastException(fromType, toType);
   }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java 
b/processing/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java
index 3d5a7f511f9..a87d5d9cd68 100644
--- a/processing/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java
+++ b/processing/src/main/java/org/apache/druid/math/expr/FunctionalExpr.java
@@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableList;
 import org.apache.druid.error.DruidException;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.math.expr.vector.ExprVectorProcessor;
+import org.apache.druid.segment.column.Types;
 
 import javax.annotation.Nullable;
 import java.util.List;
@@ -190,11 +191,22 @@ class FunctionExpr implements Expr
     try {
       return function.apply(args, bindings);
     }
-    catch (DruidException | ExpressionValidationException e) {
+    catch (ExpressionValidationException e) {
+      // ExpressionValidationException already contain function name
+      throw DruidException.forPersona(DruidException.Persona.USER)
+                          .ofCategory(DruidException.Category.INVALID_INPUT)
+                          .build(e, e.getMessage());
+    }
+    catch (Types.InvalidCastException | Types.InvalidCastBooleanException e) {
+      throw DruidException.forPersona(DruidException.Persona.USER)
+                          .ofCategory(DruidException.Category.INVALID_INPUT)
+                          .build(e, "Function[%s] encountered exception: %s", 
name, e.getMessage());
+    }
+    catch (DruidException e) {
       throw e;
     }
     catch (Exception e) {
-      throw DruidException.defensive().build(e, "Invocation of function '%s' 
encountered exception.", name);
+      throw DruidException.defensive().build(e, "Function[%s] encountered 
unknown exception.", name);
     }
   }
 
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/UnaryOperatorExpr.java 
b/processing/src/main/java/org/apache/druid/math/expr/UnaryOperatorExpr.java
index 684f3ac2520..f9f2c5bbcc2 100644
--- a/processing/src/main/java/org/apache/druid/math/expr/UnaryOperatorExpr.java
+++ b/processing/src/main/java/org/apache/druid/math/expr/UnaryOperatorExpr.java
@@ -184,7 +184,7 @@ class UnaryNotExpr extends UnaryExpr
     if (!ExpressionProcessing.useStrictBooleans()) {
       // conforming to other boolean-returning binary operators
       ExpressionType retType = ret.type().is(ExprType.DOUBLE) ? 
ExpressionType.DOUBLE : ExpressionType.LONG;
-      return ExprEval.ofBoolean(!ret.asBoolean(), retType.getType());
+      return ExprEval.ofBoolean(!ret.asBoolean(), retType);
     }
     return ExprEval.ofLongBoolean(!ret.asBoolean());
   }
diff --git 
a/processing/src/main/java/org/apache/druid/segment/column/Types.java 
b/processing/src/main/java/org/apache/druid/segment/column/Types.java
index 2a0256f7667..831f5c76ce9 100644
--- a/processing/src/main/java/org/apache/druid/segment/column/Types.java
+++ b/processing/src/main/java/org/apache/druid/segment/column/Types.java
@@ -141,4 +141,20 @@ public class Types
       super("Cannot implicitly cast [%s] to [%s]", type, other);
     }
   }
+
+  public static class InvalidCastException extends IAE
+  {
+    public InvalidCastException(TypeSignature<?> type, TypeSignature<?> other)
+    {
+      super("Invalid type, cannot cast [" + type + "] to [" + other + "]");
+    }
+  }
+
+  public static class InvalidCastBooleanException extends IAE
+  {
+    public InvalidCastBooleanException(TypeSignature<?> type)
+    {
+      super("Invalid type, cannot coerce [" + type + "] to boolean");
+    }
+  }
 }
diff --git a/processing/src/test/java/org/apache/druid/math/expr/EvalTest.java 
b/processing/src/test/java/org/apache/druid/math/expr/EvalTest.java
index 2f68840955f..f969ede92b9 100644
--- a/processing/src/test/java/org/apache/druid/math/expr/EvalTest.java
+++ b/processing/src/test/java/org/apache/druid/math/expr/EvalTest.java
@@ -28,6 +28,8 @@ import org.apache.druid.java.util.common.IAE;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.segment.column.TypeStrategies;
 import org.apache.druid.segment.column.TypeStrategiesTest;
+import org.apache.druid.segment.data.ComparableList;
+import org.apache.druid.segment.data.ComparableStringArray;
 import org.apache.druid.segment.nested.StructuredData;
 import org.apache.druid.testing.InitializedNullHandlingTest;
 import org.junit.Assert;
@@ -1683,6 +1685,18 @@ public class EvalTest extends InitializedNullHandlingTest
         ExpressionType.UNKNOWN_COMPLEX,
         someOtherComplex
     );
+
+    assertBestEffortOf(
+        ComparableStringArray.of("a", "b", "c"),
+        ExpressionType.STRING_ARRAY,
+        new Object[]{"a", "b", "c"}
+    );
+
+    assertBestEffortOf(
+        new ComparableList<>(Arrays.asList(1L, 2L)),
+        ExpressionType.LONG_ARRAY,
+        new Object[]{1L, 2L}
+    );
   }
 
   private void assertBestEffortOf(@Nullable Object val, ExpressionType 
expectedType, @Nullable Object expectedValue)
diff --git 
a/processing/src/test/java/org/apache/druid/math/expr/FunctionTest.java 
b/processing/src/test/java/org/apache/druid/math/expr/FunctionTest.java
index e38b2ea5145..670dbe93e1f 100644
--- a/processing/src/test/java/org/apache/druid/math/expr/FunctionTest.java
+++ b/processing/src/test/java/org/apache/druid/math/expr/FunctionTest.java
@@ -148,12 +148,14 @@ public class FunctionTest extends 
InitializedNullHandlingTest
         throw new RuntimeException("nested-exception");
       }
     };
-    DruidException e = Assert.assertThrows(DruidException.class,
+    DruidException e = Assert.assertThrows(
+        DruidException.class,
         () -> {
           expr.eval(bind);
-        });
+        }
+    );
 
-    assertEquals("Invocation of function 'abs' encountered exception.", 
e.getMessage());
+    assertEquals("Function[abs] encountered unknown exception.", 
e.getMessage());
     assertNotNull(e.getCause());
     assertEquals("nested-exception", e.getCause().getMessage());
   }
@@ -581,19 +583,17 @@ public class FunctionTest extends 
InitializedNullHandlingTest
       if (NullHandling.sqlCompatible()) {
         assertExpr(StringUtils.format("round(%s)", argAndType.lhs), null);
       } else {
-        try {
-          assertExpr(StringUtils.format("round(%s)", argAndType.lhs), null);
-          Assert.fail("Did not throw IllegalArgumentException");
-        }
-        catch (ExpressionValidationException e) {
-          Assert.assertEquals(
-              StringUtils.format(
-                  "Function[round] first argument should be a LONG or DOUBLE 
but got %s instead",
-                  argAndType.rhs
-              ),
-              e.getMessage()
-          );
-        }
+        Throwable t = Assert.assertThrows(
+            DruidException.class,
+            () -> assertExpr(StringUtils.format("round(%s)", argAndType.lhs), 
null)
+        );
+        Assert.assertEquals(
+            StringUtils.format(
+                "Function[round] first argument should be a LONG or DOUBLE but 
got %s instead",
+                argAndType.rhs
+            ),
+            t.getMessage()
+        );
       }
     }
   }
@@ -609,19 +609,17 @@ public class FunctionTest extends 
InitializedNullHandlingTest
 
     );
     for (Pair<String, String> argAndType : invalidArguments) {
-      try {
-        assertExpr(StringUtils.format("round(d, %s)", argAndType.lhs), null);
-        Assert.fail("Did not throw IllegalArgumentException");
-      }
-      catch (ExpressionValidationException e) {
-        Assert.assertEquals(
-            StringUtils.format(
-                "Function[round] second argument should be a LONG but got %s 
instead",
-                argAndType.rhs
-            ),
-            e.getMessage()
-        );
-      }
+      Throwable t = Assert.assertThrows(
+          DruidException.class,
+          () -> assertExpr(StringUtils.format("round(d, %s)", argAndType.lhs), 
null)
+      );
+      Assert.assertEquals(
+          StringUtils.format(
+              "Function[round] second argument should be a LONG but got %s 
instead",
+              argAndType.rhs
+          ),
+          t.getMessage()
+      );
     }
   }
 
@@ -639,13 +637,11 @@ public class FunctionTest extends 
InitializedNullHandlingTest
     assertExpr("greatest(1, 'A')", "A");
 
     // Invalid types
-    try {
-      assertExpr("greatest(1, ['A'])", null);
-      Assert.fail("Did not throw IllegalArgumentException");
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals("Function[greatest] does not accept ARRAY<STRING> 
types", e.getMessage());
-    }
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> assertExpr("greatest(1, ['A'])", null)
+    );
+    Assert.assertEquals("Function[greatest] does not accept ARRAY<STRING> 
types", t.getMessage());
 
     // Null handling
     assertExpr("greatest()", null);
@@ -667,13 +663,11 @@ public class FunctionTest extends 
InitializedNullHandlingTest
     assertExpr("least(1, 'A')", "1");
 
     // Invalid types
-    try {
-      assertExpr("least(1, [2, 3])", null);
-      Assert.fail("Did not throw IllegalArgumentException");
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals("Function[least] does not accept ARRAY<LONG> types", 
e.getMessage());
-    }
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> assertExpr("least(1, [2, 3])", null)
+    );
+    Assert.assertEquals("Function[least] does not accept ARRAY<LONG> types", 
t.getMessage());
 
     // Null handling
     assertExpr("least()", null);
@@ -754,118 +748,91 @@ public class FunctionTest extends 
InitializedNullHandlingTest
   @Test
   public void testSizeForatInvalidArgumentType()
   {
-    try {
-      //x = "foo"
-      Parser.parse("human_readable_binary_byte_format(x)", 
ExprMacroTable.nil())
-            .eval(bestEffortBindings);
-
-      // for sqlCompatible, function above returns null and goes here
-      // but for non-sqlCompatible, it must not go to here
-      Assert.assertTrue(NullHandling.sqlCompatible() ? true : false);
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals(
-          "Function[human_readable_binary_byte_format] needs a number as its 
first argument but got STRING instead",
-          e.getMessage()
-      );
-    }
-
-    try {
+    if (NullHandling.replaceWithDefault()) {
       //x = "foo"
-      Parser.parse("human_readable_binary_byte_format(1024, x)", 
ExprMacroTable.nil())
-            .eval(bestEffortBindings);
-
-      //must not go to here
-      Assert.assertTrue(false);
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals(
-          "Function[human_readable_binary_byte_format] needs a LONG as its 
second argument but got STRING instead",
-          e.getMessage()
+      Throwable t = Assert.assertThrows(
+          DruidException.class,
+          () -> Parser.parse("human_readable_binary_byte_format(x)", 
ExprMacroTable.nil())
+                      .eval(bestEffortBindings)
       );
-    }
-
-    try {
-      //of = 0F
-      Parser.parse("human_readable_binary_byte_format(1024, of)", 
ExprMacroTable.nil())
-            .eval(bestEffortBindings);
-
-      //must not go to here
-      Assert.assertTrue(false);
-    }
-    catch (ExpressionValidationException e) {
       Assert.assertEquals(
-          "Function[human_readable_binary_byte_format] needs a LONG as its 
second argument but got DOUBLE instead",
-          e.getMessage()
+          "Function[human_readable_binary_byte_format] needs a number as its 
first argument but got STRING instead",
+          t.getMessage()
       );
     }
 
-    try {
-      //of = 0F
-      Parser.parse("human_readable_binary_byte_format(1024, nonexist)", 
ExprMacroTable.nil())
-            .eval(bestEffortBindings);
+    // x = "foo"
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> Parser.parse("human_readable_binary_byte_format(1024, x)", 
ExprMacroTable.nil()).eval(bestEffortBindings)
+    );
+    Assert.assertEquals(
+        "Function[human_readable_binary_byte_format] needs a LONG as its 
second argument but got STRING instead",
+        t.getMessage()
+    );
+    //of = 0F
+    t = Assert.assertThrows(
+        DruidException.class,
+        () -> Parser.parse("human_readable_binary_byte_format(1024, of)", 
ExprMacroTable.nil()).eval(bestEffortBindings)
+    );
+    Assert.assertEquals(
+        "Function[human_readable_binary_byte_format] needs a LONG as its 
second argument but got DOUBLE instead",
+        t.getMessage()
+    );
 
-      //must not go to here
-      Assert.assertTrue(false);
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals(
-          "Function[human_readable_binary_byte_format] needs a LONG as its 
second argument but got STRING instead",
-          e.getMessage()
-      );
-    }
+    //of = 0F
+    t = Assert.assertThrows(
+        DruidException.class,
+        () -> Parser.parse("human_readable_binary_byte_format(1024, 
nonexist)", ExprMacroTable.nil())
+                    .eval(bestEffortBindings)
+    );
+    Assert.assertEquals(
+        "Function[human_readable_binary_byte_format] needs a LONG as its 
second argument but got STRING instead",
+        t.getMessage()
+    );
   }
 
   @Test
   public void testSizeFormatInvalidPrecision()
   {
-    try {
-      Parser.parse("human_readable_binary_byte_format(1024, maxLong)", 
ExprMacroTable.nil())
-            .eval(bestEffortBindings);
-      Assert.assertTrue(false);
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals(
-          "Function[human_readable_binary_byte_format] given 
precision[9223372036854775807] must be in the range of [0,3]",
-          e.getMessage()
-      );
-    }
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> Parser.parse("human_readable_binary_byte_format(1024, maxLong)", 
ExprMacroTable.nil())
+                    .eval(bestEffortBindings)
+    );
 
-    try {
-      Parser.parse("human_readable_binary_byte_format(1024, minLong)", 
ExprMacroTable.nil())
-            .eval(bestEffortBindings);
-      Assert.assertTrue(false);
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals(
-          "Function[human_readable_binary_byte_format] given 
precision[-9223372036854775808] must be in the range of [0,3]",
-          e.getMessage()
-      );
-    }
+    Assert.assertEquals(
+        "Function[human_readable_binary_byte_format] given 
precision[9223372036854775807] must be in the range of [0,3]",
+        t.getMessage()
+    );
 
-    try {
-      Parser.parse("human_readable_binary_byte_format(1024, -1)", 
ExprMacroTable.nil())
-            .eval(bestEffortBindings);
-      Assert.assertTrue(false);
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals(
-          "Function[human_readable_binary_byte_format] given precision[-1] 
must be in the range of [0,3]",
-          e.getMessage()
-      );
-    }
+    t = Assert.assertThrows(
+        DruidException.class,
+        () -> Parser.parse("human_readable_binary_byte_format(1024, minLong)", 
ExprMacroTable.nil())
+                    .eval(bestEffortBindings)
+    );
+    Assert.assertEquals(
+        "Function[human_readable_binary_byte_format] given 
precision[-9223372036854775808] must be in the range of [0,3]",
+        t.getMessage()
+    );
 
-    try {
-      Parser.parse("human_readable_binary_byte_format(1024, 4)", 
ExprMacroTable.nil())
-            .eval(bestEffortBindings);
-      Assert.assertTrue(false);
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals(
-          "Function[human_readable_binary_byte_format] given precision[4] must 
be in the range of [0,3]",
-          e.getMessage()
-      );
-    }
+    t = Assert.assertThrows(
+        DruidException.class,
+        () -> Parser.parse("human_readable_binary_byte_format(1024, -1)", 
ExprMacroTable.nil()).eval(bestEffortBindings)
+    );
+    Assert.assertEquals(
+        "Function[human_readable_binary_byte_format] given precision[-1] must 
be in the range of [0,3]",
+        t.getMessage()
+    );
+
+    t = Assert.assertThrows(
+        DruidException.class,
+        () -> Parser.parse("human_readable_binary_byte_format(1024, 4)", 
ExprMacroTable.nil()).eval(bestEffortBindings)
+    );
+    Assert.assertEquals(
+        "Function[human_readable_binary_byte_format] given precision[4] must 
be in the range of [0,3]",
+        t.getMessage()
+    );
   }
 
   @Test
@@ -923,16 +890,14 @@ public class FunctionTest extends 
InitializedNullHandlingTest
     assertExpr("bitwiseComplement(null)", null);
 
     // data truncation
-    try {
-      assertExpr("bitwiseComplement(461168601842738800000000000000.000000)", 
null);
-      Assert.fail("Did not throw IllegalArgumentException");
-    }
-    catch (ExpressionValidationException e) {
-      Assert.assertEquals(
-          "Function[bitwiseComplement] Possible data truncation, param 
[461168601842738800000000000000.000000] is out of LONG value range",
-          e.getMessage()
-      );
-    }
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> 
assertExpr("bitwiseComplement(461168601842738800000000000000.000000)", null)
+    );
+    Assert.assertEquals(
+        "Function[bitwiseComplement] Possible data truncation, param 
[461168601842738800000000000000.000000] is out of LONG value range",
+        t.getMessage()
+    );
 
     // doubles are cast
     assertExpr("bitwiseOr(2.345, 1)", 3L);
@@ -975,7 +940,10 @@ public class FunctionTest extends 
InitializedNullHandlingTest
   public void testDecodeBase64UTF()
   {
     assertExpr("decode_base64_utf8('aGVsbG8=')", "hello");
-    
assertExpr("decode_base64_utf8('V2hlbiBhbiBvbmlvbiBpcyBjdXQsIGNlcnRhaW4gKGxhY2hyeW1hdG9yKSBjb21wb3VuZHMgYXJlIHJlbGVhc2VkIGNhdXNpbmcgdGhlIG5lcnZlcyBhcm91bmQgdGhlIGV5ZXMgKGxhY3JpbWFsIGdsYW5kcykgdG8gYmVjb21lIGlycml0YXRlZC4=')",
 "When an onion is cut, certain (lachrymator) compounds are released causing 
the nerves around the eyes (lacrimal glands) to become irritated.");
+    assertExpr(
+        
"decode_base64_utf8('V2hlbiBhbiBvbmlvbiBpcyBjdXQsIGNlcnRhaW4gKGxhY2hyeW1hdG9yKSBjb21wb3VuZHMgYXJlIHJlbGVhc2VkIGNhdXNpbmcgdGhlIG5lcnZlcyBhcm91bmQgdGhlIGV5ZXMgKGxhY3JpbWFsIGdsYW5kcykgdG8gYmVjb21lIGlycml0YXRlZC4=')",
+        "When an onion is cut, certain (lachrymator) compounds are released 
causing the nerves around the eyes (lacrimal glands) to become irritated."
+    );
     assertExpr("decode_base64_utf8('eyJ0ZXN0IjogMX0=')", "{\"test\": 1}");
     assertExpr("decode_base64_utf8('')", NullHandling.sqlCompatible() ? "" : 
null);
   }
@@ -1039,6 +1007,7 @@ public class FunctionTest extends 
InitializedNullHandlingTest
       Assert.assertEquals(happiness, 
Parser.parse(StringUtils.format("%s(1,2)", tea), exprMacroTable));
     }
   }
+
   @Test
   public void testComplexDecodeNull()
   {
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteArraysQueryTest.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteArraysQueryTest.java
index 372374f1e25..e1d2d128b8b 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteArraysQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteArraysQueryTest.java
@@ -50,6 +50,7 @@ import 
org.apache.druid.query.aggregation.DoubleSumAggregatorFactory;
 import org.apache.druid.query.aggregation.ExpressionLambdaAggregatorFactory;
 import org.apache.druid.query.aggregation.FilteredAggregatorFactory;
 import org.apache.druid.query.aggregation.LongSumAggregatorFactory;
+import org.apache.druid.query.aggregation.post.ExpressionPostAggregator;
 import org.apache.druid.query.dimension.DefaultDimensionSpec;
 import org.apache.druid.query.expression.TestExprMacroTable;
 import org.apache.druid.query.extraction.SubstringDimExtractionFn;
@@ -80,6 +81,7 @@ import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.segment.incremental.IncrementalIndexSchema;
 import org.apache.druid.segment.join.JoinType;
 import org.apache.druid.segment.join.JoinableFactoryWrapper;
+import org.apache.druid.segment.virtual.ExpressionVirtualColumn;
 import 
org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory;
 import org.apache.druid.server.QueryStackTests;
 import org.apache.druid.server.SpecificSegmentsQuerySegmentWalker;
@@ -7193,4 +7195,95 @@ public class CalciteArraysQueryTest extends 
BaseCalciteQueryTest
         )
     );
   }
+
+  @Test
+  public void testArrayToMvPostaggInline()
+  {
+    cannotVectorize();
+    testQuery(
+        "WITH \"ext\" AS (\n"
+        + "  SELECT\n"
+        + "    CAST(\"c0\" AS TIMESTAMP) AS \"__time\",\n"
+        + "    STRING_TO_ARRAY(\"c1\", '<#>') AS \"strings\",\n"
+        + "    CAST(STRING_TO_ARRAY(\"c2\", '<#>') AS BIGINT ARRAY) AS 
\"longs\"\n"
+        + "  FROM (\n"
+        + "    VALUES\n"
+        + "    (0, 'A<#>B', '1<#>2'),\n"
+        + "    (0, 'C<#>D', '3<#>4')\n"
+        + "  ) AS \"t\" (\"c0\", \"c1\", \"c2\")\n"
+        + ")\n"
+        + "SELECT\n"
+        + "  ARRAY_TO_MV(\"strings\") AS \"strings\",\n"
+        + "  ARRAY_TO_MV(\"longs\") AS \"longs\",\n"
+        + "  COUNT(*) AS \"count\"\n"
+        + "FROM \"ext\"\n"
+        + "GROUP BY \"strings\", \"longs\"",
+        ImmutableList.of(
+            GroupByQuery.builder()
+                        .setDataSource(
+                            InlineDataSource.fromIterable(
+                                Arrays.asList(
+                                    new Object[]{0L, "A<#>B", "1<#>2"},
+                                    new Object[]{0L, "C<#>D", "3<#>4"}
+                                ),
+                                RowSignature.builder()
+                                            .add("c0", ColumnType.LONG)
+                                            .add("c1", ColumnType.STRING)
+                                            .add("c2", ColumnType.STRING)
+                                            .build()
+                            )
+                        )
+                        .setQuerySegmentSpec(new 
MultipleIntervalSegmentSpec(ImmutableList.of(Intervals.ETERNITY)))
+                        .setDimensions(
+                            new DefaultDimensionSpec("v0", "d0", 
ColumnType.STRING_ARRAY),
+                            new DefaultDimensionSpec("v1", "d1", 
ColumnType.LONG_ARRAY)
+                        )
+                        .setVirtualColumns(
+                            new ExpressionVirtualColumn(
+                                "v0",
+                                "string_to_array(\"c1\",'<#>')",
+                                ColumnType.STRING_ARRAY,
+                                TestExprMacroTable.INSTANCE
+                            ),
+                            new ExpressionVirtualColumn(
+                                "v1",
+                                "CAST(string_to_array(\"c2\",'<#>'), 
'ARRAY<LONG>')",
+                                ColumnType.LONG_ARRAY,
+                                TestExprMacroTable.INSTANCE
+                            )
+                        )
+                        .setAggregatorSpecs(
+                            new CountAggregatorFactory("a0")
+                        )
+                        .setPostAggregatorSpecs(
+                            new ExpressionPostAggregator(
+                                "p0",
+                                "array_to_mv(\"d0\")",
+                                null,
+                                ColumnType.STRING,
+                                TestExprMacroTable.INSTANCE
+                            ),
+                            new ExpressionPostAggregator(
+                                "p1",
+                                "array_to_mv(\"d1\")",
+                                null,
+                                ColumnType.STRING,
+                                TestExprMacroTable.INSTANCE
+                            )
+                        )
+                        .setGranularity(Granularities.ALL)
+                        .setContext(QUERY_CONTEXT_DEFAULT)
+                        .build()
+        ),
+        ImmutableList.of(
+            new Object[]{"[\"A\",\"B\"]", "[\"1\",\"2\"]", 1L},
+            new Object[]{"[\"C\",\"D\"]", "[\"3\",\"4\"]", 1L}
+        ),
+        RowSignature.builder()
+                    .add("strings", ColumnType.STRING)
+                    .add("longs", ColumnType.STRING)
+                    .add("count", ColumnType.LONG)
+                    .build()
+    );
+  }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestBase.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestBase.java
deleted file mode 100644
index 0a03bcc8aa7..00000000000
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionTestBase.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.druid.sql.calcite.expression;
-
-import org.apache.druid.sql.calcite.util.CalciteTestBase;
-import org.junit.Rule;
-import org.junit.rules.ExpectedException;
-
-public abstract class ExpressionTestBase extends CalciteTestBase
-{
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  void expectException(Class<? extends Throwable> type, String message)
-  {
-    expectedException.expect(type);
-    expectedException.expectMessage(message);
-  }
-}
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java
index 6610951f07d..2ca9323b902 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java
@@ -30,8 +30,8 @@ import org.apache.calcite.sql.fun.SqlTrimFunction;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.druid.common.config.NullHandling;
+import org.apache.druid.error.DruidException;
 import org.apache.druid.java.util.common.DateTimes;
-import org.apache.druid.math.expr.ExpressionValidationException;
 import org.apache.druid.query.expression.TestExprMacroTable;
 import org.apache.druid.query.extraction.RegexDimExtractionFn;
 import org.apache.druid.query.extraction.SubstringDimExtractionFn;
@@ -65,7 +65,9 @@ import 
org.apache.druid.sql.calcite.expression.builtin.TimeFormatOperatorConvers
 import 
org.apache.druid.sql.calcite.expression.builtin.TimeParseOperatorConversion;
 import 
org.apache.druid.sql.calcite.expression.builtin.TimeShiftOperatorConversion;
 import 
org.apache.druid.sql.calcite.expression.builtin.TruncateOperatorConversion;
+import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.joda.time.Period;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -73,7 +75,7 @@ import java.math.BigDecimal;
 import java.util.Collections;
 import java.util.Map;
 
-public class ExpressionsTest extends ExpressionTestBase
+public class ExpressionsTest extends CalciteTestBase
 {
   private static final RowSignature ROW_SIGNATURE = RowSignature
       .builder()
@@ -100,28 +102,31 @@ public class ExpressionsTest extends ExpressionTestBase
       .build();
 
   private static final Map<String, Object> BINDINGS = ImmutableMap.<String, 
Object>builder()
-      .put("t", DateTimes.of("2000-02-03T04:05:06").getMillis())
-      .put("a", 10)
-      .put("b", 25)
-      .put("p", 3)
-      .put("x", 2.25)
-      .put("y", 3.0)
-      .put("z", -2.25)
-      .put("o", 0)
-      .put("nan", Double.NaN)
-      .put("inf", Double.POSITIVE_INFINITY)
-      .put("-inf", Double.NEGATIVE_INFINITY)
-      .put("fnan", Float.NaN)
-      .put("finf", Float.POSITIVE_INFINITY)
-      .put("-finf", Float.NEGATIVE_INFINITY)
-      .put("s", "foo")
-      .put("hexstr", "EF")
-      .put("intstr", "-100")
-      .put("spacey", "  hey there  ")
-      .put("newliney", "beep\nboop")
-      .put("tstr", "2000-02-03 04:05:06")
-      .put("dstr", "2000-02-03")
-      .build();
+                                                                  .put(
+                                                                      "t",
+                                                                      
DateTimes.of("2000-02-03T04:05:06").getMillis()
+                                                                  )
+                                                                  .put("a", 10)
+                                                                  .put("b", 25)
+                                                                  .put("p", 3)
+                                                                  .put("x", 
2.25)
+                                                                  .put("y", 
3.0)
+                                                                  .put("z", 
-2.25)
+                                                                  .put("o", 0)
+                                                                  .put("nan", 
Double.NaN)
+                                                                  .put("inf", 
Double.POSITIVE_INFINITY)
+                                                                  .put("-inf", 
Double.NEGATIVE_INFINITY)
+                                                                  .put("fnan", 
Float.NaN)
+                                                                  .put("finf", 
Float.POSITIVE_INFINITY)
+                                                                  
.put("-finf", Float.NEGATIVE_INFINITY)
+                                                                  .put("s", 
"foo")
+                                                                  
.put("hexstr", "EF")
+                                                                  
.put("intstr", "-100")
+                                                                  
.put("spacey", "  hey there  ")
+                                                                  
.put("newliney", "beep\nboop")
+                                                                  .put("tstr", 
"2000-02-03 04:05:06")
+                                                                  .put("dstr", 
"2000-02-03")
+                                                                  .build();
 
   private ExpressionTestHelper testHelper;
 
@@ -1255,23 +1260,26 @@ public class ExpressionsTest extends ExpressionTestBase
     final SqlFunction roundFunction = new 
RoundOperatorConversion().calciteOperator();
 
     if (!NullHandling.sqlCompatible()) {
-      expectException(
-          ExpressionValidationException.class,
-          "Function[round] first argument should be a LONG or DOUBLE but got 
STRING instead"
+      Throwable t = Assert.assertThrows(
+          DruidException.class,
+          () -> testHelper.testExpression(
+              roundFunction,
+              testHelper.makeInputRef("s"),
+              DruidExpression.ofExpression(
+                  ColumnType.STRING,
+                  DruidExpression.functionCall("round"),
+                  ImmutableList.of(
+                      DruidExpression.ofColumn(ColumnType.STRING, "s")
+                  )
+              ),
+              NullHandling.sqlCompatible() ? null : "IAE Exception"
+          )
+      );
+      Assert.assertEquals(
+          "Function[round] first argument should be a LONG or DOUBLE but got 
STRING instead",
+          t.getMessage()
       );
     }
-    testHelper.testExpression(
-        roundFunction,
-        testHelper.makeInputRef("s"),
-        DruidExpression.ofExpression(
-            ColumnType.STRING,
-            DruidExpression.functionCall("round"),
-            ImmutableList.of(
-                DruidExpression.ofColumn(ColumnType.STRING, "s")
-            )
-        ),
-        NullHandling.sqlCompatible() ? null : "IAE Exception"
-    );
   }
 
   @Test
@@ -1279,26 +1287,26 @@ public class ExpressionsTest extends ExpressionTestBase
   {
     final SqlFunction roundFunction = new 
RoundOperatorConversion().calciteOperator();
 
-    expectException(
-        ExpressionValidationException.class,
-        "Function[round] second argument should be a LONG but got STRING 
instead"
-    );
-    testHelper.testExpressionString(
-        roundFunction,
-        ImmutableList.of(
-            testHelper.makeInputRef("x"),
-            testHelper.makeLiteral("foo")
-        ),
-        DruidExpression.ofExpression(
-            ColumnType.FLOAT,
-            DruidExpression.functionCall("round"),
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> testHelper.testExpressionString(
+            roundFunction,
             ImmutableList.of(
-                DruidExpression.ofColumn(ColumnType.FLOAT, "x"),
-                DruidExpression.ofStringLiteral("foo")
-            )
-        ),
-        "IAE Exception"
+                testHelper.makeInputRef("x"),
+                testHelper.makeLiteral("foo")
+            ),
+            DruidExpression.ofExpression(
+                ColumnType.FLOAT,
+                DruidExpression.functionCall("round"),
+                ImmutableList.of(
+                    DruidExpression.ofColumn(ColumnType.FLOAT, "x"),
+                    DruidExpression.ofStringLiteral("foo")
+                )
+            ),
+            "IAE Exception"
+        )
     );
+    Assert.assertEquals("Function[round] second argument should be a LONG but 
got STRING instead", t.getMessage());
   }
 
   @Test
@@ -2237,23 +2245,22 @@ public class ExpressionsTest extends ExpressionTestBase
   @Test
   public void testAbnormalReverseWithWrongType()
   {
-    expectException(
-        ExpressionValidationException.class,
-        "Function[reverse] needs a STRING argument but got LONG instead"
-    );
-
-    testHelper.testExpression(
-        new ReverseOperatorConversion().calciteOperator(),
-        testHelper.makeInputRef("a"),
-        DruidExpression.ofExpression(
-            ColumnType.STRING,
-            DruidExpression.functionCall("reverse"),
-            ImmutableList.of(
-                DruidExpression.ofColumn(ColumnType.LONG, "a")
-            )
-        ),
-        null
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> testHelper.testExpression(
+            new ReverseOperatorConversion().calciteOperator(),
+            testHelper.makeInputRef("a"),
+            DruidExpression.ofExpression(
+                ColumnType.STRING,
+                DruidExpression.functionCall("reverse"),
+                ImmutableList.of(
+                    DruidExpression.ofColumn(ColumnType.LONG, "a")
+                )
+            ),
+            null
+        )
     );
+    Assert.assertEquals("Function[reverse] needs a STRING argument but got 
LONG instead", t.getMessage());
   }
 
   @Test
@@ -2313,38 +2320,39 @@ public class ExpressionsTest extends ExpressionTestBase
   @Test
   public void testAbnormalRightWithNegativeNumber()
   {
-    expectException(
-        ExpressionValidationException.class,
-        "Function[right] needs a positive integer as the second argument"
-    );
-
-    testHelper.testExpressionString(
-        new RightOperatorConversion().calciteOperator(),
-        ImmutableList.of(
-            testHelper.makeInputRef("s"),
-            testHelper.makeLiteral(-1)
-        ),
-        makeExpression("right(\"s\",-1)"),
-        null
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> testHelper.testExpressionString(
+            new RightOperatorConversion().calciteOperator(),
+            ImmutableList.of(
+                testHelper.makeInputRef("s"),
+                testHelper.makeLiteral(-1)
+            ),
+            makeExpression("right(\"s\",-1)"),
+            null
+        )
     );
+    Assert.assertEquals("Function[right] needs a positive integer as the 
second argument", t.getMessage());
   }
 
   @Test
   public void testAbnormalRightWithWrongType()
   {
-    expectException(
-        ExpressionValidationException.class,
-        "Function[right] needs a STRING as first argument and a LONG as second 
argument"
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> testHelper.testExpressionString(
+            new RightOperatorConversion().calciteOperator(),
+            ImmutableList.of(
+                testHelper.makeInputRef("s"),
+                testHelper.makeInputRef("s")
+            ),
+            makeExpression("right(\"s\",\"s\")"),
+            null
+        )
     );
-
-    testHelper.testExpressionString(
-        new RightOperatorConversion().calciteOperator(),
-        ImmutableList.of(
-            testHelper.makeInputRef("s"),
-            testHelper.makeInputRef("s")
-        ),
-        makeExpression("right(\"s\",\"s\")"),
-        null
+    Assert.assertEquals(
+        "Function[right] needs a STRING as first argument and a LONG as second 
argument",
+        t.getMessage()
     );
   }
 
@@ -2405,38 +2413,39 @@ public class ExpressionsTest extends ExpressionTestBase
   @Test
   public void testAbnormalLeftWithNegativeNumber()
   {
-    expectException(
-        ExpressionValidationException.class,
-        "Function[left] needs a postive integer as second argument"
-    );
-
-    testHelper.testExpressionString(
-        new LeftOperatorConversion().calciteOperator(),
-        ImmutableList.of(
-            testHelper.makeInputRef("s"),
-            testHelper.makeLiteral(-1)
-        ),
-        makeExpression("left(\"s\",-1)"),
-        null
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> testHelper.testExpressionString(
+            new LeftOperatorConversion().calciteOperator(),
+            ImmutableList.of(
+                testHelper.makeInputRef("s"),
+                testHelper.makeLiteral(-1)
+            ),
+            makeExpression("left(\"s\",-1)"),
+            null
+        )
     );
+    Assert.assertEquals("Function[left] needs a postive integer as second 
argument", t.getMessage());
   }
 
   @Test
   public void testAbnormalLeftWithWrongType()
   {
-    expectException(
-        ExpressionValidationException.class,
-        "Function[left] needs a STRING as first argument and a LONG as second 
argument"
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> testHelper.testExpressionString(
+            new LeftOperatorConversion().calciteOperator(),
+            ImmutableList.of(
+                testHelper.makeInputRef("s"),
+                testHelper.makeInputRef("s")
+            ),
+            makeExpression("left(\"s\",\"s\")"),
+            null
+        )
     );
-
-    testHelper.testExpressionString(
-        new LeftOperatorConversion().calciteOperator(),
-        ImmutableList.of(
-            testHelper.makeInputRef("s"),
-            testHelper.makeInputRef("s")
-        ),
-        makeExpression("left(\"s\",\"s\")"),
-        null
+    Assert.assertEquals(
+        "Function[left] needs a STRING as first argument and a LONG as second 
argument",
+        t.getMessage()
     );
   }
 
@@ -2477,19 +2486,21 @@ public class ExpressionsTest extends ExpressionTestBase
   @Test
   public void testAbnormalRepeatWithWrongType()
   {
-    expectException(
-        ExpressionValidationException.class,
-        "Function[repeat] needs a STRING as first argument and a LONG as 
second argument"
+    Throwable t = Assert.assertThrows(
+        DruidException.class,
+        () -> testHelper.testExpressionString(
+            new RepeatOperatorConversion().calciteOperator(),
+            ImmutableList.of(
+                testHelper.makeInputRef("s"),
+                testHelper.makeInputRef("s")
+            ),
+            makeExpression("repeat(\"s\",\"s\")"),
+            null
+        )
     );
-
-    testHelper.testExpressionString(
-        new RepeatOperatorConversion().calciteOperator(),
-        ImmutableList.of(
-            testHelper.makeInputRef("s"),
-            testHelper.makeInputRef("s")
-        ),
-        makeExpression("repeat(\"s\",\"s\")"),
-        null
+    Assert.assertEquals(
+        "Function[repeat] needs a STRING as first argument and a LONG as 
second argument",
+        t.getMessage()
     );
   }
 
@@ -2528,7 +2539,8 @@ public class ExpressionsTest extends ExpressionTestBase
   public void testOperatorConversionsDruidUnaryDoubleFn()
   {
     testHelper.testExpressionString(
-        
OperatorConversions.druidUnaryDoubleFn("BITWISE_CONVERT_LONG_BITS_TO_DOUBLE", 
"bitwiseConvertLongBitsToDouble").calciteOperator(),
+        
OperatorConversions.druidUnaryDoubleFn("BITWISE_CONVERT_LONG_BITS_TO_DOUBLE", 
"bitwiseConvertLongBitsToDouble")
+                           .calciteOperator(),
         ImmutableList.of(
             testHelper.makeInputRef("a")
         ),
@@ -2537,7 +2549,8 @@ public class ExpressionsTest extends ExpressionTestBase
     );
 
     testHelper.testExpressionString(
-        
OperatorConversions.druidUnaryDoubleFn("BITWISE_CONVERT_LONG_BITS_TO_DOUBLE", 
"bitwiseConvertLongBitsToDouble").calciteOperator(),
+        
OperatorConversions.druidUnaryDoubleFn("BITWISE_CONVERT_LONG_BITS_TO_DOUBLE", 
"bitwiseConvertLongBitsToDouble")
+                           .calciteOperator(),
         ImmutableList.of(
             testHelper.makeInputRef("x")
         ),
@@ -2546,7 +2559,8 @@ public class ExpressionsTest extends ExpressionTestBase
     );
 
     testHelper.testExpressionString(
-        
OperatorConversions.druidUnaryDoubleFn("BITWISE_CONVERT_LONG_BITS_TO_DOUBLE", 
"bitwiseConvertLongBitsToDouble").calciteOperator(),
+        
OperatorConversions.druidUnaryDoubleFn("BITWISE_CONVERT_LONG_BITS_TO_DOUBLE", 
"bitwiseConvertLongBitsToDouble")
+                           .calciteOperator(),
         ImmutableList.of(
             testHelper.makeInputRef("s")
         ),
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/GreatestExpressionTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/GreatestExpressionTest.java
index 8418edf994e..816befbdc0d 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/GreatestExpressionTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/GreatestExpressionTest.java
@@ -28,6 +28,7 @@ import org.apache.druid.java.util.common.DateTimes;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.RowSignature;
 import 
org.apache.druid.sql.calcite.expression.builtin.GreatestOperatorConversion;
+import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -37,7 +38,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-public class GreatestExpressionTest extends ExpressionTestBase
+public class GreatestExpressionTest extends CalciteTestBase
 {
   private static final String DOUBLE_KEY = "d";
   private static final double DOUBLE_VALUE = 3.1;
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressMatchExpressionTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressMatchExpressionTest.java
index 9db4868c47e..cf1a221d7ac 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressMatchExpressionTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressMatchExpressionTest.java
@@ -25,6 +25,8 @@ import 
org.apache.druid.math.expr.ExpressionValidationException;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.RowSignature;
 import 
org.apache.druid.sql.calcite.expression.builtin.IPv4AddressMatchOperatorConversion;
+import org.apache.druid.sql.calcite.util.CalciteTestBase;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -33,7 +35,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-public class IPv4AddressMatchExpressionTest extends ExpressionTestBase
+public class IPv4AddressMatchExpressionTest extends CalciteTestBase
 {
   private static final String IPV4 = "192.168.0.1";
   private static final long IPV4_LONG = 3232235521L;
@@ -65,65 +67,73 @@ public class IPv4AddressMatchExpressionTest extends 
ExpressionTestBase
   @Test
   public void testTooFewArgs()
   {
-    expectException(IllegalArgumentException.class, "requires 2 arguments");
-
-    testExpression(
-        Collections.emptyList(),
-        buildExpectedExpression(),
-        IGNORE_EXPECTED_RESULT
+    Throwable t = Assert.assertThrows(
+        ExpressionValidationException.class,
+        () -> testExpression(
+            Collections.emptyList(),
+            buildExpectedExpression(),
+            IGNORE_EXPECTED_RESULT
+        )
     );
+    Assert.assertEquals("Function[ipv4_match] requires 2 arguments", 
t.getMessage());
   }
 
   @Test
   public void testTooManyArgs()
   {
-    expectException(IllegalArgumentException.class, "requires 2 arguments");
-
     String address = IPV4;
     String subnet = SUBNET_192_168;
-    testExpression(
-        Arrays.asList(
-            testHelper.makeLiteral(address),
-            testHelper.makeLiteral(subnet),
-            testHelper.makeLiteral(address)
-        ),
-        buildExpectedExpression(address, subnet, address),
-        IGNORE_EXPECTED_RESULT
+    Throwable t = Assert.assertThrows(
+        ExpressionValidationException.class,
+        () -> testExpression(
+            Arrays.asList(
+                testHelper.makeLiteral(address),
+                testHelper.makeLiteral(subnet),
+                testHelper.makeLiteral(address)
+            ),
+            buildExpectedExpression(address, subnet, address),
+            IGNORE_EXPECTED_RESULT
+        )
     );
+    Assert.assertEquals("Function[ipv4_match] requires 2 arguments", 
t.getMessage());
   }
 
   @Test
   public void testSubnetArgNotLiteral()
   {
-    expectException(ExpressionValidationException.class, "subnet argument must 
be a literal");
-
     String address = IPV4;
     String variableName = VAR;
-    testExpression(
-        Arrays.asList(
-            testHelper.makeLiteral(address),
-            testHelper.makeInputRef(variableName)
-        ),
-        buildExpectedExpression(address, 
testHelper.makeVariable(variableName)),
-        IGNORE_EXPECTED_RESULT
+    Throwable t = Assert.assertThrows(
+        ExpressionValidationException.class,
+        () -> testExpression(
+            Arrays.asList(
+                testHelper.makeLiteral(address),
+                testHelper.makeInputRef(variableName)
+            ),
+            buildExpectedExpression(address, 
testHelper.makeVariable(variableName)),
+            IGNORE_EXPECTED_RESULT
+        )
     );
+    Assert.assertEquals("Function[ipv4_match] subnet argument must be a 
literal", t.getMessage());
   }
 
   @Test
   public void testSubnetArgInvalid()
   {
-    expectException(IllegalArgumentException.class, "subnet arg has an invalid 
format");
-
     String address = IPV4;
     String invalidSubnet = "192.168.0.1/invalid";
-    testExpression(
-        Arrays.asList(
-            testHelper.makeLiteral(address),
-            testHelper.makeLiteral(invalidSubnet)
-        ),
-        buildExpectedExpression(address, invalidSubnet),
-        IGNORE_EXPECTED_RESULT
+    Throwable t = Assert.assertThrows(
+        ExpressionValidationException.class,
+        () -> testExpression(
+            Arrays.asList(
+                testHelper.makeLiteral(address),
+                testHelper.makeLiteral(invalidSubnet)
+            ),
+            buildExpectedExpression(address, invalidSubnet),
+            IGNORE_EXPECTED_RESULT
+        )
     );
+    Assert.assertEquals("Function[ipv4_match] subnet arg has an invalid 
format: 192.168.0.1/invalid", t.getMessage());
   }
 
   @Test
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressParseExpressionTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressParseExpressionTest.java
index 6e4273f3552..f495b6e9500 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressParseExpressionTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressParseExpressionTest.java
@@ -25,6 +25,8 @@ import 
org.apache.druid.math.expr.ExpressionValidationException;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.RowSignature;
 import 
org.apache.druid.sql.calcite.expression.builtin.IPv4AddressParseOperatorConversion;
+import org.apache.druid.sql.calcite.util.CalciteTestBase;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -33,7 +35,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-public class IPv4AddressParseExpressionTest extends ExpressionTestBase
+public class IPv4AddressParseExpressionTest extends CalciteTestBase
 {
   private static final String VALID = "192.168.0.1";
   private static final long EXPECTED = 3232235521L;
@@ -56,28 +58,32 @@ public class IPv4AddressParseExpressionTest extends 
ExpressionTestBase
   @Test
   public void testTooFewArgs()
   {
-    expectException(ExpressionValidationException.class, "requires 1 
argument");
-
-    testExpression(
-        Collections.emptyList(),
-        buildExpectedExpression(),
-        IGNORE_EXPECTED_RESULT
+    Throwable t = Assert.assertThrows(
+        ExpressionValidationException.class,
+        () -> testExpression(
+            Collections.emptyList(),
+            buildExpectedExpression(),
+            IGNORE_EXPECTED_RESULT
+        )
     );
+    Assert.assertEquals("Function[ipv4_parse] requires 1 argument", 
t.getMessage());
   }
 
   @Test
   public void testTooManyArgs()
   {
-    expectException(ExpressionValidationException.class, "requires 1 
argument");
-
-    testExpression(
-        Arrays.asList(
-            testHelper.getConstantNull(),
-            testHelper.getConstantNull()
-        ),
-        buildExpectedExpression(null, null),
-        IGNORE_EXPECTED_RESULT
+    Throwable t = Assert.assertThrows(
+        ExpressionValidationException.class,
+        () -> testExpression(
+            Arrays.asList(
+                testHelper.getConstantNull(),
+                testHelper.getConstantNull()
+            ),
+            buildExpectedExpression(null, null),
+            IGNORE_EXPECTED_RESULT
+        )
     );
+    Assert.assertEquals("Function[ipv4_parse] requires 1 argument", 
t.getMessage());
   }
 
   @Test
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressStringifyExpressionTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressStringifyExpressionTest.java
index f434b7dca10..fb1b358cb08 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressStringifyExpressionTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/IPv4AddressStringifyExpressionTest.java
@@ -25,6 +25,8 @@ import 
org.apache.druid.math.expr.ExpressionValidationException;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.RowSignature;
 import 
org.apache.druid.sql.calcite.expression.builtin.IPv4AddressStringifyOperatorConversion;
+import org.apache.druid.sql.calcite.util.CalciteTestBase;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -33,7 +35,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-public class IPv4AddressStringifyExpressionTest extends ExpressionTestBase
+public class IPv4AddressStringifyExpressionTest extends CalciteTestBase
 {
   private static final long VALID = 3232235521L;
   private static final String EXPECTED = "192.168.0.1";
@@ -57,28 +59,32 @@ public class IPv4AddressStringifyExpressionTest extends 
ExpressionTestBase
   @Test
   public void testTooFewArgs()
   {
-    expectException(ExpressionValidationException.class, "requires 1 
argument");
-
-    testExpression(
-        Collections.emptyList(),
-        buildExpectedExpression(),
-        IGNORE_EXPECTED_RESULT
+    Throwable t = Assert.assertThrows(
+        ExpressionValidationException.class,
+        () -> testExpression(
+            Collections.emptyList(),
+            buildExpectedExpression(),
+            IGNORE_EXPECTED_RESULT
+        )
     );
+    Assert.assertEquals("Function[ipv4_stringify] requires 1 argument", 
t.getMessage());
   }
 
   @Test
   public void testTooManyArgs()
   {
-    expectException(ExpressionValidationException.class, "requires 1 
argument");
-
-    testExpression(
-        Arrays.asList(
-            testHelper.makeLiteral(VALID),
-            testHelper.makeLiteral(VALID)
-        ),
-        buildExpectedExpression(VALID, VALID),
-        IGNORE_EXPECTED_RESULT
+    Throwable t = Assert.assertThrows(
+        ExpressionValidationException.class,
+        () -> testExpression(
+            Arrays.asList(
+                testHelper.makeLiteral(VALID),
+                testHelper.makeLiteral(VALID)
+            ),
+            buildExpectedExpression(VALID, VALID),
+            IGNORE_EXPECTED_RESULT
+        )
     );
+    Assert.assertEquals("Function[ipv4_stringify] requires 1 argument", 
t.getMessage());
   }
 
   @Test
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/LeastExpressionTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/LeastExpressionTest.java
index 6702769e927..496543a5dd7 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/LeastExpressionTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/LeastExpressionTest.java
@@ -28,6 +28,7 @@ import org.apache.druid.java.util.common.DateTimes;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.sql.calcite.expression.builtin.LeastOperatorConversion;
+import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -37,7 +38,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-public class LeastExpressionTest extends ExpressionTestBase
+public class LeastExpressionTest extends CalciteTestBase
 {
   private static final String DOUBLE_KEY = "d";
   private static final double DOUBLE_VALUE = 3.1;
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/TimeFormatOperatorConversionTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/TimeFormatOperatorConversionTest.java
index 204b7784281..021e3579515 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/TimeFormatOperatorConversionTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/TimeFormatOperatorConversionTest.java
@@ -27,6 +27,7 @@ import org.apache.druid.java.util.common.IAE;
 import org.apache.druid.segment.column.ColumnType;
 import org.apache.druid.segment.column.RowSignature;
 import 
org.apache.druid.sql.calcite.expression.builtin.TimeFormatOperatorConversion;
+import org.apache.druid.sql.calcite.util.CalciteTestBase;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -36,7 +37,7 @@ import java.util.Map;
 /**
  * Tests for TIME_FORMAT
  */
-public class TimeFormatOperatorConversionTest extends ExpressionTestBase
+public class TimeFormatOperatorConversionTest extends CalciteTestBase
 {
   private static final RowSignature ROW_SIGNATURE = RowSignature
       .builder()


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

Reply via email to