github.com/dolthub/go-mysql-server@v0.18.0/sql/planbuilder/factory.go (about) 1 // Copyright 2023 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package planbuilder 16 17 import ( 18 "github.com/dolthub/go-mysql-server/sql" 19 "github.com/dolthub/go-mysql-server/sql/expression" 20 "github.com/dolthub/go-mysql-server/sql/plan" 21 "github.com/dolthub/go-mysql-server/sql/transform" 22 ) 23 24 // factory functions should apply all optimizations to an expression 25 // that are always costing/simplification wins. Each function will be a series 26 // of optimizations local to this specific node. 27 // 28 // TODO: split this into a factory object/package when we start memoizing the plan 29 // TODO: switch statement for each type 30 // TODO: logging when optimizations triggered 31 32 type factory struct { 33 ctx *sql.Context 34 debug bool 35 } 36 37 func (f *factory) log(s string) { 38 if f.debug { 39 f.ctx.GetLogger().Info(s) 40 } 41 } 42 43 func (f *factory) buildProject(p *plan.Project, subquery bool) (sql.Node, error) { 44 { 45 // todo generalize this. proj->proj with subquery expression alias 46 // references are one problem. 47 if sqa, _ := p.Child.(*plan.SubqueryAlias); sqa != nil && p.Schema().Equals(sqa.Schema()) { 48 f.log("eliminated projection") 49 return sqa, nil 50 } 51 } 52 53 { 54 // project->project=>project 55 if p2, _ := p.Child.(*plan.Project); p2 != nil { 56 if !subquery { 57 // it is important to bisect subquery expression alias inputs 58 // into a separate projection with current exec impl 59 adjGraph := make(map[sql.ColumnId]sql.Expression, 0) 60 for _, e := range p2.Projections { 61 // inner projections track/collapse alias refs 62 _, err := aliasTrackAndReplace(adjGraph, e) 63 if err != nil { 64 return nil, err 65 } 66 } 67 68 var newP []sql.Expression 69 for _, e := range p.Projections { 70 //outer projections are the ones we want, with aliases replaced 71 newE, err := aliasTrackAndReplace(adjGraph, e) 72 if err != nil { 73 return nil, err 74 } 75 newP = append(newP, newE) 76 } 77 return plan.NewProject(newP, p2.Child), nil 78 } 79 } 80 } 81 return p, nil 82 } 83 84 func containsSubqueryExpr(exprs []sql.Expression) bool { 85 for _, e := range exprs { 86 subqFound := transform.InspectExpr(e, func(e sql.Expression) bool { 87 _, ok := e.(*plan.Subquery) 88 return ok 89 }) 90 if subqFound { 91 return true 92 } 93 } 94 return false 95 } 96 97 func aliasTrackAndReplace(adj map[sql.ColumnId]sql.Expression, e sql.Expression) (sql.Expression, error) { 98 var id sql.ColumnId 99 if ide, ok := e.(sql.IdExpression); ok { 100 id = ide.Id() 101 } 102 newE, _, err := transform.Expr(e, func(e sql.Expression) (sql.Expression, transform.TreeIdentity, error) { 103 switch e := e.(type) { 104 case *expression.GetField: 105 if a, _ := adj[sql.ColumnId(e.Index())]; a != nil { 106 if _, ok := a.(*expression.Alias); ok { 107 // prefer outer-most field reference, is case-sensitive 108 return a, transform.NewTree, nil 109 } 110 } 111 default: 112 } 113 return e, transform.SameTree, nil 114 }) 115 if err != nil { 116 return nil, err 117 } 118 if id > 0 { 119 adj[id] = newE 120 } 121 return newE, nil 122 } 123 124 func (f *factory) buildConvert(expr sql.Expression, castToType string, typeLength, typeScale int) (sql.Expression, error) { 125 n := expression.NewConvertWithLengthAndScale(expr, castToType, typeLength, typeScale) 126 { 127 // deduplicate redundant convert 128 if expr.Type().Equals(n.Type()) { 129 f.log("eliminated convert") 130 return expr, nil 131 } 132 } 133 return n, nil 134 } 135 136 func (f *factory) buildJoin(l, r sql.Node, op plan.JoinType, cond sql.Expression) (sql.Node, error) { 137 { 138 // fold empty joins 139 if _, empty := l.(*plan.EmptyTable); empty { 140 f.log("folded empty table join") 141 return plan.NewEmptyTableWithSchema(append(l.Schema(), r.Schema()...)), nil 142 } 143 if _, empty := r.(*plan.EmptyTable); empty && !op.IsLeftOuter() { 144 f.log("folded empty table join") 145 return plan.NewEmptyTableWithSchema(append(l.Schema(), r.Schema()...)), nil 146 } 147 } 148 149 { 150 // transpose right joins 151 if op.IsRightOuter() { 152 f.log("transposed right join") 153 return f.buildJoin(r, l, plan.JoinTypeLeftOuter, cond) 154 } 155 if op == plan.JoinTypeLateralRight { 156 f.log("transposed right join") 157 return f.buildJoin(r, l, plan.JoinTypeLateralLeft, cond) 158 } 159 } 160 return plan.NewJoin(l, r, op, cond), nil 161 } 162 163 func (f *factory) buildTableAlias(name string, child sql.Node) (plan.TableIdNode, error) { 164 { 165 // deduplicate tableAlias->tableAlias and tableAlias->subqueryAlias 166 switch n := child.(type) { 167 case *plan.TableAlias: 168 return n.WithName(name).(plan.TableIdNode), nil 169 case *plan.SubqueryAlias: 170 return n.WithName(name).(plan.TableIdNode), nil 171 case plan.TableIdNode: 172 return plan.NewTableAlias(name, child).WithId(n.Id()).WithColumns(n.Columns()), nil 173 default: 174 return plan.NewTableAlias(name, child), nil 175 } 176 } 177 }