github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/evaluator/builtin_time.go (about)

     1  // Copyright 2013 The ql Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSES/QL-LICENSE file.
     4  
     5  // Copyright 2015 PingCAP, Inc.
     6  //
     7  // Licensed under the Apache License, Version 2.0 (the "License");
     8  // you may not use this file except in compliance with the License.
     9  // You may obtain a copy of the License at
    10  //
    11  //     http://www.apache.org/licenses/LICENSE-2.0
    12  //
    13  // Unless required by applicable law or agreed to in writing, software
    14  // distributed under the License is distributed on an "AS IS" BASIS,
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package evaluator
    19  
    20  import (
    21  	"fmt"
    22  	"regexp"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/insionng/yougam/libraries/juju/errors"
    27  	"github.com/insionng/yougam/libraries/pingcap/tidb/ast"
    28  	"github.com/insionng/yougam/libraries/pingcap/tidb/context"
    29  	"github.com/insionng/yougam/libraries/pingcap/tidb/mysql"
    30  	"github.com/insionng/yougam/libraries/pingcap/tidb/util/types"
    31  )
    32  
    33  func convertToTime(arg types.Datum, tp byte) (d types.Datum, err error) {
    34  	f := types.NewFieldType(tp)
    35  	f.Decimal = mysql.MaxFsp
    36  
    37  	d, err = arg.ConvertTo(f)
    38  	if err != nil {
    39  		d.SetNull()
    40  		return d, errors.Trace(err)
    41  	}
    42  
    43  	if d.Kind() == types.KindNull {
    44  		return d, nil
    45  	}
    46  
    47  	if d.Kind() != types.KindMysqlTime {
    48  		err = errors.Errorf("need time type, but got %T", d.GetValue())
    49  		d.SetNull()
    50  		return d, err
    51  	}
    52  	return d, nil
    53  }
    54  
    55  func convertToDuration(arg types.Datum, fsp int) (d types.Datum, err error) {
    56  	f := types.NewFieldType(mysql.TypeDuration)
    57  	f.Decimal = fsp
    58  
    59  	d, err = arg.ConvertTo(f)
    60  	if err != nil {
    61  		d.SetNull()
    62  		return d, errors.Trace(err)
    63  	}
    64  
    65  	if d.Kind() == types.KindNull {
    66  		d.SetNull()
    67  		return d, nil
    68  	}
    69  
    70  	if d.Kind() != types.KindMysqlDuration {
    71  		err = errors.Errorf("need duration type, but got %T", d.GetValue())
    72  		d.SetNull()
    73  		return d, err
    74  	}
    75  	return d, nil
    76  }
    77  
    78  func builtinDate(args []types.Datum, _ context.Context) (types.Datum, error) {
    79  	return convertToTime(args[0], mysql.TypeDate)
    80  }
    81  
    82  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_day
    83  // day is a synonym for DayOfMonth
    84  func builtinDay(args []types.Datum, ctx context.Context) (types.Datum, error) {
    85  	return builtinDayOfMonth(args, ctx)
    86  }
    87  
    88  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_hour
    89  func builtinHour(args []types.Datum, _ context.Context) (types.Datum, error) {
    90  	d, err := convertToDuration(args[0], mysql.MaxFsp)
    91  	if err != nil || d.Kind() == types.KindNull {
    92  		d.SetNull()
    93  		return d, errors.Trace(err)
    94  	}
    95  
    96  	// No need to check type here.
    97  	h := int64(d.GetMysqlDuration().Hour())
    98  	d.SetInt64(h)
    99  	return d, nil
   100  }
   101  
   102  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_minute
   103  func builtinMinute(args []types.Datum, _ context.Context) (types.Datum, error) {
   104  	d, err := convertToDuration(args[0], mysql.MaxFsp)
   105  	if err != nil || d.Kind() == types.KindNull {
   106  		d.SetNull()
   107  		return d, errors.Trace(err)
   108  	}
   109  
   110  	// No need to check type here.
   111  	m := int64(d.GetMysqlDuration().Minute())
   112  	d.SetInt64(m)
   113  	return d, nil
   114  }
   115  
   116  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_second
   117  func builtinSecond(args []types.Datum, _ context.Context) (types.Datum, error) {
   118  	d, err := convertToDuration(args[0], mysql.MaxFsp)
   119  	if err != nil || d.Kind() == types.KindNull {
   120  		d.SetNull()
   121  		return d, errors.Trace(err)
   122  	}
   123  
   124  	// No need to check type here.
   125  	s := int64(d.GetMysqlDuration().Second())
   126  	d.SetInt64(s)
   127  	return d, nil
   128  }
   129  
   130  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_microsecond
   131  func builtinMicroSecond(args []types.Datum, _ context.Context) (types.Datum, error) {
   132  	d, err := convertToDuration(args[0], mysql.MaxFsp)
   133  	if err != nil || d.Kind() == types.KindNull {
   134  		d.SetNull()
   135  		return d, errors.Trace(err)
   136  	}
   137  
   138  	// No need to check type here.
   139  	m := int64(d.GetMysqlDuration().MicroSecond())
   140  	d.SetInt64(m)
   141  	return d, nil
   142  }
   143  
   144  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_month
   145  func builtinMonth(args []types.Datum, _ context.Context) (types.Datum, error) {
   146  	d, err := convertToTime(args[0], mysql.TypeDate)
   147  	if err != nil || d.Kind() == types.KindNull {
   148  		d.SetNull()
   149  		return d, errors.Trace(err)
   150  	}
   151  
   152  	// No need to check type here.
   153  	t := d.GetMysqlTime()
   154  	i := int64(0)
   155  	if t.IsZero() {
   156  		d.SetInt64(i)
   157  		return d, nil
   158  	}
   159  	i = int64(t.Month())
   160  	d.SetInt64(i)
   161  	return d, nil
   162  }
   163  
   164  func builtinNow(args []types.Datum, _ context.Context) (d types.Datum, err error) {
   165  	// TODO: if NOW is used in stored function or trigger, NOW will return the beginning time
   166  	// of the execution.
   167  	fsp := 0
   168  	if len(args) == 1 && args[0].Kind() != types.KindNull {
   169  		if fsp, err = checkFsp(args[0]); err != nil {
   170  			d.SetNull()
   171  			return d, errors.Trace(err)
   172  		}
   173  	}
   174  
   175  	t := mysql.Time{
   176  		Time: time.Now(),
   177  		Type: mysql.TypeDatetime,
   178  		// set unspecified for later round
   179  		Fsp: mysql.UnspecifiedFsp,
   180  	}
   181  
   182  	tr, err := t.RoundFrac(int(fsp))
   183  	if err != nil {
   184  		d.SetNull()
   185  		return d, errors.Trace(err)
   186  	}
   187  	d.SetMysqlTime(tr)
   188  	return d, nil
   189  }
   190  
   191  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_dayname
   192  func builtinDayName(args []types.Datum, ctx context.Context) (types.Datum, error) {
   193  	d, err := builtinWeekDay(args, ctx)
   194  	if err != nil || d.Kind() == types.KindNull {
   195  		d.SetNull()
   196  		return d, errors.Trace(err)
   197  	}
   198  	weekday := d.GetInt64()
   199  	if (weekday < 0) || (weekday >= int64(len(mysql.WeekdayNames))) {
   200  		d.SetNull()
   201  		return d, errors.Errorf("no name for invalid weekday: %d.", weekday)
   202  	}
   203  	d.SetString(mysql.WeekdayNames[weekday])
   204  	return d, nil
   205  }
   206  
   207  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_dayofmonth
   208  func builtinDayOfMonth(args []types.Datum, _ context.Context) (d types.Datum, err error) {
   209  	// TODO: some invalid format like 2000-00-00 will return 0 too.
   210  	d, err = convertToTime(args[0], mysql.TypeDate)
   211  	if err != nil || d.Kind() == types.KindNull {
   212  		d.SetNull()
   213  		return d, errors.Trace(err)
   214  	}
   215  
   216  	// No need to check type here.
   217  	t := d.GetMysqlTime()
   218  	if t.IsZero() {
   219  		d.SetInt64(int64(0))
   220  		return d, nil
   221  	}
   222  
   223  	d.SetInt64(int64(t.Day()))
   224  	return d, nil
   225  }
   226  
   227  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_dayofweek
   228  func builtinDayOfWeek(args []types.Datum, _ context.Context) (d types.Datum, err error) {
   229  	d, err = convertToTime(args[0], mysql.TypeDate)
   230  	if err != nil || d.Kind() == types.KindNull {
   231  		d.SetNull()
   232  		return d, errors.Trace(err)
   233  	}
   234  
   235  	// No need to check type here.
   236  	t := d.GetMysqlTime()
   237  	if t.IsZero() {
   238  		d.SetNull()
   239  		// TODO: log warning or return error?
   240  		return d, nil
   241  	}
   242  
   243  	// 1 is Sunday, 2 is Monday, .... 7 is Saturday
   244  	d.SetInt64(int64(t.Weekday()) + 1)
   245  	return d, nil
   246  }
   247  
   248  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_dayofyear
   249  func builtinDayOfYear(args []types.Datum, _ context.Context) (types.Datum, error) {
   250  	d, err := convertToTime(args[0], mysql.TypeDate)
   251  	if err != nil || d.Kind() == types.KindNull {
   252  		d.SetNull()
   253  		return d, errors.Trace(err)
   254  	}
   255  
   256  	t := d.GetMysqlTime()
   257  	if t.IsZero() {
   258  		// TODO: log warning or return error?
   259  		d.SetNull()
   260  		return d, nil
   261  	}
   262  
   263  	yd := int64(t.YearDay())
   264  	d.SetInt64(yd)
   265  	return d, nil
   266  }
   267  
   268  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_week
   269  func builtinWeek(args []types.Datum, _ context.Context) (types.Datum, error) {
   270  	d, err := convertToTime(args[0], mysql.TypeDate)
   271  	if err != nil || d.Kind() == types.KindNull {
   272  		d.SetNull()
   273  		return d, errors.Trace(err)
   274  	}
   275  
   276  	// No need to check type here.
   277  	t := d.GetMysqlTime()
   278  	if t.IsZero() {
   279  		// TODO: log warning or return error?
   280  		d.SetNull()
   281  		return d, nil
   282  	}
   283  
   284  	// TODO: support multi mode for week
   285  	_, week := t.ISOWeek()
   286  	wi := int64(week)
   287  	d.SetInt64(wi)
   288  	return d, nil
   289  }
   290  
   291  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_weekday
   292  func builtinWeekDay(args []types.Datum, _ context.Context) (types.Datum, error) {
   293  	d, err := convertToTime(args[0], mysql.TypeDate)
   294  	if err != nil || d.Kind() == types.KindNull {
   295  		d.SetNull()
   296  		return d, errors.Trace(err)
   297  	}
   298  
   299  	// No need to check type here.
   300  	t := d.GetMysqlTime()
   301  	if t.IsZero() {
   302  		// TODO: log warning or return error?
   303  		d.SetNull()
   304  		return d, nil
   305  	}
   306  
   307  	// Monday is 0, ... Sunday = 6 in MySQL
   308  	// but in go, Sunday is 0, ... Saturday is 6
   309  	// w will do a conversion.
   310  	w := (int64(t.Weekday()) + 6) % 7
   311  	d.SetInt64(w)
   312  	return d, nil
   313  }
   314  
   315  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_weekofyear
   316  func builtinWeekOfYear(args []types.Datum, ctx context.Context) (types.Datum, error) {
   317  	// WeekOfYear is equivalent to to Week(date, 3)
   318  	d := types.Datum{}
   319  	d.SetInt64(3)
   320  	return builtinWeek([]types.Datum{args[0], d}, ctx)
   321  }
   322  
   323  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_year
   324  func builtinYear(args []types.Datum, _ context.Context) (types.Datum, error) {
   325  	d, err := convertToTime(args[0], mysql.TypeDate)
   326  	if err != nil || d.Kind() == types.KindNull {
   327  		return d, errors.Trace(err)
   328  	}
   329  
   330  	// No need to check type here.
   331  	t := d.GetMysqlTime()
   332  	if t.IsZero() {
   333  		d.SetInt64(0)
   334  		return d, nil
   335  	}
   336  
   337  	d.SetInt64(int64(t.Year()))
   338  	return d, nil
   339  }
   340  
   341  // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_yearweek
   342  func builtinYearWeek(args []types.Datum, _ context.Context) (types.Datum, error) {
   343  	d, err := convertToTime(args[0], mysql.TypeDate)
   344  	if err != nil || d.Kind() == types.KindNull {
   345  		d.SetNull()
   346  		return d, errors.Trace(err)
   347  	}
   348  
   349  	// No need to check type here.
   350  	t := d.GetMysqlTime()
   351  	if t.IsZero() {
   352  		d.SetNull()
   353  		// TODO: log warning or return error?
   354  		return d, nil
   355  	}
   356  
   357  	// TODO: support multi mode for week
   358  	year, week := t.ISOWeek()
   359  	d.SetInt64(int64(year*100 + week))
   360  	return d, nil
   361  }
   362  
   363  func builtinSysDate(args []types.Datum, ctx context.Context) (types.Datum, error) {
   364  	// SYSDATE is not the same as NOW if NOW is used in a stored function or trigger.
   365  	// But here we can just think they are the same because we don't support stored function
   366  	// and trigger now.
   367  	return builtinNow(args, ctx)
   368  }
   369  
   370  // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_curdate
   371  func builtinCurrentDate(args []types.Datum, _ context.Context) (d types.Datum, err error) {
   372  	year, month, day := time.Now().Date()
   373  	t := mysql.Time{
   374  		Time: time.Date(year, month, day, 0, 0, 0, 0, time.Local),
   375  		Type: mysql.TypeDate, Fsp: 0}
   376  	d.SetMysqlTime(t)
   377  	return d, nil
   378  }
   379  
   380  // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_curtime
   381  func builtinCurrentTime(args []types.Datum, _ context.Context) (d types.Datum, err error) {
   382  	fsp := 0
   383  	if len(args) == 1 && args[0].Kind() != types.KindNull {
   384  		if fsp, err = checkFsp(args[0]); err != nil {
   385  			d.SetNull()
   386  			return d, errors.Trace(err)
   387  		}
   388  	}
   389  	d.SetString(time.Now().Format("15:04:05.000000"))
   390  	return convertToDuration(d, fsp)
   391  }
   392  
   393  // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_utc-date
   394  func builtinUTCDate(args []types.Datum, _ context.Context) (d types.Datum, err error) {
   395  	year, month, day := time.Now().UTC().Date()
   396  	t := mysql.Time{
   397  		Time: time.Date(year, month, day, 0, 0, 0, 0, time.UTC),
   398  		Type: mysql.TypeDate, Fsp: mysql.UnspecifiedFsp}
   399  	d.SetMysqlTime(t)
   400  	return d, nil
   401  }
   402  
   403  // See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_extract
   404  func builtinExtract(args []types.Datum, _ context.Context) (d types.Datum, err error) {
   405  	unit := args[0].GetString()
   406  	vd := args[1]
   407  
   408  	if vd.Kind() == types.KindNull {
   409  		d.SetNull()
   410  		return d, nil
   411  	}
   412  
   413  	f := types.NewFieldType(mysql.TypeDatetime)
   414  	f.Decimal = mysql.MaxFsp
   415  	val, err := vd.ConvertTo(f)
   416  	if err != nil {
   417  		d.SetNull()
   418  		return d, errors.Trace(err)
   419  	}
   420  	if val.Kind() == types.KindNull {
   421  		d.SetNull()
   422  		return d, nil
   423  	}
   424  
   425  	if val.Kind() != types.KindMysqlTime {
   426  		err = errors.Errorf("need time type, but got %T", val)
   427  		d.SetNull()
   428  		return d, err
   429  	}
   430  	t := val.GetMysqlTime()
   431  	n, err1 := mysql.ExtractTimeNum(unit, t)
   432  	if err1 != nil {
   433  		d.SetNull()
   434  		return d, errors.Trace(err1)
   435  	}
   436  	d.SetInt64(n)
   437  	return d, nil
   438  }
   439  
   440  func checkFsp(arg types.Datum) (int, error) {
   441  	fsp, err := arg.ToInt64()
   442  	if err != nil {
   443  		return 0, errors.Trace(err)
   444  	}
   445  	if int(fsp) > mysql.MaxFsp {
   446  		return 0, errors.Errorf("Too big precision %d specified. Maximum is 6.", fsp)
   447  	} else if fsp < 0 {
   448  		return 0, errors.Errorf("Invalid negative %d specified, must in [0, 6].", fsp)
   449  	}
   450  	return int(fsp), nil
   451  }
   452  
   453  func builtinDateArith(args []types.Datum, ctx context.Context) (d types.Datum, err error) {
   454  	// Op is used for distinguishing date_add and date_sub.
   455  	// args[0] -> Op
   456  	// args[1] -> Date
   457  	// args[2] -> DateArithInterval
   458  	// health check for date and interval
   459  	if args[1].Kind() == types.KindNull {
   460  		d.SetNull()
   461  		return d, nil
   462  	}
   463  	nodeDate := args[1]
   464  	nodeInterval := args[2].GetInterface().(ast.DateArithInterval)
   465  	nodeIntervalIntervalDatum := nodeInterval.Interval.GetDatum()
   466  	if nodeIntervalIntervalDatum.Kind() == types.KindNull {
   467  		d.SetNull()
   468  		return d, nil
   469  	}
   470  	// parse date
   471  	fieldType := mysql.TypeDate
   472  	var resultField *types.FieldType
   473  	switch nodeDate.Kind() {
   474  	case types.KindMysqlTime:
   475  		x := nodeDate.GetMysqlTime()
   476  		if (x.Type == mysql.TypeDatetime) || (x.Type == mysql.TypeTimestamp) {
   477  			fieldType = mysql.TypeDatetime
   478  		}
   479  	case types.KindString:
   480  		x := nodeDate.GetString()
   481  		if !mysql.IsDateFormat(x) {
   482  			fieldType = mysql.TypeDatetime
   483  		}
   484  	case types.KindInt64:
   485  		x := nodeDate.GetInt64()
   486  		if t, err1 := mysql.ParseTimeFromInt64(x); err1 == nil {
   487  			if (t.Type == mysql.TypeDatetime) || (t.Type == mysql.TypeTimestamp) {
   488  				fieldType = mysql.TypeDatetime
   489  			}
   490  		}
   491  	}
   492  	if mysql.IsClockUnit(nodeInterval.Unit) {
   493  		fieldType = mysql.TypeDatetime
   494  	}
   495  	resultField = types.NewFieldType(fieldType)
   496  	resultField.Decimal = mysql.MaxFsp
   497  	value, err := nodeDate.ConvertTo(resultField)
   498  	if err != nil {
   499  		d.SetNull()
   500  		return d, ErrInvalidOperation.Gen("DateArith invalid args, need date but get %T", nodeDate)
   501  	}
   502  	if value.Kind() == types.KindNull {
   503  		d.SetNull()
   504  		return d, ErrInvalidOperation.Gen("DateArith invalid args, need date but get %v", value.GetValue())
   505  	}
   506  	if value.Kind() != types.KindMysqlTime {
   507  		d.SetNull()
   508  		return d, ErrInvalidOperation.Gen("DateArith need time type, but got %T", value.GetValue())
   509  	}
   510  	result := value.GetMysqlTime()
   511  	// parse interval
   512  	var interval string
   513  	if strings.ToLower(nodeInterval.Unit) == "day" {
   514  		day, err1 := parseDayInterval(*nodeIntervalIntervalDatum)
   515  		if err1 != nil {
   516  			d.SetNull()
   517  			return d, ErrInvalidOperation.Gen("DateArith invalid day interval, need int but got %T", nodeIntervalIntervalDatum.GetString())
   518  		}
   519  		interval = fmt.Sprintf("%d", day)
   520  	} else {
   521  		if nodeIntervalIntervalDatum.Kind() == types.KindString {
   522  			interval = fmt.Sprintf("%v", nodeIntervalIntervalDatum.GetString())
   523  		} else {
   524  			ii, err1 := nodeIntervalIntervalDatum.ToInt64()
   525  			if err1 != nil {
   526  				d.SetNull()
   527  				return d, errors.Trace(err1)
   528  			}
   529  			interval = fmt.Sprintf("%v", ii)
   530  		}
   531  	}
   532  	year, month, day, duration, err := mysql.ExtractTimeValue(nodeInterval.Unit, interval)
   533  	if err != nil {
   534  		d.SetNull()
   535  		return d, errors.Trace(err)
   536  	}
   537  	op := args[0].GetInterface().(ast.DateArithType)
   538  	if op == ast.DateSub {
   539  		year, month, day, duration = -year, -month, -day, -duration
   540  	}
   541  	result.Time = result.Time.Add(duration)
   542  	result.Time = result.Time.AddDate(int(year), int(month), int(day))
   543  	if result.Time.Nanosecond() == 0 {
   544  		result.Fsp = 0
   545  	}
   546  	d.SetMysqlTime(result)
   547  	return d, nil
   548  }
   549  
   550  var reg = regexp.MustCompile(`[\d]+`)
   551  
   552  func parseDayInterval(value types.Datum) (int64, error) {
   553  	switch value.Kind() {
   554  	case types.KindString:
   555  		vs := value.GetString()
   556  		s := strings.ToLower(vs)
   557  		if s == "false" {
   558  			return 0, nil
   559  		} else if s == "true" {
   560  			return 1, nil
   561  		}
   562  		value.SetString(reg.FindString(vs))
   563  	}
   564  	return value.ToInt64()
   565  }