From 698c0dd8d025baf801c94995c927187bc02cf637 Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 9 Oct 2015 09:40:52 +0800 Subject: [PATCH 1/5] builtin: support time extract --- expression/builtin/builtin.go | 1 + expression/builtin/time.go | 90 +++++++++++++++++++++++++++++++++ expression/builtin/time_test.go | 35 +++++++++++++ 3 files changed, 126 insertions(+) diff --git a/expression/builtin/builtin.go b/expression/builtin/builtin.go index 88d2dac2d7..6ebb110d0c 100644 --- a/expression/builtin/builtin.go +++ b/expression/builtin/builtin.go @@ -70,6 +70,7 @@ var Funcs = map[string]Func{ "dayofmonth": {builtinDayOfMonth, 1, 1, true, false}, "dayofweek": {builtinDayOfWeek, 1, 1, true, false}, "dayofyear": {builtinDayOfYear, 1, 1, true, false}, + "extract": {builtinExtract, 2, 2, true, false}, "hour": {builtinHour, 1, 1, true, false}, "microsecond": {builtinMicroSecond, 1, 1, true, false}, "minute": {builtinMinute, 1, 1, true, false}, diff --git a/expression/builtin/time.go b/expression/builtin/time.go index 8a7c961611..34b4af4461 100644 --- a/expression/builtin/time.go +++ b/expression/builtin/time.go @@ -18,6 +18,7 @@ package builtin import ( + "strings" "time" "github.com/juju/errors" @@ -313,3 +314,92 @@ func checkFsp(arg interface{}) (int, error) { } return int(fsp), nil } + +// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_extract +func builtinExtract(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { + unit, ok := args[0].(string) + if !ok { + return nil, errors.Errorf("invalid unit type %T, must string", args[0]) + } + + v, err := convertToTime(args[1], mysql.TypeDatetime) + if err != nil { + return nil, errors.Trace(err) + } + + t := v.(mysql.Time) + if t.IsZero() { + return 0, nil + } + + // TODO: add more check + n, err := extractTime(unit, t) + return int64(n), errors.Trace(err) +} + +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/builtin/time_test.go b/expression/builtin/time_test.go index 5db28de3d3..18570236f7 100644 --- a/expression/builtin/time_test.go +++ b/expression/builtin/time_test.go @@ -266,3 +266,38 @@ func (s *testBuiltinSuite) TestSysDate(c *C) { _, err = builtinSysDate([]interface{}{-2}, nil) c.Assert(err, NotNil) } + +func (s *testBuiltinSuite) 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 { + v, err := builtinExtract([]interface{}{t.Unit, str}, nil) + c.Assert(err, IsNil) + c.Assert(v, Equals, t.Expect, Commentf("%s", t.Unit)) + } +} From f8361752aa3c4d83204217bd21d69e64747e04ab Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 9 Oct 2015 10:13:05 +0800 Subject: [PATCH 2/5] parser: support extract time function --- parser/parser.y | 34 +++++++++++++++++++++++++++++++++- parser/parser_test.go | 24 +++++++++++++++++++++++- parser/scanner.l | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/parser/parser.y b/parser/parser.y index 26e292374e..6283cd1910 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -134,6 +134,7 @@ import ( execute "EXECUTE" exists "EXISTS" explain "EXPLAIN" + extract "EXTRACT" falseKwd "false" first "FIRST" foreign "FOREIGN" @@ -199,6 +200,7 @@ import ( placeholder "PLACEHOLDER" prepare "PREPARE" primary "PRIMARY" + quarter "QUARTER" quick "QUICK" rand "RAND" references "REFERENCES" @@ -313,6 +315,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" @@ -475,6 +489,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)" @@ -1635,7 +1650,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" @@ -2155,6 +2170,17 @@ FunctionCallNonKeyword: return 1 } } +| "EXTRACT" '(' TimeUnit "FROM" Expression ')' + { + args := []expression.Expression{expression.Value{Val:$3.(string)}, $5.(expression.Expression)} + var err error + $$, err = expression.NewCall($1.(string), args, false) + if err != nil { + l := yylex.(*lexer) + l.err(err) + return 1 + } + } | "FOUND_ROWS" '(' ')' { args := []expression.Expression{} @@ -2467,6 +2493,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 0b3ed915e3..695479822d 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -494,6 +494,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 _, t := range table { @@ -517,7 +539,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 8637da38d4..41ba6b3b62 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -304,6 +304,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} @@ -356,6 +357,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} @@ -470,6 +472,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 @@ -608,6 +622,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 @@ -635,6 +657,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 @@ -652,6 +676,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) @@ -691,6 +721,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 @@ -714,6 +748,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 @@ -747,6 +783,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 @@ -799,6 +837,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 From 3514b33fc35d52115cc39998757fdbdd63d2fb55 Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 9 Oct 2015 11:29:09 +0800 Subject: [PATCH 3/5] *: use Extract expression --- expression/extract.go | 147 +++++++++++++++++++++++++++++++++++++ expression/extract_test.go | 78 ++++++++++++++++++++ expression/visitor.go | 10 +++ expression/visitor_test.go | 2 + parser/parser.y | 10 +-- 5 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 expression/extract.go create mode 100644 expression/extract_test.go diff --git a/expression/extract.go b/expression/extract.go new file mode 100644 index 0000000000..d5e738b73d --- /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 Expression 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 180161fd50..6398f6de4b 100644 --- a/expression/visitor.go +++ b/expression/visitor.go @@ -97,6 +97,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. @@ -425,3 +428,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 cf39877921..b50c46fe80 100644 --- a/expression/visitor_test.go +++ b/expression/visitor_test.go @@ -93,4 +93,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 6283cd1910..041a637830 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -2172,13 +2172,9 @@ FunctionCallNonKeyword: } | "EXTRACT" '(' TimeUnit "FROM" Expression ')' { - args := []expression.Expression{expression.Value{Val:$3.(string)}, $5.(expression.Expression)} - var err error - $$, err = expression.NewCall($1.(string), args, false) - if err != nil { - l := yylex.(*lexer) - l.err(err) - return 1 + $$ = &expression.Extract{ + Unit: $3.(string), + Date: $5.(expression.Expression), } } | "FOUND_ROWS" '(' ')' From 38f95c0d0a4a6d7aa5068c0082a9b8463dc0d866 Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 9 Oct 2015 11:29:29 +0800 Subject: [PATCH 4/5] builtin: remove extract implement --- expression/builtin/builtin.go | 1 - expression/builtin/time.go | 90 --------------------------------- expression/builtin/time_test.go | 35 ------------- 3 files changed, 126 deletions(-) diff --git a/expression/builtin/builtin.go b/expression/builtin/builtin.go index 6ebb110d0c..88d2dac2d7 100644 --- a/expression/builtin/builtin.go +++ b/expression/builtin/builtin.go @@ -70,7 +70,6 @@ var Funcs = map[string]Func{ "dayofmonth": {builtinDayOfMonth, 1, 1, true, false}, "dayofweek": {builtinDayOfWeek, 1, 1, true, false}, "dayofyear": {builtinDayOfYear, 1, 1, true, false}, - "extract": {builtinExtract, 2, 2, true, false}, "hour": {builtinHour, 1, 1, true, false}, "microsecond": {builtinMicroSecond, 1, 1, true, false}, "minute": {builtinMinute, 1, 1, true, false}, diff --git a/expression/builtin/time.go b/expression/builtin/time.go index 34b4af4461..8a7c961611 100644 --- a/expression/builtin/time.go +++ b/expression/builtin/time.go @@ -18,7 +18,6 @@ package builtin import ( - "strings" "time" "github.com/juju/errors" @@ -314,92 +313,3 @@ func checkFsp(arg interface{}) (int, error) { } return int(fsp), nil } - -// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_extract -func builtinExtract(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { - unit, ok := args[0].(string) - if !ok { - return nil, errors.Errorf("invalid unit type %T, must string", args[0]) - } - - v, err := convertToTime(args[1], mysql.TypeDatetime) - if err != nil { - return nil, errors.Trace(err) - } - - t := v.(mysql.Time) - if t.IsZero() { - return 0, nil - } - - // TODO: add more check - n, err := extractTime(unit, t) - return int64(n), errors.Trace(err) -} - -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/builtin/time_test.go b/expression/builtin/time_test.go index 18570236f7..5db28de3d3 100644 --- a/expression/builtin/time_test.go +++ b/expression/builtin/time_test.go @@ -266,38 +266,3 @@ func (s *testBuiltinSuite) TestSysDate(c *C) { _, err = builtinSysDate([]interface{}{-2}, nil) c.Assert(err, NotNil) } - -func (s *testBuiltinSuite) 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 { - v, err := builtinExtract([]interface{}{t.Unit, str}, nil) - c.Assert(err, IsNil) - c.Assert(v, Equals, t.Expect, Commentf("%s", t.Unit)) - } -} From af042795cf1a3372115632a95250f382c43d221d Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 9 Oct 2015 17:55:20 +0800 Subject: [PATCH 5/5] expression: Address comment. --- expression/extract.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expression/extract.go b/expression/extract.go index d5e738b73d..8706d82ac8 100644 --- a/expression/extract.go +++ b/expression/extract.go @@ -74,7 +74,7 @@ func (e *Extract) String() string { return fmt.Sprintf("EXTRACT(%s FROM %s)", strings.ToUpper(e.Unit), e.Date) } -// Accept implements the Expression Accept interface. +// Accept implements the Visitor Accept interface. func (e *Extract) Accept(v Visitor) (Expression, error) { return v.VisitExtract(e) }