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 }