code: func outerJoinSimplify(p *Join, predicates []expression.Expression) error {
  	var innerTable, outerTable LogicalPlan
  	child1 := p.GetChildByIndex(0).(LogicalPlan)
  	child2 := p.GetChildByIndex(1).(LogicalPlan)
  	var fullConditions []expression.Expression
  	if p.JoinType == LeftOuterJoin {
  		innerTable = child2
  		outerTable = child1
  	} else if p.JoinType == RightOuterJoin || p.JoinType == InnerJoin {
  		innerTable = child1
  		outerTable = child2
  	} else {
  		return nil
  	}
  	// first simplify embedded outer join.
  	// When trying to simplify an embedded outer join operation in a query,
  	// we must take into account the join condition for the embedding outer join together with the WHERE condition.
  	if innerPlan, ok := innerTable.(*Join); ok {
  		fullConditions = concatOnAndWhereConds(p, predicates)
  		err := outerJoinSimplify(innerPlan, fullConditions)
  		if err != nil {
  			return errors.Trace(err)
  		}
  	}
  	if outerPlan, ok := outerTable.(*Join); ok {
  		if fullConditions != nil {
  			fullConditions = concatOnAndWhereConds(p, predicates)
  		}
  		err := outerJoinSimplify(outerPlan, fullConditions)
  		if err != nil {
  			return errors.Trace(err)
  		}
  	}
  	if p.JoinType == InnerJoin {
  		return nil
  	}
  	// then simplify embedding outer join.
  	canBeSimplified := false
  	for _, expr := range predicates {
  		isOk, err := isNullRejected(p.ctx, innerTable.GetSchema(), expr)
  		if err != nil {
  			return errors.Trace(err)
  		}
  		if isOk {
  			canBeSimplified = true
  			break
  		}
  	}
  	if canBeSimplified {
  		p.JoinType = InnerJoin
  	}
  	return nil
  }
docs: // outerJoinSimplify simplifies outer join.