diff --git a/expression/extract.go b/expression/extract.go new file mode 100644 index 0000000000..8706d82ac8 --- /dev/null +++ b/expression/extract.go @@ -0,0 +1,147 @@ +// 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 ( + "fmt" + "strings" + + "github.com/juju/errors" + "github.com/pingcap/tidb/context" + mysql "github.com/pingcap/tidb/mysqldef" + "github.com/pingcap/tidb/util/types" +) + +// Extract is for time extract function. +// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_extract +type Extract struct { + Unit string + Date Expression +} + +// Clone implements the Expression Clone interface. +func (e *Extract) Clone() Expression { + n := *e + return &n +} + +// Eval implements the Expression Eval interface. +func (e *Extract) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { + v, err := e.Date.Eval(ctx, args) + if v == nil || err != nil { + return nil, errors.Trace(err) + } + + f := types.NewFieldType(mysql.TypeDatetime) + f.Decimal = mysql.MaxFsp + + v, err = types.Convert(v, f) + if v == nil || err != nil { + return nil, errors.Trace(err) + } + + t, ok := v.(mysql.Time) + if !ok { + return nil, errors.Errorf("need time type, but got %T", v) + } + + n, err1 := extractTime(e.Unit, t) + if err1 != nil { + return nil, errors.Trace(err1) + } + + return int64(n), nil +} + +// IsStatic implements the Expression IsStatic interface. +func (e *Extract) IsStatic() bool { + return e.Date.IsStatic() +} + +// String implements the Expression String interface. +func (e *Extract) String() string { + return fmt.Sprintf("EXTRACT(%s FROM %s)", strings.ToUpper(e.Unit), e.Date) +} + +// Accept implements the Visitor Accept interface. +func (e *Extract) Accept(v Visitor) (Expression, error) { + return v.VisitExtract(e) +} + +func extractTime(unit string, t mysql.Time) (int, error) { + switch strings.ToUpper(unit) { + case "MICROSECOND": + return t.Nanosecond() / 1000, nil + case "SECOND": + return t.Second(), nil + case "MINUTE": + return t.Minute(), nil + case "HOUR": + return t.Hour(), nil + case "DAY": + return t.Day(), nil + case "WEEK": + _, week := t.ISOWeek() + return week, nil + case "MONTH": + return int(t.Month()), nil + case "QUARTER": + m := int(t.Month()) + // 1 - 3 -> 1 + // 4 - 6 -> 2 + // 7 - 9 -> 3 + // 10 - 12 -> 4 + return (m + 2) / 3, nil + case "YEAR": + return t.Year(), nil + case "SECOND_MICROSECOND": + return t.Second()*1000000 + t.Nanosecond()/1000, nil + case "MINUTE_MICROSECOND": + _, m, s := t.Clock() + return m*100000000 + s*1000000 + t.Nanosecond()/1000, nil + case "MINUTE_SECOND": + _, m, s := t.Clock() + return m*100 + s, nil + case "HOUR_MICROSECOND": + h, m, s := t.Clock() + return h*10000000000 + m*100000000 + s*1000000 + t.Nanosecond()/1000, nil + case "HOUR_SECOND": + h, m, s := t.Clock() + return h*10000 + m*100 + s, nil + case "HOUR_MINUTE": + h, m, _ := t.Clock() + return h*100 + m, nil + case "DAY_MICROSECOND": + h, m, s := t.Clock() + d := t.Day() + return (d*1000000+h*10000+m*100+s)*1000000 + t.Nanosecond()/1000, nil + case "DAY_SECOND": + h, m, s := t.Clock() + d := t.Day() + return d*1000000 + h*10000 + m*100 + s, nil + case "DAY_MINUTE": + h, m, _ := t.Clock() + d := t.Day() + return d*10000 + h*100 + m, nil + case "DAY_HOUR": + h, _, _ := t.Clock() + d := t.Day() + return d*100 + h, nil + case "YEAR_MONTH": + y, m, _ := t.Date() + return y*100 + int(m), nil + default: + return 0, errors.Errorf("invalid unit %s", unit) + } +} diff --git a/expression/extract_test.go b/expression/extract_test.go new file mode 100644 index 0000000000..27d064d9d5 --- /dev/null +++ b/expression/extract_test.go @@ -0,0 +1,78 @@ +// 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 ( + "strings" + + . "github.com/pingcap/check" +) + +var _ = Suite(&testExtractSuite{}) + +type testExtractSuite struct { +} + +func (t *testExtractSuite) TestExtract(c *C) { + str := "2011-11-11 10:10:10.123456" + tbl := []struct { + Unit string + Expect int64 + }{ + {"MICROSECOND", 123456}, + {"SECOND", 10}, + {"MINUTE", 10}, + {"HOUR", 10}, + {"DAY", 11}, + {"WEEK", 45}, + {"MONTH", 11}, + {"QUARTER", 4}, + {"YEAR", 2011}, + {"SECOND_MICROSECOND", 10123456}, + {"MINUTE_MICROSECOND", 1010123456}, + {"MINUTE_SECOND", 1010}, + {"HOUR_MICROSECOND", 101010123456}, + {"HOUR_SECOND", 101010}, + {"HOUR_MINUTE", 1010}, + {"DAY_MICROSECOND", 11101010123456}, + {"DAY_SECOND", 11101010}, + {"DAY_MINUTE", 111010}, + {"DAY_HOUR", 1110}, + {"YEAR_MONTH", 201111}, + } + + for _, t := range tbl { + e := &Extract{ + Unit: t.Unit, + Date: Value{Val: str}, + } + + v, err := e.Eval(nil, nil) + c.Assert(err, IsNil) + c.Assert(v, Equals, t.Expect) + } + + // Test nil + e := &Extract{ + Unit: "SECOND", + Date: Value{Val: nil}, + } + + v, err := e.Eval(nil, nil) + c.Assert(err, IsNil) + c.Assert(v, IsNil) + + c.Assert(strings.ToUpper(e.String()), Equals, "EXTRACT(SECOND FROM NULL)") + c.Assert(e.Clone(), NotNil) +} diff --git a/expression/visitor.go b/expression/visitor.go index b62113ae2c..48903d5ab3 100644 --- a/expression/visitor.go +++ b/expression/visitor.go @@ -100,6 +100,9 @@ type Visitor interface { // VisitWhenClause visits WhenClause expression. VisitWhenClause(w *WhenClause) (Expression, error) + + // VisitExtract visits Extract expression. + VisitExtract(v *Extract) (Expression, error) } // BaseVisitor is the base implementation of Visitor. @@ -449,3 +452,10 @@ func (bv *BaseVisitor) VisitWhenClause(w *WhenClause) (Expression, error) { } return w, nil } + +// VisitExtract implements Visitor +func (bv *BaseVisitor) VisitExtract(v *Extract) (Expression, error) { + var err error + v.Date, err = v.Date.Accept(bv.V) + return v, errors.Trace(err) +} diff --git a/expression/visitor_test.go b/expression/visitor_test.go index 76e9694386..281c86c00d 100644 --- a/expression/visitor_test.go +++ b/expression/visitor_test.go @@ -87,4 +87,6 @@ func (s *testVisitorSuite) TestBase(c *C) { exp.Accept(visitor) exp = &expression.Variable{Name: "a"} exp.Accept(visitor) + exp = &expression.Extract{Unit: "SECOND", Date: expression.Value{Val: nil}} + exp.Accept(visitor) } diff --git a/parser/parser.y b/parser/parser.y index 81e82718d9..badf55444e 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -136,6 +136,7 @@ import ( execute "EXECUTE" exists "EXISTS" explain "EXPLAIN" + extract "EXTRACT" falseKwd "false" first "FIRST" foreign "FOREIGN" @@ -202,6 +203,7 @@ import ( placeholder "PLACEHOLDER" prepare "PREPARE" primary "PRIMARY" + quarter "QUARTER" quick "QUICK" rand "RAND" references "REFERENCES" @@ -316,6 +318,18 @@ import ( parseExpression "parse expression prefix" + secondMicrosecond "SECOND_MICROSECOND" + minuteMicrosecond "MINUTE_MICROSECOND" + minuteSecond "MINUTE_SECOND" + hourMicrosecond "HOUR_MICROSECOND" + hourSecond "HOUR_SECOND" + hourMinute "HOUR_MINUTE" + dayMicrosecond "DAY_MICROSECOND" + daySecond "DAY_SECOND" + dayMinute "DAY_MINUTE" + dayHour "DAY_HOUR" + yearMonth "YEAR_MONTH" + %type AlterTableStmt "Alter table statement" AlterSpecification "Alter table specification" @@ -479,6 +493,7 @@ import ( TableOptListOpt "create table option list opt" TableRef "table reference" TableRefs "table references" + TimeUnit "Time unit" TruncateTableStmt "TRANSACTION TABLE statement" UnionOpt "Union Option(empty/ALL/DISTINCT)" UnionSelect "Union select/(select)" @@ -1646,7 +1661,7 @@ UnReservedKeyword: | "START" | "GLOBAL" | "TABLES"| "TEXT" | "TIME" | "TIMESTAMP" | "TRANSACTION" | "TRUNCATE" | "UNKNOWN" | "VALUE" | "WARNINGS" | "YEAR" | "MODE" | "WEEK" | "ANY" | "SOME" | "USER" | "IDENTIFIED" | "COLLATION" | "COMMENT" | "AVG_ROW_LENGTH" | "CONNECTION" | "CHECKSUM" | "COMPRESSION" | "KEY_BLOCK_SIZE" | "MAX_ROWS" | "MIN_ROWS" -| "NATIONAL" | "ROW" +| "NATIONAL" | "ROW" | "QUARTER" NotKeywordToken: "ABS" | "COALESCE" | "CONCAT" | "CONCAT_WS" | "COUNT" | "DAY" | "DAYOFMONTH" | "DAYOFWEEK" | "DAYOFYEAR" | "FOUND_ROWS" | "GROUP_CONCAT" @@ -2186,6 +2201,13 @@ FunctionCallNonKeyword: return 1 } } +| "EXTRACT" '(' TimeUnit "FROM" Expression ')' + { + $$ = &expression.Extract{ + Unit: $3.(string), + Date: $5.(expression.Expression), + } + } | "FOUND_ROWS" '(' ')' { args := []expression.Expression{} @@ -2513,6 +2535,12 @@ FuncDatetimePrec: $$ = $2 } +TimeUnit: + "MICROSECOND" | "SECOND" | "MINUTE" | "HOUR" | "DAY" | "WEEK" +| "MONTH" | "QUARTER" | "YEAR" | "SECOND_MICROSECOND" | "MINUTE_MICROSECOND" +| "MINUTE_SECOND" | "HOUR_MICROSECOND" | "HOUR_SECOND" | "HOUR_MINUTE" +| "DAY_MICROSECOND" | "DAY_SECOND" | "DAY_MINUTE" | "DAY_HOUR" | "YEAR_MONTH" + ExpressionOpt: { $$ = nil diff --git a/parser/parser_test.go b/parser/parser_test.go index 8082e1721b..e240176ce2 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -500,6 +500,28 @@ func (s *testParserSuite) TestParser0(c *C) { // For check clause {"CREATE TABLE Customer (SD integer CHECK (SD > 0), First_Name varchar(30));", true}, + // For time extract + {`select extract(microsecond from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(second from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(minute from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(hour from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(day from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(week from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(month from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(quarter from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(year from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(second_microsecond from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(minute_microsecond from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(minute_second from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(hour_microsecond from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(hour_second from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(hour_minute from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(day_microsecond from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(day_second from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(day_minute from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(day_hour from "2011-11-11 10:10:10.123456")`, true}, + {`select extract(year_month from "2011-11-11 10:10:10.123456")`, true}, + // For as {"select 1 as a, 1 as `a`, 1 as \"a\", 1 as 'a'", true}, {`select 1 as a, 1 as "a", 1 as 'a'`, true}, @@ -532,7 +554,7 @@ func (s *testParserSuite) TestParser0(c *C) { "start", "global", "tables", "text", "time", "timestamp", "transaction", "truncate", "unknown", "value", "warnings", "year", "now", "substring", "mode", "any", "some", "user", "identified", "collation", "comment", "avg_row_length", "checksum", "compression", "connection", "key_block_size", - "max_rows", "min_rows", "national", "row", + "max_rows", "min_rows", "national", "row", "quarter", } for _, kw := range unreservedKws { src := fmt.Sprintf("SELECT %s FROM tbl;", kw) diff --git a/parser/scanner.l b/parser/scanner.l index ddefa21864..c93aa77a5c 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -306,6 +306,7 @@ engines {e}{n}{g}{i}{n}{e}{s} execute {e}{x}{e}{c}{u}{t}{e} exists {e}{x}{i}{s}{t}{s} explain {e}{x}{p}{l}{a}{i}{n} +extract {e}{x}{t}{r}{a}{c}{t} first {f}{i}{r}{s}{t} for {f}{o}{r} foreign {f}{o}{r}{e}{i}{g}{n} @@ -359,6 +360,7 @@ outer {o}{u}{t}{e}{r} password {p}{a}{s}{s}{w}{o}{r}{d} prepare {p}{r}{e}{p}{a}{r}{e} primary {p}{r}{i}{m}{a}{r}{y} +quarter {q}{u}{a}{r}{t}{e}{r} quick {q}{u}{i}{c}{k} rand {r}{a}{n}{d} repeat {r}{e}{p}{e}{a}{t} @@ -473,6 +475,18 @@ ident {idchar0}{idchars}* user_var "@"{ident} sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} +second_microsecond {s}{e}{c}{o}{n}{d}_{m}{i}{c}{r}{o}{s}{e}{c}{o}{n}{d} +minute_microsecond {m}{i}{n}{u}{t}{e}_{m}{i}{c}{r}{o}{s}{e}{c}{o}{n}{d} +minute_second {m}{i}{n}{u}{t}{e}_{s}{e}{c}{o}{n}{d} +hour_microsecond {h}{o}{u}{r}_{m}{i}{c}{r}{o}{s}{e}{c}{o}{n}{d} +hour_second {h}{o}{u}{r}_{s}{e}{c}{o}{n}{d} +hour_minute {h}{o}{u}{r}_{m}{i}{n}{u}{t}{e} +day_microsecond {d}{a}{y}_{m}{i}{c}{r}{o}{s}{e}{c}{o}{n}{d} +day_second {d}{a}{y}_{s}{e}{c}{o}{n}{d} +day_minute {d}{a}{y}_{m}{i}{n}{u}{t}{e} +day_hour {d}{a}{y}_{h}{o}{u}{r} +year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h} + %yyc c %yyn c = l.next() %yyt l.sc @@ -615,6 +629,14 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} return dayofmonth {dayofyear} lval.item = string(l.val) return dayofyear +{day_hour} lval.item = string(l.val) + return dayHour +{day_microsecond} lval.item = string(l.val) + return dayMicrosecond +{day_minute} lval.item = string(l.val) + return dayMinute +{day_second} lval.item = string(l.val) + return daySecond {deallocate} lval.item = string(l.val) return deallocate {default} return defaultKwd @@ -642,6 +664,8 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {enum} return enum {exists} return exists {explain} return explain +{extract} lval.item = string(l.val) + return extract {first} lval.item = string(l.val) return first {for} return forKwd @@ -659,6 +683,12 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {high_priority} return highPriority {hour} lval.item = string(l.val) return hour +{hour_microsecond} lval.item = string(l.val) + return hourMicrosecond +{hour_minute} lval.item = string(l.val) + return hourMinute +{hour_second} lval.item = string(l.val) + return hourSecond {identified} lval.item = string(l.val) return identified {if} lval.item = string(l.val) @@ -700,6 +730,10 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} return min {minute} lval.item = string(l.val) return minute +{minute_microsecond} lval.item = string(l.val) + return minuteMicrosecond +{minute_second} lval.item = string(l.val) + return minuteSecond {min_rows} lval.item = string(l.val) return minRows {mod} return mod @@ -723,6 +757,8 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {prepare} lval.item = string(l.val) return prepare {primary} return primary +{quarter} lval.item = string(l.val) + return quarter {quick} lval.item = string(l.val) return quick {right} return right @@ -756,6 +792,8 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} return userVar {second} lval.item = string(l.val) return second +{second_microsecond} lval.item= string(l.val) + return secondMicrosecond {select} return selectKwd {set} return set @@ -808,6 +846,8 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {xor} return xor {yearweek} lval.item = string(l.val) return yearweek +{year_month} lval.item = string(l.val) + return yearMonth {signed} lval.item = string(l.val) return signed