Merge pull request #335 from pingcap/siddontang/time-extract

support time extract
This commit is contained in:
Shen Li
2015-10-10 11:03:29 +08:00
7 changed files with 329 additions and 2 deletions

147
expression/extract.go Normal file
View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 <item>
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

View File

@ -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)

View File

@ -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