code: func (a *aggregationPushDownSolver) collectAggFuncs(agg *LogicalAggregation, join *LogicalJoin) (valid bool, leftAggFuncs, rightAggFuncs []*aggregation.AggFuncDesc) {
  	valid = true
  	leftChild := join.children[0]
  	for _, aggFunc := range agg.AggFuncs {
  		if !a.isDecomposable(aggFunc) {
  			return false, nil, nil
  		}
  		index := a.getAggFuncChildIdx(aggFunc, leftChild.Schema())
  		switch index {
  		case 0:
  			leftAggFuncs = append(leftAggFuncs, aggFunc)
  		case 1:
  			rightAggFuncs = append(rightAggFuncs, aggFunc)
  		default:
  			return false, nil, nil
  		}
  	}
  	return
  }
docs: // collectAggFuncs collects all aggregate functions and splits them into two parts: "leftAggFuncs" and "rightAggFuncs" whose
  // arguments are all from left child or right child separately. If some aggregate functions have the arguments that have
  // columns both from left and right children, the whole aggregation is forbidden to push down.