-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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: fix #3762, signed integer overflow handle in minus unary scalar function #3780
Conversation
expression/builtin_op.go
Outdated
@@ -14,6 +14,10 @@ | |||
package expression | |||
|
|||
import ( | |||
"math" | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the empty line.
expression/builtin_op_test.go
Outdated
@@ -0,0 +1,54 @@ | |||
// Copyright 2015 PingCAP, Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2015 -> 2017
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shenli done
expression/builtin_op.go
Outdated
@@ -353,7 +356,15 @@ func (b *builtinUnaryOpSig) eval(row []types.Datum) (d types.Datum, err error) { | |||
case types.KindInt64: | |||
d.SetInt64(-aDatum.GetInt64()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess opcode.Minus operation on math.MinInt64
would get wrong result as range of negative is greater then range of positive by 1.
expression/builtin_op_test.go
Outdated
}{ | ||
{uint64(9223372036854775809), "-9223372036854775809", mysql.TypeNewDecimal, true, false}, | ||
{uint64(9223372036854775810), "-9223372036854775810", mysql.TypeNewDecimal, true, false}, | ||
{uint64(9223372036854775808), "-9223372036854775808", mysql.TypeLonglong, false, false}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the result of int64(-9223372036854775808)
?
…s 2, should return datum, not row
expression/builtin_op.go
Outdated
types.SetBinChsClnFlag(bf.tp) | ||
|
||
overflow := false | ||
if arg, ok := argExpr.(*Constant); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why check overflow in type infer? If expr is not Constant, this is useless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@XuHuaiyu, because overflow will be handled only if the statement is Select, and if the args type is not constant, MySQL will just throw a overflow error out, see below:
mysql> select * from t;
Field 1: `a`
Catalog: `def`
Database: `test`
Table: `t`
Org_table: `t`
Type: LONGLONG
Collation: binary (63)
Length: 20
Max_length: 19
Decimals: 0
Flags: UNSIGNED NUM
+---------------------+
| a |
+---------------------+
| 9223372036854775809 |
| 9223372036854775808 |
+---------------------+
2 rows in set (0.00 sec)
mysql> select -a from t;
ERROR 1690 (22003): BIGINT value is out of range in ‘-(`test`.`t`.`a`)’
expression/builtin_op.go
Outdated
baseIntBuiltinFunc | ||
} | ||
|
||
// func (b *builtinUnaryMinusIntSig) eval(row []types.Datum) (d types.Datum, err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, this is not the final commit, cause some test case failed I can't fix, so I push the code to github, I will remove these code after problem fixed
} | ||
|
||
if mysql.HasUnsignedFlag(b.args[0].GetType().Flag) { | ||
uval := uint64(val) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move the overflow check logic to EvalInt
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tiancaiamao the overflow logic is only for unary minus
expression/expression.go
Outdated
@@ -170,7 +170,8 @@ func evalExprToDecimal(expr Expression, row []types.Datum, sc *variable.Statemen | |||
if val.IsNull() || err != nil { | |||
return res, val.IsNull(), errors.Trace(err) | |||
} | |||
if expr.GetTypeClass() == types.ClassDecimal { | |||
switch expr.GetTypeClass() { | |||
case types.ClassDecimal, types.ClassInt: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClassInt evaluate to Decimal automatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it will do it correctlly
expression/scalar_function.go
Outdated
if constArg, ok := arg.(*Constant); ok { | ||
val, err := constArg.Value.ConvertTo(sc, ft) | ||
if err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
errors.Trace(err)
expression/scalar_function.go
Outdated
if err != nil && terror.ErrorEqual(err, types.ErrOverflow) && | ||
sf.FuncName.L == ast.UnaryMinus && !sc.InUpdateOrDeleteStmt { | ||
// TODO: fix #3762, overflow convert it to decimal | ||
err = sf.convertArgsToDecimal(sc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reference to this rule?
How about Add function and others?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, mysql did not have any document refer to it, but there is some relevent doc: https://dev.mysql.com/doc/refman/5.7/en/out-of-range-and-overflow.html,
plan/expression_rewriter.go
Outdated
@@ -93,7 +93,8 @@ func (b *planBuilder) rewriteWithPreprocess(expr ast.ExprNode, p LogicalPlan, ag | |||
if getRowLen(er.ctxStack[0]) != 1 { | |||
return nil, nil, ErrOperandColumns.GenByArgs(1) | |||
} | |||
result := expression.FoldConstant(er.ctxStack[0]) | |||
result := expression.PlainFoldConstant(er.ctxStack[0]) | |||
// result := expression.FoldConstant(er.ctxStack[0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove it, keep the code clean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, will clean it in final commit
ret = &expression.Constant{Value: types.NewDatum(c.Value.GetRow()[1:]), RetType: c.GetType()} | ||
if getRowLen(c) == 2 { | ||
ret = &expression.Constant{Value: c.Value.GetRow()[1], RetType: c.GetType()} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why change here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because if getRowLen(c) == 2
, if return c.Value.GetRow()[1:], it will treat it as row, and build a row > row function, but it should be datum > datum in this case, see the
if f, ok := e.(*expression.ScalarFunction); ok {
args := f.GetArgs()
if len(args) == 2 { // in here, do the same thing
return args[1].Clone(), nil
}
ret, err = expression.NewFunction(ctx, f.FuncName.L, f.GetType(), args[1:]...)
return ret, errors.Trace(err)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and if not change this, the test case select row(1, 1) > row(1, 0)
will return 0
, which should return result 1
expression/builtin_op.go
Outdated
types.SetBinChsClnFlag(bf.tp) | ||
|
||
overflow := false | ||
if arg, ok := argExpr.(*Constant); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's weird here. Can we change parser to parse -9223372036854775809
as a constant decimal directly ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, image this case "--9223372036854775809". we should not do too much things in parser
Is this PR still WIP? |
yes. still wip. @shenli |
…o handle overflow gracely
executor/aggregate_test.go
Outdated
@@ -318,6 +318,7 @@ func (s *testSuite) TestAggPrune(c *C) { | |||
testleak.AfterTest(c)() | |||
}() | |||
tk := testkit.NewTestKit(c, s.store) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useless line ?
expression/builtin_op.go
Outdated
|
||
func (b *unaryMinusFunctionClass) typeInfer(argExpr Expression) (evalTp, bool) { | ||
tp := tpInt | ||
switch argExpr.GetType().Tp { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can use argExpr.getClassType() here ?
case types.ClassString, types.ClassReal:
tp = tpReal
case types.ClassDecimal:
tp = tpDecimal
expression/builtin_op.go
Outdated
case types.ClassString: | ||
tp := argExpr.GetType().Tp | ||
if types.IsTypeTime(tp) { | ||
bf, err := newBaseBuiltinFuncWithTp(args, ctx, retTp, tpDecimal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line 465 and ling 471 can be merged?
expression/builtin_op.go
Outdated
} | ||
} | ||
|
||
return sig.setSelf(sig), errors.Trace(b.verifyArgs(args)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we may put b.verifyArgs at the beginning of this func to check the count of args.
expression/builtin_op.go
Outdated
return sig.setSelf(sig), errors.Trace(b.verifyArgs(args)) | ||
} | ||
|
||
func unaryMinusDecimal(dec *types.MyDecimal) (to *types.MyDecimal, err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this func may be unnecessary?
expression/constant_fold.go
Outdated
|
||
// PlainFoldConstant does constant folding optimization | ||
// on an expression without recursively folding. | ||
func PlainFoldConstant(expr Expression) Expression { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused
expression/builtin_op.go
Outdated
@@ -338,9 +344,19 @@ func (b *builtinUnaryOpSig) eval(row []types.Datum) (d types.Datum, err error) { | |||
case opcode.Minus: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code will never reach here?
expression/builtin_op.go
Outdated
overflow := false | ||
// TODO: handle float overflow | ||
if arg, ok := argExpr.(*Constant); ok { | ||
switch arg.Value.Kind() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we may check whether mysql.HasUnsignedFlag(arg.GetType().Flag) here
rather than check arg.Value.Kind()
return nil, errors.Trace(err) | ||
} | ||
|
||
argExpr := args[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argExpr
only used in line 438, we can just use args[0]
instead of argExpr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see line 441 and 469
expression/builtin_op.go
Outdated
// TODO: handle float overflow | ||
if arg, ok := argExpr.(*Constant); ok { | ||
switch arg.Value.Kind() { | ||
case types.KindUint64: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment for the two overflow check.
expression/builtin_op.go
Outdated
|
||
to := new(types.MyDecimal) | ||
err = types.DecimalSub(new(types.MyDecimal), dec, to) | ||
return to, false, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/err/errors.Trace(err)
expression/scalar_function.go
Outdated
if !TurnOnNewExprEval { | ||
return sf.Function.eval(row) | ||
d, err = sf.Function.eval(row) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any difference?
} else { | ||
c.Assert(err, NotNil) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add test case for isDeterminstic
.
@XuHuaiyu PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rest LGTM
expression/builtin_op.go
Outdated
} | ||
|
||
func (b *unaryMinusFunctionClass) handleIntOverflow(arg *Constant) (overflow bool) { | ||
overflow = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is useless.
expression/builtin_op.go
Outdated
// -math.MinInt64 is 9223372036854775808, so if uval is more than 9223372036854775808, like | ||
// 9223372036854775809, -9223372036854775809 is less than math.MinInt64, overflow occurs. | ||
if uval > uint64(-math.MinInt64) { | ||
overflow = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just use return true
here is enough,
since the return value has been named as overflow
which is explicit.
the same as line 368
expression/builtin_op.go
Outdated
return | ||
} | ||
|
||
func (b *unaryMinusFunctionClass) typeInfer(argExpr Expression, ctx context.Context) (evalTp, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a comment for what this function does.
PTAL @zz-jason |
expression/builtin_op.go
Outdated
} | ||
|
||
// typeInfer infer unary Minus Function return type. when arg is int constant and overflow, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ infer/ infers
s/ unary Minus/ unaryMinus
s/ Function/ function
s/ when arg/ When the arg
s/ int/ an int
LGTM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
#3762
@XuHuaiyu @tiancaiamao @zimulala PTAL