github.com/dolthub/go-mysql-server@v0.18.0/sql/types/datetime.go (about)

     1  // Copyright 2022 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package types
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"reflect"
    21  	"time"
    22  
    23  	"github.com/dolthub/vitess/go/sqltypes"
    24  	"github.com/dolthub/vitess/go/vt/proto/query"
    25  	"github.com/shopspring/decimal"
    26  	"gopkg.in/src-d/go-errors.v1"
    27  
    28  	"github.com/dolthub/go-mysql-server/sql"
    29  )
    30  
    31  const zeroDateStr = "0000-00-00"
    32  
    33  const zeroTimestampDatetimeStr = "0000-00-00 00:00:00"
    34  
    35  var (
    36  	// ErrConvertingToTime is thrown when a value cannot be converted to a Time
    37  	ErrConvertingToTime = errors.NewKind("Incorrect datetime value: '%s'")
    38  
    39  	ErrConvertingToTimeOutOfRange = errors.NewKind("value %q is outside of %v range")
    40  
    41  	// datetimeTypeMaxDatetime is the maximum representable Datetime/Date value.
    42  	datetimeTypeMaxDatetime = time.Date(9999, 12, 31, 23, 59, 59, 999999000, time.UTC)
    43  
    44  	// datetimeTypeMinDatetime is the minimum representable Datetime/Date value.
    45  	datetimeTypeMinDatetime = time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)
    46  
    47  	// datetimeTypeMaxTimestamp is the maximum representable Timestamp value, which is the maximum 32-bit integer as a Unix time.
    48  	datetimeTypeMaxTimestamp = time.Unix(math.MaxInt32, 999999000)
    49  
    50  	// datetimeTypeMinTimestamp is the minimum representable Timestamp value, which is one second past the epoch.
    51  	datetimeTypeMinTimestamp = time.Unix(1, 0)
    52  
    53  	// TimestampDatetimeLayouts hold extra timestamps allowed for parsing. It does
    54  	// not have all the layouts supported by mysql. Missing are two digit year
    55  	// versions of common cases and dates that use non common separators.
    56  	//
    57  	// https://github.com/MariaDB/server/blob/mysql-5.5.36/sql-common/my_time.c#L124
    58  	TimestampDatetimeLayouts = []string{
    59  		"2006-01-02 15:4",
    60  		"2006-01-02 15:04",
    61  		"2006-01-02 15:04:",
    62  		"2006-01-02 15:04:.",
    63  		"2006-01-02 15:04:05.",
    64  		"2006-01-02 15:04:05.999999",
    65  		"2006-01-02",
    66  		"2006-1-2",
    67  		"2006-1-2 15:4:5.999999",
    68  		time.RFC3339,
    69  		time.RFC3339Nano,
    70  		"2006-01-02T15:04:05",
    71  		"20060102150405",
    72  		"20060102",
    73  		"2006/01/02",
    74  		"2006-01-02 15:04:05.999999999 -0700 MST", // represents standard Time.time.UTC()
    75  	}
    76  
    77  	// zeroTime is 0000-01-01 00:00:00 UTC which is the closest Go can get to 0000-00-00 00:00:00
    78  	zeroTime = time.Unix(-62167219200, 0).UTC()
    79  
    80  	// Date is a date with day, month and year.
    81  	Date = MustCreateDatetimeType(sqltypes.Date, 0)
    82  	// Datetime is a date and a time with default precision (no fractional seconds).
    83  	Datetime = MustCreateDatetimeType(sqltypes.Datetime, 0)
    84  	// DatetimeMaxPrecision is a date and a time with maximum precision
    85  	DatetimeMaxPrecision = MustCreateDatetimeType(sqltypes.Datetime, 6)
    86  	// Timestamp is a UNIX timestamp with default precision (no fractional seconds).
    87  	Timestamp = MustCreateDatetimeType(sqltypes.Timestamp, 0)
    88  	// TimestampMaxPrecision is a UNIX timestamp with maximum precision
    89  	TimestampMaxPrecision = MustCreateDatetimeType(sqltypes.Timestamp, 6)
    90  
    91  	datetimeValueType = reflect.TypeOf(time.Time{})
    92  )
    93  
    94  type datetimeType struct {
    95  	baseType  query.Type
    96  	precision int
    97  }
    98  
    99  var _ sql.DatetimeType = datetimeType{}
   100  var _ sql.CollationCoercible = datetimeType{}
   101  
   102  // CreateDatetimeType creates a Type dealing with all temporal types that are not TIME nor YEAR.
   103  func CreateDatetimeType(baseType query.Type, precision int) (sql.DatetimeType, error) {
   104  	switch baseType {
   105  	case sqltypes.Date, sqltypes.Datetime, sqltypes.Timestamp:
   106  		if precision < 0 || precision > 6 {
   107  			return nil, fmt.Errorf("precision must be between 0 and 6, got %d", precision)
   108  		}
   109  		return datetimeType{
   110  			baseType:  baseType,
   111  			precision: precision,
   112  		}, nil
   113  	}
   114  	return nil, sql.ErrInvalidBaseType.New(baseType.String(), "datetime")
   115  }
   116  
   117  // MustCreateDatetimeType is the same as CreateDatetimeType except it panics on errors.
   118  func MustCreateDatetimeType(baseType query.Type, precision int) sql.DatetimeType {
   119  	dt, err := CreateDatetimeType(baseType, precision)
   120  	if err != nil {
   121  		panic(err)
   122  	}
   123  	return dt
   124  }
   125  
   126  func (t datetimeType) Precision() int {
   127  	return t.precision
   128  }
   129  
   130  // Compare implements Type interface.
   131  func (t datetimeType) Compare(a interface{}, b interface{}) (int, error) {
   132  	if hasNulls, res := CompareNulls(a, b); hasNulls {
   133  		return res, nil
   134  	}
   135  
   136  	var at time.Time
   137  	var bt time.Time
   138  	var ok bool
   139  	var err error
   140  	if at, ok = a.(time.Time); !ok {
   141  		at, err = ConvertToTime(a, t)
   142  		if err != nil {
   143  			return 0, err
   144  		}
   145  	} else if t.baseType == sqltypes.Date {
   146  		at = at.Truncate(24 * time.Hour)
   147  	}
   148  	if bt, ok = b.(time.Time); !ok {
   149  		bt, err = ConvertToTime(b, t)
   150  		if err != nil {
   151  			return 0, err
   152  		}
   153  
   154  	} else if t.baseType == sqltypes.Date {
   155  		bt = bt.Truncate(24 * time.Hour)
   156  	}
   157  
   158  	if at.Before(bt) {
   159  		return -1, nil
   160  	} else if at.After(bt) {
   161  		return 1, nil
   162  	}
   163  	return 0, nil
   164  }
   165  
   166  // Convert implements Type interface.
   167  func (t datetimeType) Convert(v interface{}) (interface{}, sql.ConvertInRange, error) {
   168  	if v == nil {
   169  		return nil, sql.InRange, nil
   170  	}
   171  	res, err := ConvertToTime(v, t)
   172  	if err != nil {
   173  		return nil, sql.OutOfRange, err
   174  	}
   175  	return res, sql.InRange, nil
   176  }
   177  
   178  // precisionConversion is a conversion ratio to divide time.Second by to truncate the appropriate amount for the
   179  // precision of a type with time info
   180  var precisionConversion = [7]int{
   181  	1, 10, 100, 1_000, 10_000, 100_000, 1_000_000,
   182  }
   183  
   184  func ConvertToTime(v interface{}, t datetimeType) (time.Time, error) {
   185  	if v == nil {
   186  		return time.Time{}, nil
   187  	}
   188  
   189  	res, err := t.ConvertWithoutRangeCheck(v)
   190  	if err != nil {
   191  		return time.Time{}, err
   192  	}
   193  
   194  	if res.Equal(zeroTime) {
   195  		return zeroTime, nil
   196  	}
   197  
   198  	// Truncate the date to the precision of this type
   199  	truncationDuration := time.Second
   200  	truncationDuration /= time.Duration(precisionConversion[t.precision])
   201  	res = res.Truncate(truncationDuration)
   202  
   203  	switch t.baseType {
   204  	case sqltypes.Date:
   205  		if res.Year() < 0 || res.Year() > 9999 {
   206  			return time.Time{}, ErrConvertingToTimeOutOfRange.New(res.Format(sql.DateLayout), t.String())
   207  		}
   208  	case sqltypes.Datetime:
   209  		if res.Year() < 0 || res.Year() > 9999 {
   210  			return time.Time{}, ErrConvertingToTimeOutOfRange.New(res.Format(sql.TimestampDatetimeLayout), t.String())
   211  		}
   212  	case sqltypes.Timestamp:
   213  		if res.Before(datetimeTypeMinTimestamp) || res.After(datetimeTypeMaxTimestamp) {
   214  			return time.Time{}, ErrConvertingToTimeOutOfRange.New(res.Format(sql.TimestampDatetimeLayout), t.String())
   215  		}
   216  	}
   217  	return res, nil
   218  }
   219  
   220  // ConvertWithoutRangeCheck converts the parameter to time.Time without checking the range.
   221  func (t datetimeType) ConvertWithoutRangeCheck(v interface{}) (time.Time, error) {
   222  	var res time.Time
   223  
   224  	if bs, ok := v.([]byte); ok {
   225  		v = string(bs)
   226  	}
   227  	switch value := v.(type) {
   228  	case string:
   229  		if value == zeroDateStr || value == zeroTimestampDatetimeStr {
   230  			return zeroTime, nil
   231  		}
   232  		// TODO: consider not using time.Parse if we want to match MySQL exactly ('2010-06-03 11:22.:.:.:.:' is a valid timestamp)
   233  		parsed := false
   234  		for _, fmt := range TimestampDatetimeLayouts {
   235  			if t, err := time.Parse(fmt, value); err == nil {
   236  				res = t.UTC()
   237  				parsed = true
   238  				break
   239  			}
   240  		}
   241  		if !parsed {
   242  			return zeroTime, ErrConvertingToTime.New(v)
   243  		}
   244  	case time.Time:
   245  		res = value.UTC()
   246  		// For most integer values, we just return an error (but MySQL is more lenient for some of these). A special case
   247  		// is zero values, which are important when converting from postgres defaults.
   248  	case int:
   249  		if value == 0 {
   250  			return zeroTime, nil
   251  		}
   252  		return zeroTime, ErrConvertingToTime.New(v)
   253  	case int8:
   254  		if value == 0 {
   255  			return zeroTime, nil
   256  		}
   257  		return zeroTime, ErrConvertingToTime.New(v)
   258  	case int16:
   259  		if value == 0 {
   260  			return zeroTime, nil
   261  		}
   262  		return zeroTime, ErrConvertingToTime.New(v)
   263  	case int32:
   264  		if value == 0 {
   265  			return zeroTime, nil
   266  		}
   267  		return zeroTime, ErrConvertingToTime.New(v)
   268  	case int64:
   269  		if value == 0 {
   270  			return zeroTime, nil
   271  		}
   272  		return zeroTime, ErrConvertingToTime.New(v)
   273  	case uint:
   274  		if value == 0 {
   275  			return zeroTime, nil
   276  		}
   277  		return zeroTime, ErrConvertingToTime.New(v)
   278  	case uint8:
   279  		if value == 0 {
   280  			return zeroTime, nil
   281  		}
   282  		return zeroTime, ErrConvertingToTime.New(v)
   283  	case uint16:
   284  		if value == 0 {
   285  			return zeroTime, nil
   286  		}
   287  		return zeroTime, ErrConvertingToTime.New(v)
   288  	case uint32:
   289  		if value == 0 {
   290  			return zeroTime, nil
   291  		}
   292  		return zeroTime, ErrConvertingToTime.New(v)
   293  	case uint64:
   294  		if value == 0 {
   295  			return zeroTime, nil
   296  		}
   297  		return zeroTime, ErrConvertingToTime.New(v)
   298  	case float32:
   299  		if value == 0 {
   300  			return zeroTime, nil
   301  		}
   302  		return zeroTime, ErrConvertingToTime.New(v)
   303  	case float64:
   304  		if value == 0 {
   305  			return zeroTime, nil
   306  		}
   307  		return zeroTime, ErrConvertingToTime.New(v)
   308  	case decimal.Decimal:
   309  		if value.IsZero() {
   310  			return zeroTime, nil
   311  		}
   312  		return zeroTime, ErrConvertingToTime.New(v)
   313  	case decimal.NullDecimal:
   314  		if value.Valid && value.Decimal.IsZero() {
   315  			return zeroTime, nil
   316  		}
   317  		return zeroTime, ErrConvertingToTime.New(v)
   318  	case Timespan:
   319  		// when receiving TIME, MySQL fills in date with today
   320  		nowTimeStr := sql.Now().Format("2006-01-02")
   321  		nowTime, err := time.Parse("2006-01-02", nowTimeStr)
   322  		if err != nil {
   323  			return zeroTime, ErrConvertingToTime.New(v)
   324  		}
   325  		return nowTime.Add(value.AsTimeDuration()), nil
   326  	default:
   327  		return zeroTime, sql.ErrConvertToSQL.New(value, t)
   328  	}
   329  
   330  	if t.baseType == sqltypes.Date {
   331  		res = res.Truncate(24 * time.Hour)
   332  	}
   333  
   334  	return res, nil
   335  }
   336  
   337  func (t datetimeType) MustConvert(v interface{}) interface{} {
   338  	value, _, err := t.Convert(v)
   339  	if err != nil {
   340  		panic(err)
   341  	}
   342  	return value
   343  }
   344  
   345  // Equals implements the Type interface.
   346  func (t datetimeType) Equals(otherType sql.Type) bool {
   347  	return t.baseType == otherType.Type()
   348  }
   349  
   350  // MaxTextResponseByteLength implements the Type interface
   351  func (t datetimeType) MaxTextResponseByteLength(_ *sql.Context) uint32 {
   352  	switch t.baseType {
   353  	case sqltypes.Date:
   354  		return uint32(len(sql.DateLayout))
   355  	case sqltypes.Datetime, sqltypes.Timestamp:
   356  		return uint32(len(sql.TimestampDatetimeLayout))
   357  	default:
   358  		panic(sql.ErrInvalidBaseType.New(t.baseType.String(), "datetime"))
   359  	}
   360  }
   361  
   362  // Promote implements the Type interface.
   363  func (t datetimeType) Promote() sql.Type {
   364  	return DatetimeMaxPrecision
   365  }
   366  
   367  // SQL implements Type interface.
   368  func (t datetimeType) SQL(_ *sql.Context, dest []byte, v interface{}) (sqltypes.Value, error) {
   369  	if v == nil {
   370  		return sqltypes.NULL, nil
   371  	}
   372  
   373  	v, _, err := t.Convert(v)
   374  	if err != nil {
   375  		return sqltypes.Value{}, err
   376  	}
   377  	vt := v.(time.Time)
   378  
   379  	var typ query.Type
   380  	var val string
   381  
   382  	switch t.baseType {
   383  	case sqltypes.Date:
   384  		typ = sqltypes.Date
   385  		if vt.Equal(zeroTime) {
   386  			val = vt.Format(zeroDateStr)
   387  		} else {
   388  			val = vt.Format(sql.DateLayout)
   389  		}
   390  	case sqltypes.Datetime:
   391  		typ = sqltypes.Datetime
   392  		if vt.Equal(zeroTime) {
   393  			val = vt.Format(zeroTimestampDatetimeStr)
   394  		} else {
   395  			val = vt.Format(sql.TimestampDatetimeLayout)
   396  		}
   397  	case sqltypes.Timestamp:
   398  		typ = sqltypes.Timestamp
   399  		if vt.Equal(zeroTime) {
   400  			val = vt.Format(zeroTimestampDatetimeStr)
   401  		} else {
   402  			val = vt.Format(sql.TimestampDatetimeLayout)
   403  		}
   404  	default:
   405  		panic(sql.ErrInvalidBaseType.New(t.baseType.String(), "datetime"))
   406  	}
   407  
   408  	valBytes := AppendAndSliceString(dest, val)
   409  
   410  	return sqltypes.MakeTrusted(typ, valBytes), nil
   411  }
   412  
   413  func (t datetimeType) String() string {
   414  	switch t.baseType {
   415  	case sqltypes.Date:
   416  		return "date"
   417  	case sqltypes.Datetime:
   418  		if t.precision > 0 {
   419  			return fmt.Sprintf("datetime(%d)", t.precision)
   420  		}
   421  		return "datetime"
   422  	case sqltypes.Timestamp:
   423  		if t.precision > 0 {
   424  			return fmt.Sprintf("timestamp(%d)", t.precision)
   425  		}
   426  		return "timestamp"
   427  	default:
   428  		panic(sql.ErrInvalidBaseType.New(t.baseType.String(), "datetime"))
   429  	}
   430  }
   431  
   432  // Type implements Type interface.
   433  func (t datetimeType) Type() query.Type {
   434  	return t.baseType
   435  }
   436  
   437  // ValueType implements Type interface.
   438  func (t datetimeType) ValueType() reflect.Type {
   439  	return datetimeValueType
   440  }
   441  
   442  func (t datetimeType) Zero() interface{} {
   443  	return zeroTime
   444  }
   445  
   446  // CollationCoercibility implements sql.CollationCoercible interface.
   447  func (datetimeType) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   448  	return sql.Collation_binary, 5
   449  }
   450  
   451  // MaximumTime is the latest accepted time for this type.
   452  func (t datetimeType) MaximumTime() time.Time {
   453  	if t.baseType == sqltypes.Timestamp {
   454  		return datetimeTypeMaxTimestamp
   455  	}
   456  	return datetimeTypeMaxDatetime
   457  }
   458  
   459  // MinimumTime is the earliest accepted time for this type.
   460  func (t datetimeType) MinimumTime() time.Time {
   461  	if t.baseType == sqltypes.Timestamp {
   462  		return datetimeTypeMinTimestamp
   463  	}
   464  	return datetimeTypeMinDatetime
   465  }
   466  
   467  // ValidateTime receives a time and returns either that time or nil if it's
   468  // not a valid time.
   469  func ValidateTime(t time.Time) interface{} {
   470  	if t.After(time.Date(9999, time.December, 31, 23, 59, 59, 999999999, time.UTC)) {
   471  		return nil
   472  	}
   473  	return t
   474  }