github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/norm/inline_funcs.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package norm
    12  
    13  import (
    14  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    15  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    16  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    17  	"github.com/cockroachdb/cockroach/pkg/util"
    18  	"github.com/cockroachdb/errors"
    19  )
    20  
    21  // FindInlinableConstants returns the set of input columns that are synthesized
    22  // constant value expressions: ConstOp, TrueOp, FalseOp, or NullOp. Constant
    23  // value expressions can often be inlined into referencing expressions. Only
    24  // Project and Values operators synthesize constant value expressions.
    25  func (c *CustomFuncs) FindInlinableConstants(input memo.RelExpr) opt.ColSet {
    26  	var cols opt.ColSet
    27  	if project, ok := input.(*memo.ProjectExpr); ok {
    28  		for i := range project.Projections {
    29  			item := &project.Projections[i]
    30  			if opt.IsConstValueOp(item.Element) {
    31  				cols.Add(item.Col)
    32  			}
    33  		}
    34  	} else if values, ok := input.(*memo.ValuesExpr); ok && len(values.Rows) == 1 {
    35  		tup := values.Rows[0].(*memo.TupleExpr)
    36  		for i, scalar := range tup.Elems {
    37  			if opt.IsConstValueOp(scalar) {
    38  				cols.Add(values.Cols[i])
    39  			}
    40  		}
    41  	}
    42  	return cols
    43  }
    44  
    45  // InlineProjectionConstants recursively searches each projection expression and
    46  // replaces any references to input columns that are constant. It returns a new
    47  // Projections list containing the replaced expressions.
    48  func (c *CustomFuncs) InlineProjectionConstants(
    49  	projections memo.ProjectionsExpr, input memo.RelExpr, constCols opt.ColSet,
    50  ) memo.ProjectionsExpr {
    51  	newProjections := make(memo.ProjectionsExpr, len(projections))
    52  	for i := range projections {
    53  		item := &projections[i]
    54  		newProjections[i] = c.f.ConstructProjectionsItem(
    55  			c.inlineConstants(item.Element, input, constCols).(opt.ScalarExpr),
    56  			item.Col,
    57  		)
    58  	}
    59  	return newProjections
    60  }
    61  
    62  // InlineFilterConstants recursively searches each filter expression and
    63  // replaces any references to input columns that are constant. It returns a new
    64  // Filters list containing the replaced expressions.
    65  func (c *CustomFuncs) InlineFilterConstants(
    66  	filters memo.FiltersExpr, input memo.RelExpr, constCols opt.ColSet,
    67  ) memo.FiltersExpr {
    68  	newFilters := make(memo.FiltersExpr, len(filters))
    69  	for i := range filters {
    70  		item := &filters[i]
    71  		newFilters[i] = c.f.ConstructFiltersItem(
    72  			c.inlineConstants(item.Condition, input, constCols).(opt.ScalarExpr),
    73  		)
    74  	}
    75  	return newFilters
    76  }
    77  
    78  // inlineConstants recursively searches the given expression and replaces any
    79  // references to input columns that are constant. It returns the replaced
    80  // expression.
    81  func (c *CustomFuncs) inlineConstants(
    82  	e opt.Expr, input memo.RelExpr, constCols opt.ColSet,
    83  ) opt.Expr {
    84  	var replace ReplaceFunc
    85  	replace = func(e opt.Expr) opt.Expr {
    86  		switch t := e.(type) {
    87  		case *memo.VariableExpr:
    88  			if constCols.Contains(t.Col) {
    89  				return c.extractColumn(input, t.Col)
    90  			}
    91  			return t
    92  		}
    93  		return c.f.Replace(e, replace)
    94  	}
    95  	return replace(e)
    96  }
    97  
    98  // extractColumn searches a Project or Values input expression for the column
    99  // having the given id. It returns the expression for that column.
   100  func (c *CustomFuncs) extractColumn(input memo.RelExpr, col opt.ColumnID) opt.ScalarExpr {
   101  	if project, ok := input.(*memo.ProjectExpr); ok {
   102  		for i := range project.Projections {
   103  			item := &project.Projections[i]
   104  			if item.Col == col {
   105  				return item.Element
   106  			}
   107  		}
   108  	} else if values, ok := input.(*memo.ValuesExpr); ok && len(values.Rows) == 1 {
   109  		tup := values.Rows[0].(*memo.TupleExpr)
   110  		for i, scalar := range tup.Elems {
   111  			if values.Cols[i] == col {
   112  				return scalar
   113  			}
   114  		}
   115  	}
   116  	panic(errors.AssertionFailedf("could not find column to extract"))
   117  }
   118  
   119  // HasDuplicateRefs returns true if the target projection expressions or
   120  // passthrough columns reference any column in the given target set more than
   121  // one time, or if the projection expressions contain a correlated subquery.
   122  // For example:
   123  //
   124  //   SELECT x+1, x+2, y FROM a
   125  //
   126  // HasDuplicateRefs would be true, since the x column is referenced twice.
   127  //
   128  // Correlated subqueries are disallowed since it introduces additional
   129  // complexity for a case that's not too important for inlining. Also, skipping
   130  // correlated subqueries minimizes expensive searching in deep trees.
   131  func (c *CustomFuncs) HasDuplicateRefs(
   132  	projections memo.ProjectionsExpr, passthrough opt.ColSet, targetCols opt.ColSet,
   133  ) bool {
   134  	// Passthrough columns that reference a target column count as refs.
   135  	refs := passthrough.Intersection(targetCols)
   136  	for i := range projections {
   137  		item := &projections[i]
   138  		if item.ScalarProps().HasCorrelatedSubquery {
   139  			// Don't traverse the expression tree if there is a correlated subquery.
   140  			return true
   141  		}
   142  
   143  		// When a target column reference is found, add it to the refs set. If
   144  		// the set already contains a reference to that column, then there is a
   145  		// duplicate. findDupRefs returns true if the subtree contains at least
   146  		// one duplicate.
   147  		var findDupRefs func(e opt.Expr) bool
   148  		findDupRefs = func(e opt.Expr) bool {
   149  			switch t := e.(type) {
   150  			case *memo.VariableExpr:
   151  				// Ignore references to non-target columns.
   152  				if !targetCols.Contains(t.Col) {
   153  					return false
   154  				}
   155  
   156  				// Count Variable references.
   157  				if refs.Contains(t.Col) {
   158  					return true
   159  				}
   160  				refs.Add(t.Col)
   161  				return false
   162  
   163  			case memo.RelExpr:
   164  				// We know that this is not a correlated subquery since
   165  				// HasCorrelatedSubquery was already checked above. Uncorrelated
   166  				// subqueries never have references.
   167  				return false
   168  			}
   169  
   170  			for i, n := 0, e.ChildCount(); i < n; i++ {
   171  				if findDupRefs(e.Child(i)) {
   172  					return true
   173  				}
   174  			}
   175  			return false
   176  		}
   177  
   178  		if findDupRefs(item.Element) {
   179  			return true
   180  		}
   181  	}
   182  	return false
   183  }
   184  
   185  // CanInlineProjections returns true if all projection expressions can be
   186  // inlined. See CanInline for details.
   187  func (c *CustomFuncs) CanInlineProjections(projections memo.ProjectionsExpr) bool {
   188  	for i := range projections {
   189  		if !c.CanInline(projections[i].Element) {
   190  			return false
   191  		}
   192  	}
   193  	return true
   194  }
   195  
   196  // CanInline returns true if the given expression consists only of "simple"
   197  // operators like Variable, Const, Eq, and Plus. These operators are assumed to
   198  // be relatively inexpensive to evaluate, and therefore potentially evaluating
   199  // them multiple times is not a big concern.
   200  func (c *CustomFuncs) CanInline(scalar opt.ScalarExpr) bool {
   201  	switch scalar.Op() {
   202  	case opt.AndOp, opt.OrOp, opt.NotOp, opt.TrueOp, opt.FalseOp,
   203  		opt.EqOp, opt.NeOp, opt.LeOp, opt.LtOp, opt.GeOp, opt.GtOp,
   204  		opt.IsOp, opt.IsNotOp, opt.InOp, opt.NotInOp,
   205  		opt.VariableOp, opt.ConstOp, opt.NullOp,
   206  		opt.PlusOp, opt.MinusOp, opt.MultOp:
   207  
   208  		// Recursively verify that children are also inlinable.
   209  		for i, n := 0, scalar.ChildCount(); i < n; i++ {
   210  			if !c.CanInline(scalar.Child(i).(opt.ScalarExpr)) {
   211  				return false
   212  			}
   213  		}
   214  		return true
   215  	}
   216  	return false
   217  }
   218  
   219  // InlineSelectProject searches the filter conditions for any variable
   220  // references to columns from the given projections expression. Each variable is
   221  // replaced by the corresponding inlined projection expression.
   222  func (c *CustomFuncs) InlineSelectProject(
   223  	filters memo.FiltersExpr, projections memo.ProjectionsExpr,
   224  ) memo.FiltersExpr {
   225  	newFilters := make(memo.FiltersExpr, len(filters))
   226  	for i := range filters {
   227  		item := &filters[i]
   228  		newFilters[i] = c.f.ConstructFiltersItem(
   229  			c.inlineProjections(item.Condition, projections).(opt.ScalarExpr),
   230  		)
   231  	}
   232  	return newFilters
   233  }
   234  
   235  // InlineProjectProject searches the projection expressions for any variable
   236  // references to columns from the given input (which must be a Project
   237  // operator). Each variable is replaced by the corresponding inlined projection
   238  // expression.
   239  func (c *CustomFuncs) InlineProjectProject(
   240  	input memo.RelExpr, projections memo.ProjectionsExpr, passthrough opt.ColSet,
   241  ) memo.RelExpr {
   242  	innerProject := input.(*memo.ProjectExpr)
   243  	innerProjections := innerProject.Projections
   244  
   245  	newProjections := make(memo.ProjectionsExpr, len(projections))
   246  	for i := range projections {
   247  		item := &projections[i]
   248  		newProjections[i] = c.f.ConstructProjectionsItem(
   249  			c.inlineProjections(item.Element, innerProjections).(opt.ScalarExpr),
   250  			item.Col,
   251  		)
   252  	}
   253  
   254  	// Add any outer passthrough columns that refer to inner synthesized columns.
   255  	newPassthrough := passthrough
   256  	if !newPassthrough.Empty() {
   257  		for i := range innerProjections {
   258  			item := &innerProjections[i]
   259  			if newPassthrough.Contains(item.Col) {
   260  				newProjections = append(newProjections, *item)
   261  				newPassthrough.Remove(item.Col)
   262  			}
   263  		}
   264  	}
   265  
   266  	return c.f.ConstructProject(innerProject.Input, newProjections, newPassthrough)
   267  }
   268  
   269  // Recursively walk the tree looking for references to projection expressions
   270  // that need to be replaced.
   271  func (c *CustomFuncs) inlineProjections(e opt.Expr, projections memo.ProjectionsExpr) opt.Expr {
   272  	var replace ReplaceFunc
   273  	replace = func(e opt.Expr) opt.Expr {
   274  		switch t := e.(type) {
   275  		case *memo.VariableExpr:
   276  			for i := range projections {
   277  				if projections[i].Col == t.Col {
   278  					return projections[i].Element
   279  				}
   280  			}
   281  			return t
   282  
   283  		case memo.RelExpr:
   284  			if !c.OuterCols(t).Empty() {
   285  				// Should have prevented this in HasDuplicateRefs/HasCorrelatedSubquery.
   286  				panic(errors.AssertionFailedf("cannot inline references within correlated subqueries"))
   287  			}
   288  
   289  			// No projections references possible, since there are no outer cols.
   290  			return t
   291  		}
   292  
   293  		return c.f.Replace(e, replace)
   294  	}
   295  
   296  	return replace(e)
   297  }
   298  
   299  func (c *CustomFuncs) extractVarEqualsConst(
   300  	e opt.Expr,
   301  ) (ok bool, left *memo.VariableExpr, right *memo.ConstExpr) {
   302  	if eq, ok := e.(*memo.EqExpr); ok {
   303  		if l, ok := eq.Left.(*memo.VariableExpr); ok {
   304  			if r, ok := eq.Right.(*memo.ConstExpr); ok {
   305  				return true, l, r
   306  			}
   307  		}
   308  	}
   309  	return false, nil, nil
   310  }
   311  
   312  // CanInlineConstVar returns true if there is an opportunity in the filters to
   313  // inline a variable restricted to be a constant, as in:
   314  //   SELECT * FROM foo WHERE a = 4 AND a IN (1, 2, 3, 4).
   315  // =>
   316  //   SELECT * FROM foo WHERE a = 4 AND 4 IN (1, 2, 3, 4).
   317  func (c *CustomFuncs) CanInlineConstVar(f memo.FiltersExpr) bool {
   318  	// usedIndices tracks the set of filter indices we've used to infer constant
   319  	// values, so we don't inline into them.
   320  	var usedIndices util.FastIntSet
   321  	// fixedCols is the set of columns that the filters restrict to be a constant
   322  	// value.
   323  	var fixedCols opt.ColSet
   324  	for i := range f {
   325  		if ok, l, _ := c.extractVarEqualsConst(f[i].Condition); ok {
   326  			colType := c.mem.Metadata().ColumnMeta(l.Col).Type
   327  			if sqlbase.HasCompositeKeyEncoding(colType) {
   328  				// TODO(justin): allow inlining if the check we're doing is oblivious
   329  				// to composite-ness.
   330  				continue
   331  			}
   332  			if !fixedCols.Contains(l.Col) {
   333  				fixedCols.Add(l.Col)
   334  				usedIndices.Add(i)
   335  			}
   336  		}
   337  	}
   338  	for i := range f {
   339  		if usedIndices.Contains(i) {
   340  			continue
   341  		}
   342  		if f[i].ScalarProps().OuterCols.Intersects(fixedCols) {
   343  			return true
   344  		}
   345  	}
   346  	return false
   347  }
   348  
   349  // InlineConstVar performs the inlining detected by CanInlineConstVar.
   350  func (c *CustomFuncs) InlineConstVar(f memo.FiltersExpr) memo.FiltersExpr {
   351  	// usedIndices tracks the set of filter indices we've used to infer constant
   352  	// values, so we don't inline into them.
   353  	var usedIndices util.FastIntSet
   354  	// fixedCols is the set of columns that the filters restrict to be a constant
   355  	// value.
   356  	var fixedCols opt.ColSet
   357  	// vals maps columns which are restricted to be constant to the value they
   358  	// are restricted to.
   359  	vals := make(map[opt.ColumnID]opt.ScalarExpr)
   360  	for i := range f {
   361  		if ok, v, e := c.extractVarEqualsConst(f[i].Condition); ok {
   362  			colType := c.mem.Metadata().ColumnMeta(v.Col).Type
   363  			if sqlbase.HasCompositeKeyEncoding(colType) {
   364  				continue
   365  			}
   366  			if _, ok := vals[v.Col]; !ok {
   367  				vals[v.Col] = e
   368  				fixedCols.Add(v.Col)
   369  				usedIndices.Add(i)
   370  			}
   371  		}
   372  	}
   373  
   374  	var replace ReplaceFunc
   375  	replace = func(nd opt.Expr) opt.Expr {
   376  		if t, ok := nd.(*memo.VariableExpr); ok {
   377  			if e, ok := vals[t.Col]; ok {
   378  				return e
   379  			}
   380  		}
   381  		return c.f.Replace(nd, replace)
   382  	}
   383  
   384  	result := make(memo.FiltersExpr, len(f))
   385  	for i := range f {
   386  		inliningNeeded := f[i].ScalarProps().OuterCols.Intersects(fixedCols)
   387  		// Don't inline if we used this position to infer a constant value, or if
   388  		// the expression doesn't contain any fixed columns.
   389  		if usedIndices.Contains(i) || !inliningNeeded {
   390  			result[i] = f[i]
   391  		} else {
   392  			newCondition := replace(f[i].Condition).(opt.ScalarExpr)
   393  			result[i] = c.f.ConstructFiltersItem(newCondition)
   394  		}
   395  	}
   396  	return result
   397  }