void addFunction(final T function) {
    final List<ParameterInfo> parameters = function.parameterInfo();
    if (allFunctions.put(function.parameters(), function) != null) {
      throw new KsqlFunctionException(
          "Can't add function " + function.name()
              + " with parameters " + function.parameters()
              + " as a function with the same name and parameter types already exists "
              + allFunctions.get(function.parameters())
      );
    }

    /* Build the tree for non-variadic functions, or build the tree that includes one variadic
    argument for variadic functions. */
    final Pair<Node, Integer> variadicParentAndOffset = buildTree(
            root,
            parameters,
            function,
            false,
            false
    );
    final Node variadicParent = variadicParentAndOffset.getLeft();

    if (variadicParent != null) {
      final int offset = variadicParentAndOffset.getRight();

      // Build a branch of the tree that handles var args given as list.
      buildTree(variadicParent, parameters, function, true, false);

      // Determine which side the variadic parameter is on.
      final boolean isLeftVariadic = parameters.get(offset).isVariadic();

      /* Build a branch of the tree that handles no var args by excluding the variadic param.
      Note that non-variadic parameters that are always paired with another non-variadic parameter
      are not included in paramsWithoutVariadic. For example, if the function signature is
      (int, boolean..., double, bigint, decimal), then paramsWithoutVariadic will be
      [double, bigint]. The (int, decimal) edge will be in a node above the variadicParent, so
      we need to only include parameters that might be paired with a variadic parameter. */
      final List<ParameterInfo> paramsWithoutVariadic = isLeftVariadic
              ? parameters.subList(offset + 1, parameters.size() - offset)
              : parameters.subList(offset, parameters.size() - offset - 1);
      buildTree(
              variadicParent,
              paramsWithoutVariadic,
              function,
              false,
              false
      );

      // Build branches of the tree that handles more than two var args.
      final ParameterInfo variadicParam = isLeftVariadic
              ? parameters.get(offset)
              : parameters.get(parameters.size() - offset - 1);

      // Create copies of the variadic parameter that will be used to build the tree.
      final int maxAddedVariadics = paramsWithoutVariadic.size() + 1;
      final List<ParameterInfo> addedVariadics = Collections.nCopies(
              maxAddedVariadics,
              variadicParam
      );

      // Add the copies of the variadic parameter on the same side as the variadic parameter.
      final List<ParameterInfo> combinedAllParams = new ArrayList<>();
      int fromIndex;
      int toIndex;
      if (isLeftVariadic) {
        combinedAllParams.addAll(addedVariadics);
        combinedAllParams.addAll(paramsWithoutVariadic);
        fromIndex = maxAddedVariadics - 1;
        toIndex = combinedAllParams.size();
      } else {
        combinedAllParams.addAll(paramsWithoutVariadic);
        combinedAllParams.addAll(addedVariadics);
        fromIndex = 0;
        toIndex = combinedAllParams.size() - maxAddedVariadics + 1;
      }

      /* Successively build branches of the tree that include one additional variadic parameter
      until maxAddedVariadics have been processed. During the first iteration, buildTree()
      iterates through the already-created branch for the case where there is one variadic
      parameter. This is necessary because the variadic loop was not added when that branch
      was built, but it needs to be added if there are no non-variadic parameters (i.e.
      paramsWithoutVariadic.size() == 0 and maxAddedVariadics == 1).

      The number of nodes added here is quadratic with respect to paramsWithoutVariadic.size().
      However, this tree is only built on ksql startup, and this tree structure allows resolving
      functions with variadic arguments in the middle in linear time. The number of node generated
      as a function of paramsWithoutVariadic.size() is roughly 0.25x^2 + 1.5x + 3, which is not
      excessive for reasonable numbers of arguments. */
      while (fromIndex >= 0 && toIndex <= combinedAllParams.size()) {
        buildTree(
                variadicParent,
                combinedAllParams.subList(fromIndex, toIndex),
                function,
                false,

                /* Add the variadic loop after longest branch to handle variadic parameters not
                paired with a non-variadic parameter. */
                toIndex - fromIndex == combinedAllParams.size()

        );

        // Increment the size of the sublist in the direction of the variadic parameters.
        if (isLeftVariadic) {
          fromIndex--;
        } else {
          toIndex++;
        }

      }

    }

  }
@Test
  public void shouldThrowOnAddFunctionSameParamsExceptOneVariadic() {
    // Given:
    givenFunctions(
            function(EXPECTED, -1, STRING_VARARGS)
    );

    // When:
    final Exception e = assertThrows(
            KsqlFunctionException.class,
            () -> udfIndex.addFunction(function(EXPECTED, 0, STRING_VARARGS))
    );

    // Then:
    assertThat(e.getMessage(), startsWith("Can't add function `expected` with parameters"
            + " [ARRAY<VARCHAR>] as a function with the same name and parameter types already"
            + " exists"));
  }