github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/date.go (about) 1 // Copyright 2020-2021 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 function 16 17 import ( 18 "fmt" 19 "strings" 20 "time" 21 22 gmstime "github.com/dolthub/go-mysql-server/internal/time" 23 "github.com/dolthub/go-mysql-server/sql" 24 "github.com/dolthub/go-mysql-server/sql/expression" 25 "github.com/dolthub/go-mysql-server/sql/types" 26 ) 27 28 // DateAdd adds an interval to a date. 29 type DateAdd struct { 30 Date sql.Expression 31 Interval *expression.Interval 32 } 33 34 var _ sql.FunctionExpression = (*DateAdd)(nil) 35 var _ sql.CollationCoercible = (*DateAdd)(nil) 36 37 // NewDateAdd creates a new date add function. 38 func NewDateAdd(args ...sql.Expression) (sql.Expression, error) { 39 if len(args) != 2 { 40 return nil, sql.ErrInvalidArgumentNumber.New("DATE_ADD", 2, len(args)) 41 } 42 43 i, ok := args[1].(*expression.Interval) 44 if !ok { 45 return nil, fmt.Errorf("DATE_ADD expects an interval as second parameter") 46 } 47 48 return &DateAdd{args[0], i}, nil 49 } 50 51 // FunctionName implements sql.FunctionExpression 52 func (d *DateAdd) FunctionName() string { 53 return "date_add" 54 } 55 56 // Description implements sql.FunctionExpression 57 func (d *DateAdd) Description() string { 58 return "adds the interval to the given date." 59 } 60 61 // Children implements the sql.Expression interface. 62 func (d *DateAdd) Children() []sql.Expression { 63 return []sql.Expression{d.Date, d.Interval} 64 } 65 66 // Resolved implements the sql.Expression interface. 67 func (d *DateAdd) Resolved() bool { 68 return d.Date.Resolved() && d.Interval.Resolved() 69 } 70 71 // IsNullable implements the sql.Expression interface. 72 func (d *DateAdd) IsNullable() bool { 73 return true 74 } 75 76 // Type implements the sql.Expression interface. 77 func (d *DateAdd) Type() sql.Type { 78 sqlType := dateOffsetType(d.Date, d.Interval) 79 return sqlType 80 } 81 82 // CollationCoercibility implements the interface sql.CollationCoercible. 83 func (*DateAdd) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 84 return ctx.GetCollation(), 4 85 } 86 87 // WithChildren implements the Expression interface. 88 func (d *DateAdd) WithChildren(children ...sql.Expression) (sql.Expression, error) { 89 return NewDateAdd(children...) 90 } 91 92 // Eval implements the sql.Expression interface. 93 func (d *DateAdd) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 94 val, err := d.Date.Eval(ctx, row) 95 if err != nil { 96 return nil, err 97 } 98 99 if val == nil { 100 return nil, nil 101 } 102 103 delta, err := d.Interval.EvalDelta(ctx, row) 104 if err != nil { 105 return nil, err 106 } 107 108 if delta == nil { 109 return nil, nil 110 } 111 112 date, _, err := types.DatetimeMaxPrecision.Convert(val) 113 if err != nil { 114 ctx.Warn(1292, err.Error()) 115 return nil, nil 116 } 117 118 // return appropriate type 119 res := types.ValidateTime(delta.Add(date.(time.Time))) 120 resType := d.Type() 121 if types.IsText(resType) { 122 return res, nil 123 } 124 ret, _, err := resType.Convert(res) 125 return ret, err 126 } 127 128 func (d *DateAdd) String() string { 129 return fmt.Sprintf("%s(%s,%s)", d.FunctionName(), d.Date, d.Interval) 130 } 131 132 // NewSubDate returns a new function expression, or an error if one couldn't be created. The SUBDATE 133 // function is a synonym for DATE_SUB, with the one exception that if the second argument is NOT an 134 // explicitly declared interval, then the value is used and the interval period is assumed to be DAY. 135 // In either case, this function will actually return a *DateSub struct. 136 func NewSubDate(args ...sql.Expression) (sql.Expression, error) { 137 if len(args) != 2 { 138 return nil, sql.ErrInvalidArgumentNumber.New("SUBDATE", 2, len(args)) 139 } 140 141 // If the interval is explicitly specified, then we simply pass it all to DateSub 142 i, ok := args[1].(*expression.Interval) 143 if ok { 144 return &DateSub{args[0], i}, nil 145 } 146 147 // Otherwise, the interval period is assumed to be DAY 148 i = expression.NewInterval(args[1], "DAY") 149 return &DateSub{args[0], i}, nil 150 } 151 152 // DateSub subtracts an interval from a date. 153 type DateSub struct { 154 Date sql.Expression 155 Interval *expression.Interval 156 } 157 158 var _ sql.FunctionExpression = (*DateSub)(nil) 159 var _ sql.CollationCoercible = (*DateSub)(nil) 160 161 // NewDateSub creates a new date add function. 162 func NewDateSub(args ...sql.Expression) (sql.Expression, error) { 163 if len(args) != 2 { 164 return nil, sql.ErrInvalidArgumentNumber.New("DATE_SUB", 2, len(args)) 165 } 166 167 i, ok := args[1].(*expression.Interval) 168 if !ok { 169 return nil, fmt.Errorf("DATE_SUB expects an interval as second parameter") 170 } 171 172 return &DateSub{args[0], i}, nil 173 } 174 175 // FunctionName implements sql.FunctionExpression 176 func (d *DateSub) FunctionName() string { 177 return "date_sub" 178 } 179 180 // Description implements sql.FunctionExpression 181 func (d *DateSub) Description() string { 182 return "subtracts the interval from the given date." 183 } 184 185 // Children implements the sql.Expression interface. 186 func (d *DateSub) Children() []sql.Expression { 187 return []sql.Expression{d.Date, d.Interval} 188 } 189 190 // Resolved implements the sql.Expression interface. 191 func (d *DateSub) Resolved() bool { 192 return d.Date.Resolved() && d.Interval.Resolved() 193 } 194 195 // IsNullable implements the sql.Expression interface. 196 func (d *DateSub) IsNullable() bool { 197 return true 198 } 199 200 // Type implements the sql.Expression interface. 201 func (d *DateSub) Type() sql.Type { 202 sqlType := dateOffsetType(d.Date, d.Interval) 203 return sqlType 204 } 205 206 // CollationCoercibility implements the interface sql.CollationCoercible. 207 func (*DateSub) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 208 return ctx.GetCollation(), 4 209 } 210 211 // WithChildren implements the Expression interface. 212 func (d *DateSub) WithChildren(children ...sql.Expression) (sql.Expression, error) { 213 return NewDateSub(children...) 214 } 215 216 // Eval implements the sql.Expression interface. 217 func (d *DateSub) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 218 date, err := d.Date.Eval(ctx, row) 219 if err != nil { 220 return nil, err 221 } 222 223 if date == nil { 224 return nil, nil 225 } 226 227 date, _, err = types.DatetimeMaxPrecision.Convert(date) 228 if err != nil { 229 ctx.Warn(1292, err.Error()) 230 return nil, nil 231 } 232 233 delta, err := d.Interval.EvalDelta(ctx, row) 234 if err != nil { 235 return nil, err 236 } 237 238 if delta == nil { 239 return nil, nil 240 } 241 242 // return appropriate type 243 res := types.ValidateTime(delta.Sub(date.(time.Time))) 244 resType := d.Type() 245 if types.IsText(resType) { 246 return res, nil 247 } 248 ret, _, err := resType.Convert(res) 249 return ret, err 250 } 251 252 func (d *DateSub) String() string { 253 return fmt.Sprintf("%s(%s,%s)", d.FunctionName(), d.Date, d.Interval) 254 } 255 256 // TimestampConversion is a shorthand function for CONVERT(expr, TIMESTAMP) 257 type TimestampConversion struct { 258 Date sql.Expression 259 } 260 261 var _ sql.FunctionExpression = (*TimestampConversion)(nil) 262 var _ sql.CollationCoercible = (*TimestampConversion)(nil) 263 264 // FunctionName implements sql.FunctionExpression 265 func (t *TimestampConversion) FunctionName() string { 266 return "timestamp" 267 } 268 269 // Description implements sql.FunctionExpression 270 func (t *TimestampConversion) Description() string { 271 return "returns a timestamp value for the expression given (e.g. the string '2020-01-02')." 272 } 273 274 func (t *TimestampConversion) Resolved() bool { 275 return t.Date == nil || t.Date.Resolved() 276 } 277 278 func (t *TimestampConversion) String() string { 279 return fmt.Sprintf("%s(%s)", t.FunctionName(), t.Date) 280 } 281 282 func (t *TimestampConversion) Type() sql.Type { 283 return t.Date.Type() 284 } 285 286 // CollationCoercibility implements the interface sql.CollationCoercible. 287 func (*TimestampConversion) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 288 return sql.Collation_binary, 5 289 } 290 291 func (t *TimestampConversion) IsNullable() bool { 292 return false 293 } 294 295 func (t *TimestampConversion) Eval(ctx *sql.Context, r sql.Row) (interface{}, error) { 296 e, err := t.Date.Eval(ctx, r) 297 if err != nil { 298 return nil, err 299 } 300 ret, _, err := types.TimestampMaxPrecision.Convert(e) 301 return ret, err 302 } 303 304 func (t *TimestampConversion) Children() []sql.Expression { 305 if t.Date == nil { 306 return nil 307 } 308 return []sql.Expression{t.Date} 309 } 310 311 func (t *TimestampConversion) WithChildren(children ...sql.Expression) (sql.Expression, error) { 312 return NewTimestamp(children...) 313 } 314 315 func NewTimestamp(args ...sql.Expression) (sql.Expression, error) { 316 if len(args) != 1 { 317 return nil, sql.ErrInvalidArgumentNumber.New("TIMESTAMP", 1, len(args)) 318 } 319 return &TimestampConversion{args[0]}, nil 320 } 321 322 // DatetimeConversion is a shorthand function for CONVERT(expr, DATETIME) 323 type DatetimeConversion struct { 324 Date sql.Expression 325 } 326 327 var _ sql.FunctionExpression = (*DatetimeConversion)(nil) 328 var _ sql.CollationCoercible = (*DatetimeConversion)(nil) 329 330 // FunctionName implements sql.FunctionExpression 331 func (t *DatetimeConversion) FunctionName() string { 332 return "datetime" 333 } 334 335 // Description implements sql.FunctionExpression 336 func (t *DatetimeConversion) Description() string { 337 return "returns a DATETIME value for the expression given (e.g. the string '2020-01-02')." 338 } 339 340 func (t *DatetimeConversion) Resolved() bool { 341 return t.Date == nil || t.Date.Resolved() 342 } 343 344 func (t *DatetimeConversion) String() string { 345 return fmt.Sprintf("%s(%s)", t.FunctionName(), t.Date) 346 } 347 348 func (t *DatetimeConversion) Type() sql.Type { 349 return t.Date.Type() 350 } 351 352 // CollationCoercibility implements the interface sql.CollationCoercible. 353 func (*DatetimeConversion) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 354 return sql.Collation_binary, 5 355 } 356 357 func (t *DatetimeConversion) IsNullable() bool { 358 return false 359 } 360 361 func (t *DatetimeConversion) Eval(ctx *sql.Context, r sql.Row) (interface{}, error) { 362 e, err := t.Date.Eval(ctx, r) 363 if err != nil { 364 return nil, err 365 } 366 ret, _, err := types.DatetimeMaxPrecision.Convert(e) 367 return ret, err 368 } 369 370 func (t *DatetimeConversion) Children() []sql.Expression { 371 if t.Date == nil { 372 return nil 373 } 374 return []sql.Expression{t.Date} 375 } 376 377 func (t *DatetimeConversion) WithChildren(children ...sql.Expression) (sql.Expression, error) { 378 return NewDatetime(children...) 379 } 380 381 // NewDatetime returns a DatetimeConversion instance to handle the sql function "datetime". This is 382 // not a standard mysql function, but provides a shorthand for datetime conversions. 383 func NewDatetime(args ...sql.Expression) (sql.Expression, error) { 384 if len(args) != 1 { 385 return nil, sql.ErrInvalidArgumentNumber.New("DATETIME", 1, len(args)) 386 } 387 388 return &DatetimeConversion{args[0]}, nil 389 } 390 391 // UnixTimestamp converts the argument to the number of seconds since 1970-01-01 00:00:00 UTC. 392 // With no argument, returns number of seconds since unix epoch for the current time. 393 type UnixTimestamp struct { 394 Date sql.Expression 395 } 396 397 var _ sql.FunctionExpression = (*UnixTimestamp)(nil) 398 var _ sql.CollationCoercible = (*UnixTimestamp)(nil) 399 400 func NewUnixTimestamp(args ...sql.Expression) (sql.Expression, error) { 401 if len(args) > 1 { 402 return nil, sql.ErrInvalidArgumentNumber.New("UNIX_TIMESTAMP", 1, len(args)) 403 } 404 if len(args) == 0 { 405 return &UnixTimestamp{nil}, nil 406 } 407 return &UnixTimestamp{args[0]}, nil 408 } 409 410 // FunctionName implements sql.FunctionExpression 411 func (ut *UnixTimestamp) FunctionName() string { 412 return "unix_timestamp" 413 } 414 415 // Description implements sql.FunctionExpression 416 func (ut *UnixTimestamp) Description() string { 417 return "returns the datetime argument to the number of seconds since the Unix epoch. With no argument, returns the number of seconds since the Unix epoch for the current time." 418 } 419 420 func (ut *UnixTimestamp) Children() []sql.Expression { 421 if ut.Date != nil { 422 return []sql.Expression{ut.Date} 423 } 424 return nil 425 } 426 427 func (ut *UnixTimestamp) Resolved() bool { 428 if ut.Date != nil { 429 return ut.Date.Resolved() 430 } 431 return true 432 } 433 434 func (ut *UnixTimestamp) IsNullable() bool { 435 return true 436 } 437 438 func (ut *UnixTimestamp) Type() sql.Type { 439 return types.Float64 440 } 441 442 // CollationCoercibility implements the interface sql.CollationCoercible. 443 func (*UnixTimestamp) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 444 return sql.Collation_binary, 5 445 } 446 447 func (ut *UnixTimestamp) WithChildren(children ...sql.Expression) (sql.Expression, error) { 448 return NewUnixTimestamp(children...) 449 } 450 451 func (ut *UnixTimestamp) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 452 if ut.Date == nil { 453 return toUnixTimestamp(ctx.QueryTime()) 454 } 455 456 date, err := ut.Date.Eval(ctx, row) 457 if err != nil { 458 return nil, err 459 } 460 if date == nil { 461 return nil, nil 462 } 463 464 date, _, err = types.DatetimeMaxPrecision.Convert(date) 465 if err != nil { 466 // If we aren't able to convert the value to a date, return 0 and set 467 // a warning to match MySQL's behavior 468 ctx.Warn(1292, "Incorrect datetime value: %s", ut.Date.String()) 469 return 0, nil 470 } 471 472 // The function above returns the time value in UTC time zone. 473 // Instead, it should use the current session time zone. 474 475 // For example, if the current session TZ is set to +07:00 and given value is '2023-09-25 07:02:57', 476 // then the correct time value is '2023-09-25 07:02:57 +07:00'. 477 // Currently, we get '2023-09-25 07:02:57 +00:00' from the above function. 478 // ConvertTimeZone function is used to get the value in +07:00 TZ 479 // It will return the correct value of '2023-09-25 00:02:57 +00:00', 480 // which is equivalent of '2023-09-25 07:02:57 +07:00'. 481 stz, err := SessionTimeZone(ctx) 482 if err != nil { 483 return nil, err 484 } 485 486 ctz, ok := gmstime.ConvertTimeZone(date.(time.Time), stz, "UTC") 487 if ok { 488 date = ctz 489 } 490 491 return toUnixTimestamp(date.(time.Time)) 492 } 493 494 func toUnixTimestamp(t time.Time) (interface{}, error) { 495 ret, _, err := types.Float64.Convert(float64(t.Unix()) + float64(t.Nanosecond())/float64(1000000000)) 496 return ret, err 497 } 498 499 func (ut *UnixTimestamp) String() string { 500 if ut.Date != nil { 501 return fmt.Sprintf("%s(%s)", ut.FunctionName(), ut.Date) 502 } else { 503 return fmt.Sprintf("%s()", ut.FunctionName()) 504 } 505 } 506 507 // FromUnixtime converts the argument to a datetime. 508 type FromUnixtime struct { 509 *UnaryFunc 510 } 511 512 var _ sql.FunctionExpression = (*FromUnixtime)(nil) 513 var _ sql.CollationCoercible = (*FromUnixtime)(nil) 514 515 func NewFromUnixtime(arg sql.Expression) sql.Expression { 516 return &FromUnixtime{NewUnaryFunc(arg, "FROM_UNIXTIME", types.DatetimeMaxPrecision)} 517 } 518 519 // Description implements sql.FunctionExpression 520 func (r *FromUnixtime) Description() string { 521 return "formats Unix timestamp as a date." 522 } 523 524 // CollationCoercibility implements the interface sql.CollationCoercible. 525 func (*FromUnixtime) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 526 return sql.Collation_binary, 5 527 } 528 529 func (r *FromUnixtime) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 530 val, err := r.EvalChild(ctx, row) 531 if err != nil { 532 return nil, err 533 } 534 535 if val == nil { 536 return nil, nil 537 } 538 539 n, _, err := types.Int64.Convert(val) 540 if err != nil { 541 return nil, err 542 } 543 544 return time.Unix(n.(int64), 0), nil 545 } 546 547 func (r *FromUnixtime) WithChildren(children ...sql.Expression) (sql.Expression, error) { 548 if len(children) != 1 { 549 return nil, sql.ErrInvalidChildrenNumber.New(r, len(children), 1) 550 } 551 return NewFromUnixtime(children[0]), nil 552 } 553 554 type CurrDate struct { 555 NoArgFunc 556 } 557 558 func (c CurrDate) IsNonDeterministic() bool { 559 return true 560 } 561 562 var _ sql.FunctionExpression = CurrDate{} 563 var _ sql.CollationCoercible = CurrDate{} 564 565 // Description implements sql.FunctionExpression 566 func (c CurrDate) Description() string { 567 return "returns the current date." 568 } 569 570 func NewCurrDate() sql.Expression { 571 return CurrDate{ 572 NoArgFunc: NoArgFunc{"curdate", types.LongText}, 573 } 574 } 575 576 func NewCurrentDate() sql.Expression { 577 return CurrDate{ 578 NoArgFunc: NoArgFunc{"current_date", types.LongText}, 579 } 580 } 581 582 func currDateLogic(ctx *sql.Context, _ sql.Row) (interface{}, error) { 583 t := ctx.QueryTime() 584 return fmt.Sprintf("%d-%02d-%02d", t.Year(), t.Month(), t.Day()), nil 585 } 586 587 // Eval implements sql.Expression 588 func (c CurrDate) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { 589 return currDateLogic(ctx, row) 590 } 591 592 // CollationCoercibility implements the interface sql.CollationCoercible. 593 func (CurrDate) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 594 return sql.Collation_binary, 5 595 } 596 597 // WithChildren implements sql.Expression 598 func (c CurrDate) WithChildren(children ...sql.Expression) (sql.Expression, error) { 599 return NoArgFuncWithChildren(c, children) 600 } 601 602 // Determines the return type of a DateAdd/DateSub expression 603 // Logic is based on https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add 604 func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Type { 605 if input == nil { 606 return types.Null 607 } 608 inputType := input.Type() 609 610 // result is null if expression is null 611 if inputType == types.Null { 612 return types.Null 613 } 614 615 // set type flags 616 isInputDate := inputType == types.Date 617 isInputTime := inputType == types.Time 618 isInputDatetime := types.IsDatetimeType(inputType) || types.IsTimestampType(inputType) 619 620 // result is Datetime if expression is Datetime or Timestamp 621 if isInputDatetime { 622 return types.DatetimeMaxPrecision 623 } 624 625 // determine what kind of interval we're dealing with 626 isYmdInterval := strings.Contains(interval.Unit, "YEAR") || 627 strings.Contains(interval.Unit, "QUARTER") || 628 strings.Contains(interval.Unit, "MONTH") || 629 strings.Contains(interval.Unit, "WEEK") || 630 strings.Contains(interval.Unit, "DAY") 631 632 isHmsInterval := strings.Contains(interval.Unit, "HOUR") || 633 strings.Contains(interval.Unit, "MINUTE") || 634 strings.Contains(interval.Unit, "SECOND") 635 isMixedInterval := isYmdInterval && isHmsInterval 636 637 // handle input of Date type 638 if isInputDate { 639 if isHmsInterval || isMixedInterval { 640 // if interval contains time components, result is Datetime 641 return types.DatetimeMaxPrecision 642 } else { 643 // otherwise result is Date 644 return types.Date 645 } 646 } 647 648 // handle input of Time type 649 if isInputTime { 650 if isYmdInterval || isMixedInterval { 651 // if interval contains date components, result is Datetime 652 return types.DatetimeMaxPrecision 653 } else { 654 // otherwise result is Time 655 return types.Time 656 } 657 } 658 659 // handle dynamic input type 660 if types.IsDeferredType(inputType) { 661 if isYmdInterval && !isHmsInterval { 662 // if interval contains only date components, result is Date 663 return types.Date 664 } else { 665 // otherwise result is Datetime 666 return types.DatetimeMaxPrecision 667 } 668 } 669 670 // default type is VARCHAR 671 return types.Text 672 }