code: func extractJoinGroup(p LogicalPlan) (group []LogicalPlan, eqEdges []*expression.ScalarFunction, otherConds []expression.Expression) {
  	join, isJoin := p.(*LogicalJoin)
  	if !isJoin || join.preferJoinType > uint(0) || join.JoinType != InnerJoin || join.StraightJoin {
  		return []LogicalPlan{p}, nil, nil
  	}
  
  	lhsGroup, lhsEqualConds, lhsOtherConds := extractJoinGroup(join.children[0])
  	rhsGroup, rhsEqualConds, rhsOtherConds := extractJoinGroup(join.children[1])
  
  	group = append(group, lhsGroup...)
  	group = append(group, rhsGroup...)
  	eqEdges = append(eqEdges, join.EqualConditions...)
  	eqEdges = append(eqEdges, lhsEqualConds...)
  	eqEdges = append(eqEdges, rhsEqualConds...)
  	otherConds = append(otherConds, join.OtherConditions...)
  	otherConds = append(otherConds, lhsOtherConds...)
  	otherConds = append(otherConds, rhsOtherConds...)
  	return group, eqEdges, otherConds
  }
docs: // extractJoinGroup extracts all the join nodes connected with continuous
  // InnerJoins to construct a join group. This join group is further used to
  // construct a new join order based on a reorder algorithm.
  //
  // For example: "InnerJoin(InnerJoin(a, b), LeftJoin(c, d))"
  // results in a join group {a, b, LeftJoin(c, d)}.