From 77f8b4e4da9f5deef758639ebbd59bc0066aa6ed Mon Sep 17 00:00:00 2001 From: xia Date: Thu, 29 Oct 2015 20:34:01 +0800 Subject: [PATCH 1/9] parser: add date_sub function --- parser/parser.y | 15 +++++++++++++-- parser/scanner.l | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/parser/parser.y b/parser/parser.y index 0027cadc054ba..4e59d4f670b65 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -114,6 +114,7 @@ import ( database "DATABASE" databases "DATABASES" dateAdd "DATE_ADD" + dateSub "DATE_SUB" day "DAY" dayofmonth "DAYOFMONTH" dayofweek "DAYOFWEEK" @@ -1710,7 +1711,7 @@ UnReservedKeyword: | "NATIONAL" | "ROW" | "QUARTER" | "ESCAPE" NotKeywordToken: - "ABS" | "COALESCE" | "CONCAT" | "CONCAT_WS" | "COUNT" | "DAY" | "DATE_ADD" | "DAYOFMONTH" | "DAYOFWEEK" | "DAYOFYEAR" | "FOUND_ROWS" | "GROUP_CONCAT" + "ABS" | "COALESCE" | "CONCAT" | "CONCAT_WS" | "COUNT" | "DAY" | "DATE_ADD" | "DATE_SUB" | "DAYOFMONTH" | "DAYOFWEEK" | "DAYOFYEAR" | "FOUND_ROWS" | "GROUP_CONCAT" | "HOUR" | "IFNULL" | "LENGTH" | "LOCATE" | "MAX" | "MICROSECOND" | "MIN" | "MINUTE" | "NULLIF" | "MONTH" | "NOW" | "RAND" | "SECOND" | "SQL_CALC_FOUND_ROWS" | "SUBSTRING" %prec lowerThanLeftParen | "SUBSTRING_INDEX" | "SUM" | "TRIM" | "WEEKDAY" | "WEEKOFYEAR" | "YEARWEEK" @@ -2285,12 +2286,22 @@ FunctionCallNonKeyword: } | "DATE_ADD" '(' Expression ',' "INTERVAL" Expression TimeUnit ')' { - $$ = &expression.DateAdd{ + $$ = &expression.DateCast{ + Op:"ADD", Unit: $7.(string), Date: $3.(expression.Expression), Interval: $6.(expression.Expression), } } +| "DATE_SUB" '(' Expression ',' "INTERVAL" Expression TimeUnit ')' + { + $$ = &expression.DateCast{ + Op:"SUB", + Unit: $7.(string), + Date: $3.(expression.Expression), + Interval: $6.(expression.Expression), + } + } | "EXTRACT" '(' TimeUnit "FROM" Expression ')' { $$ = &expression.Extract{ diff --git a/parser/scanner.l b/parser/scanner.l index 6756eba6d674d..32a559f356f55 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -298,6 +298,7 @@ current_user {c}{u}{r}{r}{e}{n}{t}_{u}{s}{e}{r} database {d}{a}{t}{a}{b}{a}{s}{e} databases {d}{a}{t}{a}{b}{a}{s}{e}{s} date_add {d}{a}{t}{e}_{a}{d}{d} +date_sub {d}{a}{t}{e}_{s}{u}{b} day {d}{a}{y} dayofweek {d}{a}{y}{o}{f}{w}{e}{e}{k} dayofmonth {d}{a}{y}{o}{f}{m}{o}{n}{t}{h} @@ -648,6 +649,8 @@ year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h} {databases} return databases {date_add} lval.item = string(l.val) return dateAdd +{date_sub} lval.item = string(l.val) + return dateSub {day} lval.item = string(l.val) return day {dayofweek} lval.item = string(l.val) From c49e04eee157074b7e80454fc0e251df4b8ba5ee Mon Sep 17 00:00:00 2001 From: xia Date: Thu, 29 Oct 2015 20:34:30 +0800 Subject: [PATCH 2/9] parser: add date_sub function test --- parser/parser_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/parser/parser_test.go b/parser/parser_test.go index bb22afb43432e..3099572ea76fb 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -438,6 +438,28 @@ func (s *testParserSuite) TestBuiltin(c *C) { {`select date_add("2011-11-11 10:10:10.123456", interval "11 10:10" day_minute)`, true}, {`select date_add("2011-11-11 10:10:10.123456", interval "11 10" day_hour)`, true}, {`select date_add("2011-11-11 10:10:10.123456", interval "11-11" year_month)`, true}, + + // For date_sub + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 second)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 minute)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 hour)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 day)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 1 week)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 1 month)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 1 quarter)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 1 year)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10.10" second_microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10.10" minute_microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10" minute_second)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10:10.10" hour_microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10:10" hour_second)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10" hour_minute)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11 10:10:10.10" day_microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11 10:10:10" day_second)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11 10:10" day_minute)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11 10" day_hour)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11-11" year_month)`, true}, } s.RunTest(c, table) } From 47677955e52b3e6e3d2bb38b0a8594d031cdd43d Mon Sep 17 00:00:00 2001 From: xia Date: Fri, 30 Oct 2015 11:24:37 +0800 Subject: [PATCH 3/9] expression: rename date_add.go to date_cast.go and rename VisitDateAdd to VisitDateCast --- expression/{date_add.go => date_cast.go} | 0 expression/visitor.go | 18 +++++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) rename expression/{date_add.go => date_cast.go} (100%) diff --git a/expression/date_add.go b/expression/date_cast.go similarity index 100% rename from expression/date_add.go rename to expression/date_cast.go diff --git a/expression/visitor.go b/expression/visitor.go index 6ec16f765f5c3..27f61e00e3da5 100644 --- a/expression/visitor.go +++ b/expression/visitor.go @@ -107,8 +107,8 @@ type Visitor interface { // VisitFunctionTrim visits FunctionTrim expression. VisitFunctionTrim(v *FunctionTrim) (Expression, error) - // VisitDateAdd visits DateAdd expression. - VisitDateAdd(da *DateAdd) (Expression, error) + // VisitDateCast visits DateAdd and DateSub expression. + VisitDateCast(dc *DateCast) (Expression, error) } // BaseVisitor is the base implementation of Visitor. @@ -482,18 +482,18 @@ func (bv *BaseVisitor) VisitFunctionTrim(ss *FunctionTrim) (Expression, error) { return ss, nil } -// VisitDateAdd implements Visitor interface. -func (bv *BaseVisitor) VisitDateAdd(da *DateAdd) (Expression, error) { +// VisitDateCast implements Visitor interface. +func (bv *BaseVisitor) VisitDateCast(dc *DateCast) (Expression, error) { var err error - da.Date, err = da.Date.Accept(bv.V) + dc.Date, err = dc.Date.Accept(bv.V) if err != nil { - return da, errors.Trace(err) + return dc, errors.Trace(err) } - da.Interval, err = da.Interval.Accept(bv.V) + dc.Interval, err = dc.Interval.Accept(bv.V) if err != nil { - return da, errors.Trace(err) + return dc, errors.Trace(err) } - return da, nil + return dc, nil } From a1de92a96b0cd32d5afbde7f5f5aba8c6b68da43 Mon Sep 17 00:00:00 2001 From: xia Date: Fri, 30 Oct 2015 11:28:10 +0800 Subject: [PATCH 4/9] expression: add date_sub operation --- expression/date_cast.go | 113 ++++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/expression/date_cast.go b/expression/date_cast.go index 95cd99a0deed5..66e61fd1b97af 100644 --- a/expression/date_cast.go +++ b/expression/date_cast.go @@ -16,6 +16,7 @@ package expression import ( "fmt" "strings" + "time" "github.com/juju/errors" "github.com/pingcap/tidb/context" @@ -23,82 +24,106 @@ import ( "github.com/pingcap/tidb/util/types" ) -// DateAdd is for time date_add function. +const ( + add = "ADD" + sub = "SUB" +) + +// DateCast is used for dealing with addition and substraction of time. +// If the Op value is ADD, then do date_add function. // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add -type DateAdd struct { +// If the Op value is SUB, then do date_sub function. +// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-sub +type DateCast struct { + Op string Unit string Date Expression Interval Expression } +func (dc *DateCast) isAdd() bool { + if dc.Op == add { + return true + } + + return false +} + // Clone implements the Expression Clone interface. -func (da *DateAdd) Clone() Expression { - n := *da +func (dc *DateCast) Clone() Expression { + n := *dc return &n } +// IsStatic implements the Expression IsStatic interface. +func (dc *DateCast) IsStatic() bool { + return dc.Date.IsStatic() && dc.Interval.IsStatic() +} + +// Accept implements the Visitor Accept interface. +func (dc *DateCast) Accept(v Visitor) (Expression, error) { + return v.VisitDateCast(dc) +} + +// String implements the Expression String interface. +func (dc *DateCast) String() string { + return fmt.Sprintf("DATE_%s(%s, INTERVAL %s %s)", dc.Op, dc.Date, dc.Interval, strings.ToUpper(dc.Unit)) +} + // Eval implements the Expression Eval interface. -func (da *DateAdd) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { - dv, err := da.Date.Eval(ctx, args) - if dv == nil || err != nil { +func (dc *DateCast) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { + t, years, months, days, durations, err := dc.evalArgs(ctx, args) + if t.IsZero() || err != nil { return nil, errors.Trace(err) } + if !dc.isAdd() { + years, months, days, durations = -years, -months, -days, -durations + } + t.Time = t.Time.Add(durations) + t.Time = t.Time.AddDate(int(years), int(months), int(days)) + + // "2011-11-11 10:10:20.000000" outputs "2011-11-11 10:10:20". + if t.Time.Nanosecond() == 0 { + t.Fsp = 0 + } + + return t, nil +} + +func (dc *DateCast) evalArgs(ctx context.Context, args map[interface{}]interface{}) ( + mysql.Time, int64, int64, int64, time.Duration, error) { + dv, err := dc.Date.Eval(ctx, args) + if dv == nil || err != nil { + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) + } sv, err := types.ToString(dv) if err != nil { - return nil, errors.Trace(err) + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - f := types.NewFieldType(mysql.TypeDatetime) f.Decimal = mysql.MaxFsp - dv, err = types.Convert(sv, f) if dv == nil || err != nil { - return nil, errors.Trace(err) + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - t, ok := dv.(mysql.Time) if !ok { - return nil, errors.Errorf("need time type, but got %T", dv) + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Errorf("need time type, but got %T", dv) } - iv, err := da.Interval.Eval(ctx, args) + iv, err := dc.Interval.Eval(ctx, args) if iv == nil || err != nil { - return nil, errors.Trace(err) + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - format, err := types.ToString(iv) if err != nil { - return nil, errors.Trace(err) + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - - years, months, days, durations, err := mysql.ExtractTimeValue(da.Unit, strings.TrimSpace(format)) + years, months, days, durations, err := mysql.ExtractTimeValue(dc.Unit, strings.TrimSpace(format)) if err != nil { - return nil, errors.Trace(err) + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - t.Time = t.Time.Add(durations) - t.Time = t.Time.AddDate(int(years), int(months), int(days)) - - // "2011-11-11 10:10:20.000000" outputs "2011-11-11 10:10:20". - if t.Time.Nanosecond() == 0 { - t.Fsp = 0 - } - - return t, nil -} - -// IsStatic implements the Expression IsStatic interface. -func (da *DateAdd) IsStatic() bool { - return da.Date.IsStatic() && da.Interval.IsStatic() -} - -// String implements the Expression String interface. -func (da *DateAdd) String() string { - return fmt.Sprintf("DATE_ADD(%s, INTERVAL %s %s)", da.Date, da.Interval, strings.ToUpper(da.Unit)) -} - -// Accept implements the Visitor Accept interface. -func (da *DateAdd) Accept(v Visitor) (Expression, error) { - return v.VisitDateAdd(da) + return t, years, months, days, durations, nil } From ebd6cce01710c098b6bd41a9a6fa561b42542f09 Mon Sep 17 00:00:00 2001 From: xia Date: Fri, 30 Oct 2015 11:30:16 +0800 Subject: [PATCH 5/9] expression: rename date_add_test.go to date_cast_test.go and add date_sub operation test --- expression/date_add_test.go | 144 ------------------------------- expression/date_cast_test.go | 162 +++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 144 deletions(-) delete mode 100644 expression/date_add_test.go create mode 100644 expression/date_cast_test.go diff --git a/expression/date_add_test.go b/expression/date_add_test.go deleted file mode 100644 index 6b612dce40c3e..0000000000000 --- a/expression/date_add_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// 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 ( - . "github.com/pingcap/check" - "github.com/pingcap/tidb/mysql" -) - -var _ = Suite(&testDateAddSuite{}) - -type testDateAddSuite struct { -} - -func (t *testDateAddSuite) TestDateAdd(c *C) { - input := "2011-11-11 10:10:10" - e := &DateAdd{ - Unit: "DAY", - Date: Value{Val: input}, - Interval: Value{Val: "1"}, - } - c.Assert(e.String(), Equals, `DATE_ADD("2011-11-11 10:10:10", INTERVAL "1" DAY)`) - c.Assert(e.Clone(), NotNil) - c.Assert(e.IsStatic(), IsTrue) - - _, err := e.Eval(nil, nil) - c.Assert(err, IsNil) - - // Test null. - e = &DateAdd{ - Unit: "DAY", - Date: Value{Val: nil}, - Interval: Value{Val: "1"}, - } - - v, err := e.Eval(nil, nil) - c.Assert(err, IsNil) - c.Assert(v, IsNil) - - e = &DateAdd{ - Unit: "DAY", - Date: Value{Val: input}, - Interval: Value{Val: nil}, - } - - v, err = e.Eval(nil, nil) - c.Assert(err, IsNil) - c.Assert(v, IsNil) - - // Test eval. - tbl := []struct { - Unit string - Interval interface{} - Expect string - }{ - {"MICROSECOND", "1000", "2011-11-11 10:10:10.001000"}, - {"MICROSECOND", 1000, "2011-11-11 10:10:10.001000"}, - {"SECOND", "10", "2011-11-11 10:10:20"}, - {"MINUTE", "10", "2011-11-11 10:20:10"}, - {"HOUR", "10", "2011-11-11 20:10:10"}, - {"DAY", "11", "2011-11-22 10:10:10"}, - {"WEEK", "2", "2011-11-25 10:10:10"}, - {"MONTH", "2", "2012-01-11 10:10:10"}, - {"QUARTER", "4", "2012-11-11 10:10:10"}, - {"YEAR", "2", "2013-11-11 10:10:10"}, - {"SECOND_MICROSECOND", "10.00100000", "2011-11-11 10:10:20.100000"}, - {"SECOND_MICROSECOND", "10.0010000000", "2011-11-11 10:10:30"}, - {"SECOND_MICROSECOND", "10.0010000010", "2011-11-11 10:10:30.000010"}, - {"MINUTE_MICROSECOND", "10:10.100", "2011-11-11 10:20:20.100000"}, - {"MINUTE_SECOND", "10:10", "2011-11-11 10:20:20"}, - {"HOUR_MICROSECOND", "10:10:10.100", "2011-11-11 20:20:20.100000"}, - {"HOUR_SECOND", "10:10:10", "2011-11-11 20:20:20"}, - {"HOUR_MINUTE", "10:10", "2011-11-11 20:20:10"}, - {"DAY_MICROSECOND", "11 10:10:10.100", "2011-11-22 20:20:20.100000"}, - {"DAY_SECOND", "11 10:10:10", "2011-11-22 20:20:20"}, - {"DAY_MINUTE", "11 10:10", "2011-11-22 20:20:10"}, - {"DAY_HOUR", "11 10", "2011-11-22 20:10:10"}, - {"YEAR_MONTH", "11-1", "2022-12-11 10:10:10"}, - {"YEAR_MONTH", "11-11", "2023-10-11 10:10:10"}, - } - - for _, t := range tbl { - e := &DateAdd{ - Unit: t.Unit, - Date: Value{Val: input}, - Interval: Value{Val: t.Interval}, - } - - v, err := e.Eval(nil, nil) - c.Assert(err, IsNil) - - value, ok := v.(mysql.Time) - c.Assert(ok, IsTrue) - c.Assert(value.String(), Equals, t.Expect) - } - - // Test error. - errInput := "20111111 10:10:10" - errTbl := []struct { - Unit string - Interval interface{} - }{ - {"MICROSECOND", "abc1000"}, - {"MICROSECOND", ""}, - {"SECOND_MICROSECOND", "10"}, - {"MINUTE_MICROSECOND", "10.0000"}, - {"MINUTE_MICROSECOND", "10:10:10.0000"}, - - // MySQL support, but tidb not. - {"HOUR_MICROSECOND", "10:10.0000"}, - {"YEAR_MONTH", "10 1"}, - } - - for _, t := range errTbl { - e := &DateAdd{ - Unit: t.Unit, - Date: Value{Val: input}, - Interval: Value{Val: t.Interval}, - } - - _, err := e.Eval(nil, nil) - c.Assert(err, NotNil) - - e = &DateAdd{ - Unit: t.Unit, - Date: Value{Val: errInput}, - Interval: Value{Val: t.Interval}, - } - - v, err := e.Eval(nil, nil) - c.Assert(err, NotNil, Commentf("%s", v)) - } -} diff --git a/expression/date_cast_test.go b/expression/date_cast_test.go new file mode 100644 index 0000000000000..5e4184f0f0111 --- /dev/null +++ b/expression/date_cast_test.go @@ -0,0 +1,162 @@ +// 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 ( + . "github.com/pingcap/check" + "github.com/pingcap/tidb/mysql" +) + +var _ = Suite(&testDateCastSuite{}) + +type testDateCastSuite struct { +} + +func (t *testDateCastSuite) TestDateCast(c *C) { + input := "2011-11-11 10:10:10" + e := &DateCast{ + Op: add, + Unit: "DAY", + Date: Value{Val: input}, + Interval: Value{Val: "1"}, + } + c.Assert(e.String(), Equals, `DATE_ADD("2011-11-11 10:10:10", INTERVAL "1" DAY)`) + c.Assert(e.Clone(), NotNil) + c.Assert(e.IsStatic(), IsTrue) + _, err := e.Eval(nil, nil) + c.Assert(err, IsNil) + e.Op = sub + c.Assert(e.String(), Equals, `DATE_SUB("2011-11-11 10:10:10", INTERVAL "1" DAY)`) + + // Test null. + nullTbl := []struct { + Op string + Unit string + Date interface{} + Interval interface{} + }{ + {add, "DAY", nil, "1"}, + {add, "DAY", input, nil}, + } + for _, t := range nullTbl { + e := &DateCast{ + Op: t.Op, + Unit: t.Unit, + Date: Value{Val: t.Date}, + Interval: Value{Val: t.Interval}, + } + v, err := e.Eval(nil, nil) + c.Assert(err, IsNil) + c.Assert(v, IsNil) + e.Op = sub + v, err = e.Eval(nil, nil) + c.Assert(err, IsNil) + c.Assert(v, IsNil) + } + + // Test eval. + tbl := []struct { + Unit string + Interval interface{} + AddExpect string + SubExpect string + }{ + {"MICROSECOND", "1000", "2011-11-11 10:10:10.001000", "2011-11-11 10:10:09.999000"}, + {"MICROSECOND", 1000, "2011-11-11 10:10:10.001000", "2011-11-11 10:10:09.999000"}, + {"SECOND", "10", "2011-11-11 10:10:20", "2011-11-11 10:10:00"}, + {"MINUTE", "10", "2011-11-11 10:20:10", "2011-11-11 10:00:10"}, + {"HOUR", "10", "2011-11-11 20:10:10", "2011-11-11 00:10:10"}, + {"DAY", "11", "2011-11-22 10:10:10", "2011-10-31 10:10:10"}, + {"WEEK", "2", "2011-11-25 10:10:10", "2011-10-28 10:10:10"}, + {"MONTH", "2", "2012-01-11 10:10:10", "2011-09-11 10:10:10"}, + {"QUARTER", "4", "2012-11-11 10:10:10", "2010-11-11 10:10:10"}, + {"YEAR", "2", "2013-11-11 10:10:10", "2009-11-11 10:10:10"}, + {"SECOND_MICROSECOND", "10.00100000", "2011-11-11 10:10:20.100000", "2011-11-11 10:09:59.900000"}, + {"SECOND_MICROSECOND", "10.0010000000", "2011-11-11 10:10:30", "2011-11-11 10:09:50"}, + {"SECOND_MICROSECOND", "10.0010000010", "2011-11-11 10:10:30.000010", "2011-11-11 10:09:49.999990"}, + {"MINUTE_MICROSECOND", "10:10.100", "2011-11-11 10:20:20.100000", "2011-11-11 09:59:59.900000"}, + {"MINUTE_SECOND", "10:10", "2011-11-11 10:20:20", "2011-11-11 10:00:00"}, + {"HOUR_MICROSECOND", "10:10:10.100", "2011-11-11 20:20:20.100000", "2011-11-10 23:59:59.900000"}, + {"HOUR_SECOND", "10:10:10", "2011-11-11 20:20:20", "2011-11-11 00:00:00"}, + {"HOUR_MINUTE", "10:10", "2011-11-11 20:20:10", "2011-11-11 00:00:10"}, + {"DAY_MICROSECOND", "11 10:10:10.100", "2011-11-22 20:20:20.100000", "2011-10-30 23:59:59.900000"}, + {"DAY_SECOND", "11 10:10:10", "2011-11-22 20:20:20", "2011-10-31 00:00:00"}, + {"DAY_MINUTE", "11 10:10", "2011-11-22 20:20:10", "2011-10-31 00:00:10"}, + {"DAY_HOUR", "11 10", "2011-11-22 20:10:10", "2011-10-31 00:10:10"}, + {"YEAR_MONTH", "11-1", "2022-12-11 10:10:10", "2000-10-11 10:10:10"}, + {"YEAR_MONTH", "11-11", "2023-10-11 10:10:10", "1999-12-11 10:10:10"}, + } + for _, t := range tbl { + e := &DateCast{ + Op: add, + Unit: t.Unit, + Date: Value{Val: input}, + Interval: Value{Val: t.Interval}, + } + v, err := e.Eval(nil, nil) + c.Assert(err, IsNil) + value, ok := v.(mysql.Time) + c.Assert(ok, IsTrue) + c.Assert(value.String(), Equals, t.AddExpect) + + e.Op = sub + v, err = e.Eval(nil, nil) + c.Assert(err, IsNil) + value, ok = v.(mysql.Time) + c.Assert(ok, IsTrue) + c.Assert(value.String(), Equals, t.SubExpect) + } + + // Test error. + errInput := "20111111 10:10:10" + errTbl := []struct { + Unit string + Interval interface{} + }{ + {"MICROSECOND", "abc1000"}, + {"MICROSECOND", ""}, + {"SECOND_MICROSECOND", "10"}, + {"MINUTE_MICROSECOND", "10.0000"}, + {"MINUTE_MICROSECOND", "10:10:10.0000"}, + + // MySQL support, but tidb not. + {"HOUR_MICROSECOND", "10:10.0000"}, + {"YEAR_MONTH", "10 1"}, + } + for _, t := range errTbl { + e := &DateCast{ + Op: add, + Unit: t.Unit, + Date: Value{Val: input}, + Interval: Value{Val: t.Interval}, + } + _, err := e.Eval(nil, nil) + c.Assert(err, NotNil) + e.Date = Value{Val: errInput} + v, err := e.Eval(nil, nil) + c.Assert(err, NotNil, Commentf("%s", v)) + + e = &DateCast{ + Op: sub, + Unit: t.Unit, + Date: Value{Val: input}, + Interval: Value{Val: t.Interval}, + } + _, err = e.Eval(nil, nil) + c.Assert(err, NotNil) + e.Date = Value{Val: errInput} + v, err = e.Eval(nil, nil) + c.Assert(err, NotNil, Commentf("%s", v)) + } +} From c7e3951f734b8c17085dd47d1f4ffc65c05f49cc Mon Sep 17 00:00:00 2001 From: xia Date: Fri, 30 Oct 2015 11:48:36 +0800 Subject: [PATCH 6/9] expression: update the variable name in evalArgs --- expression/date_cast.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/expression/date_cast.go b/expression/date_cast.go index 66e61fd1b97af..d6c23e8541406 100644 --- a/expression/date_cast.go +++ b/expression/date_cast.go @@ -93,34 +93,34 @@ func (dc *DateCast) Eval(ctx context.Context, args map[interface{}]interface{}) func (dc *DateCast) evalArgs(ctx context.Context, args map[interface{}]interface{}) ( mysql.Time, int64, int64, int64, time.Duration, error) { - dv, err := dc.Date.Eval(ctx, args) - if dv == nil || err != nil { + dVal, err := dc.Date.Eval(ctx, args) + if dVal == nil || err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - sv, err := types.ToString(dv) + dValStr, err := types.ToString(dVal) if err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } f := types.NewFieldType(mysql.TypeDatetime) f.Decimal = mysql.MaxFsp - dv, err = types.Convert(sv, f) - if dv == nil || err != nil { + dVal, err = types.Convert(dValStr, f) + if dVal == nil || err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - t, ok := dv.(mysql.Time) + t, ok := dVal.(mysql.Time) if !ok { - return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Errorf("need time type, but got %T", dv) + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Errorf("need time type, but got %T", dVal) } - iv, err := dc.Interval.Eval(ctx, args) - if iv == nil || err != nil { + iVal, err := dc.Interval.Eval(ctx, args) + if iVal == nil || err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - format, err := types.ToString(iv) + iValStr, err := types.ToString(iVal) if err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - years, months, days, durations, err := mysql.ExtractTimeValue(dc.Unit, strings.TrimSpace(format)) + years, months, days, durations, err := mysql.ExtractTimeValue(dc.Unit, strings.TrimSpace(iValStr)) if err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } From 414ae7b73208c6420ac44ed0d66640a879979fbe Mon Sep 17 00:00:00 2001 From: xia Date: Fri, 30 Oct 2015 13:30:44 +0800 Subject: [PATCH 7/9] *: rename DateCast to DateArith --- expression/{date_cast.go => date_arith.go} | 38 +++++++++---------- .../{date_cast_test.go => date_arith_test.go} | 16 ++++---- expression/visitor.go | 18 ++++----- parser/parser.y | 4 +- 4 files changed, 38 insertions(+), 38 deletions(-) rename expression/{date_cast.go => date_arith.go} (75%) rename expression/{date_cast_test.go => date_arith_test.go} (95%) diff --git a/expression/date_cast.go b/expression/date_arith.go similarity index 75% rename from expression/date_cast.go rename to expression/date_arith.go index d6c23e8541406..cd32586664a68 100644 --- a/expression/date_cast.go +++ b/expression/date_arith.go @@ -29,20 +29,20 @@ const ( sub = "SUB" ) -// DateCast is used for dealing with addition and substraction of time. +// DateArith is used for dealing with addition and substraction of time. // If the Op value is ADD, then do date_add function. // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add // If the Op value is SUB, then do date_sub function. // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-sub -type DateCast struct { +type DateArith struct { Op string Unit string Date Expression Interval Expression } -func (dc *DateCast) isAdd() bool { - if dc.Op == add { +func (da *DateArith) isAdd() bool { + if da.Op == add { return true } @@ -50,34 +50,34 @@ func (dc *DateCast) isAdd() bool { } // Clone implements the Expression Clone interface. -func (dc *DateCast) Clone() Expression { - n := *dc +func (da *DateArith) Clone() Expression { + n := *da return &n } // IsStatic implements the Expression IsStatic interface. -func (dc *DateCast) IsStatic() bool { - return dc.Date.IsStatic() && dc.Interval.IsStatic() +func (da *DateArith) IsStatic() bool { + return da.Date.IsStatic() && da.Interval.IsStatic() } // Accept implements the Visitor Accept interface. -func (dc *DateCast) Accept(v Visitor) (Expression, error) { - return v.VisitDateCast(dc) +func (da *DateArith) Accept(v Visitor) (Expression, error) { + return v.VisitDateArith(da) } // String implements the Expression String interface. -func (dc *DateCast) String() string { - return fmt.Sprintf("DATE_%s(%s, INTERVAL %s %s)", dc.Op, dc.Date, dc.Interval, strings.ToUpper(dc.Unit)) +func (da *DateArith) String() string { + return fmt.Sprintf("DATE_%s(%s, INTERVAL %s %s)", da.Op, da.Date, da.Interval, strings.ToUpper(da.Unit)) } // Eval implements the Expression Eval interface. -func (dc *DateCast) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { - t, years, months, days, durations, err := dc.evalArgs(ctx, args) +func (da *DateArith) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { + t, years, months, days, durations, err := da.evalArgs(ctx, args) if t.IsZero() || err != nil { return nil, errors.Trace(err) } - if !dc.isAdd() { + if !da.isAdd() { years, months, days, durations = -years, -months, -days, -durations } t.Time = t.Time.Add(durations) @@ -91,9 +91,9 @@ func (dc *DateCast) Eval(ctx context.Context, args map[interface{}]interface{}) return t, nil } -func (dc *DateCast) evalArgs(ctx context.Context, args map[interface{}]interface{}) ( +func (da *DateArith) evalArgs(ctx context.Context, args map[interface{}]interface{}) ( mysql.Time, int64, int64, int64, time.Duration, error) { - dVal, err := dc.Date.Eval(ctx, args) + dVal, err := da.Date.Eval(ctx, args) if dVal == nil || err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } @@ -112,7 +112,7 @@ func (dc *DateCast) evalArgs(ctx context.Context, args map[interface{}]interface return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Errorf("need time type, but got %T", dVal) } - iVal, err := dc.Interval.Eval(ctx, args) + iVal, err := da.Interval.Eval(ctx, args) if iVal == nil || err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } @@ -120,7 +120,7 @@ func (dc *DateCast) evalArgs(ctx context.Context, args map[interface{}]interface if err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } - years, months, days, durations, err := mysql.ExtractTimeValue(dc.Unit, strings.TrimSpace(iValStr)) + years, months, days, durations, err := mysql.ExtractTimeValue(da.Unit, strings.TrimSpace(iValStr)) if err != nil { return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) } diff --git a/expression/date_cast_test.go b/expression/date_arith_test.go similarity index 95% rename from expression/date_cast_test.go rename to expression/date_arith_test.go index 5e4184f0f0111..3bf53fb383233 100644 --- a/expression/date_cast_test.go +++ b/expression/date_arith_test.go @@ -18,14 +18,14 @@ import ( "github.com/pingcap/tidb/mysql" ) -var _ = Suite(&testDateCastSuite{}) +var _ = Suite(&testDateArithSuite{}) -type testDateCastSuite struct { +type testDateArithSuite struct { } -func (t *testDateCastSuite) TestDateCast(c *C) { +func (t *testDateArithSuite) TestDateArith(c *C) { input := "2011-11-11 10:10:10" - e := &DateCast{ + e := &DateArith{ Op: add, Unit: "DAY", Date: Value{Val: input}, @@ -50,7 +50,7 @@ func (t *testDateCastSuite) TestDateCast(c *C) { {add, "DAY", input, nil}, } for _, t := range nullTbl { - e := &DateCast{ + e := &DateArith{ Op: t.Op, Unit: t.Unit, Date: Value{Val: t.Date}, @@ -98,7 +98,7 @@ func (t *testDateCastSuite) TestDateCast(c *C) { {"YEAR_MONTH", "11-11", "2023-10-11 10:10:10", "1999-12-11 10:10:10"}, } for _, t := range tbl { - e := &DateCast{ + e := &DateArith{ Op: add, Unit: t.Unit, Date: Value{Val: input}, @@ -135,7 +135,7 @@ func (t *testDateCastSuite) TestDateCast(c *C) { {"YEAR_MONTH", "10 1"}, } for _, t := range errTbl { - e := &DateCast{ + e := &DateArith{ Op: add, Unit: t.Unit, Date: Value{Val: input}, @@ -147,7 +147,7 @@ func (t *testDateCastSuite) TestDateCast(c *C) { v, err := e.Eval(nil, nil) c.Assert(err, NotNil, Commentf("%s", v)) - e = &DateCast{ + e = &DateArith{ Op: sub, Unit: t.Unit, Date: Value{Val: input}, diff --git a/expression/visitor.go b/expression/visitor.go index 27f61e00e3da5..9072e30c7970b 100644 --- a/expression/visitor.go +++ b/expression/visitor.go @@ -107,8 +107,8 @@ type Visitor interface { // VisitFunctionTrim visits FunctionTrim expression. VisitFunctionTrim(v *FunctionTrim) (Expression, error) - // VisitDateCast visits DateAdd and DateSub expression. - VisitDateCast(dc *DateCast) (Expression, error) + // VisitDateArith visits DateAdd and DateSub expression. + VisitDateArith(dc *DateArith) (Expression, error) } // BaseVisitor is the base implementation of Visitor. @@ -482,18 +482,18 @@ func (bv *BaseVisitor) VisitFunctionTrim(ss *FunctionTrim) (Expression, error) { return ss, nil } -// VisitDateCast implements Visitor interface. -func (bv *BaseVisitor) VisitDateCast(dc *DateCast) (Expression, error) { +// VisitDateArith implements Visitor interface. +func (bv *BaseVisitor) VisitDateArith(da *DateArith) (Expression, error) { var err error - dc.Date, err = dc.Date.Accept(bv.V) + da.Date, err = da.Date.Accept(bv.V) if err != nil { - return dc, errors.Trace(err) + return da, errors.Trace(err) } - dc.Interval, err = dc.Interval.Accept(bv.V) + da.Interval, err = da.Interval.Accept(bv.V) if err != nil { - return dc, errors.Trace(err) + return da, errors.Trace(err) } - return dc, nil + return da, nil } diff --git a/parser/parser.y b/parser/parser.y index 4e59d4f670b65..db7333517285a 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -2286,7 +2286,7 @@ FunctionCallNonKeyword: } | "DATE_ADD" '(' Expression ',' "INTERVAL" Expression TimeUnit ')' { - $$ = &expression.DateCast{ + $$ = &expression.DateArith{ Op:"ADD", Unit: $7.(string), Date: $3.(expression.Expression), @@ -2295,7 +2295,7 @@ FunctionCallNonKeyword: } | "DATE_SUB" '(' Expression ',' "INTERVAL" Expression TimeUnit ')' { - $$ = &expression.DateCast{ + $$ = &expression.DateArith{ Op:"SUB", Unit: $7.(string), Date: $3.(expression.Expression), From 480f7f358c4d423e94823987826122cb2468576a Mon Sep 17 00:00:00 2001 From: xia Date: Fri, 30 Oct 2015 14:50:12 +0800 Subject: [PATCH 8/9] *: use a const int value instead of string --- expression/date_arith.go | 25 ++++++++++++++++--------- expression/date_arith_test.go | 20 ++++++++++---------- parser/parser.y | 4 ++-- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/expression/date_arith.go b/expression/date_arith.go index cd32586664a68..06af03f4bf71e 100644 --- a/expression/date_arith.go +++ b/expression/date_arith.go @@ -25,24 +25,24 @@ import ( ) const ( - add = "ADD" - sub = "SUB" + // DateAdd is to run date_add function option. + // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add + DateAdd = 1 + // DateSub is to run date_sub function option. + // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-sub + DateSub = 2 ) // DateArith is used for dealing with addition and substraction of time. -// If the Op value is ADD, then do date_add function. -// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add -// If the Op value is SUB, then do date_sub function. -// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-sub type DateArith struct { - Op string + Op int Unit string Date Expression Interval Expression } func (da *DateArith) isAdd() bool { - if da.Op == add { + if da.Op == DateAdd { return true } @@ -67,7 +67,14 @@ func (da *DateArith) Accept(v Visitor) (Expression, error) { // String implements the Expression String interface. func (da *DateArith) String() string { - return fmt.Sprintf("DATE_%s(%s, INTERVAL %s %s)", da.Op, da.Date, da.Interval, strings.ToUpper(da.Unit)) + var str string + if da.isAdd() { + str = "DATE_ADD" + } else { + str = "DATE_SUB" + } + + return fmt.Sprintf("%s(%s, INTERVAL %s %s)", str, da.Date, da.Interval, strings.ToUpper(da.Unit)) } // Eval implements the Expression Eval interface. diff --git a/expression/date_arith_test.go b/expression/date_arith_test.go index 3bf53fb383233..19be63bccafd7 100644 --- a/expression/date_arith_test.go +++ b/expression/date_arith_test.go @@ -26,7 +26,7 @@ type testDateArithSuite struct { func (t *testDateArithSuite) TestDateArith(c *C) { input := "2011-11-11 10:10:10" e := &DateArith{ - Op: add, + Op: DateAdd, Unit: "DAY", Date: Value{Val: input}, Interval: Value{Val: "1"}, @@ -36,18 +36,18 @@ func (t *testDateArithSuite) TestDateArith(c *C) { c.Assert(e.IsStatic(), IsTrue) _, err := e.Eval(nil, nil) c.Assert(err, IsNil) - e.Op = sub + e.Op = DateSub c.Assert(e.String(), Equals, `DATE_SUB("2011-11-11 10:10:10", INTERVAL "1" DAY)`) // Test null. nullTbl := []struct { - Op string + Op int Unit string Date interface{} Interval interface{} }{ - {add, "DAY", nil, "1"}, - {add, "DAY", input, nil}, + {DateAdd, "DAY", nil, "1"}, + {DateAdd, "DAY", input, nil}, } for _, t := range nullTbl { e := &DateArith{ @@ -59,7 +59,7 @@ func (t *testDateArithSuite) TestDateArith(c *C) { v, err := e.Eval(nil, nil) c.Assert(err, IsNil) c.Assert(v, IsNil) - e.Op = sub + e.Op = DateSub v, err = e.Eval(nil, nil) c.Assert(err, IsNil) c.Assert(v, IsNil) @@ -99,7 +99,7 @@ func (t *testDateArithSuite) TestDateArith(c *C) { } for _, t := range tbl { e := &DateArith{ - Op: add, + Op: DateAdd, Unit: t.Unit, Date: Value{Val: input}, Interval: Value{Val: t.Interval}, @@ -110,7 +110,7 @@ func (t *testDateArithSuite) TestDateArith(c *C) { c.Assert(ok, IsTrue) c.Assert(value.String(), Equals, t.AddExpect) - e.Op = sub + e.Op = DateSub v, err = e.Eval(nil, nil) c.Assert(err, IsNil) value, ok = v.(mysql.Time) @@ -136,7 +136,7 @@ func (t *testDateArithSuite) TestDateArith(c *C) { } for _, t := range errTbl { e := &DateArith{ - Op: add, + Op: DateAdd, Unit: t.Unit, Date: Value{Val: input}, Interval: Value{Val: t.Interval}, @@ -148,7 +148,7 @@ func (t *testDateArithSuite) TestDateArith(c *C) { c.Assert(err, NotNil, Commentf("%s", v)) e = &DateArith{ - Op: sub, + Op: DateSub, Unit: t.Unit, Date: Value{Val: input}, Interval: Value{Val: t.Interval}, diff --git a/parser/parser.y b/parser/parser.y index db7333517285a..2a3ebc02ee3d9 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -2287,7 +2287,7 @@ FunctionCallNonKeyword: | "DATE_ADD" '(' Expression ',' "INTERVAL" Expression TimeUnit ')' { $$ = &expression.DateArith{ - Op:"ADD", + Op:expression.DateAdd, Unit: $7.(string), Date: $3.(expression.Expression), Interval: $6.(expression.Expression), @@ -2296,7 +2296,7 @@ FunctionCallNonKeyword: | "DATE_SUB" '(' Expression ',' "INTERVAL" Expression TimeUnit ')' { $$ = &expression.DateArith{ - Op:"SUB", + Op:expression.DateSub, Unit: $7.(string), Date: $3.(expression.Expression), Interval: $6.(expression.Expression), From df4cc43703ffaef37f387d821fa75d76c0de891b Mon Sep 17 00:00:00 2001 From: zimulala Date: Sat, 31 Oct 2015 17:09:01 +0800 Subject: [PATCH 9/9] expression: update comments --- expression/date_arith.go | 9 ++++++--- expression/date_arith_test.go | 2 +- expression/visitor.go | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/expression/date_arith.go b/expression/date_arith.go index 06af03f4bf71e..0a9a6c0de34ed 100644 --- a/expression/date_arith.go +++ b/expression/date_arith.go @@ -24,18 +24,21 @@ import ( "github.com/pingcap/tidb/util/types" ) +// DateArithType is type for DateArith option. +type DateArithType byte + const ( // DateAdd is to run date_add function option. // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add - DateAdd = 1 + DateAdd DateArithType = iota + 1 // DateSub is to run date_sub function option. // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-sub - DateSub = 2 + DateSub ) // DateArith is used for dealing with addition and substraction of time. type DateArith struct { - Op int + Op DateArithType Unit string Date Expression Interval Expression diff --git a/expression/date_arith_test.go b/expression/date_arith_test.go index 19be63bccafd7..2775d7d914868 100644 --- a/expression/date_arith_test.go +++ b/expression/date_arith_test.go @@ -41,7 +41,7 @@ func (t *testDateArithSuite) TestDateArith(c *C) { // Test null. nullTbl := []struct { - Op int + Op DateArithType Unit string Date interface{} Interval interface{} diff --git a/expression/visitor.go b/expression/visitor.go index 9072e30c7970b..6646d59db7f6c 100644 --- a/expression/visitor.go +++ b/expression/visitor.go @@ -107,7 +107,7 @@ type Visitor interface { // VisitFunctionTrim visits FunctionTrim expression. VisitFunctionTrim(v *FunctionTrim) (Expression, error) - // VisitDateArith visits DateAdd and DateSub expression. + // VisitDateArith visits DateArith expression. VisitDateArith(dc *DateArith) (Expression, error) }