private PushDownLevel calculatePushDownLevel(
        Collection<AggregationNode.Aggregation> values,
        List<Symbol> groupingKeys,
        ProjectNode projectNode,
        DeviceTableScanNode tableScanNode,
        SessionInfo session,
        Metadata metadata) {
      boolean hasProject = projectNode != null;
      Map<Symbol, Expression> assignments =
          hasProject ? projectNode.getAssignments().getMap() : null;
      // calculate Function part
      for (AggregationNode.Aggregation aggregation : values) {
        if (aggregation.isDistinct()) {
          return PushDownLevel.NOOP;
        }

        // all the functions can be pre-agg in AggTableScanNode

        // if expr appears in arguments of Aggregation, we don't push down
        if (hasProject
            && aggregation.getArguments().stream()
                .anyMatch(
                    argument ->
                        !(assignments.get(Symbol.from(argument)) instanceof SymbolReference))) {
          return PushDownLevel.NOOP;
        }
      }

      // calculate DataSet part
      boolean singleDeviceEntry = tableScanNode.getDeviceEntries().size() < 2;
      if (groupingKeys.isEmpty()) {
        // GlobalAggregation
        if (singleDeviceEntry) {
          return PushDownLevel.COMPLETE;
        } else {
          // We need to two-stage Aggregation to combine Aggregation result of different DeviceEntry
          return PushDownLevel.PARTIAL;
        }
      }

      List<FunctionCall> dateBinFunctionsOfTime = new ArrayList<>();
      if (groupingKeys.stream()
              .anyMatch(
                  groupingKey ->
                      hasProject
                              && !(assignments.get(groupingKey) instanceof SymbolReference
                                  || isDateBinFunctionOfTime(
                                      assignments.get(groupingKey),
                                      dateBinFunctionsOfTime,
                                      tableScanNode))
                          || tableScanNode.isMeasurementOrTimeColumn(groupingKey))
          || dateBinFunctionsOfTime.size() > 1) {
        // If expr except date_bin(time), Measurement column, or Time column appears in
        // groupingKeys, we don't push down;
        // Attention: Now we also don't push down if there are more than one date_bin function
        // appear in groupingKeys.

        return PushDownLevel.NOOP;
      } else if (singleDeviceEntry
          || ImmutableSet.copyOf(groupingKeys)
              .containsAll(tableScanNode.getIdColumnsInTableStore(metadata, session))) {
        // If all ID columns appear in groupingKeys and no Measurement column appears, we can push
        // down completely.
        return PushDownLevel.COMPLETE;
      } else {
        return PushDownLevel.PARTIAL;
      }
    }
Calculate the level of push-down, and extract the projection of date_bin(time).
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushAggregationIntoTableScan.java