@Override
    protected String visitFunctionCall(FunctionCall node, Boolean unmangleNames) {
      StringBuilder builder = new StringBuilder();

      String arguments = joinExpressions(node.getArguments(), unmangleNames);
      if (node.getArguments().isEmpty() && "count".equalsIgnoreCase(node.getName().getSuffix())) {
        arguments = "*";
      }
      if (node.isDistinct()) {
        arguments = "DISTINCT " + arguments;
      }

      if (unmangleNames && node.getName().toString().startsWith(QueryUtil.FIELD_REFERENCE_PREFIX)) {
        checkState(
            node.getArguments().size() == 1, "Expected only one argument to field reference");
        QualifiedName name =
            QualifiedName.of(QueryUtil.unmangleFieldReference(node.getName().toString()));
        builder.append(arguments).append(".").append(name);
      } else {
        builder
            .append(formatQualifiedName(node.getName()))
            .append('(')
            .append(arguments)
            .append(')');
      }

      if (node.getWindow().isPresent()) {
        builder.append(" OVER ").append(visitWindow(node.getWindow().get(), unmangleNames));
      }

      return builder.toString();
    }
    @Override
    protected Type visitFunctionCall(FunctionCall node, AnalysisContext context) {
      if (node.getWindow().isPresent()) {
        for (Expression expression : node.getWindow().get().getPartitionBy()) {
          process(expression, context);
          Type type = expressionTypes.get(expression);
          if (!type.isComparable()) {
            throw new SemanticException(
                TYPE_MISMATCH,
                node,
                "%s is not comparable, and therefore cannot be used in window function PARTITION BY",
                type);
          }
        }

        for (SortItem sortItem : node.getWindow().get().getOrderBy()) {
          process(sortItem.getSortKey(), context);
          Type type = expressionTypes.get(sortItem.getSortKey());
          if (!type.isOrderable()) {
            throw new SemanticException(
                TYPE_MISMATCH,
                node,
                "%s is not orderable, and therefore cannot be used in window function ORDER BY",
                type);
          }
        }

        if (node.getWindow().get().getFrame().isPresent()) {
          WindowFrame frame = node.getWindow().get().getFrame().get();

          if (frame.getStart().getValue().isPresent()) {
            Type type = process(frame.getStart().getValue().get(), context);
            if (!type.equals(BIGINT)) {
              throw new SemanticException(
                  TYPE_MISMATCH,
                  node,
                  "Window frame start value type must be BIGINT (actual %s)",
                  type);
            }
          }

          if (frame.getEnd().isPresent() && frame.getEnd().get().getValue().isPresent()) {
            Type type = process(frame.getEnd().get().getValue().get(), context);
            if (!type.equals(BIGINT)) {
              throw new SemanticException(
                  TYPE_MISMATCH,
                  node,
                  "Window frame end value type must be BIGINT (actual %s)",
                  type);
            }
          }
        }
      }

      ImmutableList.Builder<TypeSignature> argumentTypes = ImmutableList.builder();
      for (Expression expression : node.getArguments()) {
        argumentTypes.add(process(expression, context).getTypeSignature());
      }

      Signature function =
          functionRegistry.resolveFunction(
              node.getName(), argumentTypes.build(), context.isApproximate());
      for (int i = 0; i < node.getArguments().size(); i++) {
        Expression expression = node.getArguments().get(i);
        Type type = typeManager.getType(function.getArgumentTypes().get(i));
        requireNonNull(type, format("Type %s not found", function.getArgumentTypes().get(i)));
        if (node.isDistinct() && !type.isComparable()) {
          throw new SemanticException(
              TYPE_MISMATCH,
              node,
              "DISTINCT can only be applied to comparable types (actual: %s)",
              type);
        }
        coerceType(
            context, expression, type, String.format("Function %s argument %d", function, i));
      }
      resolvedFunctions.put(node, function);

      Type type = typeManager.getType(function.getReturnType());
      expressionTypes.put(node, type);

      return type;
    }