vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/operators/joins.go (about) 1 /* 2 Copyright 2022 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package operators 18 19 import ( 20 "vitess.io/vitess/go/vt/sqlparser" 21 "vitess.io/vitess/go/vt/vtgate/planbuilder/operators/ops" 22 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 23 "vitess.io/vitess/go/vt/vtgate/semantics" 24 ) 25 26 type JoinOp interface { 27 ops.Operator 28 GetLHS() ops.Operator 29 GetRHS() ops.Operator 30 SetLHS(ops.Operator) 31 SetRHS(ops.Operator) 32 MakeInner() 33 IsInner() bool 34 AddJoinPredicate(ctx *plancontext.PlanningContext, expr sqlparser.Expr) error 35 } 36 37 func AddPredicate(join JoinOp, ctx *plancontext.PlanningContext, expr sqlparser.Expr, joinPredicates bool, newFilter func(ops.Operator, sqlparser.Expr) ops.Operator) (ops.Operator, error) { 38 deps := ctx.SemTable.RecursiveDeps(expr) 39 switch { 40 case deps.IsSolvedBy(TableID(join.GetLHS())): 41 // predicates can always safely be pushed down to the lhs if that is all they depend on 42 lhs, err := join.GetLHS().AddPredicate(ctx, expr) 43 if err != nil { 44 return nil, err 45 } 46 join.SetLHS(lhs) 47 return join, err 48 case deps.IsSolvedBy(TableID(join.GetRHS())): 49 // if we are dealing with an outer join, always start by checking if this predicate can turn 50 // the join into an inner join 51 if !join.IsInner() && canConvertToInner(ctx, expr, TableID(join.GetRHS())) { 52 join.MakeInner() 53 } 54 55 if !joinPredicates && !join.IsInner() { 56 // if we still are dealing with an outer join 57 // we need to filter after the join has been evaluated 58 return newFilter(join, expr), nil 59 } 60 61 // For inner joins, we can just push the filtering on the RHS 62 rhs, err := join.GetRHS().AddPredicate(ctx, expr) 63 if err != nil { 64 return nil, err 65 } 66 join.SetRHS(rhs) 67 return join, err 68 69 case deps.IsSolvedBy(TableID(join)): 70 // if we are dealing with an outer join, always start by checking if this predicate can turn 71 // the join into an inner join 72 if !joinPredicates && !join.IsInner() && canConvertToInner(ctx, expr, TableID(join.GetRHS())) { 73 join.MakeInner() 74 } 75 76 if !joinPredicates && !join.IsInner() { 77 // if we still are dealing with an outer join 78 // we need to filter after the join has been evaluated 79 return newFilter(join, expr), nil 80 } 81 82 err := join.AddJoinPredicate(ctx, expr) 83 if err != nil { 84 return nil, err 85 } 86 87 return join, nil 88 } 89 return nil, nil 90 } 91 92 // we are looking for predicates like `tbl.col = <>` or `<> = tbl.col`, 93 // where tbl is on the rhs of the left outer join 94 // When a predicate uses information from an outer table, we can convert from an outer join to an inner join 95 // if the predicate is "null-intolerant". 96 // 97 // Null-intolerant in this context means that the predicate will not be true if the table columns are null. 98 // 99 // Since an outer join is an inner join with the addition of all the rows from the left-hand side that 100 // matched no rows on the right-hand, if we are later going to remove all the rows where the right-hand 101 // side did not match, we might as well turn the join into an inner join. 102 // 103 // This is based on the paper "Canonical Abstraction for Outerjoin Optimization" by J Rao et al 104 func canConvertToInner(ctx *plancontext.PlanningContext, expr sqlparser.Expr, rhs semantics.TableSet) bool { 105 isColNameFromRHS := func(e sqlparser.Expr) bool { 106 return sqlparser.IsColName(e) && ctx.SemTable.RecursiveDeps(e).IsSolvedBy(rhs) 107 } 108 switch expr := expr.(type) { 109 case *sqlparser.ComparisonExpr: 110 if expr.Operator == sqlparser.NullSafeEqualOp { 111 return false 112 } 113 114 return isColNameFromRHS(expr.Left) || isColNameFromRHS(expr.Right) 115 116 case *sqlparser.IsExpr: 117 if expr.Right != sqlparser.IsNotNullOp { 118 return false 119 } 120 121 return isColNameFromRHS(expr.Left) 122 default: 123 return false 124 } 125 }