github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/ranger/detacher.go (about) 1 // Copyright 2020 WHTCORPS INC, 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package ranger 15 16 import ( 17 "github.com/whtcorpsinc/errors" 18 "github.com/whtcorpsinc/BerolinaSQL/ast" 19 "github.com/whtcorpsinc/BerolinaSQL/perceptron" 20 "github.com/whtcorpsinc/milevadb/memex" 21 "github.com/whtcorpsinc/milevadb/stochastikctx" 22 "github.com/whtcorpsinc/milevadb/types" 23 "github.com/whtcorpsinc/milevadb/soliton/chunk" 24 "github.com/whtcorpsinc/milevadb/soliton/defCauslate" 25 ) 26 27 // detachDeferredCausetCNFConditions detaches the condition for calculating range from the other conditions. 28 // Please make sure that the top level is CNF form. 29 func detachDeferredCausetCNFConditions(sctx stochastikctx.Context, conditions []memex.Expression, checker *conditionChecker) ([]memex.Expression, []memex.Expression) { 30 var accessConditions, filterConditions []memex.Expression 31 for _, cond := range conditions { 32 if sf, ok := cond.(*memex.ScalarFunction); ok && sf.FuncName.L == ast.LogicOr { 33 dnfItems := memex.FlattenDNFConditions(sf) 34 defCausumnDNFItems, hasResidual := detachDeferredCausetDNFConditions(sctx, dnfItems, checker) 35 // If this CNF has memex that cannot be resolved as access condition, then the total DNF memex 36 // should be also appended into filter condition. 37 if hasResidual { 38 filterConditions = append(filterConditions, cond) 39 } 40 if len(defCausumnDNFItems) == 0 { 41 continue 42 } 43 rebuildDNF := memex.ComposeDNFCondition(sctx, defCausumnDNFItems...) 44 accessConditions = append(accessConditions, rebuildDNF) 45 continue 46 } 47 if !checker.check(cond) { 48 filterConditions = append(filterConditions, cond) 49 continue 50 } 51 accessConditions = append(accessConditions, cond) 52 if checker.shouldReserve { 53 filterConditions = append(filterConditions, cond) 54 checker.shouldReserve = checker.length != types.UnspecifiedLength 55 } 56 } 57 return accessConditions, filterConditions 58 } 59 60 // detachDeferredCausetDNFConditions detaches the condition for calculating range from the other conditions. 61 // Please make sure that the top level is DNF form. 62 func detachDeferredCausetDNFConditions(sctx stochastikctx.Context, conditions []memex.Expression, checker *conditionChecker) ([]memex.Expression, bool) { 63 var ( 64 hasResidualConditions bool 65 accessConditions []memex.Expression 66 ) 67 for _, cond := range conditions { 68 if sf, ok := cond.(*memex.ScalarFunction); ok && sf.FuncName.L == ast.LogicAnd { 69 cnfItems := memex.FlattenCNFConditions(sf) 70 defCausumnCNFItems, others := detachDeferredCausetCNFConditions(sctx, cnfItems, checker) 71 if len(others) > 0 { 72 hasResidualConditions = true 73 } 74 // If one part of DNF has no access condition. Then this DNF cannot get range. 75 if len(defCausumnCNFItems) == 0 { 76 return nil, true 77 } 78 rebuildCNF := memex.ComposeCNFCondition(sctx, defCausumnCNFItems...) 79 accessConditions = append(accessConditions, rebuildCNF) 80 } else if checker.check(cond) { 81 accessConditions = append(accessConditions, cond) 82 if checker.shouldReserve { 83 hasResidualConditions = true 84 checker.shouldReserve = checker.length != types.UnspecifiedLength 85 } 86 } else { 87 return nil, true 88 } 89 } 90 return accessConditions, hasResidualConditions 91 } 92 93 // getEqOrInDefCausOffset checks if the memex is a eq function that one side is constant and another is defCausumn or an 94 // in function which is `defCausumn in (constant list)`. 95 // If so, it will return the offset of this defCausumn in the slice, otherwise return -1 for not found. 96 func getEqOrInDefCausOffset(expr memex.Expression, defcaus []*memex.DeferredCauset) int { 97 f, ok := expr.(*memex.ScalarFunction) 98 if !ok { 99 return -1 100 } 101 _, defCauslation := expr.CharsetAndDefCauslation(f.GetCtx()) 102 switch f.FuncName.L { 103 case ast.LogicOr: 104 dnfItems := memex.FlattenDNFConditions(f) 105 offset := int(-1) 106 for _, dnfItem := range dnfItems { 107 curOffset := getEqOrInDefCausOffset(dnfItem, defcaus) 108 if curOffset == -1 { 109 return -1 110 } 111 if offset != -1 && curOffset != offset { 112 return -1 113 } 114 offset = curOffset 115 } 116 return offset 117 case ast.EQ, ast.NullEQ: 118 if c, ok := f.GetArgs()[0].(*memex.DeferredCauset); ok { 119 if c.RetType.EvalType() == types.ETString && !defCauslate.CompatibleDefCauslate(c.RetType.DefCauslate, defCauslation) { 120 return -1 121 } 122 if constVal, ok := f.GetArgs()[1].(*memex.Constant); ok { 123 val, err := constVal.Eval(chunk.Row{}) 124 if err != nil || val.IsNull() { 125 // treat defCaus<=>null as range scan instead of point get to avoid incorrect results 126 // when nullable unique index has multiple matches for filter x is null 127 return -1 128 } 129 for i, defCaus := range defcaus { 130 if defCaus.Equal(nil, c) { 131 return i 132 } 133 } 134 } 135 } 136 if c, ok := f.GetArgs()[1].(*memex.DeferredCauset); ok { 137 if c.RetType.EvalType() == types.ETString && !defCauslate.CompatibleDefCauslate(c.RetType.DefCauslate, defCauslation) { 138 return -1 139 } 140 if constVal, ok := f.GetArgs()[0].(*memex.Constant); ok { 141 val, err := constVal.Eval(chunk.Row{}) 142 if err != nil || val.IsNull() { 143 return -1 144 } 145 for i, defCaus := range defcaus { 146 if defCaus.Equal(nil, c) { 147 return i 148 } 149 } 150 } 151 } 152 case ast.In: 153 c, ok := f.GetArgs()[0].(*memex.DeferredCauset) 154 if !ok { 155 return -1 156 } 157 if c.RetType.EvalType() == types.ETString && !defCauslate.CompatibleDefCauslate(c.RetType.DefCauslate, defCauslation) { 158 return -1 159 } 160 for _, arg := range f.GetArgs()[1:] { 161 if _, ok := arg.(*memex.Constant); !ok { 162 return -1 163 } 164 } 165 for i, defCaus := range defcaus { 166 if defCaus.Equal(nil, c) { 167 return i 168 } 169 } 170 } 171 return -1 172 } 173 174 // extractIndexPointRangesForCNF extracts a CNF item from the input CNF memexs, such that the CNF item 175 // is totally composed of point range filters. 176 // e.g, for input CNF memexs ((a,b) in ((1,1),(2,2))) and a > 1 and ((a,b,c) in (1,1,1),(2,2,2)) 177 // ((a,b,c) in (1,1,1),(2,2,2)) would be extracted. 178 func extractIndexPointRangesForCNF(sctx stochastikctx.Context, conds []memex.Expression, defcaus []*memex.DeferredCauset, lengths []int) (*DetachRangeResult, int, error) { 179 if len(conds) < 2 { 180 return nil, -1, nil 181 } 182 var r *DetachRangeResult 183 maxNumDefCauss := int(0) 184 offset := int(-1) 185 for i, cond := range conds { 186 tmpConds := []memex.Expression{cond} 187 defCausSets := memex.ExtractDeferredCausetSet(tmpConds) 188 origDefCausNum := defCausSets.Len() 189 if origDefCausNum == 0 { 190 continue 191 } 192 if l := len(defcaus); origDefCausNum > l { 193 origDefCausNum = l 194 } 195 currDefCauss := defcaus[:origDefCausNum] 196 currLengths := lengths[:origDefCausNum] 197 res, err := DetachCondAndBuildRangeForIndex(sctx, tmpConds, currDefCauss, currLengths) 198 if err != nil { 199 return nil, -1, err 200 } 201 if len(res.Ranges) == 0 { 202 return &DetachRangeResult{}, -1, nil 203 } 204 if len(res.AccessConds) == 0 || len(res.RemainedConds) > 0 { 205 continue 206 } 207 sameLens, allPoints := true, true 208 numDefCauss := int(0) 209 for i, ran := range res.Ranges { 210 if !ran.IsPoint(sctx.GetStochastikVars().StmtCtx) { 211 allPoints = false 212 break 213 } 214 if i == 0 { 215 numDefCauss = len(ran.LowVal) 216 } else if numDefCauss != len(ran.LowVal) { 217 sameLens = false 218 break 219 } 220 } 221 if !allPoints || !sameLens { 222 continue 223 } 224 if numDefCauss > maxNumDefCauss { 225 r = res 226 offset = i 227 maxNumDefCauss = numDefCauss 228 } 229 } 230 if r != nil { 231 r.IsDNFCond = false 232 } 233 return r, offset, nil 234 } 235 236 // detachCNFCondAndBuildRangeForIndex will detach the index filters from causet filters. These conditions are connected with `and` 237 // It will first find the point query defCausumn and then extract the range query defCausumn. 238 // considerDNF is true means it will try to extract access conditions from the DNF memexs. 239 func (d *rangeDetacher) detachCNFCondAndBuildRangeForIndex(conditions []memex.Expression, tpSlice []*types.FieldType, considerDNF bool) (*DetachRangeResult, error) { 240 var ( 241 eqCount int 242 ranges []*Range 243 err error 244 ) 245 res := &DetachRangeResult{} 246 247 accessConds, filterConds, newConditions, emptyRange := ExtractEqAndInCondition(d.sctx, conditions, d.defcaus, d.lengths) 248 if emptyRange { 249 return res, nil 250 } 251 for ; eqCount < len(accessConds); eqCount++ { 252 if accessConds[eqCount].(*memex.ScalarFunction).FuncName.L != ast.EQ { 253 break 254 } 255 } 256 eqOrInCount := len(accessConds) 257 res.EqCondCount = eqCount 258 res.EqOrInCount = eqOrInCount 259 ranges, err = d.buildCNFIndexRange(tpSlice, eqOrInCount, accessConds) 260 if err != nil { 261 return res, err 262 } 263 res.Ranges = ranges 264 res.AccessConds = accessConds 265 res.RemainedConds = filterConds 266 if eqOrInCount == len(d.defcaus) || len(newConditions) == 0 { 267 res.RemainedConds = append(res.RemainedConds, newConditions...) 268 return res, nil 269 } 270 checker := &conditionChecker{ 271 defCausUniqueID: d.defcaus[eqOrInCount].UniqueID, 272 length: d.lengths[eqOrInCount], 273 shouldReserve: d.lengths[eqOrInCount] != types.UnspecifiedLength, 274 } 275 if considerDNF { 276 pointRes, offset, err := extractIndexPointRangesForCNF(d.sctx, conditions, d.defcaus, d.lengths) 277 if err != nil { 278 return nil, err 279 } 280 if pointRes != nil { 281 if len(pointRes.Ranges) == 0 { 282 return &DetachRangeResult{}, nil 283 } 284 if len(pointRes.Ranges[0].LowVal) > eqOrInCount { 285 res = pointRes 286 eqOrInCount = len(res.Ranges[0].LowVal) 287 newConditions = newConditions[:0] 288 newConditions = append(newConditions, conditions[:offset]...) 289 newConditions = append(newConditions, conditions[offset+1:]...) 290 if eqOrInCount == len(d.defcaus) || len(newConditions) == 0 { 291 res.RemainedConds = append(res.RemainedConds, newConditions...) 292 return res, nil 293 } 294 } 295 } 296 if eqOrInCount > 0 { 297 newDefCauss := d.defcaus[eqOrInCount:] 298 newLengths := d.lengths[eqOrInCount:] 299 tailRes, err := DetachCondAndBuildRangeForIndex(d.sctx, newConditions, newDefCauss, newLengths) 300 if err != nil { 301 return nil, err 302 } 303 if len(tailRes.Ranges) == 0 { 304 return &DetachRangeResult{}, nil 305 } 306 if len(tailRes.AccessConds) > 0 { 307 res.Ranges = appendRanges2PointRanges(res.Ranges, tailRes.Ranges) 308 res.AccessConds = append(res.AccessConds, tailRes.AccessConds...) 309 } 310 res.RemainedConds = append(res.RemainedConds, tailRes.RemainedConds...) 311 // For cases like `((a = 1 and b = 1) or (a = 2 and b = 2)) and c = 1` on index (a,b,c), eqOrInCount is 2, 312 // res.EqOrInCount is 0, and tailRes.EqOrInCount is 1. We should not set res.EqOrInCount to 1, otherwise, 313 // `b = CorrelatedDeferredCauset` would be extracted as access conditions as well, which is not as expected at least for now. 314 if res.EqOrInCount > 0 { 315 if res.EqOrInCount == res.EqCondCount { 316 res.EqCondCount = res.EqCondCount + tailRes.EqCondCount 317 } 318 res.EqOrInCount = res.EqOrInCount + tailRes.EqOrInCount 319 } 320 return res, nil 321 } 322 // `eqOrInCount` must be 0 when coming here. 323 res.AccessConds, res.RemainedConds = detachDeferredCausetCNFConditions(d.sctx, newConditions, checker) 324 ranges, err = d.buildCNFIndexRange(tpSlice, 0, res.AccessConds) 325 if err != nil { 326 return nil, err 327 } 328 res.Ranges = ranges 329 return res, nil 330 } 331 for _, cond := range newConditions { 332 if !checker.check(cond) { 333 filterConds = append(filterConds, cond) 334 continue 335 } 336 accessConds = append(accessConds, cond) 337 } 338 ranges, err = d.buildCNFIndexRange(tpSlice, eqOrInCount, accessConds) 339 if err != nil { 340 return nil, err 341 } 342 res.Ranges = ranges 343 res.AccessConds = accessConds 344 res.RemainedConds = filterConds 345 return res, nil 346 } 347 348 // ExtractEqAndInCondition will split the given condition into three parts by the information of index defCausumns and their lengths. 349 // accesses: The condition will be used to build range. 350 // filters: filters is the part that some access conditions need to be evaluate again since it's only the prefix part of char defCausumn. 351 // newConditions: We'll simplify the given conditions if there're multiple in conditions or eq conditions on the same defCausumn. 352 // e.g. if there're a in (1, 2, 3) and a in (2, 3, 4). This two will be combined to a in (2, 3) and pushed to newConditions. 353 // bool: indicate whether there's nil range when merging eq and in conditions. 354 func ExtractEqAndInCondition(sctx stochastikctx.Context, conditions []memex.Expression, 355 defcaus []*memex.DeferredCauset, lengths []int) ([]memex.Expression, []memex.Expression, []memex.Expression, bool) { 356 var filters []memex.Expression 357 rb := builder{sc: sctx.GetStochastikVars().StmtCtx} 358 accesses := make([]memex.Expression, len(defcaus)) 359 points := make([][]point, len(defcaus)) 360 mergedAccesses := make([]memex.Expression, len(defcaus)) 361 newConditions := make([]memex.Expression, 0, len(conditions)) 362 for _, cond := range conditions { 363 offset := getEqOrInDefCausOffset(cond, defcaus) 364 if offset == -1 { 365 newConditions = append(newConditions, cond) 366 continue 367 } 368 if accesses[offset] == nil { 369 accesses[offset] = cond 370 continue 371 } 372 // Multiple Eq/In conditions for one defCausumn in CNF, apply intersection on them 373 // Lazily compute the points for the previously visited Eq/In 374 if mergedAccesses[offset] == nil { 375 mergedAccesses[offset] = accesses[offset] 376 points[offset] = rb.build(accesses[offset]) 377 } 378 points[offset] = rb.intersection(points[offset], rb.build(cond)) 379 // Early termination if false memex found 380 if len(points[offset]) == 0 { 381 return nil, nil, nil, true 382 } 383 } 384 for i, ma := range mergedAccesses { 385 if ma == nil { 386 if accesses[i] != nil { 387 newConditions = append(newConditions, accesses[i]) 388 } 389 continue 390 } 391 accesses[i] = points2EqOrInCond(sctx, points[i], mergedAccesses[i]) 392 newConditions = append(newConditions, accesses[i]) 393 } 394 for i, cond := range accesses { 395 if cond == nil { 396 accesses = accesses[:i] 397 break 398 } 399 if lengths[i] != types.UnspecifiedLength { 400 filters = append(filters, cond) 401 } 402 } 403 // We should remove all accessConds, so that they will not be added to filter conditions. 404 newConditions = removeAccessConditions(newConditions, accesses) 405 return accesses, filters, newConditions, false 406 } 407 408 // detachDNFCondAndBuildRangeForIndex will detach the index filters from causet filters when it's a DNF. 409 // We will detach the conditions of every DNF items, then compose them to a DNF. 410 func (d *rangeDetacher) detachDNFCondAndBuildRangeForIndex(condition *memex.ScalarFunction, newTpSlice []*types.FieldType) ([]*Range, []memex.Expression, bool, error) { 411 sc := d.sctx.GetStochastikVars().StmtCtx 412 firstDeferredCausetChecker := &conditionChecker{ 413 defCausUniqueID: d.defcaus[0].UniqueID, 414 shouldReserve: d.lengths[0] != types.UnspecifiedLength, 415 length: d.lengths[0], 416 } 417 rb := builder{sc: sc} 418 dnfItems := memex.FlattenDNFConditions(condition) 419 newAccessItems := make([]memex.Expression, 0, len(dnfItems)) 420 var totalRanges []*Range 421 hasResidual := false 422 for _, item := range dnfItems { 423 if sf, ok := item.(*memex.ScalarFunction); ok && sf.FuncName.L == ast.LogicAnd { 424 cnfItems := memex.FlattenCNFConditions(sf) 425 var accesses, filters []memex.Expression 426 res, err := d.detachCNFCondAndBuildRangeForIndex(cnfItems, newTpSlice, true) 427 if err != nil { 428 return nil, nil, false, nil 429 } 430 ranges := res.Ranges 431 accesses = res.AccessConds 432 filters = res.RemainedConds 433 if len(accesses) == 0 { 434 return FullRange(), nil, true, nil 435 } 436 if len(filters) > 0 { 437 hasResidual = true 438 } 439 totalRanges = append(totalRanges, ranges...) 440 newAccessItems = append(newAccessItems, memex.ComposeCNFCondition(d.sctx, accesses...)) 441 } else if firstDeferredCausetChecker.check(item) { 442 if firstDeferredCausetChecker.shouldReserve { 443 hasResidual = true 444 firstDeferredCausetChecker.shouldReserve = d.lengths[0] != types.UnspecifiedLength 445 } 446 points := rb.build(item) 447 ranges, err := points2Ranges(sc, points, newTpSlice[0]) 448 if err != nil { 449 return nil, nil, false, errors.Trace(err) 450 } 451 totalRanges = append(totalRanges, ranges...) 452 newAccessItems = append(newAccessItems, item) 453 } else { 454 return FullRange(), nil, true, nil 455 } 456 } 457 458 totalRanges, err := UnionRanges(sc, totalRanges, d.mergeConsecutive) 459 if err != nil { 460 return nil, nil, false, errors.Trace(err) 461 } 462 463 return totalRanges, []memex.Expression{memex.ComposeDNFCondition(d.sctx, newAccessItems...)}, hasResidual, nil 464 } 465 466 // DetachRangeResult wraps up results when detaching conditions and builing ranges. 467 type DetachRangeResult struct { 468 // Ranges is the ranges extracted and built from conditions. 469 Ranges []*Range 470 // AccessConds is the extracted conditions for access. 471 AccessConds []memex.Expression 472 // RemainedConds is the filter conditions which should be kept after access. 473 RemainedConds []memex.Expression 474 // EqCondCount is the number of equal conditions extracted. 475 EqCondCount int 476 // EqOrInCount is the number of equal/in conditions extracted. 477 EqOrInCount int 478 // IsDNFCond indicates if the top layer of conditions are in DNF. 479 IsDNFCond bool 480 } 481 482 // DetachCondAndBuildRangeForIndex will detach the index filters from causet filters. 483 // The returned values are encapsulated into a struct DetachRangeResult, see its comments for explanation. 484 func DetachCondAndBuildRangeForIndex(sctx stochastikctx.Context, conditions []memex.Expression, defcaus []*memex.DeferredCauset, 485 lengths []int) (*DetachRangeResult, error) { 486 d := &rangeDetacher{ 487 sctx: sctx, 488 allConds: conditions, 489 defcaus: defcaus, 490 lengths: lengths, 491 mergeConsecutive: true, 492 } 493 return d.detachCondAndBuildRangeForDefCauss() 494 } 495 496 type rangeDetacher struct { 497 sctx stochastikctx.Context 498 allConds []memex.Expression 499 defcaus []*memex.DeferredCauset 500 lengths []int 501 mergeConsecutive bool 502 } 503 504 func (d *rangeDetacher) detachCondAndBuildRangeForDefCauss() (*DetachRangeResult, error) { 505 res := &DetachRangeResult{} 506 newTpSlice := make([]*types.FieldType, 0, len(d.defcaus)) 507 for _, defCaus := range d.defcaus { 508 newTpSlice = append(newTpSlice, newFieldType(defCaus.RetType)) 509 } 510 if len(d.allConds) == 1 { 511 if sf, ok := d.allConds[0].(*memex.ScalarFunction); ok && sf.FuncName.L == ast.LogicOr { 512 ranges, accesses, hasResidual, err := d.detachDNFCondAndBuildRangeForIndex(sf, newTpSlice) 513 if err != nil { 514 return res, errors.Trace(err) 515 } 516 res.Ranges = ranges 517 res.AccessConds = accesses 518 res.IsDNFCond = true 519 // If this DNF have something cannot be to calculate range, then all this DNF should be pushed as filter condition. 520 if hasResidual { 521 res.RemainedConds = d.allConds 522 return res, nil 523 } 524 return res, nil 525 } 526 } 527 return d.detachCNFCondAndBuildRangeForIndex(d.allConds, newTpSlice, true) 528 } 529 530 // DetachSimpleCondAndBuildRangeForIndex will detach the index filters from causet filters. 531 // It will find the point query defCausumn firstly and then extract the range query defCausumn. 532 func DetachSimpleCondAndBuildRangeForIndex(sctx stochastikctx.Context, conditions []memex.Expression, 533 defcaus []*memex.DeferredCauset, lengths []int) ([]*Range, []memex.Expression, error) { 534 newTpSlice := make([]*types.FieldType, 0, len(defcaus)) 535 for _, defCaus := range defcaus { 536 newTpSlice = append(newTpSlice, newFieldType(defCaus.RetType)) 537 } 538 d := &rangeDetacher{ 539 sctx: sctx, 540 allConds: conditions, 541 defcaus: defcaus, 542 lengths: lengths, 543 mergeConsecutive: true, 544 } 545 res, err := d.detachCNFCondAndBuildRangeForIndex(conditions, newTpSlice, false) 546 return res.Ranges, res.AccessConds, err 547 } 548 549 func removeAccessConditions(conditions, accessConds []memex.Expression) []memex.Expression { 550 filterConds := make([]memex.Expression, 0, len(conditions)) 551 for _, cond := range conditions { 552 if !memex.Contains(accessConds, cond) { 553 filterConds = append(filterConds, cond) 554 } 555 } 556 return filterConds 557 } 558 559 // ExtractAccessConditionsForDeferredCauset extracts the access conditions used for range calculation. Since 560 // we don't need to return the remained filter conditions, it is much simpler than DetachCondsForDeferredCauset. 561 func ExtractAccessConditionsForDeferredCauset(conds []memex.Expression, uniqueID int64) []memex.Expression { 562 checker := conditionChecker{ 563 defCausUniqueID: uniqueID, 564 length: types.UnspecifiedLength, 565 } 566 accessConds := make([]memex.Expression, 0, 8) 567 return memex.Filter(accessConds, conds, checker.check) 568 } 569 570 // DetachCondsForDeferredCauset detaches access conditions for specified defCausumn from other filter conditions. 571 func DetachCondsForDeferredCauset(sctx stochastikctx.Context, conds []memex.Expression, defCaus *memex.DeferredCauset) (accessConditions, otherConditions []memex.Expression) { 572 checker := &conditionChecker{ 573 defCausUniqueID: defCaus.UniqueID, 574 length: types.UnspecifiedLength, 575 } 576 return detachDeferredCausetCNFConditions(sctx, conds, checker) 577 } 578 579 // MergeDNFItems4DefCaus receives a slice of DNF conditions, merges some of them which can be built into ranges on a single defCausumn, then returns. 580 // For example, [a > 5, b > 6, c > 7, a = 1, b > 3] will become [a > 5 or a = 1, b > 6 or b > 3, c > 7]. 581 func MergeDNFItems4DefCaus(ctx stochastikctx.Context, dnfItems []memex.Expression) []memex.Expression { 582 mergedDNFItems := make([]memex.Expression, 0, len(dnfItems)) 583 defCaus2DNFItems := make(map[int64][]memex.Expression) 584 for _, dnfItem := range dnfItems { 585 defcaus := memex.ExtractDeferredCausets(dnfItem) 586 // If this condition contains multiple defCausumns, we can't merge it. 587 // If this defCausumn is _milevadb_rowid, we also can't merge it since Selectivity() doesn't handle it, or infinite recursion will happen. 588 if len(defcaus) != 1 || defcaus[0].ID == perceptron.ExtraHandleID { 589 mergedDNFItems = append(mergedDNFItems, dnfItem) 590 continue 591 } 592 593 uniqueID := defcaus[0].UniqueID 594 checker := &conditionChecker{ 595 defCausUniqueID: uniqueID, 596 length: types.UnspecifiedLength, 597 } 598 // If we can't use this condition to build range, we can't merge it. 599 // Currently, we assume if every condition in a DNF memex can pass this check, then `Selectivity` must be able to 600 // cover this entire DNF directly without recursively call `Selectivity`. If this doesn't hold in the future, this logic 601 // may cause infinite recursion in `Selectivity`. 602 if !checker.check(dnfItem) { 603 mergedDNFItems = append(mergedDNFItems, dnfItem) 604 continue 605 } 606 607 defCaus2DNFItems[uniqueID] = append(defCaus2DNFItems[uniqueID], dnfItem) 608 } 609 for _, items := range defCaus2DNFItems { 610 mergedDNFItems = append(mergedDNFItems, memex.ComposeDNFCondition(ctx, items...)) 611 } 612 return mergedDNFItems 613 }