From b7db60440bf0fd48cfa5568a862c015b38788b1e Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Fri, 22 Dec 2023 15:33:25 +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) (#49638) close pingcap/tidb#49616 --- planner/core/integration_test.go | 9 +++++ planner/core/rule_predicate_push_down.go | 45 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index c39cbf2722d35..cc440ca20953f 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -1278,6 +1278,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))`) + tk.MustUseIndex(`select /*+ tidb_inlj(t2, t1) */ * + from t2 left join t1 on t1.k=t2.k + where a>0 or (a=0 and b>0)`, "k") } func TestReadFromStorageHint(t *testing.T) { diff --git a/planner/core/rule_predicate_push_down.go b/planner/core/rule_predicate_push_down.go index 01cd085d5bd0d..6752ae27abecf 100644 --- a/planner/core/rule_predicate_push_down.go +++ b/planner/core/rule_predicate_push_down.go @@ -444,6 +444,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 { @@ -458,6 +462,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))