@Override
    public PlanNode visitAggregation(AggregationNode node, Context context) {
      PlanNode child = node.getChild().accept(this, context);
      node = (AggregationNode) node.clone();
      node.setChild(child);
      DeviceTableScanNode tableScanNode = null;
      ProjectNode projectNode = null;
      if (child instanceof DeviceTableScanNode) {
        tableScanNode = (DeviceTableScanNode) child;
      }
      if (child instanceof ProjectNode) {
        projectNode = (ProjectNode) child;
        if (projectNode.getChild() instanceof DeviceTableScanNode) {
          tableScanNode = (DeviceTableScanNode) projectNode.getChild();
        }
      }

      // only optimize AggregationNode with raw DeviceTableScanNode
      if (tableScanNode == null
          || tableScanNode instanceof AggregationTableScanNode) { // no need to optimize
        return node;
      }

      if (tableScanNode.containsNonAlignedDevice()) {
        return node;
      }

      PushDownLevel pushDownLevel =
          calculatePushDownLevel(
              node.getAggregations().values(),
              node.getGroupingKeys(),
              projectNode,
              tableScanNode,
              context.session,
              context.metadata);
      if (pushDownLevel == PushDownLevel.NOOP) { // no push-down
        return node;
      } else if (pushDownLevel == PushDownLevel.PARTIAL) { // partial push-down
        Pair<AggregationNode, AggregationNode> result =
            split(node, context.symbolAllocator, context.queryId);
        AggregationTableScanNode aggregationTableScanNode =
            combineAggregationAndTableScan(
                context.queryId.genPlanNodeId(), result.right, projectNode, tableScanNode);
        result.left.setChild(aggregationTableScanNode);
        return result.left;
      } else { // complete push-down
        return combineAggregationAndTableScan(
            context.queryId.genPlanNodeId(), node, projectNode, tableScanNode);
      }
    }
<b>Optimization phase:</b> Logical plan planning.

<p>The Aggregation may be pushed down to the DeviceTableScanNode, so that we can make use of
statistics.

<p>Attention: This optimizer depends on {@link UnaliasSymbolReferences}.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushAggregationIntoTableScan.java