diff --git a/executor/partition_table_test.go b/executor/partition_table_test.go index cccb827b0b030..3b8b50174ebc5 100644 --- a/executor/partition_table_test.go +++ b/executor/partition_table_test.go @@ -3468,8 +3468,8 @@ func TestPartitionTableExplain(t *testing.T) { "PartitionUnion 2.00 root ", "├─Batch_Point_Get 1.00 root table:t handle:[1 2], keep order:false, desc:false", "└─Batch_Point_Get 1.00 root table:t handle:[1 2], keep order:false, desc:false")) - tk.MustQuery(`explain format = 'brief' select * from t where a IN (2,3,4)`).Check(testkit.Rows("Batch_Point_Get 3.00 root table:t handle:[2 3 4], keep order:false, desc:false")) - tk.MustQuery(`explain format = 'brief' select * from t where a IN (2,3)`).Check(testkit.Rows("Batch_Point_Get 2.00 root table:t handle:[2 3], keep order:false, desc:false")) + tk.MustQuery(`explain format = 'brief' select * from t where a IN (2,3,4)`).Check(testkit.Rows("Batch_Point_Get 3.00 root table:t, partition:P0,p1,P2 handle:[2 3 4], keep order:false, desc:false")) + tk.MustQuery(`explain format = 'brief' select * from t where a IN (2,3)`).Check(testkit.Rows("Batch_Point_Get 2.00 root table:t, partition:P0,P2 handle:[2 3], keep order:false, desc:false")) // above ^^ is for completeness, the below vv is enough for Issue32719 tk.MustQuery(`explain format = 'brief' select * from t where b = 1`).Check(testkit.Rows( "PartitionUnion 1.00 root ", @@ -3553,8 +3553,8 @@ func TestPartitionTableExplain(t *testing.T) { tk.MustQuery(`explain format = 'brief' select * from t where a = 1 OR a = 2`).Check(testkit.Rows( "TableReader 2.00 root partition:p1,P2 data:TableRangeScan", "└─TableRangeScan 2.00 cop[tikv] table:t range:[1,1], [2,2], keep order:false")) - tk.MustQuery(`explain format = 'brief' select * from t where a IN (2,3,4)`).Check(testkit.Rows("Batch_Point_Get 3.00 root table:t handle:[2 3 4], keep order:false, desc:false")) - tk.MustQuery(`explain format = 'brief' select * from t where a IN (2,3)`).Check(testkit.Rows("Batch_Point_Get 2.00 root table:t handle:[2 3], keep order:false, desc:false")) + tk.MustQuery(`explain format = 'brief' select * from t where a IN (2,3,4)`).Check(testkit.Rows("Batch_Point_Get 3.00 root table:t, partition:P0,p1,P2 handle:[2 3 4], keep order:false, desc:false")) + tk.MustQuery(`explain format = 'brief' select * from t where a IN (2,3)`).Check(testkit.Rows("Batch_Point_Get 2.00 root table:t, partition:P0,P2 handle:[2 3], keep order:false, desc:false")) tk.MustQuery(`explain format = 'brief' select * from t where b = 1`).Check(testkit.Rows( "IndexReader 1.00 root partition:all index:IndexRangeScan", "└─IndexRangeScan 1.00 cop[tikv] table:t, index:b(b) range:[1,1], keep order:false")) diff --git a/planner/core/partition_pruner_test.go b/planner/core/partition_pruner_test.go index 2e7d704c8663e..387396f25235c 100644 --- a/planner/core/partition_pruner_test.go +++ b/planner/core/partition_pruner_test.go @@ -704,6 +704,22 @@ func TestHashPartitionPruning(t *testing.T) { tk.MustQuery("SELECT col1, COL3 FROM t WHERE COL1 IN (0,14158354938390,0) AND COL3 IN (3522101843073676459,-2846203247576845955,838395691793635638);").Check(testkit.Rows("0 3522101843073676459")) } +func TestIssue32815(t *testing.T) { + store, clean := testkit.CreateMockStore(t) + defer clean() + + tk := testkit.NewTestKit(t, store) + tk.MustExec("set @@tidb_partition_prune_mode='dynamic'") + tk.MustExec("USE test;") + tk.MustExec("DROP TABLE IF EXISTS t;") + tk.MustExec("create table t (a int primary key, b int, key (b)) partition by hash(a) (partition P0, partition p1, partition P2)") + tk.MustExec("insert into t values (1, 1),(2, 2),(3, 3)") + tk.MustQuery("explain select * from t where a IN (1, 2)").Check(testkit.Rows( + "Batch_Point_Get_1 2.00 root table:t, partition:p1,P2 handle:[1 2], keep order:false, desc:false")) + tk.MustQuery("explain select * from t where a IN (1, 2, 1)").Check(testkit.Rows( + "Batch_Point_Get_1 3.00 root table:t, partition:p1,P2 handle:[1 2 1], keep order:false, desc:false")) +} + func TestIssue32007(t *testing.T) { store, clean := testkit.CreateMockStore(t) defer clean() diff --git a/planner/core/point_get_plan.go b/planner/core/point_get_plan.go index afb34814f7196..01b6337ebc81f 100644 --- a/planner/core/point_get_plan.go +++ b/planner/core/point_get_plan.go @@ -270,6 +270,7 @@ type BatchPointGetPlan struct { dbName string TblInfo *model.TableInfo IndexInfo *model.IndexInfo + PartitionInfos []*model.PartitionDefinition Handles []kv.Handle HandleType *types.FieldType HandleParams []*expression.Constant // record all Parameters for Plan-Cache @@ -345,11 +346,25 @@ func (p *BatchPointGetPlan) ExplainNormalizedInfo() string { } // AccessObject implements physicalScan interface. -func (p *BatchPointGetPlan) AccessObject(_ bool) string { +func (p *BatchPointGetPlan) AccessObject(normalized bool) string { var buffer strings.Builder tblName := p.TblInfo.Name.O buffer.WriteString("table:") buffer.WriteString(tblName) + if p.PartitionInfos != nil { + if normalized { + buffer.WriteString(", partition:?") + } else { + for i, partitionInfo := range p.PartitionInfos { + if i == 0 { + buffer.WriteString(", partition:") + } else { + buffer.WriteString(",") + } + buffer.WriteString(partitionInfo.Name.O) + } + } + } if p.IndexInfo != nil { if p.IndexInfo.Primary && p.TblInfo.IsCommonHandle { buffer.WriteString(", clustered index:" + p.IndexInfo.Name.O + "(") @@ -565,9 +580,13 @@ func newBatchPointGetPlan( return nil } } + if handleCol != nil { + // condition key of where is primary key var handles = make([]kv.Handle, len(patternInExpr.List)) var handleParams = make([]*expression.Constant, len(patternInExpr.List)) + var pos2PartitionDefinition = make(map[int]*model.PartitionDefinition) + partitionInfos := make([]*model.PartitionDefinition, 0, len(patternInExpr.List)) for i, item := range patternInExpr.List { // SELECT * FROM t WHERE (key) in ((1), (2)) if p, ok := item.(*ast.ParenthesesExpr); ok { @@ -600,13 +619,39 @@ func newBatchPointGetPlan( } handles[i] = kv.IntHandle(intDatum.GetInt64()) handleParams[i] = con + pairs := []nameValuePair{{colName: handleCol.Name.L, colFieldType: item.GetType(), value: *intDatum, con: con}} + if tbl.GetPartitionInfo() != nil { + tmpPartitionDefinition, _, pos, isTableDual := getPartitionInfo(ctx, tbl, pairs) + if isTableDual { + return nil + } + if tmpPartitionDefinition != nil { + pos2PartitionDefinition[pos] = tmpPartitionDefinition + } + } + } + + posArr := make([]int, len(pos2PartitionDefinition)) + i := 0 + for pos := range pos2PartitionDefinition { + posArr[i] = pos + i++ + } + sort.Ints(posArr) + for _, pos := range posArr { + partitionInfos = append(partitionInfos, pos2PartitionDefinition[pos]) } + if len(partitionInfos) == 0 { + partitionInfos = nil + } + return BatchPointGetPlan{ - TblInfo: tbl, - Handles: handles, - HandleParams: handleParams, - HandleType: &handleCol.FieldType, - PartitionExpr: partitionExpr, + TblInfo: tbl, + Handles: handles, + HandleParams: handleParams, + HandleType: &handleCol.FieldType, + PartitionExpr: partitionExpr, + PartitionInfos: partitionInfos, }.Init(ctx, statsInfo, schema, names, 0) } @@ -661,14 +706,18 @@ func newBatchPointGetPlan( indexValues := make([][]types.Datum, len(patternInExpr.List)) indexValueParams := make([][]*expression.Constant, len(patternInExpr.List)) + partitionInfos := make([]*model.PartitionDefinition, 0, len(patternInExpr.List)) + var pos2PartitionDefinition = make(map[int]*model.PartitionDefinition) + var indexTypes []*types.FieldType for i, item := range patternInExpr.List { - // SELECT * FROM t WHERE (key) in ((1), (2)) + // SELECT * FROM t WHERE (key) in ((1), (2)) or SELECT * FROM t WHERE (key1, key2) in ((1, 1), (2, 2)) if p, ok := item.(*ast.ParenthesesExpr); ok { item = p.Expr } var values []types.Datum var valuesParams []*expression.Constant + var pairs []nameValuePair switch x := item.(type) { case *ast.RowExpr: // The `len(values) == len(valuesParams)` should be satisfied in this mode @@ -676,6 +725,7 @@ func newBatchPointGetPlan( return nil } values = make([]types.Datum, len(x.Values)) + pairs = make([]nameValuePair, 0, len(x.Values)) valuesParams = make([]*expression.Constant, len(x.Values)) initTypes := false if indexTypes == nil { // only init once @@ -683,6 +733,7 @@ func newBatchPointGetPlan( initTypes = true } for index, inner := range x.Values { + // permutations is used to match column and value. permIndex := permutations[index] switch innerX := inner.(type) { case *driver.ValueExpr: @@ -691,6 +742,7 @@ func newBatchPointGetPlan( return nil } values[permIndex] = innerX.Datum + pairs = append(pairs, nameValuePair{colName: whereColNames[index], value: innerX.Datum}) case *driver.ParamMarkerExpr: con, err := expression.ParamMarkerExpression(ctx, innerX, true) if err != nil { @@ -709,6 +761,7 @@ func newBatchPointGetPlan( if initTypes { indexTypes[permIndex] = &colInfos[index].FieldType } + pairs = append(pairs, nameValuePair{colName: whereColNames[index], value: innerX.Datum}) default: return nil } @@ -724,6 +777,8 @@ func newBatchPointGetPlan( return nil } values = []types.Datum{*dval} + valuesParams = []*expression.Constant{nil} + pairs = append(pairs, nameValuePair{colName: whereColNames[0], value: *dval}) case *driver.ParamMarkerExpr: if len(whereColNames) != 1 { return nil @@ -745,12 +800,39 @@ func newBatchPointGetPlan( if indexTypes == nil { // only init once indexTypes = []*types.FieldType{&colInfos[0].FieldType} } + pairs = append(pairs, nameValuePair{colName: whereColNames[0], value: *dval}) + default: return nil } indexValues[i] = values indexValueParams[i] = valuesParams + if tbl.GetPartitionInfo() != nil { + tmpPartitionDefinition, _, pos, isTableDual := getPartitionInfo(ctx, tbl, pairs) + if isTableDual { + return nil + } + if tmpPartitionDefinition != nil { + pos2PartitionDefinition[pos] = tmpPartitionDefinition + } + } + + } + + posArr := make([]int, len(pos2PartitionDefinition)) + i := 0 + for pos := range pos2PartitionDefinition { + posArr[i] = pos + i++ } + sort.Ints(posArr) + for _, pos := range posArr { + partitionInfos = append(partitionInfos, pos2PartitionDefinition[pos]) + } + if len(partitionInfos) == 0 { + partitionInfos = nil + } + return BatchPointGetPlan{ TblInfo: tbl, IndexInfo: matchIdxInfo, @@ -759,6 +841,7 @@ func newBatchPointGetPlan( IndexColTypes: indexTypes, PartitionColPos: pos, PartitionExpr: partitionExpr, + PartitionInfos: partitionInfos, }.Init(ctx, statsInfo, schema, names, 0) } @@ -768,6 +851,8 @@ func tryWhereIn2BatchPointGet(ctx sessionctx.Context, selStmt *ast.SelectStmt) * len(selStmt.WindowSpecs) > 0 { return nil } + // `expr1 in (1, 2) and expr2 in (1, 2)` isn't PatternInExpr, so it can't use tryWhereIn2BatchPointGet. + // (expr1, expr2) in ((1, 1), (2, 2)) can hit it. in, ok := selStmt.Where.(*ast.PatternInExpr) if !ok || in.Not || len(in.List) < 1 { return nil @@ -907,7 +992,7 @@ func tryPointGetPlan(ctx sessionctx.Context, selStmt *ast.SelectStmt, check bool var partitionInfo *model.PartitionDefinition var pos int if pi != nil { - partitionInfo, pos, isTableDual = getPartitionInfo(ctx, tbl, pairs) + partitionInfo, pos, _, isTableDual = getPartitionInfo(ctx, tbl, pairs) if isTableDual { p := newPointGetPlan(ctx, tblName.Schema.O, schema, tbl, names) p.IsTableDual = true @@ -1583,15 +1668,15 @@ func buildHandleCols(ctx sessionctx.Context, tbl *model.TableInfo, schema *expre return &IntHandleCols{col: handleCol} } -func getPartitionInfo(ctx sessionctx.Context, tbl *model.TableInfo, pairs []nameValuePair) (*model.PartitionDefinition, int, bool) { +func getPartitionInfo(ctx sessionctx.Context, tbl *model.TableInfo, pairs []nameValuePair) (*model.PartitionDefinition, int, int, bool) { partitionExpr := getPartitionExpr(ctx, tbl) if partitionExpr == nil { - return nil, 0, false + return nil, 0, 0, false } pi := tbl.GetPartitionInfo() if pi == nil { - return nil, 0, false + return nil, 0, 0, false } switch pi.Type { @@ -1599,19 +1684,19 @@ func getPartitionInfo(ctx sessionctx.Context, tbl *model.TableInfo, pairs []name expr := partitionExpr.OrigExpr col, ok := expr.(*ast.ColumnNameExpr) if !ok { - return nil, 0, false + return nil, 0, 0, false } partitionColName := col.Name if partitionColName == nil { - return nil, 0, false + return nil, 0, 0, false } for i, pair := range pairs { if partitionColName.Name.L == pair.colName { val := pair.value.GetInt64() pos := mathutil.Abs(val % int64(pi.Num)) - return &pi.Definitions[pos], i, false + return &pi.Definitions[pos], i, int(pos), false } } case model.PartitionTypeRange: @@ -1629,9 +1714,9 @@ func getPartitionInfo(ctx sessionctx.Context, tbl *model.TableInfo, pairs []name return ranges.Compare(i, val, unsigned) > 0 }) if pos >= 0 && pos < length { - return &pi.Definitions[pos], i, false + return &pi.Definitions[pos], i, pos, false } - return nil, 0, true + return nil, 0, 0, true } } } @@ -1648,15 +1733,15 @@ func getPartitionInfo(ctx sessionctx.Context, tbl *model.TableInfo, pairs []name isNull := false pos := partitionExpr.ForListPruning.LocatePartition(val, isNull) if pos >= 0 { - return &pi.Definitions[pos], i, false + return &pi.Definitions[pos], i, pos, false } - return nil, 0, true + return nil, 0, 0, true } } } } } - return nil, 0, false + return nil, 0, 0, false } func findPartitionIdx(idxInfo *model.IndexInfo, pos int, pairs []nameValuePair) int { diff --git a/planner/core/point_get_plan_test.go b/planner/core/point_get_plan_test.go index d39e95b767ab8..508e21004476d 100644 --- a/planner/core/point_get_plan_test.go +++ b/planner/core/point_get_plan_test.go @@ -662,18 +662,18 @@ func TestBatchPointGetPartition(t *testing.T) { tk.MustExec("create table t(a int primary key, b int) PARTITION BY HASH(a) PARTITIONS 4") tk.MustExec("insert into t values (1, 1), (2, 2), (3, 3), (4, 4)") tk.MustQuery("explain format = 'brief' select * from t where a in (1, 2, 3, 4)").Check(testkit.Rows( - "Batch_Point_Get 4.00 root table:t handle:[1 2 3 4], keep order:false, desc:false", + "Batch_Point_Get 4.00 root table:t, partition:p0,p1,p2,p3 handle:[1 2 3 4], keep order:false, desc:false", )) tk.MustQuery("select * from t where a in (1, 2, 3, 4)").Check(testkit.Rows("1 1", "2 2", "3 3", "4 4")) tk.MustQuery("explain format = 'brief' update t set b = b + 1 where a in (1, 2, 3, 4)").Check(testkit.Rows( - "Update N/A root N/A]\n[└─Batch_Point_Get 4.00 root table:t handle:[1 2 3 4], keep order:false, desc:false", + "Update N/A root N/A]\n[└─Batch_Point_Get 4.00 root table:t, partition:p0,p1,p2,p3 handle:[1 2 3 4], keep order:false, desc:false", )) tk.MustExec("update t set b = b + 1 where a in (1, 2, 3, 4)") tk.MustQuery("select * from t where a in (1, 2, 3, 4)").Check(testkit.Rows("1 2", "2 3", "3 4", "4 5")) tk.MustQuery("explain format = 'brief' delete from t where a in (1, 2, 3, 4)").Check(testkit.Rows( - "Delete N/A root N/A]\n[└─Batch_Point_Get 4.00 root table:t handle:[1 2 3 4], keep order:false, desc:false", + "Delete N/A root N/A]\n[└─Batch_Point_Get 4.00 root table:t, partition:p0,p1,p2,p3 handle:[1 2 3 4], keep order:false, desc:false", )) tk.MustExec("delete from t where a in (1, 2, 3, 4)") tk.MustQuery("select * from t where a in (1, 2, 3, 4)").Check(testkit.Rows()) @@ -681,26 +681,88 @@ func TestBatchPointGetPartition(t *testing.T) { tk.MustExec("drop table t") tk.MustExec("create table t(a int, b int, c int, primary key (a, b)) PARTITION BY HASH(a) PARTITIONS 4") tk.MustExec("insert into t values (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4)") + tk.MustQuery("explain format = 'brief' select * from t where a = 1 and b = 1").Check(testkit.Rows("Point_Get 1.00 root table:t, partition:p1, clustered index:PRIMARY(a, b) ")) + tk.MustQuery("explain format = 'brief' select * from t where (a, b) in ((1, 1), (2, 2), (3, 3), (4, 4))").Check(testkit.Rows( - "Batch_Point_Get 4.00 root table:t, clustered index:PRIMARY(a, b) keep order:false, desc:false", + "Batch_Point_Get 4.00 root table:t, partition:p0,p1,p2,p3, clustered index:PRIMARY(a, b) keep order:false, desc:false", )) tk.MustQuery("select * from t where (a, b) in ((1, 1), (2, 2), (3, 3), (4, 4))"). Check(testkit.Rows("1 1 1", "2 2 2", "3 3 3", "4 4 4")) tk.MustQuery("explain format = 'brief' update t set c = c + 1 where (a,b) in ((1,1),(2,2),(3,3),(4,4))").Check(testkit.Rows( - "Update N/A root N/A]\n[└─Batch_Point_Get 4.00 root table:t, clustered index:PRIMARY(a, b) keep order:false, desc:false", + "Update N/A root N/A]\n[└─Batch_Point_Get 4.00 root table:t, partition:p0,p1,p2,p3, clustered index:PRIMARY(a, b) keep order:false, desc:false", )) tk.MustExec("update t set c = c + 1 where (a,b) in ((1,1),(2,2),(3,3),(4,4))") tk.MustQuery("select * from t where (a, b) in ((1, 1), (2, 2), (3, 3), (4, 4))").Sort(). Check(testkit.Rows("1 1 2", "2 2 3", "3 3 4", "4 4 5")) tk.MustQuery("explain format = 'brief' delete from t where (a,b) in ((1,1),(2,2),(3,3),(4,4))").Check(testkit.Rows( - "Delete N/A root N/A]\n[└─Batch_Point_Get 4.00 root table:t, clustered index:PRIMARY(a, b) keep order:false, desc:false", + "Delete N/A root N/A]\n[└─Batch_Point_Get 4.00 root table:t, partition:p0,p1,p2,p3, clustered index:PRIMARY(a, b) keep order:false, desc:false", )) tk.MustExec("delete from t where (a,b) in ((1,1),(2,2),(3,3),(4,4))") tk.MustQuery("select * from t where (a, b) in ((1, 1), (2, 2), (3, 3), (4, 4))").Check(testkit.Rows()) } +func TestBatchPointGetPartitionForAccessObject(t *testing.T) { + store, clean := testkit.CreateMockStore(t) + defer clean() + + tk := testkit.NewTestKit(t, store) + tk.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") + tk.MustExec("use test") + tk.Session().GetSessionVars().EnableClusteredIndex = variable.ClusteredIndexDefModeOn + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int, b int, UNIQUE KEY (b)) PARTITION BY HASH(b) PARTITIONS 4") + tk.MustExec("insert into t values(1, 1), (2, 2), (3, 3), (4, 4)") + tk.MustQuery("explain select * from t where b in (1, 2)").Check(testkit.Rows( + "Batch_Point_Get_1 2.00 root table:t, partition:p1,p2, index:b(b) keep order:false, desc:false")) + tk.MustQuery("explain select * from t where b in (1, 2, 1)").Check(testkit.Rows( + "Batch_Point_Get_1 3.00 root table:t, partition:p1,p2, index:b(b) keep order:false, desc:false")) + + tk.MustExec("set @@session.tidb_enable_list_partition = ON") + tk.MustExec("drop table if exists t") + tk.MustExec("CREATE TABLE t (id int primary key, name_id int) PARTITION BY LIST(id) (" + + "partition p0 values IN (1, 2), " + + "partition p1 values IN (3, 4), " + + "partition p3 values IN (5))") + tk.MustExec("insert into t values(1, 1), (2, 2), (3, 3), (4, 4)") + tk.MustQuery("explain format='brief' select * from t where id in (1, 3)").Check(testkit.Rows( + "Batch_Point_Get 2.00 root table:t, partition:p0,p1 handle:[1 3], keep order:false, desc:false")) + + tk.MustExec("set @@session.tidb_enable_list_partition = ON") + tk.MustExec("drop table if exists t0") + tk.MustExec("CREATE TABLE t0 (id int primary key, name_id int) PARTITION BY LIST COLUMNS(id) (" + + "partition p0 values IN (1, 2), " + + "partition p1 values IN (3, 4), " + + "partition p3 values IN (5))") + tk.MustExec("insert into t0 values(1, 1), (2, 2), (3, 3), (4, 4)") + tk.MustQuery("explain format='brief' select * from t0 where id in (1, 3)").Check(testkit.Rows( + "TableReader 2.00 root partition:p0,p1 data:TableRangeScan]\n" + + "[└─TableRangeScan 2.00 cop[tikv] table:t0 range:[1,1], [3,3], keep order:false, stats:pseudo")) + + tk.MustExec("set @@session.tidb_enable_list_partition = ON") + tk.MustExec("drop table if exists t1") + tk.MustExec("CREATE TABLE t1 (id int, name_id int, unique key(id, name_id)) PARTITION BY LIST COLUMNS(id, name_id) (" + + "partition p0 values IN ((1, 1),(2, 2)), " + + "partition p1 values IN ((3, 3),(4, 4)), " + + "partition p3 values IN ((5, 5)))") + tk.MustExec("insert into t1 values(1, 1), (2, 2), (3, 3), (4, 4)") + tk.MustQuery("explain format='brief' select * from t1 where (id, name_id) in ((1, 1), (3, 3))").Check(testkit.Rows( + "IndexReader 2.00 root partition:p0,p1 index:IndexRangeScan]\n" + + "[└─IndexRangeScan 2.00 cop[tikv] table:t1, index:id(id, name_id) range:[1 1,1 1], [3 3,3 3], keep order:false, stats:pseudo")) + + tk.MustExec("set @@session.tidb_enable_list_partition = ON") + tk.MustExec("drop table if exists t2") + tk.MustExec("CREATE TABLE t2 (id int, name varchar(10), unique key(id, name)) PARTITION BY LIST COLUMNS(id, name) (" + + "partition p0 values IN ((1,'a'),(2,'b')), " + + "partition p1 values IN ((3,'c'),(4,'d')), " + + "partition p3 values IN ((5,'e')))") + tk.MustExec("insert into t2 values(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')") + tk.MustQuery("explain format='brief' select * from t2 where (id, name) in ((1, 'a'), (3, 'c'))").Check(testkit.Rows( + "IndexReader 2.00 root partition:p0,p1 index:IndexRangeScan]\n" + + "[└─IndexRangeScan 2.00 cop[tikv] table:t2, index:id(id, name) range:[1 \"a\",1 \"a\"], [3 \"c\",3 \"c\"], keep order:false, stats:pseudo")) +} + func TestIssue19141(t *testing.T) { // For issue 19141, fix partition selection on batch point get. store, clean := testkit.CreateMockStore(t)