@Override
  public boolean isMappable(Map<NodeRef<Expression>, TSDataType> expressionTypes) {
    if (!isAggregationFunctionExpression() && !isBuiltInScalarFunctionExpression()) {
      // this is a UDF function
      boolean isCurrentMappable =
          new UDTFInformationInferrer(functionName)
              .getAccessStrategy(
                  expressions.stream()
                      .map(Expression::getExpressionString)
                      .collect(Collectors.toList()),
                  expressions.stream()
                      .map(f -> expressionTypes.get(NodeRef.of(f)))
                      .collect(Collectors.toList()),
                  functionAttributes)
              .getAccessStrategyType()
              .equals(AccessStrategy.AccessStrategyType.MAPPABLE_ROW_BY_ROW);
      if (!isCurrentMappable) {
        return false;
      }
    }

    // Function expression is mappable only when all its child expressions are mappable
    boolean hasNonMappableChild = false;
    for (Expression child : expressions) {
      if (!child.isMappable(expressionTypes)) {
        hasNonMappableChild = true;
        break;
      }
    }
    return !hasNonMappableChild;
  }
example: select udf(a, b, udf(c)) from root.sg.d;

<p>3 expressions [root.sg.d.a, root.sg.d.b, udf(root.sg.d.c)] will be in this field.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/expression/multi/FunctionExpression.java