Merge pull request #335 from pingcap/siddontang/time-extract
support time extract
This commit is contained in:
147
expression/extract.go
Normal file
147
expression/extract.go
Normal 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)
|
||||
}
|
||||
}
|
||||
78
expression/extract_test.go
Normal file
78
expression/extract_test.go
Normal 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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user