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, executor: rewrite built-in func makeDate using new expression evaluation architecture #3533

Merged
merged 15 commits into from
Jun 23, 2017
16 changes: 16 additions & 0 deletions executor/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,22 @@ func (s *testSuite) TestStringBuiltin(c *C) {
result.Check(testkit.Rows("<nil>"))
}

func (s *testSuite) TestTimeBuiltin(c *C) {
defer func() {
s.cleanEnv(c)
testleak.AfterTest(c)()
}()
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")

// for makeDate
tk.MustExec("drop table if exists t")
tk.MustExec("create table t(a int, b double, c datetime, d time, e char(20), f bit(10))")
tk.MustExec(`insert into t values(1, 1.1, "2017-01-01 12:01:01", "12:01:01", "abcdef", 0b10101)`)
result := tk.MustQuery("select makedate(a,a), makedate(b,b), makedate(c,c), makedate(d,d), makedate(e,e), makedate(f,f), makedate(null,null), makedate(a,b) from t")
result.Check(testkit.Rows("2001-01-01 2001-01-01 <nil> <nil> <nil> 2021-01-21 <nil> 2001-01-01"))
}

func (s *testSuite) TestBuiltin(c *C) {
defer func() {
s.cleanEnv(c)
Expand Down
46 changes: 23 additions & 23 deletions expression/builtin_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -2023,35 +2023,36 @@ type makeDateFunctionClass struct {
}

func (c *makeDateFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
sig := &builtinMakeDateSig{newBaseBuiltinFunc(args, ctx)}
tp := types.NewFieldType(mysql.TypeDate)
tp.Flen = mysql.MAX_DATE_WIDTH
types.SetBinChsClnFlag(tp)
bf, err := newBaseBuiltinFuncWithTp(args, tp, ctx, tpInt, tpInt)
if err != nil {
return nil, errors.Trace(err)
}
sig := &builtinMakeDateSig{baseTimeBuiltinFunc{bf}}
return sig.setSelf(sig), errors.Trace(c.verifyArgs(args))
}

type builtinMakeDateSig struct {
baseBuiltinFunc
baseTimeBuiltinFunc
}

// eval evals a builtinMakeDateSig.
// evalTime evaluates a builtinMakeDateSig.
// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_makedate
func (b *builtinMakeDateSig) eval(row []types.Datum) (d types.Datum, err error) {
args, err := b.evalArgs(row)
if err != nil {
return d, errors.Trace(err)
func (b *builtinMakeDateSig) evalTime(row []types.Datum) (d types.Time, isNull bool, err error) {
args := b.getArgs()
var year, dayOfYear int64
year, isNull, err = args[0].EvalInt(row, b.ctx.GetSessionVars().StmtCtx)
Copy link
Contributor

Choose a reason for hiding this comment

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

sc := b.ctx.GetSessionVars().StmtCtx
so we can use it at line 2050

if isNull || err != nil {
return d, isNull, errors.Trace(err)
Copy link
Member

Choose a reason for hiding this comment

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

return d, true is clearer

}
if args[0].IsNull() || args[1].IsNull() {
return
}
sc := b.ctx.GetSessionVars().StmtCtx
year, err := args[0].ToInt64(sc)
if err != nil {
return d, errors.Trace(err)
}
dayOfYear, err := args[1].ToInt64(sc)
if err != nil {
return d, errors.Trace(err)
dayOfYear, isNull, err = args[1].EvalInt(row, b.ctx.GetSessionVars().StmtCtx)
if isNull || err != nil {
return d, isNull, errors.Trace(err)
Copy link
Member

Choose a reason for hiding this comment

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

ditto.

}
if dayOfYear <= 0 || year < 0 || year > 9999 {
return
return d, true, nil
}
if year < 70 {
year += 2000
Expand All @@ -2065,14 +2066,13 @@ func (b *builtinMakeDateSig) eval(row []types.Datum) (d types.Datum, err error)
}
retTimestamp := types.TimestampDiff("DAY", types.ZeroDate, startTime)
if retTimestamp == 0 {
return d, errorOrWarning(types.ErrInvalidTimeFormat, b.ctx)
return d, true, errorOrWarning(types.ErrInvalidTimeFormat, b.ctx)
}
ret := types.TimeFromDays(retTimestamp + dayOfYear - 1)
if ret.IsZero() || ret.Time.Year() > 9999 {
return
return d, true, nil
}
d.SetMysqlTime(ret)
return
return ret, false, nil
}

type makeTimeFunctionClass struct {
Expand Down
79 changes: 48 additions & 31 deletions expression/builtin_time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import (
"strings"
"time"

"github.com/juju/errors"
. "github.com/pingcap/check"
"github.com/pingcap/tidb/ast"
"github.com/pingcap/tidb/mysql"
"github.com/pingcap/tidb/util/charset"
"github.com/pingcap/tidb/util/mock"
"github.com/pingcap/tidb/util/testleak"
"github.com/pingcap/tidb/util/testutil"
Expand Down Expand Up @@ -1203,42 +1206,56 @@ func (s *testEvaluatorSuite) TestTimestamp(c *C) {

func (s *testEvaluatorSuite) TestMakeDate(c *C) {
defer testleak.AfterTest(c)()
tbl := []struct {
Args []interface{}
Want interface{}
cases := []struct {
args []interface{}
expected string
isNil bool
getErr bool
}{
{[]interface{}{71, 1}, "1971-01-01"},
{[]interface{}{99, 1}, "1999-01-01"},
{[]interface{}{100, 1}, "0100-01-01"},
{[]interface{}{69, 1}, "2069-01-01"},
{[]interface{}{70, 1}, "1970-01-01"},
{[]interface{}{1000, 1}, "1000-01-01"},
{[]interface{}{-1, 3660}, nil},
{[]interface{}{10000, 3660}, nil},
{[]interface{}{2060, 2900025}, "9999-12-31"},
{[]interface{}{2060, 2900026}, nil},
{[]interface{}{nil, 2900025}, nil},
{[]interface{}{2060, nil}, nil},
{[]interface{}{nil, nil}, nil},
{[]interface{}{"71", 1}, "1971-01-01"},
{[]interface{}{71, "1"}, "1971-01-01"},
{[]interface{}{"71", "1"}, "1971-01-01"},
}
Dtbl := tblToDtbl(tbl)
maketime := funcs[ast.MakeDate]
for idx, t := range Dtbl {
f, err := maketime.getFunction(datumsToConstants(t["Args"]), s.ctx)
c.Assert(err, IsNil)
got, err := f.eval(nil)
c.Assert(err, IsNil)
if t["Want"][0].Kind() == types.KindNull {
c.Assert(got.Kind(), Equals, types.KindNull, Commentf("[%v] - args:%v", idx, t["Args"]))
{[]interface{}{71, 1}, "1971-01-01", 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.

add test cases for float64 value

{[]interface{}{99, 1}, "1999-01-01", false, false},
{[]interface{}{100, 1}, "0100-01-01", false, false},
{[]interface{}{69, 1}, "2069-01-01", false, false},
{[]interface{}{70, 1}, "1970-01-01", false, false},
{[]interface{}{1000, 1}, "1000-01-01", false, false},
{[]interface{}{-1, 3660}, "", true, false},
{[]interface{}{10000, 3660}, "", true, false},
{[]interface{}{2060, 2900025}, "9999-12-31", false, false},
{[]interface{}{2060, 2900026}, "", true, false},
{[]interface{}{"71", 1}, "1971-01-01", false, false},
{[]interface{}{71, "1"}, "1971-01-01", false, false},
{[]interface{}{"71", "1"}, "1971-01-01", false, false},
{[]interface{}{nil, 2900025}, "", true, false},
{[]interface{}{2060, nil}, "", true, false},
{[]interface{}{nil, nil}, "", true, false},
{[]interface{}{errors.New("must error"), errors.New("must error")}, "", false, true},
}

for _, t := range cases {
f, err := newFunctionForTest(s.ctx, ast.MakeDate, primitiveValsToConstants(t.args)...)
c.Assert(err, IsNil)
tp := f.GetType()
c.Assert(tp.Tp, Equals, mysql.TypeDate)
c.Assert(tp.Charset, Equals, charset.CharsetBin)
c.Assert(tp.Collate, Equals, charset.CollationBin)
c.Assert(tp.Flag, Equals, uint(mysql.BinaryFlag))
c.Assert(tp.Flen, Equals, mysql.MAX_DATE_WIDTH)
d, err := f.Eval(nil)
if t.getErr {
c.Assert(err, NotNil)
} else {
want, err := t["Want"][0].ToString()
c.Assert(err, IsNil)
c.Assert(got.GetMysqlTime().String(), Equals, want, Commentf("[%v] - args:%v", idx, t["Args"]))
if t.isNil {
c.Assert(d.Kind(), Equals, types.KindNull)
} else {
c.Assert(d.GetMysqlTime().String(), Equals, t.expected)
}
}
}

f, err := funcs[ast.MakeDate].getFunction([]Expression{Zero, Zero}, s.ctx)
c.Assert(err, IsNil)
c.Assert(f.isDeterministic(), IsTrue)
}

func (s *testEvaluatorSuite) TestMakeTime(c *C) {
Expand Down
5 changes: 5 additions & 0 deletions mysql/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ const (
// AllPrivMask is the mask for PrivilegeType with all bits set to 1.
const AllPrivMask = AllPriv - 1

const (
Copy link
Contributor

Choose a reason for hiding this comment

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

add comments for these const.

// YYYY-MM-DD
MAX_DATE_WIDTH = 10
)

// Priv2UserCol is the privilege to mysql.user table column name.
var Priv2UserCol = map[PrivilegeType]string{
CreatePriv: "Create_priv",
Expand Down