github.com/dolthub/go-mysql-server@v0.18.0/sql/types/time.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  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/dolthub/vitess/go/sqltypes"
    26  	"github.com/dolthub/vitess/go/vt/proto/query"
    27  	"github.com/shopspring/decimal"
    28  	"gopkg.in/src-d/go-errors.v1"
    29  
    30  	"github.com/dolthub/go-mysql-server/sql"
    31  )
    32  
    33  var (
    34  	Time TimeType = TimespanType_{}
    35  
    36  	ErrConvertingToTimeType = errors.NewKind("value %v is not a valid Time")
    37  
    38  	timespanMinimum           int64 = -3020399000000
    39  	timespanMaximum           int64 = 3020399000000
    40  	microsecondsPerSecond     int64 = 1000000
    41  	microsecondsPerMinute     int64 = 60000000
    42  	microsecondsPerHour       int64 = 3600000000
    43  	nanosecondsPerMicrosecond int64 = 1000
    44  
    45  	timeValueType = reflect.TypeOf(Timespan(0))
    46  )
    47  
    48  // TimeType represents the TIME type.
    49  // https://dev.mysql.com/doc/refman/8.0/en/time.html
    50  // TIME is implemented as TIME(6).
    51  // The type of the returned value is Timespan.
    52  // TODO: implement parameters on the TIME type
    53  type TimeType interface {
    54  	sql.Type
    55  	// ConvertToTimespan returns a Timespan from the given interface. Follows the same conversion rules as
    56  	// Convert(), in that this will process the value based on its base-10 visual representation (for example, Convert()
    57  	// will interpret the value `1234` as 12 minutes and 34 seconds). Returns an error for nil values.
    58  	ConvertToTimespan(v interface{}) (Timespan, error)
    59  	// ConvertToTimeDuration returns a time.Duration from the given interface. Follows the same conversion rules as
    60  	// Convert(), in that this will process the value based on its base-10 visual representation (for example, Convert()
    61  	// will interpret the value `1234` as 12 minutes and 34 seconds). Returns an error for nil values.
    62  	ConvertToTimeDuration(v interface{}) (time.Duration, error)
    63  	// MicrosecondsToTimespan returns a Timespan from the given number of microseconds. This differs from Convert(), as
    64  	// that will process the value based on its base-10 visual representation (for example, Convert() will interpret
    65  	// the value `1234` as 12 minutes and 34 seconds). This clamps the given microseconds to the allowed range.
    66  	MicrosecondsToTimespan(v int64) Timespan
    67  }
    68  
    69  type TimespanType_ struct{}
    70  
    71  var _ TimeType = TimespanType_{}
    72  var _ sql.CollationCoercible = TimespanType_{}
    73  
    74  // MaxTextResponseByteLength implements the Type interface
    75  func (t TimespanType_) MaxTextResponseByteLength(_ *sql.Context) uint32 {
    76  	// 10 digits are required for a text representation without microseconds, but with microseconds
    77  	// requires 17, so return 17 as an upper limit (i.e. len(+123:00:00.999999"))
    78  	return 17
    79  }
    80  
    81  // Timespan is the value type returned by TimeType.Convert().
    82  type Timespan int64
    83  
    84  // Compare implements Type interface.
    85  func (t TimespanType_) Compare(a interface{}, b interface{}) (int, error) {
    86  	if hasNulls, res := CompareNulls(a, b); hasNulls {
    87  		return res, nil
    88  	}
    89  
    90  	as, err := t.ConvertToTimespan(a)
    91  	if err != nil {
    92  		return 0, err
    93  	}
    94  	bs, err := t.ConvertToTimespan(b)
    95  	if err != nil {
    96  		return 0, err
    97  	}
    98  
    99  	return as.Compare(bs), nil
   100  }
   101  
   102  func (t TimespanType_) Convert(v interface{}) (interface{}, sql.ConvertInRange, error) {
   103  	if v == nil {
   104  		return nil, sql.InRange, nil
   105  	}
   106  	ret, err := t.ConvertToTimespan(v)
   107  	return ret, sql.InRange, err
   108  }
   109  
   110  // MustConvert implements the Type interface.
   111  func (t TimespanType_) MustConvert(v interface{}) interface{} {
   112  	value, _, err := t.Convert(v)
   113  	if err != nil {
   114  		panic(err)
   115  	}
   116  	return value
   117  }
   118  
   119  // ConvertToTimespan converts the given interface value to a Timespan. This follows the conversion rules of MySQL, which
   120  // are based on the base-10 visual representation of numbers (for example, Time.Convert() will interpret the value
   121  // `1234` as 12 minutes and 34 seconds). Returns an error on a nil value.
   122  func (t TimespanType_) ConvertToTimespan(v interface{}) (Timespan, error) {
   123  	switch value := v.(type) {
   124  	case Timespan:
   125  		// We only create a Timespan if it's valid, so we can skip this check if we receive a Timespan.
   126  		// Timespan values are not intended to be modified by an integrator, therefore it is on the integrator if they corrupt a Timespan.
   127  		return value, nil
   128  	case int:
   129  		return t.ConvertToTimespan(int64(value))
   130  	case uint:
   131  		return t.ConvertToTimespan(int64(value))
   132  	case int8:
   133  		return t.ConvertToTimespan(int64(value))
   134  	case uint8:
   135  		return t.ConvertToTimespan(int64(value))
   136  	case int16:
   137  		return t.ConvertToTimespan(int64(value))
   138  	case uint16:
   139  		return t.ConvertToTimespan(int64(value))
   140  	case int32:
   141  		return t.ConvertToTimespan(int64(value))
   142  	case uint32:
   143  		return t.ConvertToTimespan(int64(value))
   144  	case int64:
   145  		absValue := int64Abs(value)
   146  		if absValue >= -59 && absValue <= 59 {
   147  			return t.MicrosecondsToTimespan(value * microsecondsPerSecond), nil
   148  		} else if absValue >= 100 && absValue <= 9999 {
   149  			minutes := absValue / 100
   150  			seconds := absValue % 100
   151  			if minutes <= 59 && seconds <= 59 {
   152  				microseconds := (seconds * microsecondsPerSecond) + (minutes * microsecondsPerMinute)
   153  				if value < 0 {
   154  					return t.MicrosecondsToTimespan(-1 * microseconds), nil
   155  				}
   156  				return t.MicrosecondsToTimespan(microseconds), nil
   157  			}
   158  		} else if absValue >= 10000 && absValue <= 9999999 {
   159  			hours := absValue / 10000
   160  			minutes := (absValue / 100) % 100
   161  			seconds := absValue % 100
   162  			if minutes <= 59 && seconds <= 59 {
   163  				microseconds := (seconds * microsecondsPerSecond) + (minutes * microsecondsPerMinute) + (hours * microsecondsPerHour)
   164  				if value < 0 {
   165  					return t.MicrosecondsToTimespan(-1 * microseconds), nil
   166  				}
   167  				return t.MicrosecondsToTimespan(microseconds), nil
   168  			}
   169  		}
   170  	case uint64:
   171  		return t.ConvertToTimespan(int64(value))
   172  	case float32:
   173  		return t.ConvertToTimespan(float64(value))
   174  	case float64:
   175  		intValue := int64(value)
   176  		microseconds := int64Abs(int64(math.Round((value - float64(intValue)) * float64(microsecondsPerSecond))))
   177  		absValue := int64Abs(intValue)
   178  		if absValue >= -59 && absValue <= 59 {
   179  			totalMicroseconds := (absValue * microsecondsPerSecond) + microseconds
   180  			if value < 0 {
   181  				return t.MicrosecondsToTimespan(-1 * totalMicroseconds), nil
   182  			}
   183  			return t.MicrosecondsToTimespan(totalMicroseconds), nil
   184  		} else if absValue >= 100 && absValue <= 9999 {
   185  			minutes := absValue / 100
   186  			seconds := absValue % 100
   187  			if minutes <= 59 && seconds <= 59 {
   188  				totalMicroseconds := (seconds * microsecondsPerSecond) + (minutes * microsecondsPerMinute) + microseconds
   189  				if value < 0 {
   190  					return t.MicrosecondsToTimespan(-1 * totalMicroseconds), nil
   191  				}
   192  				return t.MicrosecondsToTimespan(totalMicroseconds), nil
   193  			}
   194  		} else if absValue >= 10000 && absValue <= 9999999 {
   195  			hours := absValue / 10000
   196  			minutes := (absValue / 100) % 100
   197  			seconds := absValue % 100
   198  			if minutes <= 59 && seconds <= 59 {
   199  				totalMicroseconds := (seconds * microsecondsPerSecond) + (minutes * microsecondsPerMinute) + (hours * microsecondsPerHour) + microseconds
   200  				if value < 0 {
   201  					return t.MicrosecondsToTimespan(-1 * totalMicroseconds), nil
   202  				}
   203  				return t.MicrosecondsToTimespan(totalMicroseconds), nil
   204  			}
   205  		}
   206  	case decimal.Decimal:
   207  		return t.ConvertToTimespan(value.IntPart())
   208  	case decimal.NullDecimal:
   209  		if value.Valid {
   210  			return t.ConvertToTimespan(value.Decimal.IntPart())
   211  		}
   212  	case string:
   213  		impl, err := stringToTimespan(value)
   214  		if err == nil {
   215  			return impl, nil
   216  		}
   217  		if strings.Contains(value, ".") {
   218  			strAsDouble, err := strconv.ParseFloat(value, 64)
   219  			if err != nil {
   220  				return Timespan(0), ErrConvertingToTimeType.New(v)
   221  			}
   222  			return t.ConvertToTimespan(strAsDouble)
   223  		} else {
   224  			strAsInt, err := strconv.ParseInt(value, 10, 64)
   225  			if err != nil {
   226  				return Timespan(0), ErrConvertingToTimeType.New(v)
   227  			}
   228  			return t.ConvertToTimespan(strAsInt)
   229  		}
   230  	case time.Duration:
   231  		microseconds := value.Nanoseconds() / nanosecondsPerMicrosecond
   232  		return t.MicrosecondsToTimespan(microseconds), nil
   233  	case time.Time:
   234  		h, m, s := value.Clock()
   235  		us := int64(value.Nanosecond())/nanosecondsPerMicrosecond +
   236  			microsecondsPerSecond*int64(s) +
   237  			microsecondsPerMinute*int64(m) +
   238  			microsecondsPerHour*int64(h)
   239  		return Timespan(us), nil
   240  	}
   241  
   242  	return Timespan(0), ErrConvertingToTimeType.New(v)
   243  }
   244  
   245  // ConvertToTimeDuration implements the TimeType interface.
   246  func (t TimespanType_) ConvertToTimeDuration(v interface{}) (time.Duration, error) {
   247  	val, err := t.ConvertToTimespan(v)
   248  	if err != nil {
   249  		return time.Duration(0), err
   250  	}
   251  	return val.AsTimeDuration(), nil
   252  }
   253  
   254  // Equals implements the Type interface.
   255  func (t TimespanType_) Equals(otherType sql.Type) bool {
   256  	_, ok := otherType.(TimespanType_)
   257  	return ok
   258  }
   259  
   260  // Promote implements the Type interface.
   261  func (t TimespanType_) Promote() sql.Type {
   262  	return t
   263  }
   264  
   265  // SQL implements Type interface.
   266  func (t TimespanType_) SQL(ctx *sql.Context, dest []byte, v interface{}) (sqltypes.Value, error) {
   267  	if v == nil {
   268  		return sqltypes.NULL, nil
   269  	}
   270  	ti, err := t.ConvertToTimespan(v)
   271  	if err != nil {
   272  		return sqltypes.Value{}, err
   273  	}
   274  
   275  	val := AppendAndSliceString(dest, ti.String())
   276  
   277  	return sqltypes.MakeTrusted(sqltypes.Time, val), nil
   278  }
   279  
   280  // String implements Type interface.
   281  func (t TimespanType_) String() string {
   282  	return "time(6)"
   283  }
   284  
   285  // Type implements Type interface.
   286  func (t TimespanType_) Type() query.Type {
   287  	return sqltypes.Time
   288  }
   289  
   290  // ValueType implements Type interface.
   291  func (t TimespanType_) ValueType() reflect.Type {
   292  	return timeValueType
   293  }
   294  
   295  // Zero implements Type interface.
   296  func (t TimespanType_) Zero() interface{} {
   297  	return Timespan(0)
   298  }
   299  
   300  // CollationCoercibility implements sql.CollationCoercible interface.
   301  func (TimespanType_) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   302  	return sql.Collation_binary, 5
   303  }
   304  
   305  // No built in for absolute values on int64
   306  func int64Abs(v int64) int64 {
   307  	shift := v >> 63
   308  	return (v ^ shift) - shift
   309  }
   310  
   311  func stringToTimespan(s string) (Timespan, error) {
   312  	var negative bool
   313  	var hours int16
   314  	var minutes int8
   315  	var seconds int8
   316  	var microseconds int32
   317  
   318  	if len(s) > 0 && s[0] == '-' {
   319  		negative = true
   320  		s = s[1:]
   321  	}
   322  
   323  	comps := strings.SplitN(s, ".", 2)
   324  
   325  	// Parse microseconds
   326  	if len(comps) == 2 {
   327  		microStr := comps[1]
   328  		if len(microStr) < 6 {
   329  			microStr += strings.Repeat("0", 6-len(comps[1]))
   330  		}
   331  		microStr, remainStr := microStr[0:6], microStr[6:]
   332  		convertedMicroseconds, err := strconv.Atoi(microStr)
   333  		if err != nil {
   334  			return Timespan(0), ErrConvertingToTimeType.New(s)
   335  		}
   336  		// MySQL just uses the last digit to round up. This is weird, but matches their implementation.
   337  		if len(remainStr) > 0 && remainStr[len(remainStr)-1:] >= "5" {
   338  			convertedMicroseconds++
   339  		}
   340  		microseconds = int32(convertedMicroseconds)
   341  	}
   342  
   343  	// Parse H-M-S time
   344  	hmsComps := strings.SplitN(comps[0], ":", 3)
   345  	hms := make([]string, 3)
   346  	if len(hmsComps) >= 2 {
   347  		if len(hmsComps[0]) > 3 {
   348  			return Timespan(0), ErrConvertingToTimeType.New(s)
   349  		}
   350  		hms[0] = hmsComps[0]
   351  		if len(hmsComps[1]) > 2 {
   352  			return Timespan(0), ErrConvertingToTimeType.New(s)
   353  		}
   354  		hms[1] = hmsComps[1]
   355  		if len(hmsComps) == 3 {
   356  			if len(hmsComps[2]) > 2 {
   357  				return Timespan(0), ErrConvertingToTimeType.New(s)
   358  			}
   359  			hms[2] = hmsComps[2]
   360  		}
   361  	} else {
   362  		l := len(hmsComps[0])
   363  		hms[2] = safeSubstr(hmsComps[0], l-2, l)
   364  		hms[1] = safeSubstr(hmsComps[0], l-4, l-2)
   365  		hms[0] = safeSubstr(hmsComps[0], l-7, l-4)
   366  	}
   367  
   368  	hmsHours, err := strconv.Atoi(hms[0])
   369  	if len(hms[0]) > 0 && err != nil {
   370  		return Timespan(0), ErrConvertingToTimeType.New(s)
   371  	}
   372  	hours = int16(hmsHours)
   373  
   374  	hmsMinutes, err := strconv.Atoi(hms[1])
   375  	if len(hms[1]) > 0 && err != nil {
   376  		return Timespan(0), ErrConvertingToTimeType.New(s)
   377  	} else if hmsMinutes >= 60 {
   378  		return Timespan(0), ErrConvertingToTimeType.New(s)
   379  	}
   380  	minutes = int8(hmsMinutes)
   381  
   382  	hmsSeconds, err := strconv.Atoi(hms[2])
   383  	if len(hms[2]) > 0 && err != nil {
   384  		return Timespan(0), ErrConvertingToTimeType.New(s)
   385  	} else if hmsSeconds >= 60 {
   386  		return Timespan(0), ErrConvertingToTimeType.New(s)
   387  	}
   388  	seconds = int8(hmsSeconds)
   389  
   390  	if microseconds == int32(microsecondsPerSecond) {
   391  		microseconds = 0
   392  		seconds++
   393  	}
   394  	if seconds == 60 {
   395  		seconds = 0
   396  		minutes++
   397  	}
   398  	if minutes == 60 {
   399  		minutes = 0
   400  		hours++
   401  	}
   402  
   403  	if hours > 838 {
   404  		hours = 838
   405  		minutes = 59
   406  		seconds = 59
   407  	}
   408  
   409  	if hours == 838 && minutes == 59 && seconds == 59 {
   410  		microseconds = 0
   411  	}
   412  
   413  	return unitsToTimespan(negative, hours, minutes, seconds, microseconds), nil
   414  }
   415  
   416  func safeSubstr(s string, start int, end int) string {
   417  	if start < 0 {
   418  		start = 0
   419  	}
   420  	if end < 0 {
   421  		end = 0
   422  	}
   423  	if start > len(s) {
   424  		start = len(s)
   425  		end = len(s)
   426  	} else if end > len(s) {
   427  		end = len(s)
   428  	}
   429  	return s[start:end]
   430  }
   431  
   432  // MicrosecondsToTimespan implements the TimeType interface.
   433  func (_ TimespanType_) MicrosecondsToTimespan(v int64) Timespan {
   434  	if v < timespanMinimum {
   435  		v = timespanMinimum
   436  	} else if v > timespanMaximum {
   437  		v = timespanMaximum
   438  	}
   439  	return Timespan(v)
   440  }
   441  
   442  func unitsToTimespan(isNegative bool, hours int16, minutes int8, seconds int8, microseconds int32) Timespan {
   443  	negative := int64(1)
   444  	if isNegative {
   445  		negative = -1
   446  	}
   447  	return Timespan(negative *
   448  		(int64(microseconds) +
   449  			(int64(seconds) * microsecondsPerSecond) +
   450  			(int64(minutes) * microsecondsPerMinute) +
   451  			(int64(hours) * microsecondsPerHour)))
   452  }
   453  
   454  func (t Timespan) timespanToUnits() (isNegative bool, hours int16, minutes int8, seconds int8, microseconds int32) {
   455  	isNegative = t < 0
   456  	absV := int64Abs(int64(t))
   457  	hours = int16(absV / microsecondsPerHour)
   458  	minutes = int8((absV / microsecondsPerMinute) % 60)
   459  	seconds = int8((absV / microsecondsPerSecond) % 60)
   460  	microseconds = int32(absV % microsecondsPerSecond)
   461  	return
   462  }
   463  
   464  // String returns the Timespan formatted as a string (such as for display purposes).
   465  func (t Timespan) String() string {
   466  	isNegative, hours, minutes, seconds, microseconds := t.timespanToUnits()
   467  	sign := ""
   468  	if isNegative {
   469  		sign = "-"
   470  	}
   471  	if microseconds == 0 {
   472  		return fmt.Sprintf("%v%02d:%02d:%02d", sign, hours, minutes, seconds)
   473  	}
   474  	return fmt.Sprintf("%v%02d:%02d:%02d.%06d", sign, hours, minutes, seconds, microseconds)
   475  }
   476  
   477  // AsMicroseconds returns the Timespan in microseconds.
   478  func (t Timespan) AsMicroseconds() int64 {
   479  	// Timespan already being implemented in microseconds is an implementation detail that integrators do not need to
   480  	// know about. This is also the reason for the comparison functions.
   481  	return int64(t)
   482  }
   483  
   484  // AsTimeDuration returns the Timespan as a time.Duration.
   485  func (t Timespan) AsTimeDuration() time.Duration {
   486  	return time.Duration(t.AsMicroseconds() * nanosecondsPerMicrosecond)
   487  }
   488  
   489  // Equals returns whether the calling Timespan and given Timespan are equivalent.
   490  func (t Timespan) Equals(other Timespan) bool {
   491  	return t == other
   492  }
   493  
   494  // Compare returns an integer comparing two values. The result will be 0 if t==other, -1 if t < other, and +1 if t > other.
   495  func (t Timespan) Compare(other Timespan) int {
   496  	if t < other {
   497  		return -1
   498  	} else if t > other {
   499  		return 1
   500  	}
   501  	return 0
   502  }
   503  
   504  // Negate returns a new Timespan that has been negated.
   505  func (t Timespan) Negate() Timespan {
   506  	return -1 * t
   507  }
   508  
   509  // Add returns a new Timespan that is the sum of the calling Timespan and given Timespan. The resulting Timespan is
   510  // clamped to the allowed range.
   511  func (t Timespan) Add(other Timespan) Timespan {
   512  	v := int64(t + other)
   513  	if v < timespanMinimum {
   514  		v = timespanMinimum
   515  	} else if v > timespanMaximum {
   516  		v = timespanMaximum
   517  	}
   518  	return Timespan(v)
   519  }
   520  
   521  // Subtract returns a new Timespan that is the difference of the calling Timespan and given Timespan. The resulting
   522  // Timespan is clamped to the allowed range.
   523  func (t Timespan) Subtract(other Timespan) Timespan {
   524  	v := int64(t - other)
   525  	if v < timespanMinimum {
   526  		v = timespanMinimum
   527  	} else if v > timespanMaximum {
   528  		v = timespanMaximum
   529  	}
   530  	return Timespan(v)
   531  }