diff --git a/sessionctx/variable/session.go b/sessionctx/variable/session.go index 9a8e18a05d8c6..ebfdfbc869610 100644 --- a/sessionctx/variable/session.go +++ b/sessionctx/variable/session.go @@ -1055,6 +1055,173 @@ type SessionVars struct { // EnableINLJoinInnerMultiPattern indicates whether enable multi pattern for index join inner side EnableINLJoinInnerMultiPattern bool +<<<<<<< HEAD +======= + + // Enable late materialization: push down some selection condition to tablescan. + EnableLateMaterialization bool + + // EnableRowLevelChecksum indicates whether row level checksum is enabled. + EnableRowLevelChecksum bool + + // TiFlashComputeDispatchPolicy indicates how to dipatch task to tiflash_compute nodes. + // Only for disaggregated-tiflash mode. + TiFlashComputeDispatchPolicy tiflashcompute.DispatchPolicy + + // SlowTxnThreshold is the threshold of slow transaction logs + SlowTxnThreshold uint64 + + // LoadBasedReplicaReadThreshold is the threshold for the estimated wait duration of a store. + // If exceeding the threshold, try other stores using replica read. + LoadBasedReplicaReadThreshold time.Duration + + // OptOrderingIdxSelThresh is the threshold for optimizer to consider the ordering index. + // If there exists an index whose estimated selectivity is smaller than this threshold, the optimizer won't + // use the ExpectedCnt to adjust the estimated row count for index scan. + OptOrderingIdxSelThresh float64 + + // EnableMPPSharedCTEExecution indicates whether we enable the shared CTE execution strategy on MPP side. + EnableMPPSharedCTEExecution bool + + // OptimizerFixControl control some details of the optimizer behavior through the tidb_opt_fix_control variable. + OptimizerFixControl map[uint64]string + + // HypoIndexes are for the Index Advisor. + HypoIndexes map[string]map[string]map[string]*model.IndexInfo // dbName -> tblName -> idxName -> idxInfo + + // Runtime Filter Group + // Runtime filter type: only support IN or MIN_MAX now. + // Runtime filter type can take multiple values at the same time. + runtimeFilterTypes []RuntimeFilterType + // Runtime filter mode: only support OFF, LOCAL now + runtimeFilterMode RuntimeFilterMode +} + +var ( + // variables below are for the optimizer fix control. + + // TiDBOptFixControl44262 controls whether to allow to use dynamic-mode to access partitioning tables without global-stats (#44262). + TiDBOptFixControl44262 uint64 = 44262 + // TiDBOptFixControl44389 controls whether to consider non-point ranges of some CNF item when building ranges. + TiDBOptFixControl44389 uint64 = 44389 +) + +// GetOptimizerFixControlValue returns the specified value of the optimizer fix control. +func (s *SessionVars) GetOptimizerFixControlValue(key uint64) (value string, exist bool) { + if s.OptimizerFixControl == nil { + return "", false + } + value, exist = s.OptimizerFixControl[key] + return +} + +// planReplayerSessionFinishedTaskKeyLen is used to control the max size for the finished plan replayer task key in session +// in order to control the used memory +const planReplayerSessionFinishedTaskKeyLen = 128 + +// AddPlanReplayerFinishedTaskKey record finished task key in session +func (s *SessionVars) AddPlanReplayerFinishedTaskKey(key replayer.PlanReplayerTaskKey) { + if len(s.PlanReplayerFinishedTaskKey) >= planReplayerSessionFinishedTaskKeyLen { + s.initializePlanReplayerFinishedTaskKey() + } + s.PlanReplayerFinishedTaskKey[key] = struct{}{} +} + +func (s *SessionVars) initializePlanReplayerFinishedTaskKey() { + s.PlanReplayerFinishedTaskKey = make(map[replayer.PlanReplayerTaskKey]struct{}, planReplayerSessionFinishedTaskKeyLen) +} + +// CheckPlanReplayerFinishedTaskKey check whether the key exists +func (s *SessionVars) CheckPlanReplayerFinishedTaskKey(key replayer.PlanReplayerTaskKey) bool { + if s.PlanReplayerFinishedTaskKey == nil { + s.initializePlanReplayerFinishedTaskKey() + return false + } + _, ok := s.PlanReplayerFinishedTaskKey[key] + return ok +} + +// IsPlanReplayerCaptureEnabled indicates whether capture or continues capture enabled +func (s *SessionVars) IsPlanReplayerCaptureEnabled() bool { + return s.EnablePlanReplayerCapture || s.EnablePlanReplayedContinuesCapture +} + +// GetNewChunkWithCapacity Attempt to request memory from the chunk pool +// thread safety +func (s *SessionVars) GetNewChunkWithCapacity(fields []*types.FieldType, capacity int, maxCachesize int, pool chunk.Allocator) *chunk.Chunk { + if pool == nil { + return chunk.New(fields, capacity, maxCachesize) + } + s.ChunkPool.mu.Lock() + defer s.ChunkPool.mu.Unlock() + if pool.CheckReuseAllocSize() && (!s.GetUseChunkAlloc()) { + s.StmtCtx.SetUseChunkAlloc() + } + chk := pool.Alloc(fields, capacity, maxCachesize) + return chk +} + +// ExchangeChunkStatus give the status to preUseChunkAlloc +func (s *SessionVars) ExchangeChunkStatus() { + s.preUseChunkAlloc = s.GetUseChunkAlloc() +} + +// GetUseChunkAlloc return useChunkAlloc status +func (s *SessionVars) GetUseChunkAlloc() bool { + return s.StmtCtx.GetUseChunkAllocStatus() +} + +// SetAlloc Attempt to set the buffer pool address +func (s *SessionVars) SetAlloc(alloc chunk.Allocator) { + if !s.EnableReuseCheck { + return + } + s.ChunkPool.Alloc = alloc +} + +// IsAllocValid check if chunk reuse is enable or ChunkPool is inused. +func (s *SessionVars) IsAllocValid() bool { + if !s.EnableReuseCheck { + return false + } + s.ChunkPool.mu.Lock() + defer s.ChunkPool.mu.Unlock() + return s.ChunkPool.Alloc != nil +} + +// ClearAlloc indicates stop reuse chunk +func (s *SessionVars) ClearAlloc(alloc *chunk.Allocator, b bool) { + if !b { + s.ChunkPool.Alloc = nil + return + } + + // If an error is reported, re-apply for alloc + // Prevent the goroutine left before, affecting the execution of the next sql + // issuse 38918 + s.ChunkPool.mu.Lock() + s.ChunkPool.Alloc = nil + s.ChunkPool.mu.Unlock() + *alloc = chunk.NewAllocator() +} + +// GetPreparedStmtByName returns the prepared statement specified by stmtName. +func (s *SessionVars) GetPreparedStmtByName(stmtName string) (interface{}, error) { + stmtID, ok := s.PreparedStmtNameToID[stmtName] + if !ok { + return nil, ErrStmtNotFound + } + return s.GetPreparedStmtByID(stmtID) +} + +// GetPreparedStmtByID returns the prepared statement specified by stmtID. +func (s *SessionVars) GetPreparedStmtByID(stmtID uint32) (interface{}, error) { + stmt, ok := s.PreparedStmts[stmtID] + if !ok { + return nil, ErrStmtNotFound + } + return stmt, nil +>>>>>>> 85d6323e3a3 (util/ranger: consider good non-point ranges from CNF item (#44384)) } // InitStatementContext initializes a StatementContext, the object is reused to reduce allocation. diff --git a/util/ranger/BUILD.bazel b/util/ranger/BUILD.bazel new file mode 100644 index 0000000000000..be470b8c15d6c --- /dev/null +++ b/util/ranger/BUILD.bazel @@ -0,0 +1,70 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "ranger", + srcs = [ + "checker.go", + "detacher.go", + "points.go", + "ranger.go", + "types.go", + ], + importpath = "github.com/pingcap/tidb/util/ranger", + visibility = ["//visibility:public"], + deps = [ + "//errno", + "//expression", + "//kv", + "//parser/ast", + "//parser/charset", + "//parser/format", + "//parser/model", + "//parser/mysql", + "//parser/terror", + "//sessionctx", + "//sessionctx/stmtctx", + "//sessionctx/variable", + "//types", + "//types/parser_driver", + "//util/chunk", + "//util/codec", + "//util/collate", + "//util/dbterror", + "//util/mathutil", + "@com_github_pingcap_errors//:errors", + "@org_golang_x_exp//slices", + ], +) + +go_test( + name = "ranger_test", + timeout = "short", + srcs = [ + "bench_test.go", + "main_test.go", + "ranger_test.go", + "types_test.go", + ], + data = glob(["testdata/**"]), + flaky = True, + shard_count = 26, + deps = [ + ":ranger", + "//config", + "//expression", + "//parser/ast", + "//parser/model", + "//parser/mysql", + "//planner/core", + "//session", + "//sessionctx", + "//sessionctx/variable", + "//testkit", + "//testkit/testdata", + "//testkit/testsetup", + "//types", + "//util/collate", + "@com_github_stretchr_testify//require", + "@org_uber_go_goleak//:goleak", + ], +) diff --git a/util/ranger/detacher.go b/util/ranger/detacher.go index eb9186828269c..a276fa9c564ee 100644 --- a/util/ranger/detacher.go +++ b/util/ranger/detacher.go @@ -24,9 +24,11 @@ import ( "github.com/pingcap/tidb/parser/mysql" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/sessionctx/stmtctx" + "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/collate" + "github.com/pingcap/tidb/util/mathutil" ) // detachColumnCNFConditions detaches the condition for calculating range from the other conditions. @@ -184,18 +186,71 @@ func getPotentialEqOrInColOffset(sctx sessionctx.Context, expr expression.Expres return -1 } -// extractIndexPointRangesForCNF extracts a CNF item from the input CNF expressions, such that the CNF item -// is totally composed of point range filters. +type cnfItemRangeResult struct { + rangeResult *DetachRangeResult + offset int + // sameLenPointRanges means that each range is point range and all of them have the same column numbers(i.e., maxColNum = minColNum). + sameLenPointRanges bool + maxColNum int + minColNum int +} + +func getCNFItemRangeResult(sctx sessionctx.Context, rangeResult *DetachRangeResult, offset int) *cnfItemRangeResult { + sameLenPointRanges := true + var maxColNum, minColNum int + for i, ran := range rangeResult.Ranges { + if !ran.IsPoint(sctx) { + sameLenPointRanges = false + } + if i == 0 { + maxColNum = len(ran.LowVal) + minColNum = len(ran.LowVal) + } else { + maxColNum = mathutil.Max(maxColNum, len(ran.LowVal)) + minColNum = mathutil.Min(minColNum, len(ran.LowVal)) + } + } + if minColNum != maxColNum { + sameLenPointRanges = false + } + return &cnfItemRangeResult{ + rangeResult: rangeResult, + offset: offset, + sameLenPointRanges: sameLenPointRanges, + maxColNum: maxColNum, + minColNum: minColNum, + } +} + +func compareCNFItemRangeResult(curResult, bestResult *cnfItemRangeResult) (curIsBetter bool) { + if curResult.sameLenPointRanges && bestResult.sameLenPointRanges { + return curResult.minColNum > bestResult.minColNum + } + if !curResult.sameLenPointRanges && !bestResult.sameLenPointRanges { + if curResult.minColNum == bestResult.minColNum { + return curResult.maxColNum > bestResult.maxColNum + } + return curResult.minColNum > bestResult.minColNum + } + // Point ranges is better than non-point ranges since we can append subsequent column ranges to point ranges. + return curResult.sameLenPointRanges +} + +// extractBestCNFItemRanges builds ranges for each CNF item from the input CNF expressions and returns the best CNF +// item ranges. // e.g, for input CNF expressions ((a,b) in ((1,1),(2,2))) and a > 1 and ((a,b,c) in (1,1,1),(2,2,2)) // ((a,b,c) in (1,1,1),(2,2,2)) would be extracted. +<<<<<<< HEAD func extractIndexPointRangesForCNF(sctx sessionctx.Context, conds []expression.Expression, cols []*expression.Column, lengths []int) (*DetachRangeResult, int, []*valueInfo, error) { +======= +func extractBestCNFItemRanges(sctx sessionctx.Context, conds []expression.Expression, cols []*expression.Column, + lengths []int, rangeMaxSize int64) (*cnfItemRangeResult, []*valueInfo, error) { +>>>>>>> 85d6323e3a3 (util/ranger: consider good non-point ranges from CNF item (#44384)) if len(conds) < 2 { - return nil, -1, nil, nil + return nil, nil, nil } - var r *DetachRangeResult + var bestRes *cnfItemRangeResult columnValues := make([]*valueInfo, len(cols)) - maxNumCols := int(0) - offset := int(-1) for i, cond := range conds { tmpConds := []expression.Expression{cond} colSets := expression.ExtractColumnSet(cond) @@ -204,43 +259,25 @@ func extractIndexPointRangesForCNF(sctx sessionctx.Context, conds []expression.E } res, err := DetachCondAndBuildRangeForIndex(sctx, tmpConds, cols, lengths) if err != nil { - return nil, -1, nil, err + return nil, nil, err } if len(res.Ranges) == 0 { - return &DetachRangeResult{}, -1, nil, nil + return &cnfItemRangeResult{rangeResult: res, offset: i}, nil, nil } // take the union of the two columnValues columnValues = unionColumnValues(columnValues, res.ColumnValues) if len(res.AccessConds) == 0 || len(res.RemainedConds) > 0 { continue } - sameLens, allPoints := true, true - numCols := int(0) - for j, ran := range res.Ranges { - if !ran.IsPoint(sctx) { - allPoints = false - break - } - if j == 0 { - numCols = len(ran.LowVal) - } else if numCols != len(ran.LowVal) { - sameLens = false - break - } - } - if !allPoints || !sameLens { - continue - } - if numCols > maxNumCols { - r = res - offset = i - maxNumCols = numCols + curRes := getCNFItemRangeResult(sctx, res, i) + if bestRes == nil || compareCNFItemRangeResult(curRes, bestRes) { + bestRes = curRes } } - if r != nil { - r.IsDNFCond = false + if bestRes != nil && bestRes.rangeResult != nil { + bestRes.rangeResult.IsDNFCond = false } - return r, offset, columnValues, nil + return bestRes, columnValues, nil } func unionColumnValues(lhs, rhs []*valueInfo) []*valueInfo { @@ -314,27 +351,50 @@ func (d *rangeDetacher) detachCNFCondAndBuildRangeForIndex(conditions []expressi shouldReserve: d.lengths[eqOrInCount] != types.UnspecifiedLength, } if considerDNF { +<<<<<<< HEAD pointRes, offset, columnValues, err := extractIndexPointRangesForCNF(d.sctx, conditions, d.cols, d.lengths) +======= + bestCNFItemRes, columnValues, err := extractBestCNFItemRanges(d.sctx, conditions, d.cols, d.lengths, d.rangeMaxSize) +>>>>>>> 85d6323e3a3 (util/ranger: consider good non-point ranges from CNF item (#44384)) if err != nil { return nil, err } res.ColumnValues = unionColumnValues(res.ColumnValues, columnValues) - if pointRes != nil { - if len(pointRes.Ranges) == 0 { + if bestCNFItemRes != nil && bestCNFItemRes.rangeResult != nil { + if len(bestCNFItemRes.rangeResult.Ranges) == 0 { return &DetachRangeResult{}, nil } - if len(pointRes.Ranges[0].LowVal) > eqOrInCount { - pointRes.ColumnValues = res.ColumnValues - res = pointRes - pointRanges = pointRes.Ranges + if bestCNFItemRes.sameLenPointRanges && bestCNFItemRes.minColNum > eqOrInCount { + bestCNFItemRes.rangeResult.ColumnValues = res.ColumnValues + res = bestCNFItemRes.rangeResult + pointRanges = bestCNFItemRes.rangeResult.Ranges eqOrInCount = len(res.Ranges[0].LowVal) newConditions = newConditions[:0] - newConditions = append(newConditions, conditions[:offset]...) - newConditions = append(newConditions, conditions[offset+1:]...) + newConditions = append(newConditions, conditions[:bestCNFItemRes.offset]...) + newConditions = append(newConditions, conditions[bestCNFItemRes.offset+1:]...) if eqOrInCount == len(d.cols) || len(newConditions) == 0 { res.RemainedConds = append(res.RemainedConds, newConditions...) return res, nil } + } else { + considerCNFItemNonPointRanges := false + fixValue, ok := d.sctx.GetSessionVars().GetOptimizerFixControlValue(variable.TiDBOptFixControl44389) + if ok && variable.TiDBOptOn(fixValue) { + considerCNFItemNonPointRanges = true + } + if considerCNFItemNonPointRanges && !bestCNFItemRes.sameLenPointRanges && eqOrInCount == 0 && bestCNFItemRes.minColNum > 0 && bestCNFItemRes.maxColNum > 1 { + // When eqOrInCount is 0, if we don't enter the IF branch, we would use detachColumnCNFConditions to build + // ranges on the first index column. + // Considering minColNum > 0 and maxColNum > 1, bestCNFItemRes is better than the ranges built by detachColumnCNFConditions + // in most cases. + bestCNFItemRes.rangeResult.ColumnValues = res.ColumnValues + res = bestCNFItemRes.rangeResult + newConditions = newConditions[:0] + newConditions = append(newConditions, conditions[:bestCNFItemRes.offset]...) + newConditions = append(newConditions, conditions[bestCNFItemRes.offset+1:]...) + res.RemainedConds = append(res.RemainedConds, newConditions...) + return res, nil + } } } if eqOrInCount > 0 { diff --git a/util/ranger/ranger_test.go b/util/ranger/ranger_test.go index 9a35b94140a80..92e275b683116 100644 --- a/util/ranger/ranger_test.go +++ b/util/ranger/ranger_test.go @@ -1005,6 +1005,36 @@ func TestCompIndexMultiColDNF2(t *testing.T) { } } +<<<<<<< HEAD +======= +func TestIssue41572(t *testing.T) { + store := testkit.CreateMockStore(t) + + testKit := testkit.NewTestKit(t, store) + testKit.MustExec("use test") + testKit.MustExec("drop table if exists t") + testKit.MustExec("create table t(a varchar(100), b int, c int, d int, index idx(a, b, c))") + testKit.MustExec("insert into t values ('t',1,1,1),('t',1,3,3),('t',2,1,3),('t',2,3,1),('w',0,3,3),('z',0,1,1)") + + var input []string + var output []struct { + SQL string + Plan []string + Result []string + } + rangerSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(testKit.MustQuery("explain " + tt).Rows()) + output[i].Result = testdata.ConvertRowsToStrings(testKit.MustQuery(tt).Sort().Rows()) + }) + testKit.MustQuery("explain " + tt).Check(testkit.Rows(output[i].Plan...)) + testKit.MustQuery(tt).Sort().Check(testkit.Rows(output[i].Result...)) + } +} + +>>>>>>> 85d6323e3a3 (util/ranger: consider good non-point ranges from CNF item (#44384)) func TestPrefixIndexMultiColDNF(t *testing.T) { store, clean := testkit.CreateMockStore(t) defer clean() @@ -2116,3 +2146,498 @@ func TestShardIndexFuncSuites(t *testing.T) { require.Equal(t, fmt.Sprintf("%s", newConds), tt.outputConds) } } +<<<<<<< HEAD +======= + +func getSelectionFromQuery(t *testing.T, sctx sessionctx.Context, sql string) *plannercore.LogicalSelection { + ctx := context.Background() + stmts, err := session.Parse(sctx, sql) + require.NoError(t, err) + require.Len(t, stmts, 1) + ret := &plannercore.PreprocessorReturn{} + err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret)) + require.NoError(t, err) + p, _, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema) + require.NoError(t, err) + selection, isSelection := p.(plannercore.LogicalPlan).Children()[0].(*plannercore.LogicalSelection) + require.True(t, isSelection) + return selection +} + +func checkDetachRangeResult(t *testing.T, res *ranger.DetachRangeResult, expectedAccessConds, expectedRemainedConds, expectedRanges string) { + require.Equal(t, expectedAccessConds, fmt.Sprintf("%v", res.AccessConds)) + require.Equal(t, expectedRemainedConds, fmt.Sprintf("%v", res.RemainedConds)) + require.Equal(t, expectedRanges, fmt.Sprintf("%v", res.Ranges)) +} + +func checkRangeFallbackAndReset(t *testing.T, sctx sessionctx.Context, expectedRangeFallback bool) { + require.Equal(t, expectedRangeFallback, sctx.GetSessionVars().StmtCtx.RangeFallback) + sctx.GetSessionVars().StmtCtx.RangeFallback = false +} + +func TestRangeFallbackForDetachCondAndBuildRangeForIndex(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("create table t1 (a int, b int, c int, d int, index idx(a, b, c))") + tbl, err := dom.InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t1")) + require.NoError(t, err) + tblInfo := tbl.Meta() + sctx := tk.Session().(sessionctx.Context) + + // test CNF condition + sql := "select * from t1 where a in (10,20,30) and b in (40,50,60) and c >= 70 and c <= 80" + selection := getSelectionFromQuery(t, sctx, sql) + conds := selection.Conditions + require.Equal(t, 4, len(conds)) + cols, lengths := expression.IndexInfo2PrefixCols(tblInfo.Columns, selection.Schema().Columns, tblInfo.Indices[0]) + res, err := ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[in(test.t1.a, 10, 20, 30) in(test.t1.b, 40, 50, 60) ge(test.t1.c, 70) le(test.t1.c, 80)]", + "[]", + "[[10 40 70,10 40 80] [10 50 70,10 50 80] [10 60 70,10 60 80] [20 40 70,20 40 80] [20 50 70,20 50 80] [20 60 70,20 60 80] [30 40 70,30 40 80] [30 50 70,30 50 80] [30 60 70,30 60 80]]") + checkRangeFallbackAndReset(t, sctx, false) + quota := res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[in(test.t1.a, 10, 20, 30) in(test.t1.b, 40, 50, 60)]", + "[ge(test.t1.c, 70) le(test.t1.c, 80)]", + "[[10 40,10 40] [10 50,10 50] [10 60,10 60] [20 40,20 40] [20 50,20 50] [20 60,20 60] [30 40,30 40] [30 50,30 50] [30 60,30 60]]") + checkRangeFallbackAndReset(t, sctx, true) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[in(test.t1.a, 10, 20, 30)]", + "[in(test.t1.b, 40, 50, 60) ge(test.t1.c, 70) le(test.t1.c, 80)]", + "[[10,10] [20,20] [30,30]]") + checkRangeFallbackAndReset(t, sctx, true) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[]", + "[ge(test.t1.c, 70) le(test.t1.c, 80) in(test.t1.b, 40, 50, 60) in(test.t1.a, 10, 20, 30)]", + "[[NULL,+inf]]") + checkRangeFallbackAndReset(t, sctx, true) + + // test DNF condition + sql = "select * from t1 where a = 10 or a = 20 or a = 30" + selection = getSelectionFromQuery(t, sctx, sql) + conds = selection.Conditions + require.Equal(t, 1, len(conds)) + cols, lengths = expression.IndexInfo2PrefixCols(tblInfo.Columns, selection.Schema().Columns, tblInfo.Indices[0]) + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[or(eq(test.t1.a, 10), or(eq(test.t1.a, 20), eq(test.t1.a, 30)))]", + "[]", + "[[10,10] [20,20] [30,30]]") + checkRangeFallbackAndReset(t, sctx, false) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[]", + "[or(or(eq(test.t1.a, 10), eq(test.t1.a, 20)), eq(test.t1.a, 30))]", + "[[NULL,+inf]]") + checkRangeFallbackAndReset(t, sctx, true) + + sql = "select * from t1 where (a = 10 and b = 40) or (a = 20 and b = 50) or (a = 30 and b = 60)" + selection = getSelectionFromQuery(t, sctx, sql) + conds = selection.Conditions + require.Equal(t, 1, len(conds)) + cols, lengths = expression.IndexInfo2PrefixCols(tblInfo.Columns, selection.Schema().Columns, tblInfo.Indices[0]) + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[or(and(eq(test.t1.a, 10), eq(test.t1.b, 40)), or(and(eq(test.t1.a, 20), eq(test.t1.b, 50)), and(eq(test.t1.a, 30), eq(test.t1.b, 60))))]", + "[]", + "[[10 40,10 40] [20 50,20 50] [30 60,30 60]]") + checkRangeFallbackAndReset(t, sctx, false) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[]", + "[or(or(and(eq(test.t1.a, 10), eq(test.t1.b, 40)), and(eq(test.t1.a, 20), eq(test.t1.b, 50))), and(eq(test.t1.a, 30), eq(test.t1.b, 60)))]", + "[[NULL,+inf]]") + checkRangeFallbackAndReset(t, sctx, true) + + // test considerDNF code path + sql = "select * from t1 where (a, b) in ((10, 20), (30, 40)) and c = 50" + selection = getSelectionFromQuery(t, sctx, sql) + conds = selection.Conditions + require.Equal(t, 2, len(conds)) + cols, lengths = expression.IndexInfo2PrefixCols(tblInfo.Columns, selection.Schema().Columns, tblInfo.Indices[0]) + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[or(and(eq(test.t1.a, 10), eq(test.t1.b, 20)), and(eq(test.t1.a, 30), eq(test.t1.b, 40))) eq(test.t1.c, 50)]", + "[]", + "[[10 20 50,10 20 50] [30 40 50,30 40 50]]") + checkRangeFallbackAndReset(t, sctx, false) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[or(and(eq(test.t1.a, 10), eq(test.t1.b, 20)), and(eq(test.t1.a, 30), eq(test.t1.b, 40)))]", + "[eq(test.t1.c, 50)]", + "[[10 20,10 20] [30 40,30 40]]") + checkRangeFallbackAndReset(t, sctx, true) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[or(eq(test.t1.a, 10), eq(test.t1.a, 30))]", + "[eq(test.t1.c, 50) or(and(eq(test.t1.a, 10), eq(test.t1.b, 20)), and(eq(test.t1.a, 30), eq(test.t1.b, 40)))]", + "[[10,10] [30,30]]") + checkRangeFallbackAndReset(t, sctx, true) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[]", + // Ideal RemainedConds should be [eq(test.t1.c, 50) or(and(eq(test.t1.a, 10), eq(test.t1.b, 20)), and(eq(test.t1.a, 30), eq(test.t1.b, 40)))], but we don't remove redundant or(eq(test.t1.a, 10), eq(test.t1.a, 30)) for now. + "[eq(test.t1.c, 50) or(and(eq(test.t1.a, 10), eq(test.t1.b, 20)), and(eq(test.t1.a, 30), eq(test.t1.b, 40))) or(eq(test.t1.a, 10), eq(test.t1.a, 30))]", + "[[NULL,+inf]]") + checkRangeFallbackAndReset(t, sctx, true) + + // test prefix index + tk.MustExec("drop table if exists t2") + tk.MustExec("create table t2 (a varchar(10), b varchar(10), c varchar(10), d varchar(10), index idx(a(2), b(2), c(2)))") + tbl, err = dom.InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t2")) + require.NoError(t, err) + tblInfo = tbl.Meta() + + // test CNF condition + sql = "select * from t2 where a in ('aaa','bbb','ccc') and b in ('ddd','eee','fff') and c >= 'ggg' and c <= 'iii'" + selection = getSelectionFromQuery(t, sctx, sql) + conds = selection.Conditions + require.Equal(t, 4, len(conds)) + cols, lengths = expression.IndexInfo2PrefixCols(tblInfo.Columns, selection.Schema().Columns, tblInfo.Indices[0]) + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[in(test.t2.a, aaa, bbb, ccc) in(test.t2.b, ddd, eee, fff) ge(test.t2.c, ggg) le(test.t2.c, iii)]", + "[in(test.t2.a, aaa, bbb, ccc) in(test.t2.b, ddd, eee, fff) ge(test.t2.c, ggg) le(test.t2.c, iii)]", + "[[\"aa\" \"dd\" \"gg\",\"aa\" \"dd\" \"ii\"] [\"aa\" \"ee\" \"gg\",\"aa\" \"ee\" \"ii\"] [\"aa\" \"ff\" \"gg\",\"aa\" \"ff\" \"ii\"] [\"bb\" \"dd\" \"gg\",\"bb\" \"dd\" \"ii\"] [\"bb\" \"ee\" \"gg\",\"bb\" \"ee\" \"ii\"] [\"bb\" \"ff\" \"gg\",\"bb\" \"ff\" \"ii\"] [\"cc\" \"dd\" \"gg\",\"cc\" \"dd\" \"ii\"] [\"cc\" \"ee\" \"gg\",\"cc\" \"ee\" \"ii\"] [\"cc\" \"ff\" \"gg\",\"cc\" \"ff\" \"ii\"]]") + checkRangeFallbackAndReset(t, sctx, false) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[in(test.t2.a, aaa, bbb, ccc) in(test.t2.b, ddd, eee, fff)]", + "[in(test.t2.a, aaa, bbb, ccc) in(test.t2.b, ddd, eee, fff) ge(test.t2.c, ggg) le(test.t2.c, iii)]", + "[[\"aa\" \"dd\",\"aa\" \"dd\"] [\"aa\" \"ee\",\"aa\" \"ee\"] [\"aa\" \"ff\",\"aa\" \"ff\"] [\"bb\" \"dd\",\"bb\" \"dd\"] [\"bb\" \"ee\",\"bb\" \"ee\"] [\"bb\" \"ff\",\"bb\" \"ff\"] [\"cc\" \"dd\",\"cc\" \"dd\"] [\"cc\" \"ee\",\"cc\" \"ee\"] [\"cc\" \"ff\",\"cc\" \"ff\"]]") + checkRangeFallbackAndReset(t, sctx, true) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[in(test.t2.a, aaa, bbb, ccc)]", + "[in(test.t2.a, aaa, bbb, ccc) in(test.t2.b, ddd, eee, fff) ge(test.t2.c, ggg) le(test.t2.c, iii)]", + "[[\"aa\",\"aa\"] [\"bb\",\"bb\"] [\"cc\",\"cc\"]]") + checkRangeFallbackAndReset(t, sctx, true) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[]", + "[ge(test.t2.c, ggg) le(test.t2.c, iii) in(test.t2.a, aaa, bbb, ccc) in(test.t2.b, ddd, eee, fff)]", + "[[NULL,+inf]]") + checkRangeFallbackAndReset(t, sctx, true) + + // test DNF condition + sql = "select * from t2 where a = 'aaa' or a = 'bbb' or a = 'ccc'" + selection = getSelectionFromQuery(t, sctx, sql) + conds = selection.Conditions + require.Equal(t, 1, len(conds)) + cols, lengths = expression.IndexInfo2PrefixCols(tblInfo.Columns, selection.Schema().Columns, tblInfo.Indices[0]) + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[or(eq(test.t2.a, aaa), or(eq(test.t2.a, bbb), eq(test.t2.a, ccc)))]", + "[or(or(eq(test.t2.a, aaa), eq(test.t2.a, bbb)), eq(test.t2.a, ccc))]", + "[[\"aa\",\"aa\"] [\"bb\",\"bb\"] [\"cc\",\"cc\"]]") + checkRangeFallbackAndReset(t, sctx, false) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[]", + "[or(or(eq(test.t2.a, aaa), eq(test.t2.a, bbb)), eq(test.t2.a, ccc))]", + "[[NULL,+inf]]") + checkRangeFallbackAndReset(t, sctx, true) + + sql = "select * from t2 where (a = 'aaa' and b = 'ddd') or (a = 'bbb' and b = 'eee') or (a = 'ccc' and b = 'fff')" + selection = getSelectionFromQuery(t, sctx, sql) + conds = selection.Conditions + require.Equal(t, 1, len(conds)) + cols, lengths = expression.IndexInfo2PrefixCols(tblInfo.Columns, selection.Schema().Columns, tblInfo.Indices[0]) + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[or(and(eq(test.t2.a, aaa), eq(test.t2.b, ddd)), or(and(eq(test.t2.a, bbb), eq(test.t2.b, eee)), and(eq(test.t2.a, ccc), eq(test.t2.b, fff))))]", + "[or(or(and(eq(test.t2.a, aaa), eq(test.t2.b, ddd)), and(eq(test.t2.a, bbb), eq(test.t2.b, eee))), and(eq(test.t2.a, ccc), eq(test.t2.b, fff)))]", + "[[\"aa\" \"dd\",\"aa\" \"dd\"] [\"bb\" \"ee\",\"bb\" \"ee\"] [\"cc\" \"ff\",\"cc\" \"ff\"]]") + checkRangeFallbackAndReset(t, sctx, false) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[]", + "[or(or(and(eq(test.t2.a, aaa), eq(test.t2.b, ddd)), and(eq(test.t2.a, bbb), eq(test.t2.b, eee))), and(eq(test.t2.a, ccc), eq(test.t2.b, fff)))]", + "[[NULL,+inf]]") + checkRangeFallbackAndReset(t, sctx, true) + + // test considerDNF code path + sql = "select * from t2 where (a, b) in (('aaa', 'bbb'), ('ccc', 'ddd')) and c = 'eee'" + selection = getSelectionFromQuery(t, sctx, sql) + conds = selection.Conditions + require.Equal(t, 2, len(conds)) + cols, lengths = expression.IndexInfo2PrefixCols(tblInfo.Columns, selection.Schema().Columns, tblInfo.Indices[0]) + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[or(eq(test.t2.a, aaa), eq(test.t2.a, ccc))]", + "[eq(test.t2.c, eee) or(and(eq(test.t2.a, aaa), eq(test.t2.b, bbb)), and(eq(test.t2.a, ccc), eq(test.t2.b, ddd)))]", + "[[\"aa\",\"aa\"] [\"cc\",\"cc\"]]") + checkRangeFallbackAndReset(t, sctx, false) + quota = res.Ranges.MemUsage() - 1 + res, err = ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, quota) + require.NoError(t, err) + checkDetachRangeResult(t, res, + "[]", + "[eq(test.t2.c, eee) or(and(eq(test.t2.a, aaa), eq(test.t2.b, bbb)), and(eq(test.t2.a, ccc), eq(test.t2.b, ddd))) or(eq(test.t2.a, aaa), eq(test.t2.a, ccc))]", + "[[NULL,+inf]]") + checkRangeFallbackAndReset(t, sctx, true) +} + +func TestRangeFallbackForBuildTableRange(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (a int primary key, b int)") + tbl, err := dom.InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t")) + require.NoError(t, err) + tblInfo := tbl.Meta() + sctx := tk.Session().(sessionctx.Context) + sql := "select * from t where a in (10,20,30,40,50)" + selection := getSelectionFromQuery(t, sctx, sql) + conds := selection.Conditions + require.Equal(t, 1, len(conds)) + col := expression.ColInfo2Col(selection.Schema().Columns, tblInfo.Columns[0]) + var filters []expression.Expression + conds, filters = ranger.DetachCondsForColumn(sctx, conds, col) + require.Equal(t, 1, len(conds)) + require.Equal(t, 0, len(filters)) + ranges, access, remained, err := ranger.BuildTableRange(conds, sctx, col.RetType, 0) + require.NoError(t, err) + require.Equal(t, "[[10,10] [20,20] [30,30] [40,40] [50,50]]", fmt.Sprintf("%v", ranges)) + require.Equal(t, "[in(test.t.a, 10, 20, 30, 40, 50)]", fmt.Sprintf("%v", access)) + require.Equal(t, "[]", fmt.Sprintf("%v", remained)) + checkRangeFallbackAndReset(t, sctx, false) + quota := ranges.MemUsage() - 1 + ranges, access, remained, err = ranger.BuildTableRange(conds, sctx, col.RetType, quota) + require.NoError(t, err) + require.Equal(t, "[[-inf,+inf]]", fmt.Sprintf("%v", ranges)) + require.Equal(t, "[]", fmt.Sprintf("%v", access)) + require.Equal(t, "[in(test.t.a, 10, 20, 30, 40, 50)]", fmt.Sprintf("%v", remained)) + checkRangeFallbackAndReset(t, sctx, true) +} + +func TestRangeFallbackForBuildColumnRange(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (a varchar(20), b int not null)") + tbl, err := dom.InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t")) + require.NoError(t, err) + tblInfo := tbl.Meta() + sctx := tk.Session().(sessionctx.Context) + sql := "select * from t where a in ('aaa','bbb','ccc','ddd','eee')" + selection := getSelectionFromQuery(t, sctx, sql) + conds := selection.Conditions + require.Equal(t, 1, len(conds)) + cola := expression.ColInfo2Col(selection.Schema().Columns, tblInfo.Columns[0]) + var filters []expression.Expression + conds, filters = ranger.DetachCondsForColumn(sctx, conds, cola) + require.Equal(t, 1, len(conds)) + require.Equal(t, 0, len(filters)) + ranges, access, remained, err := ranger.BuildColumnRange(conds, sctx, cola.RetType, types.UnspecifiedLength, 0) + require.NoError(t, err) + require.Equal(t, "[[\"aaa\",\"aaa\"] [\"bbb\",\"bbb\"] [\"ccc\",\"ccc\"] [\"ddd\",\"ddd\"] [\"eee\",\"eee\"]]", fmt.Sprintf("%v", ranges)) + require.Equal(t, "[in(test.t.a, aaa, bbb, ccc, ddd, eee)]", fmt.Sprintf("%v", access)) + require.Equal(t, "[]", fmt.Sprintf("%v", remained)) + checkRangeFallbackAndReset(t, sctx, false) + quota := ranges.MemUsage() - 1 + ranges, access, remained, err = ranger.BuildColumnRange(conds, sctx, cola.RetType, types.UnspecifiedLength, quota) + require.NoError(t, err) + require.Equal(t, "[[NULL,+inf]]", fmt.Sprintf("%v", ranges)) + require.Equal(t, "[]", fmt.Sprintf("%v", access)) + require.Equal(t, "[in(test.t.a, aaa, bbb, ccc, ddd, eee)]", fmt.Sprintf("%v", remained)) + checkRangeFallbackAndReset(t, sctx, true) + sql = "select * from t where b in (10,20,30)" + selection = getSelectionFromQuery(t, sctx, sql) + conds = selection.Conditions + require.Equal(t, 1, len(conds)) + colb := expression.ColInfo2Col(selection.Schema().Columns, tblInfo.Columns[1]) + conds, filters = ranger.DetachCondsForColumn(sctx, conds, colb) + require.Equal(t, 1, len(conds)) + require.Equal(t, 0, len(filters)) + ranges, access, remained, err = ranger.BuildColumnRange(conds, sctx, colb.RetType, types.UnspecifiedLength, 1) + require.NoError(t, err) + require.Equal(t, "[[-inf,+inf]]", fmt.Sprintf("%v", ranges)) + require.Equal(t, "[]", fmt.Sprintf("%v", access)) + require.Equal(t, "[in(test.t.b, 10, 20, 30)]", fmt.Sprintf("%v", remained)) +} + +func TestPrefixIndexRange(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec(` +create table t( + a varchar(50), + b varchar(50), + c text(50), + d varbinary(50), + index idx_a(a(2)), + index idx_ab(a(2), b(2)), + index idx_c(c(2)), + index idx_d(d(2)) +)`) + tk.MustExec("set tidb_opt_prefix_index_single_scan = 1") + + tests := []struct { + indexPos int + exprStr string + accessConds string + filterConds string + resultStr string + }{ + { + indexPos: 0, + exprStr: "a is null", + accessConds: "[isnull(test.t.a)]", + filterConds: "[]", + resultStr: "[[NULL,NULL]]", + }, + { + indexPos: 0, + exprStr: "a is not null", + accessConds: "[not(isnull(test.t.a))]", + filterConds: "[]", + resultStr: "[[-inf,+inf]]", + }, + { + indexPos: 1, + exprStr: "a = 'a' and b is null", + accessConds: "[eq(test.t.a, a) isnull(test.t.b)]", + filterConds: "[eq(test.t.a, a)]", + resultStr: "[[\"a\" NULL,\"a\" NULL]]", + }, + { + indexPos: 1, + exprStr: "a = 'a' and b is not null", + accessConds: "[eq(test.t.a, a) not(isnull(test.t.b))]", + filterConds: "[eq(test.t.a, a)]", + resultStr: "[[\"a\" -inf,\"a\" +inf]]", + }, + { + indexPos: 2, + exprStr: "c is null", + accessConds: "[isnull(test.t.c)]", + filterConds: "[]", + resultStr: "[[NULL,NULL]]", + }, + { + indexPos: 2, + exprStr: "c is not null", + accessConds: "[not(isnull(test.t.c))]", + filterConds: "[]", + resultStr: "[[-inf,+inf]]", + }, + { + indexPos: 3, + exprStr: "d is null", + accessConds: "[isnull(test.t.d)]", + filterConds: "[]", + resultStr: "[[NULL,NULL]]", + }, + { + indexPos: 3, + exprStr: "d is not null", + accessConds: "[not(isnull(test.t.d))]", + filterConds: "[]", + resultStr: "[[-inf,+inf]]", + }, + } + + collate.SetNewCollationEnabledForTest(true) + defer func() { collate.SetNewCollationEnabledForTest(false) }() + ctx := context.Background() + for _, tt := range tests { + sql := "select * from t where " + tt.exprStr + sctx := tk.Session() + stmts, err := session.Parse(sctx, sql) + require.NoError(t, err, fmt.Sprintf("error %v, for expr %s", err, tt.exprStr)) + require.Len(t, stmts, 1) + ret := &plannercore.PreprocessorReturn{} + err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret)) + require.NoError(t, err, fmt.Sprintf("error %v, for resolve name, expr %s", err, tt.exprStr)) + p, _, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema) + require.NoError(t, err, fmt.Sprintf("error %v, for build plan, expr %s", err, tt.exprStr)) + selection := p.(plannercore.LogicalPlan).Children()[0].(*plannercore.LogicalSelection) + tbl := selection.Children()[0].(*plannercore.DataSource).TableInfo() + require.NotNil(t, selection, fmt.Sprintf("expr:%v", tt.exprStr)) + conds := make([]expression.Expression, len(selection.Conditions)) + for i, cond := range selection.Conditions { + conds[i] = expression.PushDownNot(sctx, cond) + } + cols, lengths := expression.IndexInfo2PrefixCols(tbl.Columns, selection.Schema().Columns, tbl.Indices[tt.indexPos]) + require.NotNil(t, cols) + res, err := ranger.DetachCondAndBuildRangeForIndex(sctx, conds, cols, lengths, 0) + require.NoError(t, err) + require.Equal(t, tt.accessConds, fmt.Sprintf("%s", res.AccessConds), fmt.Sprintf("wrong access conditions for expr: %s", tt.exprStr)) + require.Equal(t, tt.filterConds, fmt.Sprintf("%s", res.RemainedConds), fmt.Sprintf("wrong filter conditions for expr: %s", tt.exprStr)) + got := fmt.Sprintf("%v", res.Ranges) + require.Equal(t, tt.resultStr, got, fmt.Sprintf("different for expr %s", tt.exprStr)) + } +} + +func TestIssue44389(t *testing.T) { + store := testkit.CreateMockStore(t) + + testKit := testkit.NewTestKit(t, store) + testKit.MustExec("use test") + testKit.MustExec("drop table if exists t") + testKit.MustExec("create table t(a varchar(100), b int, c int, index idx_ab(a, b))") + testKit.MustExec("insert into t values ('kk', 1, 10), ('kk', 1, 20), ('hh', 2, 10), ('hh', 3, 10), ('xx', 4, 10), ('yy', 5, 10), ('yy', 6, 20), ('zz', 7, 10)") + testKit.MustExec("set @@tidb_opt_fix_control = '44389:ON'") + + var input []string + var output []struct { + SQL string + Plan []string + Result []string + } + rangerSuiteData.LoadTestCases(t, &input, &output) + for i, tt := range input { + testdata.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = testdata.ConvertRowsToStrings(testKit.MustQuery("explain " + tt).Rows()) + output[i].Result = testdata.ConvertRowsToStrings(testKit.MustQuery(tt).Sort().Rows()) + }) + testKit.MustQuery("explain " + tt).Check(testkit.Rows(output[i].Plan...)) + testKit.MustQuery(tt).Sort().Check(testkit.Rows(output[i].Result...)) + } +} +>>>>>>> 85d6323e3a3 (util/ranger: consider good non-point ranges from CNF item (#44384)) diff --git a/util/ranger/testdata/ranger_suite_in.json b/util/ranger/testdata/ranger_suite_in.json index c1641c0f251f7..beb004ff1a521 100644 --- a/util/ranger/testdata/ranger_suite_in.json +++ b/util/ranger/testdata/ranger_suite_in.json @@ -109,5 +109,12 @@ "select * from IDT_20755 use index (u_m_col) where col1 = \"xxxxxxxxxxxxxxx\" and col2 in (72, 73) and col3 != \"2024-10-19 08:55:32\"", "select * from IDT_20755 use index (u_m_col) where col1 = \"xxxxxxxxxxxxxxx\" and col2 in (72, 73, 74) and col3 != \"2024-10-19 08:55:32\"" ] + }, + { + "name": "TestIssue44389", + "cases": [ + "select * from t where c = 10 and (a = 'xx' or (a = 'kk' and b = 1))", + "select * from t where c = 10 and ((a = 'xx' or a = 'yy') or ((a = 'kk' and b = 1) or (a = 'hh' and b = 2)))" + ] } ] diff --git a/util/ranger/testdata/ranger_suite_out.json b/util/ranger/testdata/ranger_suite_out.json index f9d4ede451753..887d93ab5085a 100644 --- a/util/ranger/testdata/ranger_suite_out.json +++ b/util/ranger/testdata/ranger_suite_out.json @@ -691,5 +691,38 @@ ] } ] + }, + { + "Name": "TestIssue44389", + "Cases": [ + { + "SQL": "select * from t where c = 10 and (a = 'xx' or (a = 'kk' and b = 1))", + "Plan": [ + "IndexLookUp_11 0.01 root ", + "├─IndexRangeScan_8(Build) 10.10 cop[tikv] table:t, index:idx_ab(a, b) range:[\"kk\" 1,\"kk\" 1], [\"xx\",\"xx\"], keep order:false, stats:pseudo", + "└─Selection_10(Probe) 0.01 cop[tikv] eq(test.t.c, 10)", + " └─TableRowIDScan_9 10.10 cop[tikv] table:t keep order:false, stats:pseudo" + ], + "Result": [ + "kk 1 10", + "xx 4 10" + ] + }, + { + "SQL": "select * from t where c = 10 and ((a = 'xx' or a = 'yy') or ((a = 'kk' and b = 1) or (a = 'hh' and b = 2)))", + "Plan": [ + "IndexLookUp_11 0.02 root ", + "├─IndexRangeScan_8(Build) 20.20 cop[tikv] table:t, index:idx_ab(a, b) range:[\"hh\" 2,\"hh\" 2], [\"kk\" 1,\"kk\" 1], [\"xx\",\"xx\"], [\"yy\",\"yy\"], keep order:false, stats:pseudo", + "└─Selection_10(Probe) 0.02 cop[tikv] eq(test.t.c, 10)", + " └─TableRowIDScan_9 20.20 cop[tikv] table:t keep order:false, stats:pseudo" + ], + "Result": [ + "hh 2 10", + "kk 1 10", + "xx 4 10", + "yy 5 10" + ] + } + ] } ]