code: func (s *joinReorderGreedySolver) solve(joinNodePlans []LogicalPlan) (LogicalPlan, error) {
  	for _, node := range joinNodePlans {
  		_, err := node.recursiveDeriveStats()
  		if err != nil {
  			return nil, err
  		}
  		s.curJoinGroup = append(s.curJoinGroup, &jrNode{
  			p:       node,
  			cumCost: s.baseNodeCumCost(node),
  		})
  	}
  	sort.SliceStable(s.curJoinGroup, func(i, j int) bool {
  		return s.curJoinGroup[i].cumCost < s.curJoinGroup[j].cumCost
  	})
  
  	var cartesianGroup []LogicalPlan
  	for len(s.curJoinGroup) > 0 {
  		newNode, err := s.constructConnectedJoinTree()
  		if err != nil {
  			return nil, err
  		}
  		cartesianGroup = append(cartesianGroup, newNode.p)
  	}
  
  	return s.makeBushyJoin(cartesianGroup), nil
  }
docs: // solve reorders the join nodes in the group based on a greedy algorithm.
  //
  // For each node having a join equal condition with the current join tree in
  // the group, calculate the cumulative join cost of that node and the join
  // tree, choose the node with the smallest cumulative cost to join with the
  // current join tree.
  //
  // cumulative join cost = CumCount(lhs) + CumCount(rhs) + RowCount(join)
  //   For base node, its CumCount equals to the sum of the count of its subtree.
  //   See baseNodeCumCost for more details.
  // TODO: this formula can be changed to real physical cost in future.
  //
  // For the nodes and join trees which don't have a join equal condition to
  // connect them, we make a bushy join tree to do the cartesian joins finally.