private PlanNode processRawMultiChildNode(
      MultiChildProcessNode node, DistributionPlanContext context, boolean isTimeJoin) {
    MultiChildProcessNode root = (MultiChildProcessNode) node.clone();
    Map<TRegionReplicaSet, List<SourceNode>> sourceGroup = groupBySourceNodes(node, context);

    // For the source nodes which belong to same data region, add a TimeJoinNode for them
    // and make the new TimeJoinNode as the child of current TimeJoinNode
    // TODO: (xingtanzjr) optimize the procedure here to remove duplicated TimeJoinNode
    boolean addParent = false;
    for (Map.Entry<TRegionReplicaSet, List<SourceNode>> entry : sourceGroup.entrySet()) {
      TRegionReplicaSet region = entry.getKey();
      List<SourceNode> seriesScanNodes = entry.getValue();
      if (seriesScanNodes.size() == 1 && (!context.isForceAddParent() || isTimeJoin)) {
        root.addChild(seriesScanNodes.get(0));
        continue;
      }
      // If size of RegionGroup = 1, we should not create new MultiChildNode as the parent.
      // If size of RegionGroup > 1, we need to consider the value of `forceAddParent`.
      // If `forceAddParent` is true, we should not create new MultiChildNode as the parent, either.
      // At last, we can use the parameter `addParent` to judge whether to create new
      // MultiChildNode.
      boolean appendToRootDirectly =
          sourceGroup.size() == 1 || (!addParent && !context.isForceAddParent());
      if (appendToRootDirectly) {
        // In non-last query, this code can be reached at most once
        // And we set region as MainFragmentLocatedRegion, the others Region should transfer data to
        // this region
        context.queryContext.setMainFragmentLocatedRegion(region);
        seriesScanNodes.forEach(root::addChild);
        addParent = true;
      } else {
        // We clone a TimeJoinNode from root to make the params to be consistent.
        // But we need to assign a new ID to it
        MultiChildProcessNode parentOfGroup = (MultiChildProcessNode) root.clone();
        parentOfGroup.setPlanNodeId(context.queryContext.getQueryId().genPlanNodeId());
        seriesScanNodes.forEach(parentOfGroup::addChild);
        root.addChild(parentOfGroup);
      }
    }

    // Process the other children which are not SeriesSourceNode
    for (PlanNode child : node.getChildren()) {
      if (!(child instanceof SeriesSourceNode)) {
        // In a general logical query plan, the children of TimeJoinNode should only be
        // SeriesScanNode or SeriesAggregateScanNode
        // So this branch should not be touched.
        List<PlanNode> children = visit(child, context);
        children.forEach(root::addChild);
      }
    }
    return root;
  }
aggregation align by device, and aggregation is `count_if` or `diff`, or aggregation used with
group by parameter (session, variation, count), use the old aggregation logic
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/distribution/SourceRewriter.java