github.com/dolthub/go-mysql-server@v0.18.0/sql/analyzer/filters.go (about)

     1  // Copyright 2020-2021 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 analyzer
    16  
    17  import (
    18  	"reflect"
    19  	"strings"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  	"github.com/dolthub/go-mysql-server/sql/expression"
    23  	"github.com/dolthub/go-mysql-server/sql/plan"
    24  	"github.com/dolthub/go-mysql-server/sql/transform"
    25  )
    26  
    27  type filtersByTable map[string][]sql.Expression
    28  
    29  func newFiltersByTable() filtersByTable {
    30  	return make(filtersByTable)
    31  }
    32  
    33  func (f filtersByTable) merge(f2 filtersByTable) {
    34  	for k, exprs := range f2 {
    35  		f[k] = append(f[k], exprs...)
    36  	}
    37  }
    38  
    39  func (f filtersByTable) size() int {
    40  	return len(f)
    41  }
    42  
    43  // getFiltersByTable returns a map of table name to filter expressions on that table for the node provided. Any
    44  // predicates that contain no table or more than one table are not included in the result.
    45  func getFiltersByTable(n sql.Node) filtersByTable {
    46  	filters := newFiltersByTable()
    47  	transform.Inspect(n, func(node sql.Node) bool {
    48  		switch node := node.(type) {
    49  		case *plan.Filter:
    50  			fs := exprToTableFilters(node.Expression)
    51  			filters.merge(fs)
    52  		}
    53  		if o, ok := node.(sql.OpaqueNode); ok {
    54  			return !o.Opaque()
    55  		}
    56  		return true
    57  	})
    58  
    59  	return filters
    60  }
    61  
    62  // exprToTableFilters returns a map of table name to filter expressions on that table for all parts of the expression
    63  // given, split at AND. Any expressions that contain subquerys, or refer to more than one table, are not included in
    64  // the result.
    65  func exprToTableFilters(expr sql.Expression) filtersByTable {
    66  	filters := newFiltersByTable()
    67  	for _, expr := range expression.SplitConjunction(expr) {
    68  		var seenTables = make(map[string]bool)
    69  		var lastTable string
    70  		hasSubquery := false
    71  		sql.Inspect(expr, func(e sql.Expression) bool {
    72  			f, ok := e.(*expression.GetField)
    73  			if ok {
    74  				if !seenTables[f.Table()] {
    75  					seenTables[f.Table()] = true
    76  					lastTable = f.Table()
    77  				}
    78  			} else if _, isSubquery := e.(*plan.Subquery); isSubquery {
    79  				hasSubquery = true
    80  				return false
    81  			}
    82  
    83  			return true
    84  		})
    85  
    86  		if len(seenTables) == 1 && !hasSubquery {
    87  			filters[lastTable] = append(filters[lastTable], expr)
    88  		}
    89  	}
    90  
    91  	return filters
    92  }
    93  
    94  type filterSet struct {
    95  	filterPredicates    []sql.Expression
    96  	filtersByTable      filtersByTable
    97  	handledFilters      []sql.Expression
    98  	handledIndexFilters []string
    99  	tableAliases        TableAliases
   100  }
   101  
   102  // newFilterSet returns a new filter set that will track available filters with the filters and aliases given. Aliases
   103  // are necessary to normalize expressions from indexes when in the presence of aliases.
   104  func newFilterSet(filter sql.Expression, filtersByTable filtersByTable, tableAliases TableAliases) *filterSet {
   105  	return &filterSet{
   106  		filterPredicates: expression.SplitConjunction(filter),
   107  		filtersByTable:   filtersByTable,
   108  		tableAliases:     tableAliases,
   109  	}
   110  }
   111  
   112  // availableFiltersForTable returns the filters that are still available for the table given (not previously marked
   113  // handled)
   114  func (fs *filterSet) availableFiltersForTable(ctx *sql.Context, table string) []sql.Expression {
   115  	filters, ok := fs.filtersByTable[strings.ToLower(table)]
   116  	if !ok {
   117  		return nil
   118  	}
   119  	return fs.subtractUsedIndexes(ctx, subtractExprSet(filters, fs.handledFilters))
   120  }
   121  
   122  // unhandledPredicates returns the filters that are still available (not previously marked handled)
   123  func (fs *filterSet) unhandledPredicates(ctx *sql.Context) []sql.Expression {
   124  	var available []sql.Expression
   125  	for _, e := range fs.filterPredicates {
   126  		available = append(available, fs.subtractUsedIndexes(ctx, subtractExprSet([]sql.Expression{e}, fs.handledFilters))...)
   127  	}
   128  	return available
   129  }
   130  
   131  // handledCount returns the number of filter expressions that have been marked as handled
   132  func (fs *filterSet) handledCount() int {
   133  	return len(fs.handledIndexFilters) + len(fs.handledFilters)
   134  }
   135  
   136  // markFilterUsed marks the filter given as handled, so it will no longer be returned by availableFiltersForTable
   137  func (fs *filterSet) markFiltersHandled(exprs ...sql.Expression) {
   138  	fs.handledFilters = append(fs.handledFilters, exprs...)
   139  }
   140  
   141  // markIndexesHandled marks the indexes given as handled, so expressions on them will no longer be returned by
   142  // availableFiltersForTable
   143  // TODO: this is currently unused because we can't safely remove indexed predicates from the filter in all cases
   144  func (fs *filterSet) markIndexesHandled(indexes []sql.Index) {
   145  	for _, index := range indexes {
   146  		fs.handledIndexFilters = append(fs.handledIndexFilters, index.Expressions()...)
   147  	}
   148  }
   149  
   150  // subtractExprSet returns all expressions in the first parameter that aren't present in the second.
   151  func subtractExprSet(all, toSubtract []sql.Expression) []sql.Expression {
   152  	var remainder []sql.Expression
   153  
   154  	for _, e := range all {
   155  		var found bool
   156  		for _, s := range toSubtract {
   157  			if reflect.DeepEqual(e, s) {
   158  				found = true
   159  				break
   160  			}
   161  		}
   162  
   163  		if !found {
   164  			remainder = append(remainder, e)
   165  		}
   166  	}
   167  
   168  	return remainder
   169  }
   170  
   171  // subtractUsedIndexes returns the filter expressions given with used indexes subtracted off.
   172  func (fs *filterSet) subtractUsedIndexes(ctx *sql.Context, all []sql.Expression) []sql.Expression {
   173  	var remainder []sql.Expression
   174  
   175  	// Careful: index expressions are always normalized (contain actual table names), whereas filter expressions can
   176  	// contain aliases for both expressions and table names. We want to normalize all expressions for comparison, but
   177  	// return the original expressions.
   178  	normalized := normalizeExpressions(fs.tableAliases, all...)
   179  
   180  	for i, e := range normalized {
   181  		var found bool
   182  
   183  		cmpStr := e.String()
   184  		comparable, ok := e.(expression.Comparer)
   185  		if ok {
   186  			left, right := comparable.Left(), comparable.Right()
   187  			if _, ok := left.(*expression.GetField); ok {
   188  				cmpStr = left.String()
   189  			} else {
   190  				cmpStr = right.String()
   191  			}
   192  		}
   193  
   194  		for _, s := range fs.handledIndexFilters {
   195  			if cmpStr == s {
   196  				found = true
   197  				break
   198  			}
   199  		}
   200  
   201  		if !found {
   202  			remainder = append(remainder, all[i])
   203  		}
   204  	}
   205  
   206  	return remainder
   207  }