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

executor: fix a bug for 'int column <cmp> non-int constant' (#11050) #11194

Merged
merged 2 commits into from
Jul 11, 2019
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
110 changes: 80 additions & 30 deletions expression/builtin_compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,13 @@ func isTemporalColumn(expr Expression) bool {
}

// tryToConvertConstantInt tries to convert a constant with other type to a int constant.
func tryToConvertConstantInt(ctx sessionctx.Context, isUnsigned bool, con *Constant) (_ *Constant, isAlwaysFalse bool) {
// isExceptional indicates whether the 'int column [cmp] const' might be true/false.
// If isExceptional is true, ExecptionalVal is returned. Or, CorrectVal is returned.
// CorrectVal: The computed result. If the constant can be converted to int without exception, return the val. Else return 'con'(the input).
// ExceptionalVal : It is used to get more information to check whether 'int column [cmp] const' is true/false
// If the op == LT,LE,GT,GE and it gets an Overflow when converting, return inf/-inf.
// If the op == EQ,NullEQ and the constant can never be equal to the int column, return ‘con’(the input, a non-int constant).
func tryToConvertConstantInt(ctx sessionctx.Context, targetFieldType *types.FieldType, con *Constant) (_ *Constant, isExceptional bool) {
if con.GetType().EvalType() == types.ETInt {
return con, false
}
Expand All @@ -1079,37 +1085,48 @@ func tryToConvertConstantInt(ctx sessionctx.Context, isUnsigned bool, con *Const
return con, false
}
sc := ctx.GetSessionVars().StmtCtx
fieldType := types.NewFieldType(mysql.TypeLonglong)
if isUnsigned {
fieldType.Flag |= mysql.UnsignedFlag
}
dt, err = dt.ConvertTo(sc, fieldType)

dt, err = dt.ConvertTo(sc, targetFieldType)
if err != nil {
return con, terror.ErrorEqual(err, types.ErrOverflow)
if terror.ErrorEqual(err, types.ErrOverflow) {
return &Constant{
Value: dt,
RetType: targetFieldType,
}, true
}
return con, false
}
return &Constant{
Value: dt,
RetType: fieldType,
RetType: targetFieldType,
DeferredExpr: con.DeferredExpr,
}, false
}

// RefineComparedConstant changes an non-integer constant argument to its ceiling or floor result by the given op.
// isAlwaysFalse indicates whether the int column "con" is false.
func RefineComparedConstant(ctx sessionctx.Context, isUnsigned bool, con *Constant, op opcode.Op) (_ *Constant, isAlwaysFalse bool) {
// RefineComparedConstant changes a non-integer constant argument to its ceiling or floor result by the given op.
// isExceptional indicates whether the 'int column [cmp] const' might be true/false.
// If isExceptional is true, ExecptionalVal is returned. Or, CorrectVal is returned.
// CorrectVal: The computed result. If the constant can be converted to int without exception, return the val. Else return 'con'(the input).
// ExceptionalVal : It is used to get more information to check whether 'int column [cmp] const' is true/false
// If the op == LT,LE,GT,GE and it gets an Overflow when converting, return inf/-inf.
// If the op == EQ,NullEQ and the constant can never be equal to the int column, return ‘con’(the input, a non-int constant).
func RefineComparedConstant(ctx sessionctx.Context, targetFieldType types.FieldType, con *Constant, op opcode.Op) (_ *Constant, isExceptional bool) {
dt, err := con.Eval(chunk.Row{})
if err != nil {
return con, false
}
sc := ctx.GetSessionVars().StmtCtx
intFieldType := types.NewFieldType(mysql.TypeLonglong)
if isUnsigned {
intFieldType.Flag |= mysql.UnsignedFlag
}

var intDatum types.Datum
intDatum, err = dt.ConvertTo(sc, intFieldType)
intDatum, err = dt.ConvertTo(sc, &targetFieldType)
if err != nil {
return con, terror.ErrorEqual(err, types.ErrOverflow)
if terror.ErrorEqual(err, types.ErrOverflow) {
return &Constant{
Value: intDatum,
RetType: &targetFieldType,
}, true
}
return con, false
}
c, err := intDatum.CompareDatum(sc, &con.Value)
if err != nil {
Expand All @@ -1118,20 +1135,20 @@ func RefineComparedConstant(ctx sessionctx.Context, isUnsigned bool, con *Consta
if c == 0 {
return &Constant{
Value: intDatum,
RetType: intFieldType,
RetType: &targetFieldType,
DeferredExpr: con.DeferredExpr,
}, false
}
switch op {
case opcode.LT, opcode.GE:
resultExpr := NewFunctionInternal(ctx, ast.Ceil, types.NewFieldType(mysql.TypeUnspecified), con)
if resultCon, ok := resultExpr.(*Constant); ok {
return tryToConvertConstantInt(ctx, isUnsigned, resultCon)
return tryToConvertConstantInt(ctx, &targetFieldType, resultCon)
}
case opcode.LE, opcode.GT:
resultExpr := NewFunctionInternal(ctx, ast.Floor, types.NewFieldType(mysql.TypeUnspecified), con)
if resultCon, ok := resultExpr.(*Constant); ok {
return tryToConvertConstantInt(ctx, isUnsigned, resultCon)
return tryToConvertConstantInt(ctx, &targetFieldType, resultCon)
}
case opcode.NullEQ, opcode.EQ:
switch con.RetType.EvalType() {
Expand Down Expand Up @@ -1159,7 +1176,7 @@ func RefineComparedConstant(ctx sessionctx.Context, isUnsigned bool, con *Consta
}
return &Constant{
Value: intDatum,
RetType: intFieldType,
RetType: &targetFieldType,
DeferredExpr: con.DeferredExpr,
}, false
}
Expand All @@ -1175,27 +1192,60 @@ func (c *compareFunctionClass) refineArgs(ctx sessionctx.Context, args []Express
arg1IsInt := arg1Type.EvalType() == types.ETInt
arg0, arg0IsCon := args[0].(*Constant)
arg1, arg1IsCon := args[1].(*Constant)
isAlways, finalArg0, finalArg1 := false, args[0], args[1]
isExceptional, finalArg0, finalArg1 := false, args[0], args[1]
isPositiveInfinite, isNegativeInfinite := false, false
// int non-constant [cmp] non-int constant
if arg0IsInt && !arg0IsCon && !arg1IsInt && arg1IsCon {
finalArg1, isAlways = RefineComparedConstant(ctx, mysql.HasUnsignedFlag(arg0Type.Flag), arg1, c.op)
arg1, isExceptional = RefineComparedConstant(ctx, *arg0Type, arg1, c.op)
finalArg1 = arg1
if isExceptional && arg1.RetType.EvalType() == types.ETInt {
// Judge it is inf or -inf
// For int:
// inf: 01111111 & 1 == 1
// -inf: 10000000 & 1 == 0
// For uint:
// inf: 11111111 & 1 == 1
// -inf: 00000000 & 0 == 0
if arg1.Value.GetInt64()&1 == 1 {
isPositiveInfinite = true
} else {
isNegativeInfinite = true
}
}
}
// non-int constant [cmp] int non-constant
if arg1IsInt && !arg1IsCon && !arg0IsInt && arg0IsCon {
finalArg0, isAlways = RefineComparedConstant(ctx, mysql.HasUnsignedFlag(arg1Type.Flag), arg0, symmetricOp[c.op])
arg0, isExceptional = RefineComparedConstant(ctx, *arg1Type, arg0, symmetricOp[c.op])
finalArg0 = arg0
if isExceptional && arg0.RetType.EvalType() == types.ETInt {
if arg0.Value.GetInt64()&1 == 1 {
isNegativeInfinite = true
} else {
isPositiveInfinite = true
}
}
}
if !isAlways {
return []Expression{finalArg0, finalArg1}

if isExceptional && (c.op == opcode.EQ || c.op == opcode.NullEQ) {
// This will always be false.
return []Expression{Zero.Clone(), One.Clone()}
}
switch c.op {
case opcode.LT, opcode.LE:
if isPositiveInfinite {
// If the op is opcode.LT, opcode.LE
// This will always be true.
// If the op is opcode.GT, opcode.GE
// This will always be false.
return []Expression{Zero.Clone(), One.Clone()}
case opcode.EQ, opcode.NullEQ, opcode.GT, opcode.GE:
}
if isNegativeInfinite {
// If the op is opcode.GT, opcode.GE
// This will always be true.
// If the op is opcode.LT, opcode.LE
// This will always be false.
return []Expression{One.Clone(), Zero.Clone()}
}
return args

return []Expression{finalArg0, finalArg1}
}

// getFunction sets compare built-in function signatures for various types.
Expand Down
4 changes: 4 additions & 0 deletions expression/builtin_compare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (s *testEvaluatorSuite) TestCompareFunctionWithRefine(c *C) {
{"'1.1' != a", "ne(1.1, cast(a))"},
{"'123456789123456711111189' = a", "0"},
{"123456789123456789.12345 = a", "0"},
{"123456789123456789123456789.12345 > a", "1"},
{"-123456789123456789123456789.12345 > a", "0"},
{"123456789123456789123456789.12345 < a", "0"},
{"-123456789123456789123456789.12345 < a", "1"},
// This cast can not be eliminated,
// since converting "aaaa" to an int will cause DataTruncate error.
{"'aaaa'=a", "eq(cast(aaaa), cast(a))"},
Expand Down
14 changes: 14 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4373,3 +4373,17 @@ func (s *testIntegrationSuite) TestExprPushdownBlacklist(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustQuery(`select * from mysql.expr_pushdown_blacklist`).Check(testkit.Rows())
}

func (s *testIntegrationSuite) TestIssue10675(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
tk.MustExec(`drop table if exists t;`)
tk.MustExec(`create table t(a int);`)
tk.MustExec(`insert into t values(1);`)
tk.MustQuery(`select * from t where a < -184467440737095516167.1;`).Check(testkit.Rows())
tk.MustQuery(`select * from t where a > -184467440737095516167.1;`).Check(
testkit.Rows("1"))
tk.MustQuery(`select * from t where a < 184467440737095516167.1;`).Check(
testkit.Rows("1"))
tk.MustQuery(`select * from t where a > 184467440737095516167.1;`).Check(testkit.Rows())
}
9 changes: 7 additions & 2 deletions expression/simple_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"github.com/pingcap/parser/opcode"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/types/parser_driver"
driver "github.com/pingcap/tidb/types/parser_driver"
"github.com/pingcap/tidb/util"
)

Expand Down Expand Up @@ -468,10 +468,15 @@ func (sr *simpleRewriter) inToExpression(lLen int, not bool, tp *types.FieldType
return
}
leftEt := leftFt.EvalType()

if leftEt == types.ETInt {
for i := 0; i < len(elems); i++ {
if c, ok := elems[i].(*Constant); ok {
elems[i], _ = RefineComparedConstant(sr.ctx, mysql.HasUnsignedFlag(leftFt.Flag), c, opcode.EQ)
var isExceptional bool
elems[i], isExceptional = RefineComparedConstant(sr.ctx, *leftFt, c, opcode.EQ)
if isExceptional {
elems[i] = c
}
}
}
}
Expand Down
8 changes: 6 additions & 2 deletions planner/core/expression_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/types/parser_driver"
driver "github.com/pingcap/tidb/types/parser_driver"
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tidb/util/stringutil"
)
Expand Down Expand Up @@ -1127,7 +1127,11 @@ func (er *expressionRewriter) inToExpression(lLen int, not bool, tp *types.Field
if leftEt == types.ETInt {
for i := 1; i < len(args); i++ {
if c, ok := args[i].(*expression.Constant); ok {
args[i], _ = expression.RefineComparedConstant(er.ctx, mysql.HasUnsignedFlag(leftFt.Flag), c, opcode.EQ)
var isExceptional bool
args[i], isExceptional = expression.RefineComparedConstant(er.ctx, *leftFt, c, opcode.EQ)
if isExceptional {
args[i] = c
}
}
}
}
Expand Down