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: fix #3762, signed integer overflow handle in minus unary scalar function #3780

Merged
merged 24 commits into from
Jul 26, 2017

Conversation

winkyao
Copy link
Contributor

@winkyao winkyao commented Jul 17, 2017

@@ -14,6 +14,10 @@
package expression

import (
"math"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the empty line.

@@ -0,0 +1,54 @@
// Copyright 2015 PingCAP, Inc.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2015 -> 2017

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shenli done

@@ -353,7 +356,15 @@ func (b *builtinUnaryOpSig) eval(row []types.Datum) (d types.Datum, err error) {
case types.KindInt64:
d.SetInt64(-aDatum.GetInt64())
Copy link
Contributor

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.

}{
{uint64(9223372036854775809), "-9223372036854775809", mysql.TypeNewDecimal, true, false},
{uint64(9223372036854775810), "-9223372036854775810", mysql.TypeNewDecimal, true, false},
{uint64(9223372036854775808), "-9223372036854775808", mysql.TypeLonglong, false, false},
Copy link
Contributor

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) ?

@XuHuaiyu XuHuaiyu added the WIP label Jul 18, 2017
types.SetBinChsClnFlag(bf.tp)

overflow := false
if arg, ok := argExpr.(*Constant); ok {
Copy link
Contributor

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.

Copy link
Contributor Author

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`)’

baseIntBuiltinFunc
}

// func (b *builtinUnaryMinusIntSig) eval(row []types.Datum) (d types.Datum, err error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed?

Copy link
Contributor Author

@winkyao winkyao Jul 21, 2017

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)
Copy link
Contributor

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 ?

Copy link
Contributor Author

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

@@ -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:
Copy link
Contributor

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?

Copy link
Contributor Author

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

if constArg, ok := arg.(*Constant); ok {
val, err := constArg.Value.ConvertTo(sc, ft)
if err != nil {
return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors.Trace(err)

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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,

@@ -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])
Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why change here?

Copy link
Contributor Author

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)
	}

Copy link
Contributor Author

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

types.SetBinChsClnFlag(bf.tp)

overflow := false
if arg, ok := argExpr.(*Constant); ok {
Copy link
Member

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 ?

Copy link
Contributor Author

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

@shenli
Copy link
Member

shenli commented Jul 25, 2017

Is this PR still WIP?

@winkyao
Copy link
Contributor Author

winkyao commented Jul 25, 2017

yes. still wip. @shenli

@winkyao
Copy link
Contributor Author

winkyao commented Jul 25, 2017

@winkyao winkyao removed the WIP label Jul 25, 2017
@@ -318,6 +318,7 @@ func (s *testSuite) TestAggPrune(c *C) {
testleak.AfterTest(c)()
}()
tk := testkit.NewTestKit(c, s.store)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless line ?


func (b *unaryMinusFunctionClass) typeInfer(argExpr Expression) (evalTp, bool) {
tp := tpInt
switch argExpr.GetType().Tp {
Copy link
Contributor

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

case types.ClassString:
tp := argExpr.GetType().Tp
if types.IsTypeTime(tp) {
bf, err := newBaseBuiltinFuncWithTp(args, ctx, retTp, tpDecimal)
Copy link
Contributor

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?

}
}

return sig.setSelf(sig), errors.Trace(b.verifyArgs(args))
Copy link
Contributor

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.

return sig.setSelf(sig), errors.Trace(b.verifyArgs(args))
}

func unaryMinusDecimal(dec *types.MyDecimal) (to *types.MyDecimal, err error) {
Copy link
Contributor

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?


// PlainFoldConstant does constant folding optimization
// on an expression without recursively folding.
func PlainFoldConstant(expr Expression) Expression {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused

@winkyao
Copy link
Contributor Author

winkyao commented Jul 25, 2017

@XuHuaiyu

@@ -338,9 +344,19 @@ func (b *builtinUnaryOpSig) eval(row []types.Datum) (d types.Datum, err error) {
case opcode.Minus:
Copy link
Contributor

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?

overflow := false
// TODO: handle float overflow
if arg, ok := argExpr.(*Constant); ok {
switch arg.Value.Kind() {
Copy link
Contributor

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]
Copy link
Contributor

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.

Copy link
Contributor Author

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

// TODO: handle float overflow
if arg, ok := argExpr.(*Constant); ok {
switch arg.Value.Kind() {
case types.KindUint64:
Copy link
Contributor

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.


to := new(types.MyDecimal)
err = types.DecimalSub(new(types.MyDecimal), dec, to)
return to, false, err
Copy link
Contributor

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)

if !TurnOnNewExprEval {
return sf.Function.eval(row)
d, err = sf.Function.eval(row)
Copy link
Contributor

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)
}
}
Copy link
Contributor

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.

@winkyao
Copy link
Contributor Author

winkyao commented Jul 25, 2017

@XuHuaiyu PTAL

Copy link
Contributor

@XuHuaiyu XuHuaiyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rest LGTM

}

func (b *unaryMinusFunctionClass) handleIntOverflow(arg *Constant) (overflow bool) {
overflow = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is useless.

// -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
Copy link
Contributor

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

return
}

func (b *unaryMinusFunctionClass) typeInfer(argExpr Expression, ctx context.Context) (evalTp, bool) {
Copy link
Contributor

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.

@XuHuaiyu
Copy link
Contributor

PTAL @zz-jason

@winkyao winkyao closed this Jul 26, 2017
@winkyao winkyao reopened this Jul 26, 2017
}

// typeInfer infer unary Minus Function return type. when arg is int constant and overflow,
Copy link
Contributor

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

@XuHuaiyu XuHuaiyu added the status/LGT1 Indicates that a PR has LGTM 1. label Jul 26, 2017
@XuHuaiyu
Copy link
Contributor

LGTM

Copy link
Member

@hanfei1991 hanfei1991 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@hanfei1991 hanfei1991 merged commit e008c6c into master Jul 26, 2017
@hanfei1991 hanfei1991 deleted the winkyao/fix_issue_3762 branch July 26, 2017 03:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status/LGT1 Indicates that a PR has LGTM 1. type/compatibility
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants