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  }