diff --git a/executor/executor_test.go b/executor/executor_test.go index 62dffaec044cc..09331a27ffd03 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -803,6 +803,22 @@ func (s *testSuite) TestStringBuiltin(c *C) { result.Check(testkit.Rows("")) } +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 2021-01-21 2001-01-01")) +} + func (s *testSuite) TestBuiltin(c *C) { defer func() { s.cleanEnv(c) diff --git a/expression/builtin_time.go b/expression/builtin_time.go index 4de45133a2153..12366a0d10fcf 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -2023,35 +2023,37 @@ 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.MaxDateWidth + 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) - } - if args[0].IsNull() || args[1].IsNull() { - return - } +func (b *builtinMakeDateSig) evalTime(row []types.Datum) (d types.Time, isNull bool, err error) { + args := b.getArgs() sc := b.ctx.GetSessionVars().StmtCtx - year, err := args[0].ToInt64(sc) - if err != nil { - return d, errors.Trace(err) + var year, dayOfYear int64 + year, isNull, err = args[0].EvalInt(row, sc) + if isNull || err != nil { + return d, true, errors.Trace(err) } - dayOfYear, err := args[1].ToInt64(sc) - if err != nil { - return d, errors.Trace(err) + dayOfYear, isNull, err = args[1].EvalInt(row, sc) + if isNull || err != nil { + return d, true, errors.Trace(err) } if dayOfYear <= 0 || year < 0 || year > 9999 { - return + return d, true, nil } if year < 70 { year += 2000 @@ -2065,14 +2067,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 { diff --git a/expression/builtin_time_test.go b/expression/builtin_time_test.go index 19abe14cbf077..2a32ddca8addd 100644 --- a/expression/builtin_time_test.go +++ b/expression/builtin_time_test.go @@ -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" @@ -1203,42 +1206,57 @@ 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}, + {[]interface{}{71.1, 1.89}, "1971-01-02", false, false}, + {[]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.MaxDateWidth) + 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) { diff --git a/mysql/const.go b/mysql/const.go index 2a8bcc8c8984e..37dba47d1cce0 100644 --- a/mysql/const.go +++ b/mysql/const.go @@ -193,6 +193,12 @@ const ( // AllPrivMask is the mask for PrivilegeType with all bits set to 1. const AllPrivMask = AllPriv - 1 +// MySQL type maximum length. +const ( + // MaxDateWidth YYYY-MM-DD. + MaxDateWidth = 10 +) + // Priv2UserCol is the privilege to mysql.user table column name. var Priv2UserCol = map[PrivilegeType]string{ CreatePriv: "Create_priv",