Skip to content

Commit ab57dc6

Browse files
authored
expression, planner: do not eliminate the innermost NOT when push down not (#16451)
1 parent 779d72e commit ab57dc6

File tree

7 files changed

+41
-32
lines changed

7 files changed

+41
-32
lines changed

expression/builtin_control.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (c *caseWhenFunctionClass) getFunction(ctx sessionctx.Context, args []Expre
174174
}
175175
argTps := make([]types.EvalType, 0, l)
176176
for i := 0; i < l-1; i += 2 {
177-
if args[i], err = wrapWithIsTrue(ctx, true, args[i], false); err != nil {
177+
if args[i], err = wrapWithIsTrue(ctx, true, args[i]); err != nil {
178178
return nil, err
179179
}
180180
argTps = append(argTps, types.ETInt, tp)
@@ -474,7 +474,7 @@ func (c *ifFunctionClass) getFunction(ctx sessionctx.Context, args []Expression)
474474
}
475475
retTp := InferType4ControlFuncs(args[1].GetType(), args[2].GetType())
476476
evalTps := retTp.EvalType()
477-
args[0], err = wrapWithIsTrue(ctx, true, args[0], false)
477+
args[0], err = wrapWithIsTrue(ctx, true, args[0])
478478
if err != nil {
479479
return nil, err
480480
}

expression/builtin_op.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ func (c *logicAndFunctionClass) getFunction(ctx sessionctx.Context, args []Expre
6767
if err != nil {
6868
return nil, err
6969
}
70-
args[0], err = wrapWithIsTrue(ctx, true, args[0], false)
70+
args[0], err = wrapWithIsTrue(ctx, true, args[0])
7171
if err != nil {
7272
return nil, errors.Trace(err)
7373
}
74-
args[1], err = wrapWithIsTrue(ctx, true, args[1], false)
74+
args[1], err = wrapWithIsTrue(ctx, true, args[1])
7575
if err != nil {
7676
return nil, errors.Trace(err)
7777
}
@@ -117,11 +117,11 @@ func (c *logicOrFunctionClass) getFunction(ctx sessionctx.Context, args []Expres
117117
if err != nil {
118118
return nil, err
119119
}
120-
args[0], err = wrapWithIsTrue(ctx, true, args[0], false)
120+
args[0], err = wrapWithIsTrue(ctx, true, args[0])
121121
if err != nil {
122122
return nil, errors.Trace(err)
123123
}
124-
args[1], err = wrapWithIsTrue(ctx, true, args[1], false)
124+
args[1], err = wrapWithIsTrue(ctx, true, args[1])
125125
if err != nil {
126126
return nil, errors.Trace(err)
127127
}

expression/expression.go

+2-10
Original file line numberDiff line numberDiff line change
@@ -388,17 +388,9 @@ func IsBinaryLiteral(expr Expression) bool {
388388
// The `keepNull` controls what the istrue function will return when `arg` is null:
389389
// 1. keepNull is true and arg is null, the istrue function returns null.
390390
// 2. keepNull is false and arg is null, the istrue function returns 0.
391-
// The `wrapForInt` indicates whether we need to wrapIsTrue for non-logical Expression with int type.
392-
func wrapWithIsTrue(ctx sessionctx.Context, keepNull bool, arg Expression, wrapForInt bool) (Expression, error) {
391+
func wrapWithIsTrue(ctx sessionctx.Context, keepNull bool, arg Expression) (Expression, error) {
393392
if arg.GetType().EvalType() == types.ETInt {
394-
if !wrapForInt {
395-
return arg, nil
396-
}
397-
if child, ok := arg.(*ScalarFunction); ok {
398-
if _, isLogicalOp := logicalOps[child.FuncName.L]; isLogicalOp {
399-
return arg, nil
400-
}
401-
}
393+
return arg, nil
402394
}
403395
fc := &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsTruth, 1, 1}, opcode.IsTruth, keepNull}
404396
f, err := fc.getFunction(ctx, []Expression{arg})

expression/util.go

+13-7
Original file line numberDiff line numberDiff line change
@@ -328,16 +328,22 @@ func pushNotAcrossExpr(ctx sessionctx.Context, expr Expression, not bool) (_ Exp
328328
if f, ok := expr.(*ScalarFunction); ok {
329329
switch f.FuncName.L {
330330
case ast.UnaryNot:
331-
child, err := wrapWithIsTrue(ctx, true, f.GetArgs()[0], true)
332-
if err != nil {
333-
return expr, false
334-
}
335331
var childExpr Expression
336-
childExpr, changed = pushNotAcrossExpr(f.GetCtx(), child, !not)
337-
if !changed && !not {
332+
// UnaryNot only returns 0/1/NULL, thus we should not eliminate the
333+
// innermost NOT if the arg is not a logical operator.
334+
switch child := f.GetArgs()[0].(type) {
335+
case *ScalarFunction:
336+
if _, isLogicalOp := logicalOps[child.FuncName.L]; !isLogicalOp {
337+
return expr, false
338+
}
339+
childExpr, changed = pushNotAcrossExpr(f.GetCtx(), f.GetArgs()[0], !not)
340+
if !changed && !not {
341+
return expr, false
342+
}
343+
return childExpr, true
344+
case *Column:
338345
return expr, false
339346
}
340-
return childExpr, true
341347
case ast.LT, ast.GE, ast.GT, ast.LE, ast.EQ, ast.NE:
342348
if not {
343349
return NewFunctionInternal(f.GetCtx(), oppositeOp[f.FuncName.L], f.GetType(), f.GetArgs()...), true

expression/util_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -127,33 +127,33 @@ func (s *testUtilSuite) TestPushDownNot(c *check.C) {
127127
c.Assert(notFunc.Equal(ctx, notFuncCopy), check.IsTrue)
128128

129129
// issue 15725
130-
// (not not a) should be optimized to (a is true)
130+
// (not not a) should not be optimized to (a)
131131
notFunc = newFunction(ast.UnaryNot, col)
132132
notFunc = newFunction(ast.UnaryNot, notFunc)
133133
ret = PushDownNot(ctx, notFunc)
134-
c.Assert(ret.Equal(ctx, newFunction(ast.IsTruth, col)), check.IsTrue)
134+
c.Assert(ret.Equal(ctx, col), check.IsFalse)
135135

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

143-
// (not not not a) should be optimized to (not (a is true))
143+
// (not not not a) should be optimized to (not a)
144144
notFunc = newFunction(ast.UnaryNot, col)
145145
notFunc = newFunction(ast.UnaryNot, notFunc)
146146
notFunc = newFunction(ast.UnaryNot, notFunc)
147147
ret = PushDownNot(ctx, notFunc)
148-
c.Assert(ret.Equal(ctx, newFunction(ast.UnaryNot, newFunction(ast.IsTruth, col))), check.IsTrue)
148+
c.Assert(ret.Equal(ctx, newFunction(ast.UnaryNot, col)), check.IsTrue)
149149

150-
// (not not not not a) should be optimized to (a is true)
150+
// (not not not not a) should be optimized to (not not a)
151151
notFunc = newFunction(ast.UnaryNot, col)
152152
notFunc = newFunction(ast.UnaryNot, notFunc)
153153
notFunc = newFunction(ast.UnaryNot, notFunc)
154154
notFunc = newFunction(ast.UnaryNot, notFunc)
155155
ret = PushDownNot(ctx, notFunc)
156-
c.Assert(ret.Equal(ctx, newFunction(ast.IsTruth, col)), check.IsTrue)
156+
c.Assert(ret.Equal(ctx, newFunction(ast.UnaryNot, newFunction(ast.UnaryNot, col))), check.IsTrue)
157157
}
158158

159159
func (s *testUtilSuite) TestFilter(c *check.C) {

planner/core/integration_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,14 @@ func (s *testIntegrationSuite) TestIssue15813(c *C) {
320320
tk.MustExec("CREATE INDEX i0 ON t1(c0)")
321321
tk.MustQuery("select /*+ MERGE_JOIN(t0, t1) */ * from t0, t1 where t0.c0 = t1.c0").Check(testkit.Rows())
322322
}
323+
324+
func (s *testIntegrationSuite) TestIssue16440(c *C) {
325+
tk := testkit.NewTestKit(c, s.store)
326+
327+
tk.MustExec("use test")
328+
tk.MustExec("drop table if exists t1")
329+
tk.MustExec("create table t1(c0 int)")
330+
tk.MustExec("INSERT INTO t1(c0) VALUES (NULL);")
331+
tk.MustQuery("SELECT t1.c0 FROM t1 WHERE NOT t1.c0;").Check(testkit.Rows())
332+
tk.MustExec("drop table t1")
333+
}

planner/core/testdata/plan_suite_out.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@
687687
},
688688
{
689689
"SQL": "select a from t where not a",
690-
"Best": "TableReader(Table(t))"
690+
"Best": "IndexReader(Index(t.c_d_e)[[NULL,+inf]]->Sel([not(test.t.a)]))"
691691
},
692692
{
693693
"SQL": "select a from t where c in (1)",

0 commit comments

Comments
 (0)