From 41ed0e508d901753f4fac3bd3aae9b13bd202481 Mon Sep 17 00:00:00 2001 From: Ari Ekmekji Date: Fri, 19 Jul 2024 11:42:01 -0700 Subject: [PATCH] planner: use ordered index with is null predicate (#54253) close pingcap/tidb#54188 --- pkg/planner/core/casetest/index/BUILD.bazel | 2 +- pkg/planner/core/casetest/index/index_test.go | 12 +++++ pkg/util/ranger/detacher.go | 51 +++++++++++++------ .../planner/core/casetest/integration.result | 7 +++ tests/integrationtest/r/util/ranger.result | 5 +- .../t/planner/core/casetest/integration.test | 2 + 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/pkg/planner/core/casetest/index/BUILD.bazel b/pkg/planner/core/casetest/index/BUILD.bazel index 50849a3516dfa..84411f57244fe 100644 --- a/pkg/planner/core/casetest/index/BUILD.bazel +++ b/pkg/planner/core/casetest/index/BUILD.bazel @@ -9,7 +9,7 @@ go_test( ], data = glob(["testdata/**"]), flaky = True, - shard_count = 5, + shard_count = 6, deps = [ "//pkg/testkit", "//pkg/testkit/testdata", diff --git a/pkg/planner/core/casetest/index/index_test.go b/pkg/planner/core/casetest/index/index_test.go index 773897f6b0ccb..6e068b7982aa0 100644 --- a/pkg/planner/core/casetest/index/index_test.go +++ b/pkg/planner/core/casetest/index/index_test.go @@ -209,3 +209,15 @@ func TestRangeIntersection(t *testing.T) { tk.MustQuery(sql).Sort().Check(testkit.Rows(output[i].Result...)) } } + +func TestOrderedIndexWithIsNull(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("CREATE TABLE t1 (a int key, b int, c int, index (b, c));") + tk.MustQuery("explain select a from t1 where b is null order by c").Check(testkit.Rows( + "Projection_6 10.00 root test.t1.a", + "└─IndexReader_12 10.00 root index:IndexRangeScan_11", + " └─IndexRangeScan_11 10.00 cop[tikv] table:t1, index:b(b, c) range:[NULL,NULL], keep order:true, stats:pseudo", + )) +} diff --git a/pkg/util/ranger/detacher.go b/pkg/util/ranger/detacher.go index d20a370e2b76b..c68181f25926b 100644 --- a/pkg/util/ranger/detacher.go +++ b/pkg/util/ranger/detacher.go @@ -182,6 +182,16 @@ func getPotentialEqOrInColOffset(sctx *rangerctx.RangerContext, expr expression. return i } } + case ast.IsNull: + c, ok := f.GetArgs()[0].(*expression.Column) + if !ok { + return -1 + } + for i, col := range cols { + if col.EqualColumn(c) { + return i + } + } } return -1 } @@ -635,27 +645,34 @@ func allEqOrIn(expr expression.Expression) bool { } } return true - case ast.EQ, ast.NullEQ, ast.In: + case ast.EQ, ast.NullEQ, ast.In, ast.IsNull: return true } return false } func extractValueInfo(expr expression.Expression) *valueInfo { - if f, ok := expr.(*expression.ScalarFunction); ok && (f.FuncName.L == ast.EQ || f.FuncName.L == ast.NullEQ) { - getValueInfo := func(c *expression.Constant) *valueInfo { - mutable := c.ParamMarker != nil || c.DeferredExpr != nil - var value *types.Datum - if !mutable { - value = &c.Value + if f, ok := expr.(*expression.ScalarFunction); ok { + if f.FuncName.L == ast.IsNull { + val := &types.Datum{} + val.SetNull() + return &valueInfo{value: val, mutable: false} + } + if f.FuncName.L == ast.EQ || f.FuncName.L == ast.NullEQ { + getValueInfo := func(c *expression.Constant) *valueInfo { + mutable := c.ParamMarker != nil || c.DeferredExpr != nil + var value *types.Datum + if !mutable { + value = &c.Value + } + return &valueInfo{value, mutable} + } + if c, ok := f.GetArgs()[0].(*expression.Constant); ok { + return getValueInfo(c) + } + if c, ok := f.GetArgs()[1].(*expression.Constant); ok { + return getValueInfo(c) } - return &valueInfo{value, mutable} - } - if c, ok := f.GetArgs()[0].(*expression.Constant); ok { - return getValueInfo(c) - } - if c, ok := f.GetArgs()[1].(*expression.Constant); ok { - return getValueInfo(c) } } return nil @@ -714,8 +731,12 @@ func ExtractEqAndInCondition(sctx *rangerctx.RangerContext, conditions []express if ma == nil { if accesses[i] != nil { if allEqOrIn(accesses[i]) { - newConditions = append(newConditions, accesses[i]) columnValues[i] = extractValueInfo(accesses[i]) + if columnValues[i] != nil && columnValues[i].value != nil && columnValues[i].value.IsNull() { + accesses[i] = nil + } else { + newConditions = append(newConditions, accesses[i]) + } } else { accesses[i] = nil } diff --git a/tests/integrationtest/r/planner/core/casetest/integration.result b/tests/integrationtest/r/planner/core/casetest/integration.result index 9c708270041a0..09c6bad54fd27 100644 --- a/tests/integrationtest/r/planner/core/casetest/integration.result +++ b/tests/integrationtest/r/planner/core/casetest/integration.result @@ -600,6 +600,13 @@ explain format = 'brief' select * from t3 where a >= 'a' and a <= 'a' and b = 'b id estRows task access object operator info IndexReader 0.33 root index:IndexRangeScan └─IndexRangeScan 0.33 cop[tikv] table:t3, index:a(a, b, c) range:("a" "b" "c","a" "b" +inf], keep order:false, stats:pseudo +explain format = 'brief' select * from t1 where a is null or a in (1, 2, 3); +id estRows task access object operator info +IndexReader 260.00 root index:IndexRangeScan +└─IndexRangeScan 260.00 cop[tikv] table:t1, index:a(a, b, c) range:[NULL,NULL], [1,3], keep order:false, stats:pseudo +explain format = 'brief' select * from t1 where a is null and a = 1; +id estRows task access object operator info +TableDual 0.00 root rows:0 drop table if exists t1; CREATE TABLE t1 ( key1 int(11) NOT NULL, diff --git a/tests/integrationtest/r/util/ranger.result b/tests/integrationtest/r/util/ranger.result index b105defcf11aa..7acee71598f07 100644 --- a/tests/integrationtest/r/util/ranger.result +++ b/tests/integrationtest/r/util/ranger.result @@ -482,9 +482,8 @@ a b c 1 2 2 explain format='brief' select * from t where a = 1 and (b is null or b = 2) and c > 1; id estRows task access object operator info -IndexReader 0.07 root index:Selection -└─Selection 0.07 cop[tikv] gt(util__ranger.t.c, 1) - └─IndexRangeScan 0.20 cop[tikv] table:t, index:a(a, b, c) range:[1 NULL,1 NULL], [1 2,1 2], keep order:false, stats:pseudo +IndexReader 0.67 root index:IndexRangeScan +└─IndexRangeScan 0.67 cop[tikv] table:t, index:a(a, b, c) range:(1 NULL 1,1 NULL +inf], (1 2 1,1 2 +inf], keep order:false, stats:pseudo select * from t where a = 1 and (b is null or b = 2) and c > 1; a b c 1 2 2 diff --git a/tests/integrationtest/t/planner/core/casetest/integration.test b/tests/integrationtest/t/planner/core/casetest/integration.test index f98ed0a9355e1..078d405d5846a 100644 --- a/tests/integrationtest/t/planner/core/casetest/integration.test +++ b/tests/integrationtest/t/planner/core/casetest/integration.test @@ -173,6 +173,8 @@ explain format = 'brief' select * from t0 where a > 1 and a < 3 order by b limit explain format = 'brief' select * from t1 where a >= 2 and a <= 2 and b = 2 and c > 2; explain format = 'brief' select * from t2 where a >= 2.5 and a <= 2.5 order by b limit 2; explain format = 'brief' select * from t3 where a >= 'a' and a <= 'a' and b = 'b' and c > 'c'; +explain format = 'brief' select * from t1 where a is null or a in (1, 2, 3); +explain format = 'brief' select * from t1 where a is null and a = 1; # TestIssue22105 drop table if exists t1;