docs: AbstractPlanNode convertToSerialAggregation(AbstractPlanNode aggregateNode, OrderByPlanNode orderbyNode) {
          assert(aggregateNode instanceof HashAggregatePlanNode);
          HashAggregatePlanNode hashAggr = (HashAggregatePlanNode) aggregateNode;
          List<AbstractExpression> groupbys = new ArrayList<>(hashAggr.getGroupByExpressions());
          List<AbstractExpression> orderbys = new ArrayList<>(orderbyNode.getSortExpressions());
          Set<Integer> coveredGroupByColumns = new HashSet<>();
  
          Iterator<AbstractExpression> orderbyIt = orderbys.iterator();
          while (orderbyIt.hasNext()) {
              AbstractExpression orderby = orderbyIt.next();
              int idx = 0;
              for (AbstractExpression groupby : groupbys) {
                  if (!coveredGroupByColumns.contains(idx)) {
                      if (orderby.equals(groupby)) {
                          orderbyIt.remove();
                          coveredGroupByColumns.add(idx);
                          break;
                      }
                  }
                  ++idx;
              }
          }
          if (orderbys.isEmpty() && groupbys.size() == coveredGroupByColumns.size()) {
              // All GROUP BY expressions are also ORDER BY - Serial aggregation
              return AggregatePlanNode.convertToSerialAggregatePlanNode(hashAggr);
          }
          if (orderbys.isEmpty() && !coveredGroupByColumns.isEmpty() ) {
              // Partial aggregation
              List<Integer> coveredGroupByColumnList = new ArrayList<>();
              coveredGroupByColumnList.addAll(coveredGroupByColumns);
              return AggregatePlanNode.convertToPartialAggregatePlanNode(hashAggr, coveredGroupByColumnList);
          }
          return aggregateNode;
      }