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 }