public PlanNode visitQueryBody(
      QueryStatement queryStatement,
      Set<Expression> sourceExpressions,
      Set<Expression> sourceTransformExpressions,
      Expression whereExpression,
      Set<Expression> aggregationExpressions,
      Expression groupByExpression,
      MPPQueryContext context) {
    LogicalPlanBuilder planBuilder = new LogicalPlanBuilder(analysis, context);
    if (aggregationExpressions == null) {
      // raw data query
      planBuilder =
          planBuilder
              .planRawDataSource(
                  sourceExpressions,
                  queryStatement.getResultTimeOrder(),
                  0,
                  pushDownLimitToScanNode(queryStatement, analysis),
                  analysis.isLastLevelUseWildcard())
              .planWhereAndSourceTransform(
                  whereExpression,
                  sourceTransformExpressions,
                  queryStatement.isGroupByTime(),
                  queryStatement.getResultTimeOrder());
    } else {
      // aggregation query
      planBuilder =
          planBuilder
              .planRawDataSource(
                  sourceExpressions,
                  queryStatement.getResultTimeOrder(),
                  0,
                  0,
                  analysis.isLastLevelUseWildcard())
              .planWhereAndSourceTransform(
                  whereExpression,
                  sourceTransformExpressions,
                  queryStatement.isGroupByTime(),
                  queryStatement.getResultTimeOrder());

      boolean outputPartial =
          queryStatement.isGroupByLevel()
              || queryStatement.isGroupByTag()
              || (queryStatement.isGroupByTime()
                  && analysis.getGroupByTimeParameter().hasOverlap());
      AggregationStep curStep = outputPartial ? AggregationStep.PARTIAL : AggregationStep.SINGLE;
      planBuilder =
          planBuilder.planRawDataAggregation(
              aggregationExpressions,
              groupByExpression,
              analysis.getGroupByTimeParameter(),
              analysis.getGroupByParameter(),
              queryStatement.isOutputEndTime(),
              curStep,
              queryStatement.getResultTimeOrder());

      if (queryStatement.isGroupByTime() && analysis.getGroupByTimeParameter().hasOverlap()) {
        curStep =
            (queryStatement.isGroupByLevel() || queryStatement.isGroupByTag())
                ? AggregationStep.INTERMEDIATE
                : AggregationStep.FINAL;
        planBuilder =
            planBuilder.planSlidingWindowAggregation(
                aggregationExpressions,
                analysis.getGroupByTimeParameter(),
                curStep,
                queryStatement.getResultTimeOrder());
      }

      if (queryStatement.isGroupByLevel()) {
        planBuilder =
            planBuilder.planGroupByLevel(
                analysis.getCrossGroupByExpressions(),
                analysis.getGroupByTimeParameter(),
                queryStatement.getResultTimeOrder());
      } else if (queryStatement.isGroupByTag()) {
        planBuilder =
            planBuilder.planGroupByTag(
                analysis.getCrossGroupByExpressions(),
                analysis.getTagKeys(),
                analysis.getTagValuesToGroupedTimeseriesOperands(),
                analysis.getGroupByTimeParameter(),
                queryStatement.getResultTimeOrder());
      }

      if (queryStatement.isOutputEndTime()) {
        context.getTypeProvider().setTreeModelType(ENDTIME, TSDataType.INT64);
        if (queryStatement.isGroupByTime()) {
          planBuilder =
              planBuilder.planEndTimeColumnInject(
                  analysis.getGroupByTimeParameter(),
                  queryStatement.getResultTimeOrder().isAscending());
        }
      }
    }

    return planBuilder.getRoot();
  }
This visitor is used to generate a logical plan for the statement and returns the {@link
PlanNode}.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/LogicalPlanVisitor.java