From aaa65215093aad6dfc959ae745e81caff2c61df6 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Tue, 27 Feb 2024 19:41:07 +0800 Subject: [PATCH] planner: fix the issue that the optimizer cannot convert OUTER JOIN to INNER JOIN with nested AND/OR in some cases (#49625) (#49639) close pingcap/tidb#49616 --- planner/core/integration_test.go | 11 +++++- planner/core/rule_predicate_push_down.go | 45 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index 21a43594242fa..df0d04d1e6381 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -402,7 +402,7 @@ func TestAggPushDownEngine(t *testing.T) { " └─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo")) } -func TestIssue15110(t *testing.T) { +func TestIssue15110And49616(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") @@ -436,6 +436,15 @@ func TestIssue15110(t *testing.T) { tk.MustExec("set @@session.tidb_isolation_read_engines = 'tiflash'") tk.MustExec("explain format = 'brief' SELECT count(*) FROM crm_rd_150m dataset_48 WHERE (CASE WHEN (month(dataset_48.customer_first_date)) <= 30 THEN '新客' ELSE NULL END) IS NOT NULL;") + + // for #49616 + tk.MustExec(`use test`) + tk.MustExec("set @@session.tidb_isolation_read_engines = 'tikv'") + tk.MustExec(`create table t1 (k int, a int)`) + tk.MustExec(`create table t2 (k int, b int, key(k))`) + require.True(t, tk.HasPlan(`select /*+ tidb_inlj(t2, t1) */ * + from t2 left join t1 on t1.k=t2.k + where a>0 or (a=0 and b>0)`, `IndexJoin`)) } func TestIssue40910(t *testing.T) { diff --git a/planner/core/rule_predicate_push_down.go b/planner/core/rule_predicate_push_down.go index 06918adbd6419..8114f9b9120ff 100644 --- a/planner/core/rule_predicate_push_down.go +++ b/planner/core/rule_predicate_push_down.go @@ -439,6 +439,10 @@ func isNullRejected(ctx sessionctx.Context, schema *expression.Schema, expr expr sc.InNullRejectCheck = false }() for _, cond := range expression.SplitCNFItems(expr) { + if isNullRejectedSpecially(ctx, schema, expr) { + return true + } + result := expression.EvaluateExprWithNull(ctx, schema, cond) x, ok := result.(*expression.Constant) if !ok { @@ -453,6 +457,47 @@ func isNullRejected(ctx sessionctx.Context, schema *expression.Schema, expr expr return false } +// isNullRejectedSpecially handles some null-rejected cases specially, since the current in +// EvaluateExprWithNull is too strict for some cases, e.g. #49616. +func isNullRejectedSpecially(ctx sessionctx.Context, schema *expression.Schema, expr expression.Expression) bool { + return specialNullRejectedCase1(ctx, schema, expr) // only 1 case now +} + +// specialNullRejectedCase1 is mainly for #49616. +// Case1 specially handles `null-rejected OR (null-rejected AND {others})`, then no matter what the result +// of `{others}` is (True, False or Null), the result of this predicate is null, so this predicate is null-rejected. +func specialNullRejectedCase1(ctx sessionctx.Context, schema *expression.Schema, expr expression.Expression) bool { + isFunc := func(e expression.Expression, lowerFuncName string) *expression.ScalarFunction { + f, ok := e.(*expression.ScalarFunction) + if !ok { + return nil + } + if f.FuncName.L == lowerFuncName { + return f + } + return nil + } + orFunc := isFunc(expr, ast.LogicOr) + if orFunc == nil { + return false + } + for i := 0; i < 2; i++ { + andFunc := isFunc(orFunc.GetArgs()[i], ast.LogicAnd) + if andFunc == nil { + continue + } + if !isNullRejected(ctx, schema, orFunc.GetArgs()[1-i]) { + continue // the other side should be null-rejected: null-rejected OR (... AND ...) + } + for _, andItem := range expression.SplitCNFItems(andFunc) { + if isNullRejected(ctx, schema, andItem) { + return true // hit the case in the comment: null-rejected OR (null-rejected AND ...) + } + } + } + return false +} + // PredicatePushDown implements LogicalPlan PredicatePushDown interface. func (p *LogicalProjection) PredicatePushDown(predicates []expression.Expression, opt *logicalOptimizeOp) (ret []expression.Expression, retPlan LogicalPlan) { canBePushed := make([]expression.Expression, 0, len(predicates))