From 4ac6ce960a4ffc09258ee05dedda82e7954fba94 Mon Sep 17 00:00:00 2001 From: zyguan Date: Sun, 5 Feb 2017 01:55:15 +0800 Subject: [PATCH 1/2] *: add builtin function UTC_TIMESTAMP --- ast/functions.go | 1 + expression/builtin.go | 1 + expression/builtin_time.go | 70 +++++++++++++++++++++++++-------- expression/builtin_time_test.go | 66 ++++++++++++++++++++----------- parser/misc.go | 1 + parser/parser.y | 11 +++++- parser/parser_test.go | 5 +++ plan/typeinferer.go | 4 +- plan/typeinferer_test.go | 1 + 9 files changed, 117 insertions(+), 43 deletions(-) diff --git a/ast/functions.go b/ast/functions.go index c7f206a24530e..b3c8359029b6d 100644 --- a/ast/functions.go +++ b/ast/functions.go @@ -121,6 +121,7 @@ const ( TimeDiff = "timediff" TimestampDiff = "timestampdiff" UTCDate = "utc_date" + UTCTimestamp = "utc_timestamp" UnixTimestamp = "unix_timestamp" Week = "week" Weekday = "weekday" diff --git a/expression/builtin.go b/expression/builtin.go index a2257646386a0..cb821e3b45736 100644 --- a/expression/builtin.go +++ b/expression/builtin.go @@ -181,6 +181,7 @@ var funcs = map[string]functionClass{ ast.Sysdate: &sysDateFunctionClass{baseFunctionClass{ast.Sysdate, 0, 1}}, ast.Time: &timeFunctionClass{baseFunctionClass{ast.Time, 1, 1}}, ast.UTCDate: &utcDateFunctionClass{baseFunctionClass{ast.UTCDate, 0, 0}}, + ast.UTCTimestamp: &utcTimestampFunctionClass{baseFunctionClass{ast.UnixTimestamp, 0, 1}}, ast.Week: &weekFunctionClass{baseFunctionClass{ast.Week, 1, 2}}, ast.Weekday: &weekDayFunctionClass{baseFunctionClass{ast.Weekday, 1, 1}}, ast.WeekOfYear: &weekOfYearFunctionClass{baseFunctionClass{ast.WeekOfYear, 1, 1}}, diff --git a/expression/builtin_time.go b/expression/builtin_time.go index c670fc012861f..321ce677c8392 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -60,6 +60,7 @@ var ( _ functionClass = ¤tTimeFunctionClass{} _ functionClass = &timeFunctionClass{} _ functionClass = &utcDateFunctionClass{} + _ functionClass = &utcTimestampFunctionClass{} _ functionClass = &extractFunctionClass{} _ functionClass = &arithmeticFunctionClass{} _ functionClass = &unixTimestampFunctionClass{} @@ -93,11 +94,25 @@ var ( _ builtinFunc = &builtinCurrentTimeSig{} _ builtinFunc = &builtinTimeSig{} _ builtinFunc = &builtinUTCDateSig{} + _ builtinFunc = &builtinUTCTimestampSig{} _ builtinFunc = &builtinExtractSig{} _ builtinFunc = &builtinArithmeticSig{} _ builtinFunc = &builtinUnixTimestampSig{} ) +func convertTimeToMysqlTime(t time.Time, fsp int) (types.Time, error) { + tr, err := types.RoundFrac(t, int(fsp)) + if err != nil { + return types.Time{}, errors.Trace(err) + } + + return types.Time{ + Time: types.FromGoTime(tr), + Type: mysql.TypeDatetime, + Fsp: fsp, + }, nil +} + func convertToTime(sc *variable.StatementContext, arg types.Datum, tp byte) (d types.Datum, err error) { f := types.NewFieldType(tp) f.Decimal = types.MaxFsp @@ -526,24 +541,15 @@ func builtinNow(args []types.Datum, ctx context.Context) (d types.Datum, err err sc := ctx.GetSessionVars().StmtCtx if len(args) == 1 && !args[0].IsNull() { if fsp, err = checkFsp(sc, args[0]); err != nil { - d.SetNull() return d, errors.Trace(err) } } - tr, err := types.RoundFrac(time.Now(), int(fsp)) + t, err := convertTimeToMysqlTime(time.Now(), fsp) if err != nil { - d.SetNull() return d, errors.Trace(err) } - t := types.Time{ - Time: types.FromGoTime(tr), - Type: mysql.TypeDatetime, - // set unspecified for later round - Fsp: fsp, - } - d.SetMysqlTime(t) return d, nil } @@ -950,16 +956,12 @@ func (b *builtinFromUnixTimeSig) eval(row []types.Datum) (d types.Datum, err err if fracDigitsNumber > types.MaxFsp { fsp = types.MaxFsp } - tr, err := types.RoundFrac(time.Unix(integralPart, fractionalPart), fsp) + + t, err := convertTimeToMysqlTime(time.Unix(integralPart, fractionalPart), fsp) if err != nil { return d, errors.Trace(err) } - t := types.Time{ - Time: types.FromGoTime(tr), - Type: mysql.TypeDatetime, - Fsp: fsp, - } if args[0].Kind() == types.KindString { // Keep consistent with MySQL. t.Fsp = types.MaxFsp } @@ -1143,6 +1145,42 @@ func (b *builtinUTCDateSig) eval(_ []types.Datum) (d types.Datum, err error) { return d, nil } +type utcTimestampFunctionClass struct { + baseFunctionClass +} + +func (c *utcTimestampFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) { + return &builtinUTCTimestampSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args)) +} + +type builtinUTCTimestampSig struct { + baseBuiltinFunc +} + +// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_utc-timestamp +func (b *builtinUTCTimestampSig) eval(row []types.Datum) (d types.Datum, err error) { + args, err := b.evalArgs(row) + if err != nil { + return types.Datum{}, errors.Trace(err) + } + + fsp := 0 + sc := b.ctx.GetSessionVars().StmtCtx + if len(args) == 1 && !args[0].IsNull() { + if fsp, err = checkFsp(sc, args[0]); err != nil { + return d, errors.Trace(err) + } + } + + t, err := convertTimeToMysqlTime(time.Now().UTC(), fsp) + if err != nil { + return d, errors.Trace(err) + } + + d.SetMysqlTime(t) + return d, nil +} + type extractFunctionClass struct { baseFunctionClass } diff --git a/expression/builtin_time_test.go b/expression/builtin_time_test.go index 00162d9d6c8ca..29a36824cb205 100644 --- a/expression/builtin_time_test.go +++ b/expression/builtin_time_test.go @@ -418,34 +418,52 @@ func (s *testEvaluatorSuite) TestClock(c *C) { } } -func (s *testEvaluatorSuite) TestNow(c *C) { +func (s *testEvaluatorSuite) TestNowAndUTCTimestamp(c *C) { defer testleak.AfterTest(c)() - fc := funcs[ast.Now] - f, err := fc.getFunction(datumsToConstants(nil), s.ctx) - c.Assert(err, IsNil) - v, err := f.eval(nil) - c.Assert(err, IsNil) - t := v.GetMysqlTime() - // we canot use a constant value to check now, so here - // just to check whether has fractional seconds part. - c.Assert(strings.Contains(t.String(), "."), IsFalse) - f, err = fc.getFunction(datumsToConstants(types.MakeDatums(6)), s.ctx) - c.Assert(err, IsNil) - v, err = f.eval(nil) - c.Assert(err, IsNil) - t = v.GetMysqlTime() - c.Assert(strings.Contains(t.String(), "."), IsTrue) + gotime := func(t types.Time, l *time.Location) time.Time { + tt, err := t.Time.GoTime(l) + c.Assert(err, IsNil) + return tt + } - f, err = fc.getFunction(datumsToConstants(types.MakeDatums(8)), s.ctx) - c.Assert(err, IsNil) - _, err = f.eval(nil) - c.Assert(err, NotNil) + for _, x := range []struct { + fc functionClass + now func() time.Time + }{ + {funcs[ast.Now], func() time.Time { return time.Now() }}, + {funcs[ast.UTCTimestamp], func() time.Time { return time.Now().UTC() }}, + } { + f, err := x.fc.getFunction(datumsToConstants(nil), s.ctx) + c.Assert(err, IsNil) + v, err := f.eval(nil) + ts := x.now() + c.Assert(err, IsNil) + t := v.GetMysqlTime() + // we canot use a constant value to check timestamp funcs, so here + // just to check the fractional seconds part and the time delta. + c.Assert(strings.Contains(t.String(), "."), IsFalse) + c.Assert(ts.Sub(gotime(t, ts.Location())), LessEqual, time.Second) - f, err = fc.getFunction(datumsToConstants(types.MakeDatums(-2)), s.ctx) - c.Assert(err, IsNil) - _, err = f.eval(nil) - c.Assert(err, NotNil) + f, err = x.fc.getFunction(datumsToConstants(types.MakeDatums(6)), s.ctx) + c.Assert(err, IsNil) + v, err = f.eval(nil) + ts = x.now() + c.Assert(err, IsNil) + t = v.GetMysqlTime() + c.Assert(strings.Contains(t.String(), "."), IsTrue) + c.Assert(ts.Sub(gotime(t, ts.Location())), LessEqual, time.Millisecond) + + f, err = x.fc.getFunction(datumsToConstants(types.MakeDatums(8)), s.ctx) + c.Assert(err, IsNil) + _, err = f.eval(nil) + c.Assert(err, NotNil) + + f, err = x.fc.getFunction(datumsToConstants(types.MakeDatums(-2)), s.ctx) + c.Assert(err, IsNil) + _, err = f.eval(nil) + c.Assert(err, NotNil) + } } func (s *testEvaluatorSuite) TestSysDate(c *C) { diff --git a/parser/misc.go b/parser/misc.go index 3b42871a0ba3a..0410718bc87cb 100644 --- a/parser/misc.go +++ b/parser/misc.go @@ -190,6 +190,7 @@ var tokenMap = map[string]int{ "CROSS": cross, "CURDATE": curDate, "UTC_DATE": utcDate, + "UTC_TIMESTAMP": utcTimestamp, "CURRENT_DATE": currentDate, "CURTIME": curTime, "CURRENT_TIME": currentTime, diff --git a/parser/parser.y b/parser/parser.y index 748c578e77f59..27f0d7e4d6102 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -201,6 +201,7 @@ import ( use "USE" using "USING" utcDate "UTC_DATE" + utcTimestamp "UTC_TIMESTAMP" values "VALUES" varcharType "VARCHAR" varbinaryType "VARBINARY" @@ -2149,7 +2150,7 @@ ReservedKeyword: | "SCHEMA" | "SCHEMAS" | "SECOND_MICROSECOND" | "SELECT" | "SET" | "SHOW" | "SMALLINT" | "STARTING" | "TABLE" | "TERMINATED" | "THEN" | "TINYBLOB" | "TINYINT" | "TINYTEXT" | "TO" | "TRAILING" | "TRUE" | "UNION" | "UNIQUE" | "UNLOCK" | "UNSIGNED" -| "UPDATE" | "USE" | "USING" | "UTC_DATE" | "VALUES" | "VARBINARY" | "VARCHAR" +| "UPDATE" | "USE" | "USING" | "UTC_DATE" | "UTC_TIMESTAMP" | "VALUES" | "VARBINARY" | "VARCHAR" | "WHEN" | "WHERE" | "WRITE" | "XOR" | "YEAR_MONTH" | "ZEROFILL" /* | "DELAYED" | "HIGH_PRIORITY" | "LOW_PRIORITY"| "WITH" @@ -3078,6 +3079,14 @@ FunctionCallNonKeyword: { $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: []ast.ExprNode{$3.(ast.ExprNode)}} } +| "UTC_TIMESTAMP" '(' ')' + { + $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1)} + } +| "UTC_TIMESTAMP" '(' Expression ')' + { + $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: []ast.ExprNode{$3.(ast.ExprNode)}} + } | "WEEKDAY" '(' Expression ')' { $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: []ast.ExprNode{$3.(ast.ExprNode)}} diff --git a/parser/parser_test.go b/parser/parser_test.go index 6558608f41c51..b6cea2d3fe5ae 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -584,6 +584,11 @@ func (s *testParserSuite) TestBuiltin(c *C) { {"select curtime()", true}, {"select curtime(6)", true}, + // select utc_timestamp + {"select utc_timestamp", false}, + {"select utc_timestamp()", true}, + {"select utc_timestamp(6)", true}, + // for microsecond, second, minute, hour {"SELECT MICROSECOND('2009-12-31 23:59:59.000010');", true}, {"SELECT SECOND('10:05:03');", true}, diff --git a/plan/typeinferer.go b/plan/typeinferer.go index bbbe8e7d9a398..e1d5921a73444 100644 --- a/plan/typeinferer.go +++ b/plan/typeinferer.go @@ -302,13 +302,13 @@ func (v *typeInferrer) handleFuncCallExpr(x *ast.FuncCallExpr) { case "curtime", "current_time", "timediff": tp = types.NewFieldType(mysql.TypeDuration) tp.Decimal = v.getFsp(x) - case "current_timestamp", "date_add", "date_sub", "adddate", "subdate": + case "date_add", "date_sub", "adddate", "subdate": tp = types.NewFieldType(mysql.TypeDatetime) case "microsecond", "second", "minute", "hour", "day", "week", "month", "year", "dayofweek", "dayofmonth", "dayofyear", "weekday", "weekofyear", "yearweek", "datediff", "found_rows", "length", "extract", "locate", "unix_timestamp": tp = types.NewFieldType(mysql.TypeLonglong) - case "now", "sysdate": + case "now", "sysdate", "current_timestamp", "utc_timestamp": tp = types.NewFieldType(mysql.TypeDatetime) tp.Decimal = v.getFsp(x) case "from_unixtime": diff --git a/plan/typeinferer_test.go b/plan/typeinferer_test.go index 2ece0acb74367..226b9a8b34618 100644 --- a/plan/typeinferer_test.go +++ b/plan/typeinferer_test.go @@ -107,6 +107,7 @@ func (ts *testTypeInferrerSuite) TestInferType(c *C) { {"current_time()", mysql.TypeDuration, charset.CharsetBin}, {"curtime()", mysql.TypeDuration, charset.CharsetBin}, {"current_timestamp()", mysql.TypeDatetime, charset.CharsetBin}, + {"utc_timestamp()", mysql.TypeDatetime, charset.CharsetBin}, {"microsecond('2009-12-31 23:59:59.000010')", mysql.TypeLonglong, charset.CharsetBin}, {"second('2009-12-31 23:59:59.000010')", mysql.TypeLonglong, charset.CharsetBin}, {"minute('2009-12-31 23:59:59.000010')", mysql.TypeLonglong, charset.CharsetBin}, From 72e7e5f3a95f6afc437b6260ae7bb0917b9cc8ef Mon Sep 17 00:00:00 2001 From: zyguan Date: Sun, 5 Feb 2017 11:46:17 +0800 Subject: [PATCH 2/2] parser: make parentheses of UTC_TIMESTAMP negligible --- parser/parser.y | 12 ++++++------ parser/parser_test.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/parser/parser.y b/parser/parser.y index 27f0d7e4d6102..db0edc21b0bad 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -3079,13 +3079,13 @@ FunctionCallNonKeyword: { $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: []ast.ExprNode{$3.(ast.ExprNode)}} } -| "UTC_TIMESTAMP" '(' ')' +| "UTC_TIMESTAMP" FuncDatetimePrec { - $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1)} - } -| "UTC_TIMESTAMP" '(' Expression ')' - { - $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: []ast.ExprNode{$3.(ast.ExprNode)}} + args := []ast.ExprNode{} + if $2 != nil { + args = append(args, $2.(ast.ExprNode)) + } + $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: args} } | "WEEKDAY" '(' Expression ')' { diff --git a/parser/parser_test.go b/parser/parser_test.go index b6cea2d3fe5ae..54ad253444b1f 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -585,7 +585,7 @@ func (s *testParserSuite) TestBuiltin(c *C) { {"select curtime(6)", true}, // select utc_timestamp - {"select utc_timestamp", false}, + {"select utc_timestamp", true}, {"select utc_timestamp()", true}, {"select utc_timestamp(6)", true},