Files
tidb/expression/binop_test.go
2015-09-25 16:24:59 +08:00

557 lines
13 KiB
Go

// Copyright 2015 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"errors"
"time"
. "github.com/pingcap/check"
"github.com/pingcap/tidb/expression/builtin"
"github.com/pingcap/tidb/model"
mysql "github.com/pingcap/tidb/mysqldef"
"github.com/pingcap/tidb/parser/opcode"
"github.com/pingcap/tidb/util/types"
)
var _ = Suite(&testBinOpSuite{})
type testBinOpSuite struct {
}
func (s *testBinOpSuite) SetUpSuite(c *C) {
}
func (s *testBinOpSuite) TestComparisonOp(c *C) {
tbl := []struct {
lhs interface{}
op opcode.Op
rhs interface{}
result int64 // 0 for false, 1 for true
}{
// test EQ
{1, opcode.EQ, 2, 0},
{false, opcode.EQ, false, 1},
{false, opcode.EQ, true, 0},
{true, opcode.EQ, true, 1},
{true, opcode.EQ, false, 0},
{"1", opcode.EQ, true, 1},
{"1", opcode.EQ, false, 0},
// test NEQ
{1, opcode.NE, 2, 1},
{false, opcode.NE, false, 0},
{false, opcode.NE, true, 1},
{true, opcode.NE, true, 0},
{"1", opcode.NE, true, 0},
{"1", opcode.NE, false, 1},
// test GT, GE
{1, opcode.GT, 0, 1},
{1, opcode.GT, 1, 0},
{1, opcode.GE, 1, 1},
{3.14, opcode.GT, 3, 1},
{3.14, opcode.GE, 3.14, 1},
// test LT, LE
{1, opcode.LT, 2, 1},
{1, opcode.LT, 1, 0},
{1, opcode.LE, 1, 1},
}
for _, t := range tbl {
expr := NewBinaryOperation(t.op, Value{t.lhs}, Value{t.rhs})
v, err := expr.Eval(nil, nil)
c.Assert(err, IsNil)
val, err := types.ToBool(v)
c.Assert(err, IsNil)
c.Assert(val, Equals, t.result)
}
// test nil
nilTbl := []struct {
lhs interface{}
op opcode.Op
rhs interface{}
}{
{nil, opcode.EQ, nil},
{nil, opcode.EQ, 1},
{nil, opcode.NE, nil},
{nil, opcode.NE, 1},
{nil, opcode.LT, nil},
{nil, opcode.LT, 1},
{nil, opcode.LE, nil},
{nil, opcode.LE, 1},
{nil, opcode.GT, nil},
{nil, opcode.GT, 1},
{nil, opcode.GE, nil},
{nil, opcode.GE, 1},
}
for _, t := range nilTbl {
expr := NewBinaryOperation(t.op, Value{t.lhs}, Value{t.rhs})
v, err := expr.Eval(nil, nil)
c.Assert(err, IsNil)
c.Assert(v, IsNil)
}
// test error
mock := mockExpr{
isStatic: false,
err: errors.New("must error"),
}
errTbl := []struct {
arg interface{}
op opcode.Op
}{
{1, opcode.EQ},
{1.1, opcode.NE},
{mysql.NewDecimalFromInt(1, 0), opcode.LT},
{1, opcode.LE},
{1.1, opcode.GT},
{1, opcode.GE},
}
for _, t := range errTbl {
expr := NewBinaryOperation(t.op, Value{t.arg}, mock)
_, err := expr.Eval(nil, nil)
c.Assert(err, NotNil)
c.Assert(expr.Clone(), NotNil)
expr = NewBinaryOperation(t.op, mock, Value{t.arg})
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
c.Assert(expr.Clone(), NotNil)
}
mock.err = nil
mock.val = "abc"
for _, t := range errTbl {
expr := NewBinaryOperation(t.op, Value{t.arg}, mock)
_, err := expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr = NewBinaryOperation(t.op, mock, Value{t.arg})
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
}
expr := BinaryOperation{Op: opcode.Plus, L: Value{1}, R: Value{1}}
_, err := expr.evalComparisonOp(nil, nil)
c.Assert(err, NotNil)
}
func (s *testBinOpSuite) TestIdentRelOp(c *C) {
tbl := []struct {
lhs Expression
op opcode.Op
rhs Expression
}{
{&Ident{}, opcode.LT, Value{1}},
{&Ident{}, opcode.LE, Value{1}},
{&Ident{}, opcode.GT, Value{1}},
{&Ident{}, opcode.GE, Value{1}},
{&Ident{}, opcode.EQ, Value{1}},
{&Ident{}, opcode.NE, Value{1}},
{&Ident{}, opcode.Plus, Value{1}},
}
m := map[interface{}]interface{}{
builtin.ExprEvalArgAggEmpty: struct{}{},
}
for _, t := range tbl {
expr := NewBinaryOperation(t.op, t.lhs, t.rhs)
_, err := expr.Eval(nil, m)
c.Assert(err, IsNil)
expr = NewBinaryOperation(t.op, t.rhs, t.lhs)
_, err = expr.Eval(nil, m)
c.Assert(err, IsNil)
}
f := func(name string) *Ident {
return &Ident{
model.NewCIStr(name),
}
}
// IsIdentRelOpVal
relTbl := []struct {
lhs Expression
op opcode.Op
rhs Expression
ret bool
}{
{f("id"), opcode.LT, Value{1}, true},
{f("id"), opcode.LE, Value{1}, true},
{f("id"), opcode.GT, Value{1}, true},
{f("id"), opcode.GE, Value{1}, true},
{f("id"), opcode.EQ, Value{1}, true},
{f("id"), opcode.NE, Value{1}, true},
{f("id"), opcode.Plus, Value{1}, false},
{f("db.id"), opcode.NE, Value{1}, false},
{f("id"), opcode.LT, f("name"), false},
{Value{1}, opcode.NE, Value{1}, false},
}
for _, t := range relTbl {
expr := &BinaryOperation{Op: t.op, L: t.lhs, R: t.rhs}
b, _, _, err := expr.IsIdentRelOpVal()
c.Assert(err, IsNil)
c.Assert(t.ret, Equals, b)
}
}
func (s *testBinOpSuite) TestLogicOp(c *C) {
tbl := []struct {
lhs interface{}
op opcode.Op
rhs interface{}
ret interface{}
}{
{nil, opcode.AndAnd, 1, nil},
{nil, opcode.AndAnd, 0, 0},
{nil, opcode.OrOr, 1, 1},
{nil, opcode.OrOr, 0, nil},
{nil, opcode.LogicXor, 1, nil},
{nil, opcode.LogicXor, 0, nil},
{1, opcode.AndAnd, 0, 0},
{1, opcode.AndAnd, 1, 1},
{1, opcode.OrOr, 0, 1},
{1, opcode.OrOr, 1, 1},
{0, opcode.OrOr, 0, 0},
{1, opcode.LogicXor, 0, 1},
{1, opcode.LogicXor, 1, 0},
{0, opcode.LogicXor, 0, 0},
{0, opcode.LogicXor, 1, 1},
}
for _, t := range tbl {
expr := NewBinaryOperation(t.op, Value{t.lhs}, Value{t.rhs})
v, err := expr.Eval(nil, nil)
c.Assert(err, IsNil)
switch x := t.ret.(type) {
case nil:
c.Assert(v, IsNil)
case int:
c.Assert(v, DeepEquals, int64(x))
}
c.Assert(expr.Clone(), NotNil)
}
// test error
mock := mockExpr{
isStatic: false,
err: errors.New("must error"),
}
errTbl := []struct {
arg interface{}
op opcode.Op
}{
{1, opcode.AndAnd},
{0, opcode.OrOr},
{1, opcode.LogicXor},
}
for _, t := range errTbl {
expr := NewBinaryOperation(t.op, Value{t.arg}, mock)
_, err := expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr = NewBinaryOperation(t.op, mock, Value{t.arg})
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
}
mock.err = nil
mock.val = errors.New("invalid value type")
for _, t := range errTbl {
expr := NewBinaryOperation(t.op, Value{t.arg}, mock)
_, err := expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr = NewBinaryOperation(t.op, mock, Value{t.arg})
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
}
expr := BinaryOperation{
L: Value{1},
R: Value{1},
Op: opcode.Plus,
}
_, err := expr.evalLogicOp(nil, nil)
c.Assert(err, NotNil)
}
func (s *testBinOpSuite) TestBitOp(c *C) {
tbl := []struct {
lhs interface{}
op opcode.Op
rhs interface{}
ret interface{}
}{
{1, opcode.And, 1, 1},
{1, opcode.Or, 1, 1},
{1, opcode.Xor, 1, 0},
{1, opcode.LeftShift, 1, 2},
{2, opcode.RightShift, 1, 1},
{nil, opcode.And, 1, nil},
{1, opcode.And, nil, nil},
{nil, opcode.Or, 1, nil},
{nil, opcode.Xor, 1, nil},
{nil, opcode.LeftShift, 1, nil},
{nil, opcode.RightShift, 1, nil},
}
for _, t := range tbl {
expr := NewBinaryOperation(t.op, Value{t.lhs}, Value{t.rhs})
v, err := expr.Eval(nil, nil)
c.Assert(err, IsNil)
switch x := t.ret.(type) {
case nil:
c.Assert(v, IsNil)
case int:
c.Assert(v, DeepEquals, uint64(x))
}
}
// test error
errTbl := []struct {
arg interface{}
op opcode.Op
}{
{1, opcode.And},
{1, opcode.Or},
{1, opcode.Xor},
{1, opcode.LeftShift},
{1, opcode.RightShift},
}
mock := mockExpr{
val: errors.New("error object"),
}
for _, t := range errTbl {
expr := NewBinaryOperation(t.op, Value{t.arg}, mock)
_, err := expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr = NewBinaryOperation(t.op, mock, Value{t.arg})
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
}
mock.val = nil
mock.err = errors.New("must error")
expr := &BinaryOperation{
Op: opcode.And,
L: Value{1},
R: mock,
}
_, err := expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr.Op = opcode.Plus
expr.R = Value{1}
_, err = expr.evalBitOp(nil, nil)
c.Assert(err, NotNil)
}
func (s *testBinOpSuite) TestNumericOp(c *C) {
tbl := []struct {
lhs interface{}
op opcode.Op
rhs interface{}
ret interface{}
}{
// plus
{1, opcode.Plus, 1, 2},
{1, opcode.Plus, uint64(1), 2},
{1, opcode.Plus, "1", 2},
{1, opcode.Plus, mysql.NewDecimalFromInt(1, 0), 2},
{uint64(1), opcode.Plus, 1, 2},
{uint64(1), opcode.Plus, uint64(1), 2},
{1, opcode.Plus, []byte("1"), 2},
{1, opcode.Plus, mysql.Hex{Value: 1}, 2},
{1, opcode.Plus, mysql.Bit{Value: 1, Width: 1}, 2},
{1, opcode.Plus, mysql.Enum{Name: "a", Value: 1}, 2},
{1, opcode.Plus, mysql.Set{Name: "a", Value: 1}, 2},
// minus
{1, opcode.Minus, 1, 0},
{1, opcode.Minus, uint64(1), 0},
{1, opcode.Minus, float64(1), 0},
{1, opcode.Minus, mysql.NewDecimalFromInt(1, 0), 0},
{uint64(1), opcode.Minus, 1, 0},
{uint64(1), opcode.Minus, uint64(1), 0},
{mysql.NewDecimalFromInt(1, 0), opcode.Minus, 1, 0},
{"1", opcode.Minus, []byte("1"), 0},
// mul
{1, opcode.Mul, 1, 1},
{1, opcode.Mul, uint64(1), 1},
{1, opcode.Mul, float64(1), 1},
{1, opcode.Mul, mysql.NewDecimalFromInt(1, 0), 1},
{uint64(1), opcode.Mul, 1, 1},
{uint64(1), opcode.Mul, uint64(1), 1},
{mysql.Time{}, opcode.Mul, 0, 0},
{mysql.ZeroDuration, opcode.Mul, 0, 0},
{mysql.Time{Time: time.Now(), Fsp: 0, Type: mysql.TypeDatetime}, opcode.Mul, 0, 0},
{mysql.Time{Time: time.Now(), Fsp: 6, Type: mysql.TypeDatetime}, opcode.Mul, 0, 0},
{mysql.Duration{Duration: 100000000, Fsp: 6}, opcode.Mul, 0, 0},
// div
{1, opcode.Div, float64(1), 1},
{1, opcode.Div, float64(0), nil},
{1, opcode.Div, 2, 0.5},
{1, opcode.Div, 0, nil},
// int div
{1, opcode.IntDiv, 2, 0},
{1, opcode.IntDiv, uint64(2), 0},
{1, opcode.IntDiv, 0, nil},
{1, opcode.IntDiv, uint64(0), nil},
{uint64(1), opcode.IntDiv, 2, 0},
{uint64(1), opcode.IntDiv, uint64(2), 0},
{uint64(1), opcode.IntDiv, 0, nil},
{uint64(1), opcode.IntDiv, uint64(0), nil},
{1.0, opcode.IntDiv, 2.0, 0},
{1.0, opcode.IntDiv, 0, nil},
// mod
{10, opcode.Mod, 2, 0},
{10, opcode.Mod, uint64(2), 0},
{10, opcode.Mod, 0, nil},
{10, opcode.Mod, uint64(0), nil},
{-10, opcode.Mod, uint64(2), 0},
{uint64(10), opcode.Mod, 2, 0},
{uint64(10), opcode.Mod, uint64(2), 0},
{uint64(10), opcode.Mod, 0, nil},
{uint64(10), opcode.Mod, uint64(0), nil},
{uint64(10), opcode.Mod, -2, 0},
{float64(10), opcode.Mod, 2, 0},
{float64(10), opcode.Mod, 0, nil},
{mysql.NewDecimalFromInt(10, 0), opcode.Mod, 2, 0},
{mysql.NewDecimalFromInt(10, 0), opcode.Mod, 0, nil},
}
for _, t := range tbl {
expr := NewBinaryOperation(t.op, Value{t.lhs}, Value{t.rhs})
v, err := expr.Eval(nil, nil)
c.Assert(err, IsNil)
switch v.(type) {
case nil:
c.Assert(t.ret, IsNil)
default:
// we use float64 as the result type check for all.
f, err := types.ToFloat64(v)
c.Assert(err, IsNil)
r, err := types.ToFloat64(t.ret)
c.Assert(err, IsNil)
c.Assert(r, Equals, f)
}
}
// test error
expr := &BinaryOperation{}
_, err := expr.evalPlus(1, 1)
c.Assert(err, NotNil)
_, err = expr.evalMinus(1, 1)
c.Assert(err, NotNil)
_, err = expr.evalMul(1, 1)
c.Assert(err, NotNil)
_, err = expr.evalDiv("abc", 1)
c.Assert(err, NotNil)
_, err = expr.evalDiv(float64(1), "abc")
c.Assert(err, NotNil)
_, err = expr.evalDiv(1, "abc")
c.Assert(err, NotNil)
_, err = expr.evalIntDiv("abc", 1)
c.Assert(err, NotNil)
_, err = expr.evalIntDiv(1, "abc")
c.Assert(err, NotNil)
_, err = expr.evalMod("abc", 1)
c.Assert(err, NotNil)
expr.L = Value{1}
expr.R = Value{1}
_, err = expr.evalArithmeticOp(nil, nil)
c.Assert(err, NotNil)
expr.L = mockExpr{err: errors.New("must error")}
_, err = expr.evalArithmeticOp(nil, nil)
c.Assert(err, NotNil)
expr.L = Value{"abc"}
expr.R = Value{1}
_, err = expr.evalArithmeticOp(nil, nil)
c.Assert(err, NotNil)
expr.L = Value{[]byte("abc")}
expr.R = Value{1}
_, err = expr.evalArithmeticOp(nil, nil)
c.Assert(err, NotNil)
expr.L = Value{1}
expr.R = Value{"abc"}
_, err = expr.evalArithmeticOp(nil, nil)
c.Assert(err, NotNil)
expr.Op = 0
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr.L = NewTestRow(1, 2)
expr.R = Value{nil}
expr.Op = opcode.Plus
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr.Op = opcode.LE
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr.R = NewTestRow(1, 2)
expr.Op = opcode.Plus
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
expr.L = NewTestRow(1, 2)
expr.R = NewTestRow(1, 2)
expr.Op = opcode.Plus
_, err = expr.Eval(nil, nil)
c.Assert(err, NotNil)
}