Skip to content

Commit e349c7c

Browse files
authoredApr 24, 2024··
planner: extract null check related code to util null_misc (#52840)
ref #51664, ref #52714
1 parent 773ce7e commit e349c7c

File tree

5 files changed

+102
-79
lines changed

5 files changed

+102
-79
lines changed
 

‎pkg/planner/core/logical_plan_builder.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -562,13 +562,13 @@ func (p *LogicalJoin) ExtractOnCondition(
562562
}
563563
if leftCol != nil && rightCol != nil {
564564
if deriveLeft {
565-
if isNullRejected(ctx, leftSchema, expr) && !mysql.HasNotNullFlag(leftCol.RetType.GetFlag()) {
565+
if util.IsNullRejected(ctx, leftSchema, expr) && !mysql.HasNotNullFlag(leftCol.RetType.GetFlag()) {
566566
notNullExpr := expression.BuildNotNullExpr(ctx.GetExprCtx(), leftCol)
567567
leftCond = append(leftCond, notNullExpr)
568568
}
569569
}
570570
if deriveRight {
571-
if isNullRejected(ctx, rightSchema, expr) && !mysql.HasNotNullFlag(rightCol.RetType.GetFlag()) {
571+
if util.IsNullRejected(ctx, rightSchema, expr) && !mysql.HasNotNullFlag(rightCol.RetType.GetFlag()) {
572572
notNullExpr := expression.BuildNotNullExpr(ctx.GetExprCtx(), rightCol)
573573
rightCond = append(rightCond, notNullExpr)
574574
}

‎pkg/planner/core/logical_plans.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ func (p *LogicalProjection) ExtractFD() *fd.FDSet {
875875
// the dependent columns in scalar function should be also considered as output columns as well.
876876
outputColsUniqueIDs.Insert(int(one.UniqueID))
877877
}
878-
notnull := isNullRejected(p.SCtx(), p.schema, x)
878+
notnull := util.IsNullRejected(p.SCtx(), p.schema, x)
879879
if notnull || determinants.SubsetOf(fds.NotNullCols) {
880880
notnullColsUniqueIDs.Insert(scalarUniqueID)
881881
}
@@ -1015,7 +1015,7 @@ func (la *LogicalAggregation) ExtractFD() *fd.FDSet {
10151015
determinants.Insert(int(one.UniqueID))
10161016
groupByColsOutputCols.Insert(int(one.UniqueID))
10171017
}
1018-
notnull := isNullRejected(la.SCtx(), la.schema, x)
1018+
notnull := util.IsNullRejected(la.SCtx(), la.schema, x)
10191019
if notnull || determinants.SubsetOf(fds.NotNullCols) {
10201020
notnullColsUniqueIDs.Insert(scalarUniqueID)
10211021
}
@@ -1182,7 +1182,7 @@ func extractNotNullFromConds(conditions []expression.Expression, p base.LogicalP
11821182
for _, condition := range conditions {
11831183
var cols []*expression.Column
11841184
cols = expression.ExtractColumnsFromExpressions(cols, []expression.Expression{condition}, nil)
1185-
if isNullRejected(p.SCtx(), p.Schema(), condition) {
1185+
if util.IsNullRejected(p.SCtx(), p.Schema(), condition) {
11861186
for _, col := range cols {
11871187
notnullColsUniqueIDs.Insert(int(col.UniqueID))
11881188
}

‎pkg/planner/core/rule_predicate_push_down.go

+2-74
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ func simplifyOuterJoin(p *LogicalJoin, predicates []expression.Expression) {
407407
if expression.ExprFromSchema(expr, outerTable.Schema()) {
408408
continue
409409
}
410-
isOk := isNullRejected(p.SCtx(), innerTable.Schema(), expr)
410+
isOk := util.IsNullRejected(p.SCtx(), innerTable.Schema(), expr)
411411
if isOk {
412412
canBeSimplified = true
413413
break
@@ -418,78 +418,6 @@ func simplifyOuterJoin(p *LogicalJoin, predicates []expression.Expression) {
418418
}
419419
}
420420

421-
// isNullRejected check whether a condition is null-rejected
422-
// A condition would be null-rejected in one of following cases:
423-
// If it is a predicate containing a reference to an inner table that evaluates to UNKNOWN or FALSE when one of its arguments is NULL.
424-
// If it is a conjunction containing a null-rejected condition as a conjunct.
425-
// If it is a disjunction of null-rejected conditions.
426-
func isNullRejected(ctx base.PlanContext, schema *expression.Schema, expr expression.Expression) bool {
427-
exprCtx := ctx.GetNullRejectCheckExprCtx()
428-
expr = expression.PushDownNot(exprCtx, expr)
429-
if expression.ContainOuterNot(expr) {
430-
return false
431-
}
432-
sc := ctx.GetSessionVars().StmtCtx
433-
for _, cond := range expression.SplitCNFItems(expr) {
434-
if isNullRejectedSpecially(ctx, schema, expr) {
435-
return true
436-
}
437-
438-
result := expression.EvaluateExprWithNull(exprCtx, schema, cond)
439-
x, ok := result.(*expression.Constant)
440-
if !ok {
441-
continue
442-
}
443-
if x.Value.IsNull() {
444-
return true
445-
} else if isTrue, err := x.Value.ToBool(sc.TypeCtxOrDefault()); err == nil && isTrue == 0 {
446-
return true
447-
}
448-
}
449-
return false
450-
}
451-
452-
// isNullRejectedSpecially handles some null-rejected cases specially, since the current in
453-
// EvaluateExprWithNull is too strict for some cases, e.g. #49616.
454-
func isNullRejectedSpecially(ctx base.PlanContext, schema *expression.Schema, expr expression.Expression) bool {
455-
return specialNullRejectedCase1(ctx, schema, expr) // only 1 case now
456-
}
457-
458-
// specialNullRejectedCase1 is mainly for #49616.
459-
// Case1 specially handles `null-rejected OR (null-rejected AND {others})`, then no matter what the result
460-
// of `{others}` is (True, False or Null), the result of this predicate is null, so this predicate is null-rejected.
461-
func specialNullRejectedCase1(ctx base.PlanContext, schema *expression.Schema, expr expression.Expression) bool {
462-
isFunc := func(e expression.Expression, lowerFuncName string) *expression.ScalarFunction {
463-
f, ok := e.(*expression.ScalarFunction)
464-
if !ok {
465-
return nil
466-
}
467-
if f.FuncName.L == lowerFuncName {
468-
return f
469-
}
470-
return nil
471-
}
472-
orFunc := isFunc(expr, ast.LogicOr)
473-
if orFunc == nil {
474-
return false
475-
}
476-
for i := 0; i < 2; i++ {
477-
andFunc := isFunc(orFunc.GetArgs()[i], ast.LogicAnd)
478-
if andFunc == nil {
479-
continue
480-
}
481-
if !isNullRejected(ctx, schema, orFunc.GetArgs()[1-i]) {
482-
continue // the other side should be null-rejected: null-rejected OR (... AND ...)
483-
}
484-
for _, andItem := range expression.SplitCNFItems(andFunc) {
485-
if isNullRejected(ctx, schema, andItem) {
486-
return true // hit the case in the comment: null-rejected OR (null-rejected AND ...)
487-
}
488-
}
489-
}
490-
return false
491-
}
492-
493421
// PredicatePushDown implements base.LogicalPlan PredicatePushDown interface.
494422
func (p *LogicalExpand) PredicatePushDown(predicates []expression.Expression, opt *coreusage.LogicalOptimizeOp) (ret []expression.Expression, retPlan base.LogicalPlan) {
495423
// Note that, grouping column related predicates can't be pushed down, since grouping column has nullability change after Expand OP itself.
@@ -723,7 +651,7 @@ func deriveNotNullExpr(ctx base.PlanContext, expr expression.Expression, schema
723651
if childCol == nil {
724652
childCol = schema.RetrieveColumn(arg1)
725653
}
726-
if isNullRejected(ctx, schema, expr) && !mysql.HasNotNullFlag(childCol.RetType.GetFlag()) {
654+
if util.IsNullRejected(ctx, schema, expr) && !mysql.HasNotNullFlag(childCol.RetType.GetFlag()) {
727655
return expression.BuildNotNullExpr(ctx.GetExprCtx(), childCol)
728656
}
729657
return nil

‎pkg/planner/util/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ go_library(
66
"byitem.go",
77
"expression.go",
88
"misc.go",
9+
"null_misc.go",
910
"path.go",
1011
],
1112
importpath = "github.com/pingcap/tidb/pkg/planner/util",

‎pkg/planner/util/null_misc.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2024 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package util
16+
17+
import (
18+
"github.com/pingcap/tidb/pkg/expression"
19+
"github.com/pingcap/tidb/pkg/parser/ast"
20+
"github.com/pingcap/tidb/pkg/planner/context"
21+
)
22+
23+
// IsNullRejected check whether a condition is null-rejected
24+
// A condition would be null-rejected in one of following cases:
25+
// If it is a predicate containing a reference to an inner table that evaluates to UNKNOWN or FALSE
26+
// when one of its arguments is NULL.
27+
// If it is a conjunction containing a null-rejected condition as a conjunct.
28+
// If it is a disjunction of null-rejected conditions.
29+
func IsNullRejected(ctx context.PlanContext, schema *expression.Schema, expr expression.Expression) bool {
30+
exprCtx := ctx.GetNullRejectCheckExprCtx()
31+
expr = expression.PushDownNot(exprCtx, expr)
32+
if expression.ContainOuterNot(expr) {
33+
return false
34+
}
35+
sc := ctx.GetSessionVars().StmtCtx
36+
for _, cond := range expression.SplitCNFItems(expr) {
37+
if isNullRejectedSpecially(ctx, schema, expr) {
38+
return true
39+
}
40+
41+
result := expression.EvaluateExprWithNull(exprCtx, schema, cond)
42+
x, ok := result.(*expression.Constant)
43+
if !ok {
44+
continue
45+
}
46+
if x.Value.IsNull() {
47+
return true
48+
} else if isTrue, err := x.Value.ToBool(sc.TypeCtxOrDefault()); err == nil && isTrue == 0 {
49+
return true
50+
}
51+
}
52+
return false
53+
}
54+
55+
// isNullRejectedSpecially handles some null-rejected cases specially, since the current in
56+
// EvaluateExprWithNull is too strict for some cases, e.g. #49616.
57+
func isNullRejectedSpecially(ctx context.PlanContext, schema *expression.Schema, expr expression.Expression) bool {
58+
return specialNullRejectedCase1(ctx, schema, expr) // only 1 case now
59+
}
60+
61+
// specialNullRejectedCase1 is mainly for #49616.
62+
// Case1 specially handles `null-rejected OR (null-rejected AND {others})`, then no matter what the result
63+
// of `{others}` is (True, False or Null), the result of this predicate is null, so this predicate is null-rejected.
64+
func specialNullRejectedCase1(ctx context.PlanContext, schema *expression.Schema, expr expression.Expression) bool {
65+
isFunc := func(e expression.Expression, lowerFuncName string) *expression.ScalarFunction {
66+
f, ok := e.(*expression.ScalarFunction)
67+
if !ok {
68+
return nil
69+
}
70+
if f.FuncName.L == lowerFuncName {
71+
return f
72+
}
73+
return nil
74+
}
75+
orFunc := isFunc(expr, ast.LogicOr)
76+
if orFunc == nil {
77+
return false
78+
}
79+
for i := 0; i < 2; i++ {
80+
andFunc := isFunc(orFunc.GetArgs()[i], ast.LogicAnd)
81+
if andFunc == nil {
82+
continue
83+
}
84+
if !IsNullRejected(ctx, schema, orFunc.GetArgs()[1-i]) {
85+
continue // the other side should be null-rejected: null-rejected OR (... AND ...)
86+
}
87+
for _, andItem := range expression.SplitCNFItems(andFunc) {
88+
if IsNullRejected(ctx, schema, andItem) {
89+
return true // hit the case in the comment: null-rejected OR (null-rejected AND ...)
90+
}
91+
}
92+
}
93+
return false
94+
}

0 commit comments

Comments
 (0)
Please sign in to comment.