private List<TSDataType> getOutputColumnTypesOfTimeJoinNode(PlanNode node) {
    // Only templated device situation can invoke this method,
    // the children of TimeJoinNode can only be ScanNode or TimeJoinNode
    List<TSDataType> dataTypes = new ArrayList<>();
    for (PlanNode child : node.getChildren()) {
      if (child instanceof SeriesScanNode) {
        dataTypes.add(((SeriesScanNode) child).getSeriesPath().getSeriesType());
      } else if (child instanceof AlignedSeriesScanNode) {
        dataTypes.add(((AlignedSeriesScanNode) child).getAlignedPath().getSeriesType());
      } else if (child instanceof FullOuterTimeJoinNode
          || child instanceof InnerTimeJoinNode
          || child instanceof LeftOuterTimeJoinNode) {
        dataTypes.addAll(getOutputColumnTypesOfTimeJoinNode(child));
      } else {
        LOGGER.error(
            "Unexpected PlanNode in getOutputColumnTypesOfTimeJoinNode, type: {}",
            child.getOutputColumnNames());
      }
    }
    return dataTypes;
  }
This Visitor is responsible for transferring PlanNode Tree to Operator Tree.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/OperatorTreeGenerator.java