Skip to content

Commit

Permalink
expression: add new scalar function IsTruthWithNull (#19621) (#19901)
Browse files Browse the repository at this point in the history
  • Loading branch information
ti-srebot authored Sep 17, 2020
1 parent 11bae21 commit e6f07c5
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 76 deletions.
2 changes: 1 addition & 1 deletion executor/reload_expr_pushdown_blacklist.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ var funcName2Alias = map[string]string{
"case": ast.Case,
"regexp": ast.Regexp,
"is null": ast.IsNull,
"is true": ast.IsTruth,
"is true": ast.IsTruthWithoutNull,
"is false": ast.IsFalsity,
"values": ast.Values,
"bit_count": ast.BitCount,
Expand Down
71 changes: 36 additions & 35 deletions expression/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,41 +519,42 @@ var funcs = map[string]functionClass{
ast.GetLock: &lockFunctionClass{baseFunctionClass{ast.GetLock, 2, 2}},
ast.ReleaseLock: &releaseLockFunctionClass{baseFunctionClass{ast.ReleaseLock, 1, 1}},

ast.LogicAnd: &logicAndFunctionClass{baseFunctionClass{ast.LogicAnd, 2, 2}},
ast.LogicOr: &logicOrFunctionClass{baseFunctionClass{ast.LogicOr, 2, 2}},
ast.LogicXor: &logicXorFunctionClass{baseFunctionClass{ast.LogicXor, 2, 2}},
ast.GE: &compareFunctionClass{baseFunctionClass{ast.GE, 2, 2}, opcode.GE},
ast.LE: &compareFunctionClass{baseFunctionClass{ast.LE, 2, 2}, opcode.LE},
ast.EQ: &compareFunctionClass{baseFunctionClass{ast.EQ, 2, 2}, opcode.EQ},
ast.NE: &compareFunctionClass{baseFunctionClass{ast.NE, 2, 2}, opcode.NE},
ast.LT: &compareFunctionClass{baseFunctionClass{ast.LT, 2, 2}, opcode.LT},
ast.GT: &compareFunctionClass{baseFunctionClass{ast.GT, 2, 2}, opcode.GT},
ast.NullEQ: &compareFunctionClass{baseFunctionClass{ast.NullEQ, 2, 2}, opcode.NullEQ},
ast.Plus: &arithmeticPlusFunctionClass{baseFunctionClass{ast.Plus, 2, 2}},
ast.Minus: &arithmeticMinusFunctionClass{baseFunctionClass{ast.Minus, 2, 2}},
ast.Mod: &arithmeticModFunctionClass{baseFunctionClass{ast.Mod, 2, 2}},
ast.Div: &arithmeticDivideFunctionClass{baseFunctionClass{ast.Div, 2, 2}},
ast.Mul: &arithmeticMultiplyFunctionClass{baseFunctionClass{ast.Mul, 2, 2}},
ast.IntDiv: &arithmeticIntDivideFunctionClass{baseFunctionClass{ast.IntDiv, 2, 2}},
ast.BitNeg: &bitNegFunctionClass{baseFunctionClass{ast.BitNeg, 1, 1}},
ast.And: &bitAndFunctionClass{baseFunctionClass{ast.And, 2, 2}},
ast.LeftShift: &leftShiftFunctionClass{baseFunctionClass{ast.LeftShift, 2, 2}},
ast.RightShift: &rightShiftFunctionClass{baseFunctionClass{ast.RightShift, 2, 2}},
ast.UnaryNot: &unaryNotFunctionClass{baseFunctionClass{ast.UnaryNot, 1, 1}},
ast.Or: &bitOrFunctionClass{baseFunctionClass{ast.Or, 2, 2}},
ast.Xor: &bitXorFunctionClass{baseFunctionClass{ast.Xor, 2, 2}},
ast.UnaryMinus: &unaryMinusFunctionClass{baseFunctionClass{ast.UnaryMinus, 1, 1}},
ast.In: &inFunctionClass{baseFunctionClass{ast.In, 2, -1}},
ast.IsTruth: &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsTruth, 1, 1}, opcode.IsTruth, false},
ast.IsFalsity: &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsFalsity, 1, 1}, opcode.IsFalsity, false},
ast.Like: &likeFunctionClass{baseFunctionClass{ast.Like, 3, 3}},
ast.Regexp: &regexpFunctionClass{baseFunctionClass{ast.Regexp, 2, 2}},
ast.Case: &caseWhenFunctionClass{baseFunctionClass{ast.Case, 1, -1}},
ast.RowFunc: &rowFunctionClass{baseFunctionClass{ast.RowFunc, 2, -1}},
ast.SetVar: &setVarFunctionClass{baseFunctionClass{ast.SetVar, 2, 2}},
ast.GetVar: &getVarFunctionClass{baseFunctionClass{ast.GetVar, 1, 1}},
ast.BitCount: &bitCountFunctionClass{baseFunctionClass{ast.BitCount, 1, 1}},
ast.GetParam: &getParamFunctionClass{baseFunctionClass{ast.GetParam, 1, 1}},
ast.LogicAnd: &logicAndFunctionClass{baseFunctionClass{ast.LogicAnd, 2, 2}},
ast.LogicOr: &logicOrFunctionClass{baseFunctionClass{ast.LogicOr, 2, 2}},
ast.LogicXor: &logicXorFunctionClass{baseFunctionClass{ast.LogicXor, 2, 2}},
ast.GE: &compareFunctionClass{baseFunctionClass{ast.GE, 2, 2}, opcode.GE},
ast.LE: &compareFunctionClass{baseFunctionClass{ast.LE, 2, 2}, opcode.LE},
ast.EQ: &compareFunctionClass{baseFunctionClass{ast.EQ, 2, 2}, opcode.EQ},
ast.NE: &compareFunctionClass{baseFunctionClass{ast.NE, 2, 2}, opcode.NE},
ast.LT: &compareFunctionClass{baseFunctionClass{ast.LT, 2, 2}, opcode.LT},
ast.GT: &compareFunctionClass{baseFunctionClass{ast.GT, 2, 2}, opcode.GT},
ast.NullEQ: &compareFunctionClass{baseFunctionClass{ast.NullEQ, 2, 2}, opcode.NullEQ},
ast.Plus: &arithmeticPlusFunctionClass{baseFunctionClass{ast.Plus, 2, 2}},
ast.Minus: &arithmeticMinusFunctionClass{baseFunctionClass{ast.Minus, 2, 2}},
ast.Mod: &arithmeticModFunctionClass{baseFunctionClass{ast.Mod, 2, 2}},
ast.Div: &arithmeticDivideFunctionClass{baseFunctionClass{ast.Div, 2, 2}},
ast.Mul: &arithmeticMultiplyFunctionClass{baseFunctionClass{ast.Mul, 2, 2}},
ast.IntDiv: &arithmeticIntDivideFunctionClass{baseFunctionClass{ast.IntDiv, 2, 2}},
ast.BitNeg: &bitNegFunctionClass{baseFunctionClass{ast.BitNeg, 1, 1}},
ast.And: &bitAndFunctionClass{baseFunctionClass{ast.And, 2, 2}},
ast.LeftShift: &leftShiftFunctionClass{baseFunctionClass{ast.LeftShift, 2, 2}},
ast.RightShift: &rightShiftFunctionClass{baseFunctionClass{ast.RightShift, 2, 2}},
ast.UnaryNot: &unaryNotFunctionClass{baseFunctionClass{ast.UnaryNot, 1, 1}},
ast.Or: &bitOrFunctionClass{baseFunctionClass{ast.Or, 2, 2}},
ast.Xor: &bitXorFunctionClass{baseFunctionClass{ast.Xor, 2, 2}},
ast.UnaryMinus: &unaryMinusFunctionClass{baseFunctionClass{ast.UnaryMinus, 1, 1}},
ast.In: &inFunctionClass{baseFunctionClass{ast.In, 2, -1}},
ast.IsTruthWithoutNull: &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsTruthWithoutNull, 1, 1}, opcode.IsTruth, false},
ast.IsTruthWithNull: &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsTruthWithNull, 1, 1}, opcode.IsTruth, true},
ast.IsFalsity: &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsFalsity, 1, 1}, opcode.IsFalsity, false},
ast.Like: &likeFunctionClass{baseFunctionClass{ast.Like, 3, 3}},
ast.Regexp: &regexpFunctionClass{baseFunctionClass{ast.Regexp, 2, 2}},
ast.Case: &caseWhenFunctionClass{baseFunctionClass{ast.Case, 1, -1}},
ast.RowFunc: &rowFunctionClass{baseFunctionClass{ast.RowFunc, 2, -1}},
ast.SetVar: &setVarFunctionClass{baseFunctionClass{ast.SetVar, 2, 2}},
ast.GetVar: &getVarFunctionClass{baseFunctionClass{ast.GetVar, 1, 1}},
ast.BitCount: &bitCountFunctionClass{baseFunctionClass{ast.BitCount, 1, 1}},
ast.GetParam: &getParamFunctionClass{baseFunctionClass{ast.GetParam, 1, 1}},

// encryption and compression functions
ast.AesDecrypt: &aesDecryptFunctionClass{baseFunctionClass{ast.AesDecrypt, 2, 3}},
Expand Down
2 changes: 1 addition & 1 deletion expression/builtin_op_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ func (s *testEvaluatorSuite) TestIsTrueOrFalse(c *C) {
}

for _, tc := range testCases {
isTrueSig, err := funcs[ast.IsTruth].getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(tc.args...)))
isTrueSig, err := funcs[ast.IsTruthWithoutNull].getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(tc.args...)))
c.Assert(err, IsNil)
c.Assert(isTrueSig, NotNil)

Expand Down
3 changes: 2 additions & 1 deletion expression/expr_to_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ func (pc PbConverter) canFuncBePushed(sf *ScalarFunction) bool {
ast.In,
ast.IsNull,
ast.Like,
ast.IsTruth,
ast.IsTruthWithoutNull,
ast.IsTruthWithNull,
ast.IsFalsity,

// arithmetical functions.
Expand Down
12 changes: 10 additions & 2 deletions expression/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,15 +396,23 @@ func wrapWithIsTrue(ctx sessionctx.Context, keepNull bool, arg Expression) (Expr
if arg.GetType().EvalType() == types.ETInt {
return arg, nil
}
fc := &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsTruth, 1, 1}, opcode.IsTruth, keepNull}
var fc *isTrueOrFalseFunctionClass
if keepNull {
fc = &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsTruthWithNull, 1, 1}, opcode.IsTruth, keepNull}
} else {
fc = &isTrueOrFalseFunctionClass{baseFunctionClass{ast.IsTruthWithoutNull, 1, 1}, opcode.IsTruth, keepNull}
}
f, err := fc.getFunction(ctx, []Expression{arg})
if err != nil {
return nil, err
}
sf := &ScalarFunction{
FuncName: model.NewCIStr(ast.IsTruth),
FuncName: model.NewCIStr(ast.IsTruthWithoutNull),
Function: f,
RetType: f.getRetTp(),
}
if keepNull {
sf.FuncName = model.NewCIStr(ast.IsTruthWithNull)
}
return FoldConstant(sf), nil
}
23 changes: 23 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5162,3 +5162,26 @@ func (s *testIntegrationSuite) TestIssue19804(c *C) {
_, err = tk.Exec("alter table t change a a set('a', 'b', 'c', 'e', 'f');")
c.Assert(err, NotNil)
}

func (s *testIntegrationSuite) TestIssue17476(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
tk.MustExec("DROP TABLE IF EXISTS `table_float`;")
tk.MustExec("DROP TABLE IF EXISTS `table_int_float_varchar`;")
tk.MustExec("CREATE TABLE `table_float` (`id_1` int(16) NOT NULL AUTO_INCREMENT,`col_float_1` float DEFAULT NULL,PRIMARY KEY (`id_1`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=97635;")
tk.MustExec("CREATE TABLE `table_int_float_varchar` " +
"(`id_6` int(16) NOT NULL AUTO_INCREMENT," +
"`col_int_6` int(16) DEFAULT NULL,`col_float_6` float DEFAULT NULL," +
"`col_varchar_6` varchar(511) DEFAULT NULL,PRIMARY KEY (`id_6`)," +
"KEY `vhyen` (`id_6`,`col_int_6`,`col_float_6`,`col_varchar_6`(1))," +
"KEY `zzylq` (`id_6`,`col_int_6`,`col_float_6`,`col_varchar_6`(1))) " +
"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=90818;")

tk.MustExec("INSERT INTO `table_float` VALUES (1,NULL),(2,0.1),(3,0),(4,-0.1),(5,-0.1),(6,NULL),(7,0.5),(8,0),(9,0),(10,NULL),(11,1),(12,1.5),(13,NULL),(14,NULL);")
tk.MustExec("INSERT INTO `table_int_float_varchar` VALUES (1,0,0.1,'true'),(2,-1,1.5,'2020-02-02 02:02:00'),(3,NULL,1.5,NULL),(4,65535,0.1,'true'),(5,NULL,0.1,'1'),(6,-1,1.5,'2020-02-02 02:02:00'),(7,-1,NULL,''),(8,NULL,-0.1,NULL),(9,NULL,-0.1,'1'),(10,-1,NULL,''),(11,NULL,1.5,'false'),(12,-1,0,NULL),(13,0,-0.1,NULL),(14,-1,NULL,'-0'),(15,65535,-1,'1'),(16,NULL,0.5,NULL),(17,-1,NULL,NULL);")
tk.MustQuery(`select count(*) from table_float
JOIN table_int_float_varchar AS tmp3 ON (tmp3.col_varchar_6 AND NULL)
IS NULL WHERE col_int_6=0;`).Check(testkit.Rows("14"))
tk.MustQuery(`SELECT count(*) FROM (table_float JOIN table_int_float_varchar AS tmp3 ON (tmp3.col_varchar_6 AND NULL) IS NULL);`).Check(testkit.Rows("154"))
tk.MustQuery(`SELECT * FROM (table_int_float_varchar AS tmp3) WHERE (col_varchar_6 AND NULL) IS NULL AND col_int_6=0;`).Check(testkit.Rows("13 0 -0.1 <nil>"))
}
2 changes: 1 addition & 1 deletion expression/simple_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ func (sr *simpleRewriter) notToExpression(hasNot bool, op string, tp *types.Fiel

func (sr *simpleRewriter) isTrueToScalarFunc(v *ast.IsTruthExpr) {
arg := sr.pop()
op := ast.IsTruth
op := ast.IsTruthWithoutNull
if v.True == 0 {
op = ast.IsFalsity
}
Expand Down
30 changes: 15 additions & 15 deletions expression/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,21 +270,21 @@ func timeZone2Duration(tz string) time.Duration {
}

var logicalOps = map[string]struct{}{
ast.LT: {},
ast.GE: {},
ast.GT: {},
ast.LE: {},
ast.EQ: {},
ast.NE: {},
ast.UnaryNot: {},
ast.LogicAnd: {},
ast.LogicOr: {},
ast.LogicXor: {},
ast.In: {},
ast.IsNull: {},
ast.IsTruth: {},
ast.IsFalsity: {},
ast.Like: {},
ast.LT: {},
ast.GE: {},
ast.GT: {},
ast.LE: {},
ast.EQ: {},
ast.NE: {},
ast.UnaryNot: {},
ast.LogicAnd: {},
ast.LogicOr: {},
ast.LogicXor: {},
ast.In: {},
ast.IsNull: {},
ast.IsTruthWithoutNull: {},
ast.IsFalsity: {},
ast.Like: {},
}

var oppositeOp = map[string]string{
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ require (
github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e
github.com/pingcap/kvproto v0.0.0-20200311073257-e53d835099b0
github.com/pingcap/log v0.0.0-20190715063458-479153f07ebd
github.com/pingcap/parser v3.0.17-0.20200901060850-21ac2654ca11+incompatible
github.com/pingcap/parser v3.0.17-0.20200917074335-8b16316f8a6e+incompatible
github.com/pingcap/pd v1.1.0-beta.0.20191223090411-ea2b748f6ee2
github.com/pingcap/tidb-tools v3.0.6-0.20191119150227-ff0a3c6e5763+incompatible
github.com/pingcap/tipb v0.0.0-20200426072559-d2c068e96eb3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ github.com/pingcap/kvproto v0.0.0-20200311073257-e53d835099b0 h1:dXXNHvDwAEN1YNg
github.com/pingcap/kvproto v0.0.0-20200311073257-e53d835099b0/go.mod h1:QMdbTAXCHzzygQzqcG9uVUgU2fKeSN1GmfMiykdSzzY=
github.com/pingcap/log v0.0.0-20190715063458-479153f07ebd h1:hWDol43WY5PGhsh3+8794bFHY1bPrmu6bTalpssCrGg=
github.com/pingcap/log v0.0.0-20190715063458-479153f07ebd/go.mod h1:WpHUKhNZ18v116SvGrmjkA9CBhYmuUTKL+p8JC9ANEw=
github.com/pingcap/parser v3.0.17-0.20200901060850-21ac2654ca11+incompatible h1:pymLtwPxqonEYzYNIZ5CXUKi9gx1AdwrdTlJTU2N5KE=
github.com/pingcap/parser v3.0.17-0.20200901060850-21ac2654ca11+incompatible/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA=
github.com/pingcap/parser v3.0.17-0.20200917074335-8b16316f8a6e+incompatible h1:A82fSnaJr+jRR9X0DquF+JhHdygw1I+YV5rrqWf95DY=
github.com/pingcap/parser v3.0.17-0.20200917074335-8b16316f8a6e+incompatible/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA=
github.com/pingcap/pd v1.1.0-beta.0.20191223090411-ea2b748f6ee2 h1:NL23b8tsg6M1QpSQedK14/Jx++QeyKL2rGiBvXAQVfA=
github.com/pingcap/pd v1.1.0-beta.0.20191223090411-ea2b748f6ee2/go.mod h1:b4gaAPSxaVVtaB+EHamV4Nsv8JmTdjlw0cTKmp4+dRQ=
github.com/pingcap/tidb-tools v3.0.6-0.20191119150227-ff0a3c6e5763+incompatible h1:I8HirWsu1MZp6t9G/g8yKCEjJJxtHooKakEgccvdJ4M=
Expand Down
2 changes: 1 addition & 1 deletion planner/core/expression_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,7 @@ func (er *expressionRewriter) positionToScalarFunc(v *ast.PositionExpr) {

func (er *expressionRewriter) isTrueToScalarFunc(v *ast.IsTruthExpr) {
stkLen := len(er.ctxStack)
op := ast.IsTruth
op := ast.IsTruthWithoutNull
if v.True == 0 {
op = ast.IsFalsity
}
Expand Down
2 changes: 1 addition & 1 deletion util/ranger/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (c *conditionChecker) checkScalarFunction(scalar *expression.ScalarFunction
}
case ast.IsNull:
return c.checkColumn(scalar.GetArgs()[0])
case ast.IsTruth, ast.IsFalsity:
case ast.IsTruthWithoutNull, ast.IsTruthWithNull, ast.IsFalsity:
if s, ok := scalar.GetArgs()[0].(*expression.Column); ok {
if s.RetType.EvalType() == types.ETString {
return false
Expand Down
23 changes: 8 additions & 15 deletions util/ranger/points.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"github.com/pingcap/tidb/sessionctx/stmtctx"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tipb/go-tipb"
)

// Error instances.
Expand Down Expand Up @@ -475,9 +474,10 @@ func (r *builder) newBuildFromPatternLike(expr *expression.ScalarFunction) []poi

func (r *builder) buildFromNot(expr *expression.ScalarFunction) []point {
switch n := expr.FuncName.L; n {
case ast.IsTruth:
keepNull := r.isTrueKeepNull(expr)
return r.buildFromIsTrue(expr, 1, keepNull)
case ast.IsTruthWithoutNull:
return r.buildFromIsTrue(expr, 1, false)
case ast.IsTruthWithNull:
return r.buildFromIsTrue(expr, 1, true)
case ast.IsFalsity:
return r.buildFromIsFalse(expr, 1)
case ast.In:
Expand Down Expand Up @@ -532,9 +532,10 @@ func (r *builder) buildFromScalarFunc(expr *expression.ScalarFunction) []point {
return r.intersection(r.build(expr.GetArgs()[0]), r.build(expr.GetArgs()[1]))
case ast.LogicOr:
return r.union(r.build(expr.GetArgs()[0]), r.build(expr.GetArgs()[1]))
case ast.IsTruth:
keepNull := r.isTrueKeepNull(expr)
return r.buildFromIsTrue(expr, 0, keepNull)
case ast.IsTruthWithoutNull:
return r.buildFromIsTrue(expr, 0, false)
case ast.IsTruthWithNull:
return r.buildFromIsTrue(expr, 0, true)
case ast.IsFalsity:
return r.buildFromIsFalse(expr, 0)
case ast.In:
Expand Down Expand Up @@ -595,11 +596,3 @@ func (r *builder) merge(a, b []point, union bool) []point {
}
return merged
}

func (r *builder) isTrueKeepNull(expr *expression.ScalarFunction) bool {
switch expr.Function.PbCode() {
case tipb.ScalarFuncSig_DecimalIsTrueWithNull, tipb.ScalarFuncSig_RealIsTrueWithNull, tipb.ScalarFuncSig_IntIsTrueWithNull:
return true
}
return false
}

0 comments on commit e6f07c5

Please sign in to comment.