github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/norm/reject_nulls_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/opt/props"
    17  	"github.com/cockroachdb/errors"
    18  )
    19  
    20  // RejectNullCols returns the set of columns that are candidates for NULL
    21  // rejection filter pushdown. See the Relational.Rule.RejectNullCols comment for
    22  // more details.
    23  func (c *CustomFuncs) RejectNullCols(in memo.RelExpr) opt.ColSet {
    24  	return DeriveRejectNullCols(in)
    25  }
    26  
    27  // HasNullRejectingFilter returns true if the filter causes some of the columns
    28  // in nullRejectCols to be non-null. For example, if nullRejectCols = (x, z),
    29  // filters such as x < 5, x = y, and z IS NOT NULL all satisfy this property.
    30  func (c *CustomFuncs) HasNullRejectingFilter(
    31  	filters memo.FiltersExpr, nullRejectCols opt.ColSet,
    32  ) bool {
    33  	for i := range filters {
    34  		constraints := filters[i].ScalarProps().Constraints
    35  		if constraints == nil {
    36  			continue
    37  		}
    38  
    39  		notNullFilterCols := constraints.ExtractNotNullCols(c.f.evalCtx)
    40  		if notNullFilterCols.Intersects(nullRejectCols) {
    41  			return true
    42  		}
    43  	}
    44  	return false
    45  }
    46  
    47  // NullRejectAggVar scans through the list of aggregate functions and returns
    48  // the Variable input of the first aggregate that is not ConstAgg. Such an
    49  // aggregate must exist, since this is only called if at least one eligible
    50  // null-rejection column was identified by the deriveGroupByRejectNullCols
    51  // method (see its comment for more details).
    52  func (c *CustomFuncs) NullRejectAggVar(
    53  	aggs memo.AggregationsExpr, nullRejectCols opt.ColSet,
    54  ) *memo.VariableExpr {
    55  	for i := range aggs {
    56  		if nullRejectCols.Contains(aggs[i].Col) {
    57  			return memo.ExtractAggFirstVar(aggs[i].Agg)
    58  		}
    59  	}
    60  	panic(errors.AssertionFailedf("expected aggregation not found"))
    61  }
    62  
    63  // DeriveRejectNullCols returns the set of columns that are candidates for NULL
    64  // rejection filter pushdown. See the Relational.Rule.RejectNullCols comment for
    65  // more details.
    66  func DeriveRejectNullCols(in memo.RelExpr) opt.ColSet {
    67  	// Lazily calculate and store the RejectNullCols value.
    68  	relProps := in.Relational()
    69  	if relProps.IsAvailable(props.RejectNullCols) {
    70  		return relProps.Rule.RejectNullCols
    71  	}
    72  	relProps.SetAvailable(props.RejectNullCols)
    73  
    74  	// TODO(andyk): Add other operators to make null rejection more comprehensive.
    75  	switch in.Op() {
    76  	case opt.InnerJoinOp, opt.InnerJoinApplyOp:
    77  		// Pass through null-rejecting columns from both inputs.
    78  		if in.Child(0).(memo.RelExpr).Relational().OuterCols.Empty() {
    79  			relProps.Rule.RejectNullCols.UnionWith(DeriveRejectNullCols(in.Child(0).(memo.RelExpr)))
    80  		}
    81  		if in.Child(1).(memo.RelExpr).Relational().OuterCols.Empty() {
    82  			relProps.Rule.RejectNullCols.UnionWith(DeriveRejectNullCols(in.Child(1).(memo.RelExpr)))
    83  		}
    84  
    85  	case opt.LeftJoinOp, opt.LeftJoinApplyOp:
    86  		// Pass through null-rejection columns from left input, and request null-
    87  		// rejection on right columns.
    88  		if in.Child(0).(memo.RelExpr).Relational().OuterCols.Empty() {
    89  			relProps.Rule.RejectNullCols.UnionWith(DeriveRejectNullCols(in.Child(0).(memo.RelExpr)))
    90  		}
    91  		relProps.Rule.RejectNullCols.UnionWith(in.Child(1).(memo.RelExpr).Relational().OutputCols)
    92  
    93  	case opt.RightJoinOp:
    94  		// Pass through null-rejection columns from right input, and request null-
    95  		// rejection on left columns.
    96  		relProps.Rule.RejectNullCols = in.Child(0).(memo.RelExpr).Relational().OutputCols
    97  		if in.Child(1).(memo.RelExpr).Relational().OuterCols.Empty() {
    98  			relProps.Rule.RejectNullCols.UnionWith(DeriveRejectNullCols(in.Child(1).(memo.RelExpr)))
    99  		}
   100  
   101  	case opt.FullJoinOp:
   102  		// Request null-rejection on all output columns.
   103  		relProps.Rule.RejectNullCols = relProps.OutputCols
   104  
   105  	case opt.GroupByOp, opt.ScalarGroupByOp:
   106  		relProps.Rule.RejectNullCols = deriveGroupByRejectNullCols(in)
   107  
   108  	case opt.ProjectOp:
   109  		// Pass through all null-rejection columns that the Project passes through.
   110  		// The PushSelectIntoProject rule is able to push the IS NOT NULL filter
   111  		// below the Project for these columns.
   112  		rejectNullCols := DeriveRejectNullCols(in.Child(0).(memo.RelExpr))
   113  		relProps.Rule.RejectNullCols = relProps.OutputCols.Intersection(rejectNullCols)
   114  	}
   115  
   116  	return relProps.Rule.RejectNullCols
   117  }
   118  
   119  // deriveGroupByRejectNullCols returns the set of GroupBy columns that are
   120  // eligible for null rejection. If an aggregate input column has requested null
   121  // rejection, then pass along its request if the following criteria are met:
   122  //
   123  //   1. The aggregate function ignores null values, meaning that its value
   124  //      would not change if input null values are filtered.
   125  //
   126  //   2. The aggregate function returns null if its input is empty. And since
   127  //      by #1, the presence of nulls does not alter the result, the aggregate
   128  //      function would return null if its input contains only null values.
   129  //
   130  //   3. No other input columns are referenced by other aggregate functions in
   131  //      the GroupBy (all functions must refer to the same column), with the
   132  //      possible exception of ConstAgg. A ConstAgg aggregate can be safely
   133  //      ignored because all rows in each group must have the same value for this
   134  //      column, so it doesn't matter which rows are filtered.
   135  //
   136  func deriveGroupByRejectNullCols(in memo.RelExpr) opt.ColSet {
   137  	input := in.Child(0).(memo.RelExpr)
   138  	aggs := *in.Child(1).(*memo.AggregationsExpr)
   139  
   140  	var rejectNullCols opt.ColSet
   141  	var savedInColID opt.ColumnID
   142  	for i := range aggs {
   143  		agg := memo.ExtractAggFunc(aggs[i].Agg)
   144  		aggOp := agg.Op()
   145  
   146  		if aggOp == opt.ConstAggOp {
   147  			continue
   148  		}
   149  
   150  		// Criteria #1 and #2.
   151  		if !opt.AggregateIgnoresNulls(aggOp) || !opt.AggregateIsNullOnEmpty(aggOp) {
   152  			// Can't reject nulls for the aggregate.
   153  			return opt.ColSet{}
   154  		}
   155  
   156  		// Get column ID of aggregate's Variable operator input.
   157  		inColID := agg.Child(0).(*memo.VariableExpr).Col
   158  
   159  		// Criteria #3.
   160  		if savedInColID != 0 && savedInColID != inColID {
   161  			// Multiple columns used by aggregate functions, so can't reject nulls
   162  			// for any of them.
   163  			return opt.ColSet{}
   164  		}
   165  		savedInColID = inColID
   166  
   167  		if !DeriveRejectNullCols(input).Contains(inColID) {
   168  			// Input has not requested null rejection on the input column.
   169  			return opt.ColSet{}
   170  		}
   171  
   172  		// Can possibly reject column, but keep searching, since if
   173  		// multiple columns are used by aggregate functions, then nulls
   174  		// can't be rejected on any column.
   175  		rejectNullCols.Add(aggs[i].Col)
   176  	}
   177  	return rejectNullCols
   178  }