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 }