diff --git a/executor/builder.go b/executor/builder.go index 64b53ff4161f1..4383f8ed74f4d 100644 --- a/executor/builder.go +++ b/executor/builder.go @@ -500,10 +500,11 @@ func (b *executorBuilder) buildAggregation(v *plan.PhysicalAggregation) Executor func (b *executorBuilder) buildSelection(v *plan.Selection) Executor { exec := &SelectionExec{ - Src: b.build(v.Children()[0]), - Condition: expression.ComposeCNFCondition(b.ctx, v.Conditions...), - schema: v.Schema(), - ctx: b.ctx, + Src: b.build(v.Children()[0]), + schema: v.Schema(), + ctx: b.ctx, + scanController: v.ScanController, + Conditions: v.Conditions, } return exec } diff --git a/executor/executor.go b/executor/executor.go index 6edc7d4bb2a6e..8c9f493aadfe2 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -473,6 +473,12 @@ type SelectionExec struct { Condition expression.Expression ctx context.Context schema *expression.Schema + + // scanController will tell whether this selection need to + // control the condition of below scan executor. + scanController bool + controllerInit bool + Conditions []expression.Expression } // Schema implements the Executor Schema interface. @@ -480,8 +486,54 @@ func (e *SelectionExec) Schema() *expression.Schema { return e.schema } +// initController will init the conditions of the below scan executor. +// It will first substitute the correlated column to constant, then build range and filter by new conditions. +func (e *SelectionExec) initController() error { + sc := e.ctx.GetSessionVars().StmtCtx + client := e.ctx.GetClient() + newConds := make([]expression.Expression, 0, len(e.Conditions)) + for _, cond := range e.Conditions { + newCond, err := expression.SubstituteCorCol2Constant(cond.Clone()) + if err != nil { + return errors.Trace(err) + } + newConds = append(newConds, newCond) + } + + switch x := e.Src.(type) { + case *XSelectTableExec: + accessCondition, restCondtion := plan.DetachTableScanConditions(newConds, x.tableInfo) + x.where, _, _ = plan.ExpressionsToPB(sc, restCondtion, client) + ranges, err := plan.BuildTableRange(accessCondition, sc) + if err != nil { + return errors.Trace(err) + } + x.ranges = ranges + case *XSelectIndexExec: + x.indexPlan.AccessCondition, newConds = plan.DetachIndexScanConditions(newConds, x.indexPlan) + idxConds, tblConds := plan.DetachIndexFilterConditions(newConds, x.indexPlan.Index.Columns, x.indexPlan.Table) + x.indexPlan.IndexConditionPBExpr, _, _ = plan.ExpressionsToPB(sc, idxConds, client) + x.indexPlan.TableConditionPBExpr, _, _ = plan.ExpressionsToPB(sc, tblConds, client) + err := plan.BuildIndexRange(sc, x.indexPlan) + if err != nil { + return errors.Trace(err) + } + x.where = x.indexPlan.TableConditionPBExpr + default: + return errors.Errorf("Error type of Executor: %T", x) + } + return nil +} + // Next implements the Executor Next interface. func (e *SelectionExec) Next() (*Row, error) { + if e.scanController && !e.controllerInit { + err := e.initController() + if err != nil { + return nil, errors.Trace(err) + } + e.controllerInit = true + } for { srcRow, err := e.Src.Next() if err != nil { @@ -490,11 +542,18 @@ func (e *SelectionExec) Next() (*Row, error) { if srcRow == nil { return nil, nil } - match, err := expression.EvalBool(e.Condition, srcRow.Data, e.ctx) - if err != nil { - return nil, errors.Trace(err) + allMatch := true + for _, cond := range e.Conditions { + match, err := expression.EvalBool(cond, srcRow.Data, e.ctx) + if err != nil { + return nil, errors.Trace(err) + } + if !match { + allMatch = false + break + } } - if match { + if allMatch { return srcRow, nil } } @@ -502,6 +561,9 @@ func (e *SelectionExec) Next() (*Row, error) { // Close implements the Executor Close interface. func (e *SelectionExec) Close() error { + if e.scanController { + e.controllerInit = false + } return e.Src.Close() } diff --git a/executor/executor_test.go b/executor/executor_test.go index 5f13325e57ba4..fbb19af47dce4 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -1278,3 +1278,16 @@ func (s *testSuite) TestHistoryRead(c *C) { tk.MustExec("set @@tidb_snapshot = ''") tk.MustQuery("select * from history_read order by a").Check(testkit.Rows("2 ", "4 ", "8 8", "9 9")) } + +func (s *testSuite) TestScanControlSelection(c *C) { + defer func() { + s.cleanEnv(c) + testleak.AfterTest(c)() + }() + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int primary key, b int, c int, index idx_b(b))") + tk.MustExec("insert into t values (1, 1, 1), (2, 1, 1), (3, 1, 2), (4, 2, 3)") + tk.MustQuery("select (select count(1) k from t s where s.b = t1.c) from t t1").Check(testkit.Rows("3", "3", "1", "0")) +} diff --git a/executor/explain_test.go b/executor/explain_test.go index 552b3cbfe5488..a4c00387981d0 100644 --- a/executor/explain_test.go +++ b/executor/explain_test.go @@ -529,6 +529,80 @@ func (s *testSuite) TestExplain(c *C) { "index filter conditions": null, "table filter conditions": null } +}`, + }, + }, + { + "select (select count(1) k from t1 s where s.c1 = t1.c1 having k != 0) from t1", + []string{ + "TableScan_12", "TableScan_13", "Selection_4", "StreamAgg_15", "Selection_10", "Apply_16", "Projection_2", + }, + []string{ + "Apply_16", "Selection_4", "StreamAgg_15", "Selection_10", "Apply_16", "Projection_2", "", + }, + []string{ + `{ + "db": "test", + "table": "t1", + "desc": false, + "keep order": false, + "push down info": { + "limit": 0, + "access conditions": null, + "index filter conditions": null, + "table filter conditions": null + } +}`, + `{ + "db": "test", + "table": "t1", + "desc": false, + "keep order": false, + "push down info": { + "limit": 0, + "access conditions": null, + "index filter conditions": null, + "table filter conditions": null + } +}`, + `{ + "condition": [ + "eq(s.c1, test.t1.c1)" + ], + "scanController": true, + "child": "TableScan_13" +}`, + `{ + "AggFuncs": [ + "count(1)" + ], + "GroupByItems": null, + "child": "Selection_4" +}`, + `{ + "condition": [ + "ne(aggregation_5_col_0, 0)" + ], + "scanController": false, + "child": "StreamAgg_15" +}`, + `{ + "innerPlan": "Selection_10", + "outerPlan": "TableScan_12", + "join": { + "eqCond": null, + "leftCond": null, + "rightCond": null, + "otherCond": null, + "leftPlan": "TableScan_12", + "rightPlan": "Selection_10" + } +}`, + `{ + "exprs": [ + "k" + ], + "child": "Apply_16" }`, }, }, diff --git a/expression/util.go b/expression/util.go index 8d23ecaad538b..e3bb275947bd3 100644 --- a/expression/util.go +++ b/expression/util.go @@ -175,3 +175,40 @@ func (d *distinctChecker) Check(values []interface{}) (bool, error) { d.existingKeys[key] = true return true, nil } + +// SubstituteCorCol2Constant will substitute correlated column to constant value which it contains. +// If the args of one scalar function are all constant, we will substitute it to constant. +func SubstituteCorCol2Constant(expr Expression) (Expression, error) { + switch x := expr.(type) { + case *ScalarFunction: + allConstant := true + newArgs := make([]Expression, 0, len(x.GetArgs())) + for _, arg := range x.GetArgs() { + newArg, err := SubstituteCorCol2Constant(arg) + if err != nil { + return nil, errors.Trace(err) + } + _, ok := newArg.(*Constant) + newArgs = append(newArgs, newArg) + allConstant = allConstant && ok + } + if allConstant { + val, err := x.Eval(nil) + if err != nil { + return nil, errors.Trace(err) + } + return &Constant{Value: val}, nil + } + var newSf Expression + if x.FuncName.L == ast.Cast { + newSf = NewCastFunc(x.RetType, newArgs[0], x.GetCtx()) + } else { + newSf, _ = NewFunction(x.GetCtx(), x.FuncName.L, x.GetType(), newArgs...) + } + return newSf, nil + case *CorrelatedColumn: + return &Constant{Value: *x.Data, RetType: x.GetType()}, nil + default: + return x.Clone(), nil + } +} diff --git a/expression/util_test.go b/expression/util_test.go index e2beda5e51524..35cb93e9ed0d2 100644 --- a/expression/util_test.go +++ b/expression/util_test.go @@ -15,7 +15,11 @@ package expression import ( "github.com/pingcap/check" + "github.com/pingcap/tidb/ast" + "github.com/pingcap/tidb/mysql" + "github.com/pingcap/tidb/util/mock" "github.com/pingcap/tidb/util/testleak" + "github.com/pingcap/tidb/util/types" ) var _ = check.Suite(&testUtilSuite{}) @@ -43,3 +47,20 @@ func (s *testUtilSuite) TestDistinct(c *check.C) { c.Assert(d, check.Equals, t.expect) } } + +func (s *testUtilSuite) TestSubstituteCorCol2Constant(c *check.C) { + defer testleak.AfterTest(c)() + ctx := mock.NewContext() + corCol1 := &CorrelatedColumn{Data: &One.Value} + corCol2 := &CorrelatedColumn{Data: &One.Value} + cast := NewCastFunc(types.NewFieldType(mysql.TypeLonglong), corCol1, ctx) + plus := newFunction(ast.Plus, cast, corCol2) + plus2 := newFunction(ast.Plus, plus, One) + ans := &Constant{Value: types.NewIntDatum(3)} + ret, err := SubstituteCorCol2Constant(plus2) + c.Assert(err, check.IsNil) + c.Assert(ret.Equal(ans, ctx), check.IsTrue) + col1 := &Column{Index: 1} + newCol, err := SubstituteCorCol2Constant(col1) + c.Assert(newCol.Equal(col1, ctx), check.IsTrue) +} diff --git a/plan/decorrelate.go b/plan/decorrelate.go index ef40bfaa69de5..a8914598b1a36 100644 --- a/plan/decorrelate.go +++ b/plan/decorrelate.go @@ -158,6 +158,11 @@ func (s *decorrelateSolver) optimize(p LogicalPlan, _ context.Context, _ *idAllo } } } + if sel, ok := p.(*Selection); ok { + if _, ok := p.Children()[0].(*DataSource); ok { + _, sel.canControlScan = sel.makeScanController(true) + } + } newChildren := make([]Plan, 0, len(p.Children())) for _, child := range p.Children() { np, _ := s.optimize(child.(LogicalPlan), nil, nil) diff --git a/plan/expr_to_pb.go b/plan/expr_to_pb.go index 454888424b6f5..8d47579424b0d 100644 --- a/plan/expr_to_pb.go +++ b/plan/expr_to_pb.go @@ -25,7 +25,8 @@ import ( "github.com/pingcap/tipb/go-tipb" ) -func expressionsToPB(sc *variable.StatementContext, exprs []expression.Expression, client kv.Client) (pbExpr *tipb.Expr, pushed []expression.Expression, remained []expression.Expression) { +// ExpressionsToPB converts expression to tipb.Expr. +func ExpressionsToPB(sc *variable.StatementContext, exprs []expression.Expression, client kv.Client) (pbExpr *tipb.Expr, pushed []expression.Expression, remained []expression.Expression) { pc := pbConverter{client: client, sc: sc} for _, expr := range exprs { v := pc.exprToPB(expr) diff --git a/plan/logical_plans.go b/plan/logical_plans.go index 00ea4ce9f3249..fa87728dbc530 100644 --- a/plan/logical_plans.go +++ b/plan/logical_plans.go @@ -147,6 +147,14 @@ type Selection struct { // onTable means if this selection's child is a table scan or index scan. onTable bool + + // If ScanController is true, then the child of this selection is a scan, + // which use pk or index. we will record the accessConditions, idxConditions, + // and tblConditions to control the below plan. + ScanController bool + + // We will check this at decorrelate phase. + canControlScan bool } func (p *Selection) extractCorrelatedCols() []*expression.CorrelatedColumn { diff --git a/plan/physical_plan_builder.go b/plan/physical_plan_builder.go index 91cd4d1cedecb..350600e6e3ba9 100644 --- a/plan/physical_plan_builder.go +++ b/plan/physical_plan_builder.go @@ -69,13 +69,14 @@ func (p *DataSource) convert2TableScan(prop *requiredProperty) (*physicalPlanInf for _, cond := range sel.Conditions { conds = append(conds, cond.Clone()) } - ts.AccessCondition, newSel.Conditions = detachTableScanConditions(conds, table) + ts.AccessCondition, newSel.Conditions = DetachTableScanConditions(conds, table) ts.TableConditionPBExpr, ts.tableFilterConditions, newSel.Conditions = - expressionsToPB(sc, newSel.Conditions, client) - err := buildTableRange(ts) + ExpressionsToPB(sc, newSel.Conditions, client) + ranges, err := BuildTableRange(ts.AccessCondition, sc) if err != nil { return nil, errors.Trace(err) } + ts.Ranges = ranges if len(newSel.Conditions) > 0 { newSel.SetChildren(ts) newSel.onTable = true @@ -144,16 +145,16 @@ func (p *DataSource) convert2IndexScan(prop *requiredProperty, index *model.Inde for _, cond := range sel.Conditions { conds = append(conds, cond.Clone()) } - is.AccessCondition, newSel.Conditions = detachIndexScanConditions(conds, is) + is.AccessCondition, newSel.Conditions = DetachIndexScanConditions(conds, is) memDB := infoschema.IsMemoryDB(p.DBName.L) isDistReq := !memDB && client != nil && client.SupportRequestType(kv.ReqTypeIndex, 0) if isDistReq { - idxConds, tblConds := detachIndexFilterConditions(newSel.Conditions, is.Index.Columns, is.Table) - is.IndexConditionPBExpr, is.indexFilterConditions, idxConds = expressionsToPB(sc, idxConds, client) - is.TableConditionPBExpr, is.tableFilterConditions, tblConds = expressionsToPB(sc, tblConds, client) + idxConds, tblConds := DetachIndexFilterConditions(newSel.Conditions, is.Index.Columns, is.Table) + is.IndexConditionPBExpr, is.indexFilterConditions, idxConds = ExpressionsToPB(sc, idxConds, client) + is.TableConditionPBExpr, is.tableFilterConditions, tblConds = ExpressionsToPB(sc, tblConds, client) newSel.Conditions = append(idxConds, tblConds...) } - err := buildIndexRange(p.ctx.GetSessionVars().StmtCtx, is) + err := BuildIndexRange(p.ctx.GetSessionVars().StmtCtx, is) if err != nil { if !terror.ErrorEqual(err, types.ErrTruncated) { return nil, errors.Trace(err) @@ -990,6 +991,130 @@ func (p *Union) convert2PhysicalPlan(prop *requiredProperty) (*physicalPlanInfo, return info, nil } +// makeScanController will try to build a selection that controls the below scan's filter condition, +// and return a physicalPlanInfo. If the onlyJudge is true, it will only check whether this selection +// can become a scan controller without building the physical plan. +func (p *Selection) makeScanController(onlyJudge bool) (*physicalPlanInfo, bool) { + var ( + child PhysicalPlan + corColConds []expression.Expression + pkCol *expression.Column + ) + ds := p.children[0].(*DataSource) + indices, includeTableScan := availableIndices(ds.indexHints, ds.tableInfo) + for _, expr := range p.Conditions { + if !expr.IsCorrelated() { + continue + } + cond := pushDownNot(expr, false, nil) + corCols := extractCorColumns(cond) + for _, col := range corCols { + *col.Data = expression.One.Value + } + newCond, _ := expression.SubstituteCorCol2Constant(cond) + corColConds = append(corColConds, newCond) + } + if ds.tableInfo.PKIsHandle && includeTableScan { + for i, col := range ds.Columns { + if mysql.HasPriKeyFlag(col.Flag) { + pkCol = ds.schema.Columns[i] + break + } + } + } + if pkCol != nil { + if onlyJudge { + checker := conditionChecker{ + pkName: pkCol.ColName, + length: types.UnspecifiedLength, + } + // Pk should satisfies the same property as the index. + for _, cond := range corColConds { + if checker.check(cond) { + return nil, true + } + } + return nil, false + } + ts := &PhysicalTableScan{ + Table: ds.tableInfo, + Columns: ds.Columns, + TableAsName: ds.TableAsName, + DBName: ds.DBName, + physicalTableSource: physicalTableSource{client: ds.ctx.GetClient()}, + } + ts.tp = Tbl + ts.allocator = ds.allocator + ts.SetSchema(ds.schema) + ts.initIDAndContext(ds.ctx) + if ds.ctx.Txn() != nil { + ts.readOnly = p.ctx.Txn().IsReadOnly() + } else { + ts.readOnly = true + } + child = ts + } else { + var chosenPlan *PhysicalIndexScan + for _, expr := range p.Conditions { + if !expr.IsCorrelated() { + continue + } + cond, _ := expression.SubstituteCorCol2Constant(expr) + corColConds = append(corColConds, cond) + } + for _, idx := range indices { + condsBackUp := make([]expression.Expression, 0, len(corColConds)) + for _, cond := range corColConds { + condsBackUp = append(condsBackUp, cond.Clone()) + } + is := &PhysicalIndexScan{ + Table: ds.tableInfo, + Index: idx, + Columns: ds.Columns, + TableAsName: ds.TableAsName, + OutOfOrder: true, + DBName: ds.DBName, + physicalTableSource: physicalTableSource{client: ds.ctx.GetClient()}, + } + is.tp = Idx + is.allocator = ds.allocator + is.SetSchema(ds.schema) + is.initIDAndContext(ds.ctx) + if is.ctx.Txn() != nil { + is.readOnly = p.ctx.Txn().IsReadOnly() + } else { + is.readOnly = true + } + accessConds, _ := DetachIndexScanConditions(condsBackUp, is) + if len(accessConds) == 0 { + continue + } + if onlyJudge { + return nil, true + } + better := chosenPlan == nil || chosenPlan.accessEqualCount < is.accessEqualCount + better = better || (chosenPlan.accessEqualCount == is.accessEqualCount && chosenPlan.accessInAndEqCount < is.accessInAndEqCount) + if better { + chosenPlan = is + } + is.DoubleRead = !isCoveringIndex(is.Columns, is.Index.Columns, is.Table.PKIsHandle) + } + if chosenPlan == nil && onlyJudge { + return nil, false + } + child = chosenPlan + } + newSel := *p + newSel.ScanController = true + newSel.SetChildren(child) + info := &physicalPlanInfo{ + p: &newSel, + count: uint64(ds.statisticTable.Count), + } + info.cost = float64(info.count) * selectionFactor + return info, true +} + // convert2PhysicalPlan implements the LogicalPlan convert2PhysicalPlan interface. func (p *Selection) convert2PhysicalPlan(prop *requiredProperty) (*physicalPlanInfo, error) { info, err := p.getPlanInfo(prop) @@ -999,6 +1124,11 @@ func (p *Selection) convert2PhysicalPlan(prop *requiredProperty) (*physicalPlanI if info != nil { return info, nil } + if p.canControlScan { + info, _ = p.makeScanController(false) + p.storePlanInfo(prop, info) + return info, nil + } // Firstly, we try to push order. info, err = p.convert2PhysicalPlanPushOrder(prop) if err != nil { @@ -1342,7 +1472,8 @@ func addCachePlan(p PhysicalPlan, allocator *idAllocator) []*expression.Correlat newChildren := make([]Plan, 0, len(p.Children())) for _, child := range p.Children() { childCorCols := addCachePlan(child.(PhysicalPlan), allocator) - if len(selfCorCols) > 0 && len(childCorCols) == 0 { + // If p is a Selection and controls the access condition of below scan plan, there shouldn't have a cache plan. + if sel, ok := p.(*Selection); len(selfCorCols) > 0 && len(childCorCols) == 0 && (!ok || !sel.ScanController) { newChild := &Cache{} newChild.tp = "Cache" newChild.allocator = allocator diff --git a/plan/physical_plan_test.go b/plan/physical_plan_test.go index eea745295e4a9..c158fc516cead 100644 --- a/plan/physical_plan_test.go +++ b/plan/physical_plan_test.go @@ -1002,3 +1002,55 @@ func (s *testPlanSuite) TestRangeBuilder(c *C) { c.Assert(got, Equals, ca.resultStr, Commentf("different for expr %s", ca.exprStr)) } } + +func (s *testPlanSuite) TestScanController(c *C) { + defer testleak.AfterTest(c)() + cases := []struct { + sql string + ans string + }{ + { + sql: "select (select count(1) k from t s where s.a = t.a having k != 0) from t", + ans: "Apply{Table(t)->Table(t)->Selection->StreamAgg}->Projection", + }, + { + sql: "select (select count(1) k from t s where s.b = t.b having k != 0) from t", + ans: "Apply{Table(t)->Table(t)->Cache->Selection->StreamAgg}->Projection", + }, + { + sql: "select (select count(1) k from t s where s.f = t.f having k != 0) from t", + ans: "Apply{Table(t)->Index(t.f)[]->Selection->StreamAgg}->Projection", + }, + } + for _, ca := range cases { + comment := Commentf("for %s", ca.sql) + stmt, err := s.ParseOneStmt(ca.sql, "", "") + c.Assert(err, IsNil, comment) + ast.SetFlag(stmt) + + is, err := mockResolve(stmt) + c.Assert(err, IsNil) + + builder := &planBuilder{ + allocator: new(idAllocator), + ctx: mockContext(), + colMapper: make(map[*ast.ColumnNameExpr]int), + is: is, + } + p := builder.build(stmt) + c.Assert(builder.err, IsNil) + lp := p.(LogicalPlan) + _, lp, err = lp.PredicatePushDown(nil) + c.Assert(err, IsNil) + lp.PruneColumns(lp.Schema().Columns) + dSolver := &decorrelateSolver{} + lp, err = dSolver.optimize(lp, mockContext(), new(idAllocator)) + c.Assert(err, IsNil) + lp.ResolveIndicesAndCorCols() + info, err := lp.convert2PhysicalPlan(&requiredProperty{}) + pp := info.p + pp = EliminateProjection(pp) + addCachePlan(pp, builder.allocator) + c.Assert(ToString(pp), Equals, ca.ans, Commentf("for %s", ca.sql)) + } +} diff --git a/plan/physical_plans.go b/plan/physical_plans.go index 5f195b31bbefa..3d5907837b200 100644 --- a/plan/physical_plans.go +++ b/plan/physical_plans.go @@ -740,7 +740,8 @@ func (p *Selection) MarshalJSON() ([]byte, error) { buffer := bytes.NewBufferString("{") buffer.WriteString(fmt.Sprintf(""+ " \"condition\": %s,\n"+ - " \"child\": \"%s\"\n}", conds, p.children[0].ID())) + " \"scanController\": %v,"+ + " \"child\": \"%s\"\n}", conds, p.ScanController, p.children[0].ID())) return buffer.Bytes(), nil } diff --git a/plan/refiner.go b/plan/refiner.go index e12bb375d4b8f..07b4b24038a38 100644 --- a/plan/refiner.go +++ b/plan/refiner.go @@ -31,7 +31,8 @@ var fullRange = []rangePoint{ {value: types.MaxValueDatum()}, } -func buildIndexRange(sc *variable.StatementContext, p *PhysicalIndexScan) error { +// BuildIndexRange will build range of index for PhysicalIndexScan +func BuildIndexRange(sc *variable.StatementContext, p *PhysicalIndexScan) error { rb := rangeBuilder{sc: sc} for i := 0; i < p.accessInAndEqCount; i++ { // Build ranges for equal or in access conditions. @@ -167,7 +168,8 @@ func checkIndexCondition(condition expression.Expression, indexColumns []*model. return true } -func detachIndexFilterConditions(conditions []expression.Expression, indexColumns []*model.IndexColumn, table *model.TableInfo) ([]expression.Expression, []expression.Expression) { +// DetachIndexFilterConditions will detach the access conditions from other conditions. +func DetachIndexFilterConditions(conditions []expression.Expression, indexColumns []*model.IndexColumn, table *model.TableInfo) ([]expression.Expression, []expression.Expression) { var pKName model.CIStr if table.PKIsHandle { for _, colInfo := range table.Columns { @@ -188,7 +190,8 @@ func detachIndexFilterConditions(conditions []expression.Expression, indexColumn return indexConditions, tableConditions } -func detachIndexScanConditions(conditions []expression.Expression, indexScan *PhysicalIndexScan) ([]expression.Expression, []expression.Expression) { +// DetachIndexScanConditions will detach the index filters from table filters. +func DetachIndexScanConditions(conditions []expression.Expression, indexScan *PhysicalIndexScan) ([]expression.Expression, []expression.Expression) { accessConds := make([]expression.Expression, len(indexScan.Index.Columns)) var filterConds []expression.Expression // pushDownNot here can convert query 'not (a != 1)' to 'a = 1'. @@ -246,8 +249,8 @@ func detachIndexScanConditions(conditions []expression.Expression, indexScan *Ph return accessConds, filterConds } -// detachTableScanConditions distinguishes between access conditions and filter conditions from conditions. -func detachTableScanConditions(conditions []expression.Expression, table *model.TableInfo) ([]expression.Expression, []expression.Expression) { +// DetachTableScanConditions distinguishes between access conditions and filter conditions from conditions. +func DetachTableScanConditions(conditions []expression.Expression, table *model.TableInfo) ([]expression.Expression, []expression.Expression) { var pkName model.CIStr if table.PKIsHandle { for _, colInfo := range table.Columns { @@ -283,22 +286,25 @@ func detachTableScanConditions(conditions []expression.Expression, table *model. return accessConditions, filterConditions } -func buildTableRange(p *PhysicalTableScan) error { - if len(p.AccessCondition) == 0 { - p.Ranges = []TableRange{{math.MinInt64, math.MaxInt64}} - return nil +// BuildTableRange will build range of pk for PhysicalTableScan +func BuildTableRange(accessConditions []expression.Expression, sc *variable.StatementContext) ([]TableRange, error) { + if len(accessConditions) == 0 { + return []TableRange{{math.MinInt64, math.MaxInt64}}, nil } - rb := rangeBuilder{sc: p.ctx.GetSessionVars().StmtCtx} + rb := rangeBuilder{sc: sc} rangePoints := fullRange - for _, cond := range p.AccessCondition { + for _, cond := range accessConditions { rangePoints = rb.intersection(rangePoints, rb.build(cond)) if rb.err != nil { - return errors.Trace(rb.err) + return nil, errors.Trace(rb.err) } } - p.Ranges = rb.buildTableRanges(rangePoints) - return errors.Trace(rb.err) + ranges := rb.buildTableRanges(rangePoints) + if rb.err != nil { + return nil, errors.Trace(rb.err) + } + return ranges, nil } // conditionChecker checks if this condition can be pushed to index plan.