@Override
    public LogicalExpression visitFunctionCall(
        FunctionCall call, FunctionImplementationRegistry registry) {
      List<LogicalExpression> args = Lists.newArrayList();
      for (int i = 0; i < call.args.size(); ++i) {
        LogicalExpression newExpr = call.args.get(i).accept(this, registry);
        args.add(newExpr);
      }

      // replace with a new function call, since its argument could be changed.
      call = new FunctionCall(call.getDefinition(), args, call.getPosition());

      // call function resolver, get the best match.
      FunctionResolver resolver = FunctionResolverFactory.getResolver(call);
      DrillFuncHolder matchedFuncHolder =
          resolver.getBestMatch(registry.getMethods().get(call.getDefinition().getName()), call);

      // new arg lists, possible with implicit cast inserted.
      List<LogicalExpression> argsWithCast = Lists.newArrayList();

      if (matchedFuncHolder == null) {
        // TODO: found no matched funcholder. Raise exception here?
        return validateNewExpr(call);
      } else {
        // Compare parm type against arg type. Insert cast on top of arg, whenever necessary.
        for (int i = 0; i < call.args.size(); ++i) {
          MajorType parmType = matchedFuncHolder.getParmMajorType(i);

          // Case 1: If  1) the argument is NullExpression
          //            2) the parameter of matchedFuncHolder allows null input, or func's
          // null_handling is NULL_IF_NULL (means null and non-null are exchangable).
          //        then replace NullExpression with a TypedNullConstant
          if (call.args.get(i).equals(NullExpression.INSTANCE)
              && (parmType.getMode().equals(DataMode.OPTIONAL)
                  || matchedFuncHolder.getNullHandling() == NullHandling.NULL_IF_NULL)) {
            argsWithCast.add(new TypedNullConstant(parmType));
          } else if (Types.softEquals(
              parmType,
              call.args.get(i).getMajorType(),
              matchedFuncHolder.getNullHandling() == NullHandling.NULL_IF_NULL)) {
            // Case 2: argument and parameter matches. Do nothing.
            argsWithCast.add(call.args.get(i));
          } else {
            // Case 3: insert cast if param type is different from arg type.
            FunctionDefinition castFuncDef =
                CastFunctionDefs.getCastFuncDef(parmType.getMinorType());
            List<LogicalExpression> castArgs = Lists.newArrayList();
            castArgs.add(call.args.get(i)); // input_expr
            argsWithCast.add(new FunctionCall(castFuncDef, castArgs, ExpressionPosition.UNKNOWN));
          }
        }
      }

      return validateNewExpr(
          new FunctionCall(call.getDefinition(), argsWithCast, call.getPosition()));
    }
 public void resolveHash(
     DrillConfig config,
     LogicalExpression arg,
     TypeProtos.MajorType expectedArg,
     TypeProtos.MajorType expectedOut,
     TypeProtos.DataMode expectedBestInputMode,
     FunctionImplementationRegistry registry)
     throws JClassAlreadyExistsException, IOException {
   List<LogicalExpression> args = new ArrayList<>();
   args.add(arg);
   String[] registeredNames = {"hash"};
   FunctionCall call = new FunctionCall("hash", args, ExpressionPosition.UNKNOWN);
   FunctionResolver resolver = FunctionResolverFactory.getResolver(call);
   DrillFuncHolder matchedFuncHolder = registry.findDrillFunction(resolver, call);
   assertEquals(expectedBestInputMode, matchedFuncHolder.getParmMajorType(0).getMode());
 }