Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expression, planner: do not eliminate the innermost NOT when push down not (#16451) #16477

Merged
merged 4 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions expression/builtin_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func (c *caseWhenFunctionClass) getFunction(ctx sessionctx.Context, args []Expre
}
argTps := make([]types.EvalType, 0, l)
for i := 0; i < l-1; i += 2 {
if args[i], err = wrapWithIsTrue(ctx, true, args[i], false); err != nil {
if args[i], err = wrapWithIsTrue(ctx, true, args[i]); err != nil {
return nil, err
}
argTps = append(argTps, types.ETInt, tp)
Expand Down Expand Up @@ -474,7 +474,7 @@ func (c *ifFunctionClass) getFunction(ctx sessionctx.Context, args []Expression)
}
retTp := InferType4ControlFuncs(args[1].GetType(), args[2].GetType())
evalTps := retTp.EvalType()
args[0], err = wrapWithIsTrue(ctx, true, args[0], false)
args[0], err = wrapWithIsTrue(ctx, true, args[0])
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions expression/builtin_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ func (c *logicAndFunctionClass) getFunction(ctx sessionctx.Context, args []Expre
if err != nil {
return nil, err
}
args[0], err = wrapWithIsTrue(ctx, true, args[0], false)
args[0], err = wrapWithIsTrue(ctx, true, args[0])
if err != nil {
return nil, errors.Trace(err)
}
args[1], err = wrapWithIsTrue(ctx, true, args[1], false)
args[1], err = wrapWithIsTrue(ctx, true, args[1])
if err != nil {
return nil, errors.Trace(err)
}
Expand Down Expand Up @@ -117,11 +117,11 @@ func (c *logicOrFunctionClass) getFunction(ctx sessionctx.Context, args []Expres
if err != nil {
return nil, err
}
args[0], err = wrapWithIsTrue(ctx, true, args[0], false)
args[0], err = wrapWithIsTrue(ctx, true, args[0])
if err != nil {
return nil, errors.Trace(err)
}
args[1], err = wrapWithIsTrue(ctx, true, args[1], false)
args[1], err = wrapWithIsTrue(ctx, true, args[1])
if err != nil {
return nil, errors.Trace(err)
}
Expand Down
12 changes: 2 additions & 10 deletions expression/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,17 +391,9 @@ func IsBinaryLiteral(expr Expression) bool {
// The `keepNull` controls what the istrue function will return when `arg` is null:
// 1. keepNull is true and arg is null, the istrue function returns null.
// 2. keepNull is false and arg is null, the istrue function returns 0.
// The `wrapForInt` indicates whether we need to wrapIsTrue for non-logical Expression with int type.
func wrapWithIsTrue(ctx sessionctx.Context, keepNull bool, arg Expression, wrapForInt bool) (Expression, error) {
func wrapWithIsTrue(ctx sessionctx.Context, keepNull bool, arg Expression) (Expression, error) {
if arg.GetType().EvalType() == types.ETInt {
if !wrapForInt {
return arg, nil
}
if child, ok := arg.(*ScalarFunction); ok {
if _, isLogicalOp := logicalOps[child.FuncName.L]; isLogicalOp {
return arg, nil
}
}
return arg, nil
}
fc := &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsTruth, 1, 1}, opcode.IsTruth, keepNull}
f, err := fc.getFunction(ctx, []Expression{arg})
Expand Down
20 changes: 13 additions & 7 deletions expression/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,16 +328,22 @@ func pushNotAcrossExpr(ctx sessionctx.Context, expr Expression, not bool) (_ Exp
if f, ok := expr.(*ScalarFunction); ok {
switch f.FuncName.L {
case ast.UnaryNot:
child, err := wrapWithIsTrue(ctx, true, f.GetArgs()[0], true)
if err != nil {
return expr, false
}
var childExpr Expression
childExpr, changed = pushNotAcrossExpr(f.GetCtx(), child, !not)
if !changed && !not {
// UnaryNot only returns 0/1/NULL, thus we should not eliminate the
// innermost NOT if the arg is not a logical operator.
switch child := f.GetArgs()[0].(type) {
case *ScalarFunction:
if _, isLogicalOp := logicalOps[child.FuncName.L]; !isLogicalOp {
return expr, false
}
childExpr, changed = pushNotAcrossExpr(f.GetCtx(), f.GetArgs()[0], !not)
if !changed && !not {
return expr, false
}
return childExpr, true
case *Column:
return expr, false
}
return childExpr, true
case ast.LT, ast.GE, ast.GT, ast.LE, ast.EQ, ast.NE:
if not {
return NewFunctionInternal(f.GetCtx(), oppositeOp[f.FuncName.L], f.GetType(), f.GetArgs()...), true
Expand Down
16 changes: 8 additions & 8 deletions expression/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,33 +127,33 @@ func (s *testUtilSuite) TestPushDownNot(c *check.C) {
c.Assert(notFunc.Equal(ctx, notFuncCopy), check.IsTrue)

// issue 15725
// (not not a) should be optimized to (a is true)
// (not not a) should not be optimized to (a)
notFunc = newFunction(ast.UnaryNot, col)
notFunc = newFunction(ast.UnaryNot, notFunc)
ret = PushDownNot(ctx, notFunc)
c.Assert(ret.Equal(ctx, newFunction(ast.IsTruth, col)), check.IsTrue)
c.Assert(ret.Equal(ctx, col), check.IsFalse)

// (not not (a+1)) should be optimized to (a+1 is true)
// (not not (a+1)) should not be optimized to (a+1)
plusFunc := newFunction(ast.Plus, col, One)
notFunc = newFunction(ast.UnaryNot, plusFunc)
notFunc = newFunction(ast.UnaryNot, notFunc)
ret = PushDownNot(ctx, notFunc)
c.Assert(ret.Equal(ctx, newFunction(ast.IsTruth, plusFunc)), check.IsTrue)
c.Assert(ret.Equal(ctx, col), check.IsFalse)

// (not not not a) should be optimized to (not (a is true))
// (not not not a) should be optimized to (not a)
notFunc = newFunction(ast.UnaryNot, col)
notFunc = newFunction(ast.UnaryNot, notFunc)
notFunc = newFunction(ast.UnaryNot, notFunc)
ret = PushDownNot(ctx, notFunc)
c.Assert(ret.Equal(ctx, newFunction(ast.UnaryNot, newFunction(ast.IsTruth, col))), check.IsTrue)
c.Assert(ret.Equal(ctx, newFunction(ast.UnaryNot, col)), check.IsTrue)

// (not not not not a) should be optimized to (a is true)
// (not not not not a) should be optimized to (not not a)
notFunc = newFunction(ast.UnaryNot, col)
notFunc = newFunction(ast.UnaryNot, notFunc)
notFunc = newFunction(ast.UnaryNot, notFunc)
notFunc = newFunction(ast.UnaryNot, notFunc)
ret = PushDownNot(ctx, notFunc)
c.Assert(ret.Equal(ctx, newFunction(ast.IsTruth, col)), check.IsTrue)
c.Assert(ret.Equal(ctx, newFunction(ast.UnaryNot, newFunction(ast.UnaryNot, col))), check.IsTrue)
}

func (s *testUtilSuite) TestFilter(c *check.C) {
Expand Down
11 changes: 11 additions & 0 deletions planner/core/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,17 @@ func (s *testIntegrationSuite) TestIssue15813(c *C) {
tk.MustQuery("select /*+ MERGE_JOIN(t0, t1) */ * from t0, t1 where t0.c0 = t1.c0").Check(testkit.Rows())
}

func (s *testIntegrationSuite) TestIssue16440(c *C) {
tk := testkit.NewTestKit(c, s.store)

tk.MustExec("use test")
tk.MustExec("drop table if exists t1")
tk.MustExec("create table t1(c0 int)")
tk.MustExec("INSERT INTO t1(c0) VALUES (NULL);")
tk.MustQuery("SELECT t1.c0 FROM t1 WHERE NOT t1.c0;").Check(testkit.Rows())
tk.MustExec("drop table t1")
}

func (s *testIntegrationSuite) TestIssue15846(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
Expand Down
2 changes: 1 addition & 1 deletion planner/core/testdata/plan_suite_out.json
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@
},
{
"SQL": "select a from t where not a",
"Best": "TableReader(Table(t))"
"Best": "IndexReader(Index(t.f)[[NULL,+inf]]->Sel([not(test.t.a)]))"
},
{
"SQL": "select a from t where c in (1)",
Expand Down