public PostAggregationFunction(String functionName, ColumnDataType[] argumentTypes) {
    String canonicalName = FunctionRegistry.canonicalize(functionName);
    FunctionInfo functionInfo = FunctionRegistry.lookupFunctionInfo(canonicalName, argumentTypes);
    if (functionInfo == null) {
      if (FunctionRegistry.contains(canonicalName)) {
        throw new IllegalArgumentException(
            String.format("Unsupported function: %s with argument types: %s", functionName,
                Arrays.toString(argumentTypes)));
      } else {
        throw new IllegalArgumentException(String.format("Unsupported function: %s", functionName));
      }
    }
    _functionInvoker = new FunctionInvoker(functionInfo);
    Class<?>[] parameterClasses = _functionInvoker.getParameterClasses();
    PinotDataType[] parameterTypes = _functionInvoker.getParameterTypes();
    int numArguments = argumentTypes.length;
    int numParameters = parameterClasses.length;
    Preconditions.checkArgument(numArguments == numParameters,
        "Wrong number of arguments for method: %s, expected: %s, actual: %s", functionInfo.getMethod(), numParameters,
        numArguments);
    for (int i = 0; i < numParameters; i++) {
      Preconditions.checkArgument(parameterTypes[i] != null, "Unsupported parameter class: %s for method: %s",
          parameterClasses[i], functionInfo.getMethod());
    }
    _argumentTypes = new PinotDataType[numArguments];
    for (int i = 0; i < numArguments; i++) {
      _argumentTypes[i] = PinotDataType.getPinotDataTypeForExecution(argumentTypes[i]);
    }
    ColumnDataType resultType = FunctionUtils.getColumnDataType(_functionInvoker.getResultClass());
    // Handle unrecognized result class with STRING
    _resultType = resultType != null ? resultType : ColumnDataType.STRING;
  }
@Test
  public void testPostAggregationFunction() {
    // Plus
    PostAggregationFunction function =
        new PostAggregationFunction("plus", new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.LONG});
    assertEquals(function.getResultType(), ColumnDataType.DOUBLE);
    assertEquals(function.invoke(new Object[]{1, 2L}), 3.0);

    // Minus
    function = new PostAggregationFunction("MINUS", new ColumnDataType[]{ColumnDataType.FLOAT, ColumnDataType.DOUBLE});
    assertEquals(function.getResultType(), ColumnDataType.DOUBLE);
    assertEquals(function.invoke(new Object[]{3f, 4.0}), -1.0);

    // Times
    function = new PostAggregationFunction("tImEs", new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT});
    assertEquals(function.getResultType(), ColumnDataType.DOUBLE);
    assertEquals(function.invoke(new Object[]{"5", 6}), 30.0);

    // Reverse
    function = new PostAggregationFunction("reverse", new ColumnDataType[]{ColumnDataType.LONG});
    assertEquals(function.getResultType(), ColumnDataType.STRING);
    assertEquals(function.invoke(new Object[]{"1234567890"}), "0987654321");

    // ST_AsText
    function = new PostAggregationFunction("ST_AsText", new ColumnDataType[]{ColumnDataType.BYTES});
    assertEquals(function.getResultType(), ColumnDataType.STRING);
    assertEquals(function.invoke(
        new Object[]{GeometrySerializer.serialize(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(10, 20)))}),
        "POINT (10 20)");

    // Cast
    function = new PostAggregationFunction("cast", new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.STRING});
    assertEquals(function.getResultType(), ColumnDataType.OBJECT);
    assertEquals(function.invoke(new Object[]{1, "LONG"}), 1L);

    // NOT
    function = new PostAggregationFunction("not", new ColumnDataType[]{ColumnDataType.BOOLEAN});
    assertEquals(function.getResultType(), ColumnDataType.BOOLEAN);
    assertFalse((Boolean) function.invoke(new Object[]{true}));
    assertTrue((Boolean) function.invoke(new Object[]{false}));

    // isDistinctFrom
    function = new PostAggregationFunction("isDistinctFrom",
        new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.STRING});
    assertEquals(function.getResultType(), ColumnDataType.BOOLEAN);
    assertFalse((Boolean) function.invoke(new Object[]{null, null}));
    assertFalse((Boolean) function.invoke(new Object[]{"a", "a"}));
    assertTrue((Boolean) function.invoke(new Object[]{null, "a"}));
    assertTrue((Boolean) function.invoke(new Object[]{"a", null}));
    assertTrue((Boolean) function.invoke(new Object[]{"a", "b"}));

    // isNotDistinctFrom
    function = new PostAggregationFunction("isNotDistinctFrom",
        new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.STRING});
    assertEquals(function.getResultType(), ColumnDataType.BOOLEAN);
    assertTrue((Boolean) function.invoke(new Object[]{null, null}));
    assertTrue((Boolean) function.invoke(new Object[]{"a", "a"}));
    assertFalse((Boolean) function.invoke(new Object[]{null, "a"}));
    assertFalse((Boolean) function.invoke(new Object[]{"a", null}));
    assertFalse((Boolean) function.invoke(new Object[]{"a", "b"}));

    // Coalesce
    function = new PostAggregationFunction("coalesce", new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.STRING,
    ColumnDataType.BOOLEAN});
    assertEquals(function.getResultType(), ColumnDataType.OBJECT);
    assertNull(function.invoke(new Object[]{null, null, null}));
    assertEquals(function.invoke(new Object[]{null, "1", null}), "1");
    assertEquals(function.invoke(new Object[]{1, "2", false}), 1);
    assertEquals(function.invoke(new Object[]{null, null, true}), true);
  }