diff --git a/expression/partition_pruner.go b/expression/partition_pruner.go index bbf106a25cd6a..b3531b27d0508 100644 --- a/expression/partition_pruner.go +++ b/expression/partition_pruner.go @@ -79,7 +79,18 @@ func (p *hashPartitionPruner) reduceColumnEQ() bool { func (p *hashPartitionPruner) reduceConstantEQ() bool { for _, con := range p.conditions { - col, cond := validEqualCond(p.ctx, con) + var col *Column + var cond *Constant + if fn, ok := con.(*ScalarFunction); ok { + if fn.FuncName.L == ast.IsNull { + col, ok = fn.GetArgs()[0].(*Column) + if ok { + cond = NewNull() + } + } else { + col, cond = validEqualCond(p.ctx, con) + } + } if col != nil { id := p.getColID(col) if p.constantMap[id] != nil { @@ -94,18 +105,18 @@ func (p *hashPartitionPruner) reduceConstantEQ() bool { return false } -func (p *hashPartitionPruner) tryEvalPartitionExpr(piExpr Expression) (val int64, success bool, isNil bool) { +func (p *hashPartitionPruner) tryEvalPartitionExpr(piExpr Expression) (val int64, success bool, isNull bool) { switch pi := piExpr.(type) { case *ScalarFunction: if pi.FuncName.L == ast.Plus || pi.FuncName.L == ast.Minus || pi.FuncName.L == ast.Mul || pi.FuncName.L == ast.Div { left, right := pi.GetArgs()[0], pi.GetArgs()[1] - leftVal, ok, isNil := p.tryEvalPartitionExpr(left) - if !ok { - return 0, ok, isNil + leftVal, ok, isNull := p.tryEvalPartitionExpr(left) + if !ok || isNull { + return 0, ok, isNull } - rightVal, ok, isNil := p.tryEvalPartitionExpr(right) - if !ok { - return 0, ok, isNil + rightVal, ok, isNull := p.tryEvalPartitionExpr(right) + if !ok || isNull { + return 0, ok, isNull } switch pi.FuncName.L { case ast.Plus: @@ -123,11 +134,11 @@ func (p *hashPartitionPruner) tryEvalPartitionExpr(piExpr Expression) (val int64 val := p.constantMap[idx] if val != nil { pi.GetArgs()[0] = val - ret, _, err := pi.EvalInt(p.ctx, chunk.Row{}) + ret, isNull, err := pi.EvalInt(p.ctx, chunk.Row{}) if err != nil { return 0, false, false } - return ret, true, false + return ret, true, isNull } return 0, false, false } @@ -137,7 +148,7 @@ func (p *hashPartitionPruner) tryEvalPartitionExpr(piExpr Expression) (val int64 return 0, false, false } if val.IsNull() { - return 0, false, true + return 0, true, true } if val.Kind() == types.KindInt64 { return val.GetInt64(), true, false @@ -164,8 +175,8 @@ func newHashPartitionPruner() *hashPartitionPruner { } // solve eval the hash partition expression, the first return value represent the result of partition expression. The second -// return value is whether eval success. The third return value represent whether the eval result of partition value is null. -func (p *hashPartitionPruner) solve(ctx sessionctx.Context, conds []Expression, piExpr Expression) (val int64, ok bool, isNil bool) { +// return value is whether eval success. The third return value represent whether the query conditions is always false. +func (p *hashPartitionPruner) solve(ctx sessionctx.Context, conds []Expression, piExpr Expression) (val int64, ok bool, isAlwaysFalse bool) { p.ctx = ctx for _, cond := range conds { p.conditions = append(p.conditions, SplitCNFItems(cond)...) @@ -177,16 +188,19 @@ func (p *hashPartitionPruner) solve(ctx sessionctx.Context, conds []Expression, p.insertCol(col) } p.constantMap = make([]*Constant, p.numColumn) - conflict := p.reduceConstantEQ() - if conflict { - return 0, false, conflict + isAlwaysFalse = p.reduceConstantEQ() + if isAlwaysFalse { + return 0, false, isAlwaysFalse + } + isAlwaysFalse = p.reduceColumnEQ() + if isAlwaysFalse { + return 0, false, isAlwaysFalse } - conflict = p.reduceColumnEQ() - if conflict { - return 0, false, conflict + res, ok, isNull := p.tryEvalPartitionExpr(piExpr) + if isNull && ok { + return 0, ok, false } - res, ok, isNil := p.tryEvalPartitionExpr(piExpr) - return res, ok, isNil + return res, ok, false } // FastLocateHashPartition is used to get hash partition quickly. diff --git a/expression/partition_pruner_test.go b/expression/partition_pruner_test.go index 8f181d821169a..a132b6b84377b 100644 --- a/expression/partition_pruner_test.go +++ b/expression/partition_pruner_test.go @@ -69,6 +69,8 @@ func (s *testSuite2) TestHashPartitionPruner(c *C) { tk.MustExec("create table t3(id int, a int, b int, primary key(id, a)) partition by hash(id) partitions 10;") tk.MustExec("create table t4(d datetime, a int, b int, primary key(d, a)) partition by hash(year(d)) partitions 10;") tk.MustExec("create table t5(d date, a int, b int, primary key(d, a)) partition by hash(month(d)) partitions 10;") + tk.MustExec("create table t6(a int, b int) partition by hash(a) partitions 3;") + tk.MustExec("create table t7(a int, b int) partition by hash(a + b) partitions 10;") var input []string var output []struct { diff --git a/expression/testdata/partition_pruner_in.json b/expression/testdata/partition_pruner_in.json index 6f6e80fa85ff3..17edf2109826c 100644 --- a/expression/testdata/partition_pruner_in.json +++ b/expression/testdata/partition_pruner_in.json @@ -2,23 +2,23 @@ { "name": "TestHashPartitionPruner", "cases": [ - // Point Select. "explain select * from t1 where id = 7 and a = 6", "explain select * from t3 where id = 9 and a = 1", "explain select * from t2 where id = 9 and a = -110", "explain select * from t1 where id = -17", "explain select * from t2 where id = a and a = b and b = 2", - // Join. "explain select * from t1 join t2 on (t1.id = t2.id) where t1.id = 5 and t2.a = 7", "explain select * from t1 left join t2 on t1.id = 1 and t2.a = 2 where t2.id = 7", "explain select * from t2 join t1 on t1.id = t2.id and t2.a = t1.id and t2.id = 12", - // Negtive cases. "explain select * from t1 left join t2 on true where t1.a = 1 and false", "explain select * from t1 left join t2 on true where t1.a = 1 and null", "explain select * from t1 left join t2 on true where t1.a = null", - // Case with date. "explain select * from t4 where d = '2019-10-07 10:40:00' and a = 1", - "explain select * from t5 where d = '2019-10-07'" + "explain select * from t5 where d = '2019-10-07'", + "explain select * from t6 where a is null", + "explain select * from t6 where b is null", + "explain select * from t5 where d is null", + "explain select * from t7 where b = -3 and a is null" ] } ] diff --git a/expression/testdata/partition_pruner_out.json b/expression/testdata/partition_pruner_out.json index e3dcc64190589..9061476013d43 100644 --- a/expression/testdata/partition_pruner_out.json +++ b/expression/testdata/partition_pruner_out.json @@ -90,6 +90,43 @@ "├─IndexRangeScan_9(Build) 10.00 cop[tikv] table:t5, partition:p0, index:PRIMARY(d, a) range:[2019-10-07,2019-10-07], keep order:false, stats:pseudo", "└─TableRowIDScan_10(Probe) 10.00 cop[tikv] table:t5, partition:p0 keep order:false, stats:pseudo" ] + }, + { + "SQL": "explain select * from t6 where a is null", + "Result": [ + "TableReader_8 10.00 root data:Selection_7", + "└─Selection_7 10.00 cop[tikv] isnull(test_partition.t6.a)", + " └─TableFullScan_6 10000.00 cop[tikv] table:t6, partition:p0 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain select * from t6 where b is null", + "Result": [ + "Union_9 30.00 root ", + "├─TableReader_12 10.00 root data:Selection_11", + "│ └─Selection_11 10.00 cop[tikv] isnull(test_partition.t6.b)", + "│ └─TableFullScan_10 10000.00 cop[tikv] table:t6, partition:p0 keep order:false, stats:pseudo", + "├─TableReader_15 10.00 root data:Selection_14", + "│ └─Selection_14 10.00 cop[tikv] isnull(test_partition.t6.b)", + "│ └─TableFullScan_13 10000.00 cop[tikv] table:t6, partition:p1 keep order:false, stats:pseudo", + "└─TableReader_18 10.00 root data:Selection_17", + " └─Selection_17 10.00 cop[tikv] isnull(test_partition.t6.b)", + " └─TableFullScan_16 10000.00 cop[tikv] table:t6, partition:p2 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain select * from t5 where d is null", + "Result": [ + "TableDual_6 0.00 root rows:0" + ] + }, + { + "SQL": "explain select * from t7 where b = -3 and a is null", + "Result": [ + "TableReader_8 0.01 root data:Selection_7", + "└─Selection_7 0.01 cop[tikv] eq(test_partition.t7.b, -3), isnull(test_partition.t7.a)", + " └─TableFullScan_6 10000.00 cop[tikv] table:t7, partition:p0 keep order:false, stats:pseudo" + ] } ] }