Skip to content

Commit

Permalink
Merge branch 'master' into zimuxia/issue525
Browse files Browse the repository at this point in the history
Conflicts:
	expression/variable.go
	parser/scanner.l
	plan/plans/show.go
	plan/plans/show_test.go
	stmt/stmts/set.go
  • Loading branch information
zimulala committed Nov 9, 2015
2 parents f35ec29 + b803cab commit 4150855
Show file tree
Hide file tree
Showing 20 changed files with 508 additions and 103 deletions.
27 changes: 20 additions & 7 deletions ast/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,26 +338,39 @@ func (n *FuncTrimExpr) IsStatic() bool {
return n.Str.IsStatic() && n.RemStr.IsStatic()
}

// DateArithType is type for DateArith option.
// DateArithType is type for DateArith type.
type DateArithType byte

const (
// DateAdd is to run date_add function option.
// DateAdd is to run adddate or date_add function option.
// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_adddate
// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add
DateAdd DateArithType = iota + 1
// DateSub is to run date_sub function option.
// DateSub is to run subdate or date_sub function option.
// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_subdate
// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-sub
DateSub
// DateArithDaysForm is to run adddate or subdate function with days form Flag.
DateArithDaysForm
)

// DateArithInterval is the struct of DateArith interval part.
type DateArithInterval struct {
// Form is the flag of DateArith running form.
// The function runs with interval or days.
Form DateArithType
Unit string
Interval ExprNode
}

// FuncDateArithExpr is the struct for date arithmetic functions.
type FuncDateArithExpr struct {
funcNode

Op DateArithType
Unit string
Date ExprNode
Interval ExprNode
// Op is used for distinguishing date_add and date_sub.
Op DateArithType
Date ExprNode
DateArithInterval
}

// Accept implements Node Accept interface.
Expand Down
93 changes: 70 additions & 23 deletions expression/date_arith.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package expression

import (
"fmt"
"regexp"
"strings"
"time"

Expand All @@ -28,22 +29,38 @@ import (
type DateArithType byte

const (
// DateAdd is to run date_add function option.
// DateAdd is to run adddate or date_add function option.
// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_adddate
// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add
DateAdd DateArithType = iota + 1
// DateSub is to run date_sub function option.
// DateSub is to run subdate or date_sub function option.
// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_subdate
// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-sub
DateSub
// DateArithDaysForm is to run adddate or subdate function with days form Flag.
DateArithDaysForm
)

// DateArith is used for dealing with addition and substraction of time.
type DateArith struct {
Op DateArithType
Unit string
// Op is used for distinguishing date_add and date_sub.
Op DateArithType
// Form is the flag of DateArith running form.
// The function runs with interval or days.
Form DateArithType
Date Expression
Unit string
Interval Expression
}

type evalArgsResult struct {
time mysql.Time
year int64
month int64
day int64
duration time.Duration
}

func (da *DateArith) isAdd() bool {
if da.Op == DateAdd {
return true
Expand Down Expand Up @@ -82,58 +99,88 @@ func (da *DateArith) String() string {

// Eval implements the Expression Eval interface.
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 {
val, err := da.evalArgs(ctx, args)
if val.time.IsZero() || err != nil {
return nil, errors.Trace(err)
}

if !da.isAdd() {
years, months, days, durations = -years, -months, -days, -durations
val.year, val.month, val.day, val.duration =
-val.year, -val.month, -val.day, -val.duration
}
t.Time = t.Time.Add(durations)
t.Time = t.Time.AddDate(int(years), int(months), int(days))
val.time.Time = val.time.Time.Add(val.duration)
val.time.Time = val.time.Time.AddDate(int(val.year), int(val.month), int(val.day))

// "2011-11-11 10:10:20.000000" outputs "2011-11-11 10:10:20".
if t.Time.Nanosecond() == 0 {
t.Fsp = 0
if val.time.Time.Nanosecond() == 0 {
val.time.Fsp = 0
}

return t, nil
return val.time, nil
}

func (da *DateArith) evalArgs(ctx context.Context, args map[interface{}]interface{}) (
mysql.Time, int64, int64, int64, time.Duration, error) {
*evalArgsResult, error) {
ret := &evalArgsResult{time: mysql.ZeroTimestamp}

dVal, err := da.Date.Eval(ctx, args)
if dVal == nil || err != nil {
return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err)
return ret, errors.Trace(err)
}
dValStr, err := types.ToString(dVal)
if err != nil {
return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err)
return ret, errors.Trace(err)
}
f := types.NewFieldType(mysql.TypeDatetime)
f.Decimal = mysql.MaxFsp
dVal, err = types.Convert(dValStr, f)
if dVal == nil || err != nil {
return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err)
return ret, errors.Trace(err)
}
t, ok := dVal.(mysql.Time)

var ok bool
ret.time, ok = dVal.(mysql.Time)
if !ok {
return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Errorf("need time type, but got %T", dVal)
return ret, errors.Errorf("need time type, but got %T", dVal)
}

iVal, err := da.Interval.Eval(ctx, args)
if iVal == nil || err != nil {
return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err)
ret.time = mysql.ZeroTimestamp
return ret, errors.Trace(err)
}
// handle adddate(expr,days) or subdate(expr,days) form
if da.Form == DateArithDaysForm {
if iVal, err = da.evalDaysForm(iVal); err != nil {
return ret, errors.Trace(err)
}
}

iValStr, err := types.ToString(iVal)
if err != nil {
return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err)
return ret, errors.Trace(err)
}
years, months, days, durations, err := mysql.ExtractTimeValue(da.Unit, strings.TrimSpace(iValStr))
ret.year, ret.month, ret.day, ret.duration, err = mysql.ExtractTimeValue(da.Unit, strings.TrimSpace(iValStr))
if err != nil {
return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err)
return ret, errors.Trace(err)
}

return ret, nil
}

var reg = regexp.MustCompile(`[\d]+`)

func (da *DateArith) evalDaysForm(val interface{}) (interface{}, error) {
switch val.(type) {
case string:
if strings.ToLower(val.(string)) == "false" {
return 0, nil
}
if strings.ToLower(val.(string)) == "true" {
return 1, nil
}
val = reg.FindString(val.(string))
}

return t, years, months, days, durations, nil
return types.ToInt64(val)
}
43 changes: 37 additions & 6 deletions expression/date_arith_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,42 @@ func (t *testDateArithSuite) TestDateArith(c *C) {
c.Assert(value.String(), Equals, t.SubExpect)
}

// Test eval for adddate and subdate with days form
tblDays := []struct {
Interval interface{}
AddExpect string
SubExpect string
}{
{"20", "2011-12-01 10:10:10", "2011-10-22 10:10:10"},
{19.88, "2011-12-01 10:10:10", "2011-10-22 10:10:10"},
{"19.88", "2011-11-30 10:10:10", "2011-10-23 10:10:10"},
{"20-11", "2011-12-01 10:10:10", "2011-10-22 10:10:10"},
{"20,11", "2011-12-01 10:10:10", "2011-10-22 10:10:10"},
{"1000", "2014-08-07 10:10:10", "2009-02-14 10:10:10"},
{"true", "2011-11-12 10:10:10", "2011-11-10 10:10:10"},
}
for _, t := range tblDays {
e := &DateArith{
Op: DateAdd,
Form: DateArithDaysForm,
Unit: "day",
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 = DateSub
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 {
Expand Down Expand Up @@ -147,12 +183,7 @@ func (t *testDateArithSuite) TestDateArith(c *C) {
v, err := e.Eval(nil, nil)
c.Assert(err, NotNil, Commentf("%s", v))

e = &DateArith{
Op: DateSub,
Unit: t.Unit,
Date: Value{Val: input},
Interval: Value{Val: t.Interval},
}
e.Op = DateSub
_, err = e.Eval(nil, nil)
c.Assert(err, NotNil)
e.Date = Value{Val: errInput}
Expand Down
3 changes: 2 additions & 1 deletion expression/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (v *Variable) String() string {
func (v *Variable) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) {
name := strings.ToLower(v.Name)
sessionVars := variable.GetSessionVars(ctx)
globalVars := variable.GetGlobalVarAccessor(ctx)
if !v.IsSystem {
// user vars
if value, ok := sessionVars.Users[name]; ok {
Expand All @@ -82,7 +83,7 @@ func (v *Variable) Eval(ctx context.Context, args map[interface{}]interface{}) (
return value, nil
}
}
value, err := ctx.(variable.GlobalVarAccessor).GetGlobalSysVar(ctx, name)
value, err := globalVars.GetGlobalSysVar(ctx, name)
if err != nil {
return nil, errors.Trace(err)
}
Expand Down
4 changes: 3 additions & 1 deletion expression/variable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ type testVariableSuite struct {
}

func (s *testVariableSuite) SetUpSuite(c *C) {
s.ctx = mock.NewContext()
nc := mock.NewContext()
s.ctx = nc
variable.BindSessionVars(s.ctx)
variable.BindGlobalVarAccessor(s.ctx, nc)
}

func (s *testVariableSuite) TestVariable(c *C) {
Expand Down
3 changes: 3 additions & 0 deletions optimizer/convert_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,9 @@ func (c *expressionConverter) funcDateArith(v *ast.FuncDateArithExpr) {
case ast.DateSub:
oldDateArith.Op = expression.DateSub
}
if v.Form == ast.DateArithDaysForm {
oldDateArith.Form = expression.DateArithDaysForm
}
c.exprMap[v] = oldDateArith
}

Expand Down
25 changes: 22 additions & 3 deletions optimizer/convert_stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ func convertUpdate(converter *expressionConverter, v *ast.UpdateStmt) (*stmts.Up
return oldUpdate, nil
}

func getInnerFromParentheses(expr ast.ExprNode) ast.ExprNode {
if pexpr, ok := expr.(*ast.ParenthesesExpr); ok {
return getInnerFromParentheses(pexpr.Expr)
}
return expr
}

func convertSelect(converter *expressionConverter, s *ast.SelectStmt) (*stmts.SelectStmt, error) {
oldSelect := &stmts.SelectStmt{
Distinct: s.Distinct,
Expand All @@ -189,9 +196,21 @@ func convertSelect(converter *expressionConverter, s *ast.SelectStmt) (*stmts.Se
if err != nil {
return nil, errors.Trace(err)
}
// TODO: handle parenthesesed column name expression, which should not set AsName.
if _, ok := oldField.Expr.(*expression.Ident); !ok && oldField.AsName == "" {
oldField.AsName = val.Text()
if oldField.AsName == "" {
innerExpr := getInnerFromParentheses(val.Expr)
switch innerExpr.(type) {
case *ast.ColumnNameExpr:
// Do not set column name as name and remove parentheses.
oldField.Expr = converter.exprMap[innerExpr]
case *ast.ValueExpr:
if innerExpr.Text() != "" {
oldField.AsName = innerExpr.Text()
} else {
oldField.AsName = val.Text()
}
default:
oldField.AsName = val.Text()
}
}
} else if val.WildCard != nil {
str := "*"
Expand Down
Loading

0 comments on commit 4150855

Please sign in to comment.