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  }