From 695c9a59aa2e95b59f74e00ae2aa3a6115cc014a Mon Sep 17 00:00:00 2001 From: James Cor Date: Mon, 29 Jul 2024 17:00:58 -0700 Subject: [PATCH 1/4] consider precision and add more tests --- sql/expression/function/date.go | 102 ++++++++++++++++++++++----- sql/expression/function/date_test.go | 38 ++++++++-- sql/types/datetime.go | 15 ++-- 3 files changed, 128 insertions(+), 27 deletions(-) diff --git a/sql/expression/function/date.go b/sql/expression/function/date.go index ccb4e6e954..5a0cb98175 100644 --- a/sql/expression/function/date.go +++ b/sql/expression/function/date.go @@ -26,6 +26,7 @@ import ( "github.com/dolthub/go-mysql-server/sql/expression" "github.com/dolthub/go-mysql-server/sql/transform" "github.com/dolthub/go-mysql-server/sql/types" + "github.com/dolthub/vitess/go/vt/proto/query" ) // NewAddDate returns a new function expression, or an error if one couldn't be created. The ADDDATE @@ -132,20 +133,43 @@ func (d *DateAdd) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - date, _, err = types.DatetimeMaxPrecision.Convert(date) + var dateVal interface{} + dateVal, _, err = types.DatetimeMaxPrecision.Convert(date) if err != nil { ctx.Warn(1292, err.Error()) return nil, nil } // return appropriate type - res := types.ValidateTime(delta.Add(date.(time.Time))) + res := types.ValidateTime(delta.Add(dateVal.(time.Time))) + if res == nil { + return nil, nil + } + resType := d.Type() if types.IsText(resType) { - return res, nil + // If the input is a properly formatted date/datetime, the output should be the same type. + if dateStr, isStr := date.(string); isStr { + for _, layout := range types.DateOnlyLayouts { + if _, pErr := time.Parse(layout, dateStr); pErr != nil { + continue + } + return res.(time.Time).Format(sql.DateLayout), nil + } + } + + ret, _, cErr := resType.Convert(res) + if cErr != nil { + return nil, cErr + } + return ret, nil } + ret, _, err := resType.Convert(res) - return ret, err + if err != nil { + return nil, err + } + return ret, nil } func (d *DateAdd) String() string { @@ -256,20 +280,43 @@ func (d *DateSub) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return nil, nil } - date, _, err = types.DatetimeMaxPrecision.Convert(date) + var dateVal interface{} + dateVal, _, err = types.DatetimeMaxPrecision.Convert(date) if err != nil { ctx.Warn(1292, err.Error()) return nil, nil } // return appropriate type - res := types.ValidateTime(delta.Sub(date.(time.Time))) + res := types.ValidateTime(delta.Sub(dateVal.(time.Time))) + if res == nil { + return nil, nil + } + resType := d.Type() if types.IsText(resType) { - return res, nil + // If the input is a properly formatted date/datetime, the output should be the same type. + if dateStr, isStr := date.(string); isStr { + for _, layout := range types.DateOnlyLayouts { + if _, pErr := time.Parse(layout, dateStr); pErr != nil { + continue + } + return res.(time.Time).Format(sql.DateLayout), nil + } + } + + ret, _, cErr := resType.Convert(res) + if cErr != nil { + return nil, cErr + } + return ret, nil } + ret, _, err := resType.Convert(res) - return ret, err + if err != nil { + return nil, err + } + return ret, nil } func (d *DateSub) String() string { @@ -734,6 +781,20 @@ func (c CurrDate) WithChildren(children ...sql.Expression) (sql.Expression, erro return NoArgFuncWithChildren(c, children) } +func isYmdInterval(interval *expression.Interval) bool { + return strings.Contains(interval.Unit, "YEAR") || + strings.Contains(interval.Unit, "QUARTER") || + strings.Contains(interval.Unit, "MONTH") || + strings.Contains(interval.Unit, "WEEK") || + strings.Contains(interval.Unit, "DAY") +} + +func isHmsInterval(interval *expression.Interval) bool { + return strings.Contains(interval.Unit, "HOUR") || + strings.Contains(interval.Unit, "MINUTE") || + strings.Contains(interval.Unit, "SECOND") +} + // Determines the return type of a DateAdd/DateSub expression // Logic is based on https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Type { @@ -750,12 +811,6 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ // set type flags isInputDate := inputType == types.Date isInputTime := inputType == types.Time - isInputDatetime := types.IsDatetimeType(inputType) || types.IsTimestampType(inputType) - - // result is Datetime if expression is Datetime or Timestamp - if isInputDatetime { - return types.DatetimeMaxPrecision - } // determine what kind of interval we're dealing with isYmdInterval := strings.Contains(interval.Unit, "YEAR") || @@ -767,13 +822,26 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ isHmsInterval := strings.Contains(interval.Unit, "HOUR") || strings.Contains(interval.Unit, "MINUTE") || strings.Contains(interval.Unit, "SECOND") + isMixedInterval := isYmdInterval && isHmsInterval + var prec int + if dtType, ok := inputType.(sql.DatetimeType); ok { + if dtType.Precision() > 0 { + prec = 6 + } + } + if strings.Contains(interval.Unit, "MICROSECOND") && prec < 6{ + prec = 6 + } else if strings.Contains(interval.Unit, "SECOND") && prec < 2 { + prec = 2 + } + // handle input of Date type if isInputDate { if isHmsInterval || isMixedInterval { // if interval contains time components, result is Datetime - return types.DatetimeMaxPrecision + return types.MustCreateDatetimeType(query.Type_DATETIME, prec) } else { // otherwise result is Date return types.Date @@ -784,7 +852,7 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ if isInputTime { if isYmdInterval || isMixedInterval { // if interval contains date components, result is Datetime - return types.DatetimeMaxPrecision + return types.MustCreateDatetimeType(query.Type_DATETIME, prec) } else { // otherwise result is Time return types.Time @@ -798,7 +866,7 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ return types.Date } else { // otherwise result is Datetime - return types.DatetimeMaxPrecision + return types.MustCreateDatetimeType(query.Type_DATETIME, prec) } } diff --git a/sql/expression/function/date_test.go b/sql/expression/function/date_test.go index cea57bf7ab..25576625bb 100644 --- a/sql/expression/function/date_test.go +++ b/sql/expression/function/date_test.go @@ -41,13 +41,43 @@ func TestAddDate(t *testing.T) { _, err = NewAddDate(expression.NewLiteral("2018-05-02", types.LongText)) require.Error(err) + var expected, result interface{} + var f sql.Expression + + f, err = NewAddDate( + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 123456000, time.UTC), types.Date), + expression.NewLiteral(int64(1), types.Int64)) + require.NoError(err) + expected = time.Date(2018, 5, 3, 0, 0,0, 0, time.UTC) + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewAddDate( + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 0, time.UTC), types.Datetime), + expression.NewLiteral(int64(1), types.Int64)) + require.NoError(err) + expected = time.Date(2018, 5, 3, 12, 34,56, 0, time.UTC) + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewAddDate( + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 123456000, time.UTC), types.DatetimeMaxPrecision), + expression.NewLiteral(int64(1), types.Int64)) + require.NoError(err) + expected = time.Date(2018, 5, 3, 12, 34,56, 123456000, time.UTC) + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + // If the second argument is NOT an interval, then it's assumed to be a day interval - f, err := NewAddDate( + f, err = NewAddDate( expression.NewLiteral("2018-05-02", types.LongText), expression.NewLiteral(int64(1), types.Int64)) require.NoError(err) - expected := time.Date(2018, time.May, 3, 0, 0, 0, 0, time.UTC) - result, err := f.Eval(ctx, sql.Row{}) + expected = "2018-05-03" + result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) @@ -89,7 +119,7 @@ func TestAddDate(t *testing.T) { expression.NewLiteral("2018-05-02", types.Text), expression.NewLiteral(int64(1_000_000), types.Int64)) require.NoError(err) - expected = time.Date(4756, time.March, 29, 0, 0, 0, 0, time.UTC) + expected = "4756-03-29" result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) diff --git a/sql/types/datetime.go b/sql/types/datetime.go index 692b3e8bda..f846ca3247 100644 --- a/sql/types/datetime.go +++ b/sql/types/datetime.go @@ -50,29 +50,32 @@ var ( // datetimeTypeMinTimestamp is the minimum representable Timestamp value, which is one second past the epoch. datetimeTypeMinTimestamp = time.Unix(1, 0) + DateOnlyLayouts = []string{ + "20060102", + "2006-1-2", + "2006-01-02", + "2006/01/02", + } + // TimestampDatetimeLayouts hold extra timestamps allowed for parsing. It does // not have all the layouts supported by mysql. Missing are two digit year // versions of common cases and dates that use non common separators. // // https://github.com/MariaDB/server/blob/mysql-5.5.36/sql-common/my_time.c#L124 - TimestampDatetimeLayouts = []string{ + TimestampDatetimeLayouts = append(DateOnlyLayouts, []string{ "2006-01-02 15:4", "2006-01-02 15:04", "2006-01-02 15:04:", "2006-01-02 15:04:.", "2006-01-02 15:04:05.", "2006-01-02 15:04:05.999999", - "2006-01-02", - "2006-1-2", "2006-1-2 15:4:5.999999", time.RFC3339, time.RFC3339Nano, "2006-01-02T15:04:05", "20060102150405", - "20060102", - "2006/01/02", "2006-01-02 15:04:05.999999999 -0700 MST", // represents standard Time.time.UTC() - } + }...) // zeroTime is 0000-01-01 00:00:00 UTC which is the closest Go can get to 0000-00-00 00:00:00 zeroTime = time.Unix(-62167219200, 0).UTC() From 655f2b9a338e9e6698303c5afe25d7a534534875 Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 30 Jul 2024 13:18:03 -0700 Subject: [PATCH 2/4] tests --- enginetest/enginetests.go | 18 ++- enginetest/queries/queries.go | 4 +- sql/expression/function/date.go | 73 +++++------ sql/expression/function/date_test.go | 177 ++++++++++++++++++++++++++- sql/type.go | 4 + 5 files changed, 222 insertions(+), 54 deletions(-) diff --git a/enginetest/enginetests.go b/enginetest/enginetests.go index 8c04d1b642..b460f1fb2b 100644 --- a/enginetest/enginetests.go +++ b/enginetest/enginetests.go @@ -5302,14 +5302,28 @@ func TestPrepared(t *testing.T, harness Harness) { }, { Query: "SELECT DATE_ADD(TIMESTAMP(?), INTERVAL 1 DAY);", - Expected: []sql.Row{{time.Date(2022, time.October, 27, 13, 14, 15, 0, time.UTC)}}, + Expected: []sql.Row{{"2022-10-27 13:14:15"}}, + Bindings: map[string]*query.BindVariable{ + "v1": mustBuildBindVariable(time.Date(2022, time.October, 26, 13, 14, 15, 0, time.UTC)), + }, + }, + { + Query: "SELECT DATE_ADD(TIMESTAMP(?), INTERVAL 1 DAY);", + Expected: []sql.Row{{"2022-10-27 13:14:15"}}, Bindings: map[string]*query.BindVariable{ "v1": mustBuildBindVariable("2022-10-26 13:14:15"), }, }, { Query: "SELECT DATE_ADD(?, INTERVAL 1 DAY);", - Expected: []sql.Row{{time.Date(2022, time.October, 27, 13, 14, 15, 0, time.UTC)}}, + Expected: []sql.Row{{"2022-10-27 13:14:15"}}, + Bindings: map[string]*query.BindVariable{ + "v1": mustBuildBindVariable(time.Date(2022, time.October, 26, 13, 14, 15, 0, time.UTC)), + }, + }, + { + Query: "SELECT DATE_ADD(?, INTERVAL 1 DAY);", + Expected: []sql.Row{{"2022-10-27 13:14:15"}}, Bindings: map[string]*query.BindVariable{ "v1": mustBuildBindVariable("2022-10-26 13:14:15"), }, diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index 0d636a39fc..a192012e8f 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -6309,7 +6309,7 @@ Select * from ( }, { Query: "SELECT DATE_ADD('2018-05-02', INTERVAL 1 day)", - Expected: []sql.Row{{time.Date(2018, time.May, 3, 0, 0, 0, 0, time.UTC)}}, + Expected: []sql.Row{{"2018-05-03"}}, }, { Query: "SELECT DATE_ADD(DATE('2018-05-02'), INTERVAL 1 day)", @@ -6321,7 +6321,7 @@ Select * from ( }, { Query: "SELECT DATE_SUB('2018-05-02', INTERVAL 1 DAY)", - Expected: []sql.Row{{time.Date(2018, time.May, 1, 0, 0, 0, 0, time.UTC)}}, + Expected: []sql.Row{{"2018-05-01"}}, }, { Query: "SELECT DATE_SUB(DATE('2018-05-02'), INTERVAL 1 DAY)", diff --git a/sql/expression/function/date.go b/sql/expression/function/date.go index 5a0cb98175..e2aefedde0 100644 --- a/sql/expression/function/date.go +++ b/sql/expression/function/date.go @@ -26,8 +26,7 @@ import ( "github.com/dolthub/go-mysql-server/sql/expression" "github.com/dolthub/go-mysql-server/sql/transform" "github.com/dolthub/go-mysql-server/sql/types" - "github.com/dolthub/vitess/go/vt/proto/query" -) + ) // NewAddDate returns a new function expression, or an error if one couldn't be created. The ADDDATE // function is a synonym for DATE_ADD, with the one exception that if the second argument is NOT an @@ -148,8 +147,14 @@ func (d *DateAdd) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { resType := d.Type() if types.IsText(resType) { - // If the input is a properly formatted date/datetime, the output should be the same type. + // If the input is a properly formatted date/datetime string, the output should also be a string if dateStr, isStr := date.(string); isStr { + if res.(time.Time).Nanosecond() > 0 { + return res.(time.Time).Format(sql.DatetimeLayoutNoTrim), nil + } + if isHmsInterval(d.Interval) { + return res.(time.Time).Format(sql.TimestampDatetimeLayout), nil + } for _, layout := range types.DateOnlyLayouts { if _, pErr := time.Parse(layout, dateStr); pErr != nil { continue @@ -157,12 +162,6 @@ func (d *DateAdd) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return res.(time.Time).Format(sql.DateLayout), nil } } - - ret, _, cErr := resType.Convert(res) - if cErr != nil { - return nil, cErr - } - return ret, nil } ret, _, err := resType.Convert(res) @@ -295,8 +294,14 @@ func (d *DateSub) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { resType := d.Type() if types.IsText(resType) { - // If the input is a properly formatted date/datetime, the output should be the same type. + // If the input is a properly formatted date/datetime string, the output should also be a string if dateStr, isStr := date.(string); isStr { + if res.(time.Time).Nanosecond() > 0 { + return res.(time.Time).Format(sql.DatetimeLayoutNoTrim), nil + } + if isHmsInterval(d.Interval) { + return res.(time.Time).Format(sql.TimestampDatetimeLayout), nil + } for _, layout := range types.DateOnlyLayouts { if _, pErr := time.Parse(layout, dateStr); pErr != nil { continue @@ -304,12 +309,6 @@ func (d *DateSub) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { return res.(time.Time).Format(sql.DateLayout), nil } } - - ret, _, cErr := resType.Convert(res) - if cErr != nil { - return nil, cErr - } - return ret, nil } ret, _, err := resType.Convert(res) @@ -808,40 +807,24 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ return types.Null } + if types.IsDatetimeType(inputType) || types.IsTimestampType(inputType) { + return types.DatetimeMaxPrecision + } + // set type flags isInputDate := inputType == types.Date isInputTime := inputType == types.Time // determine what kind of interval we're dealing with - isYmdInterval := strings.Contains(interval.Unit, "YEAR") || - strings.Contains(interval.Unit, "QUARTER") || - strings.Contains(interval.Unit, "MONTH") || - strings.Contains(interval.Unit, "WEEK") || - strings.Contains(interval.Unit, "DAY") - - isHmsInterval := strings.Contains(interval.Unit, "HOUR") || - strings.Contains(interval.Unit, "MINUTE") || - strings.Contains(interval.Unit, "SECOND") - - isMixedInterval := isYmdInterval && isHmsInterval - - var prec int - if dtType, ok := inputType.(sql.DatetimeType); ok { - if dtType.Precision() > 0 { - prec = 6 - } - } - if strings.Contains(interval.Unit, "MICROSECOND") && prec < 6{ - prec = 6 - } else if strings.Contains(interval.Unit, "SECOND") && prec < 2 { - prec = 2 - } + isYmd := isYmdInterval(interval) + isHms := isHmsInterval(interval) + isMixed := isYmd && isHms // handle input of Date type if isInputDate { - if isHmsInterval || isMixedInterval { + if isHms || isMixed { // if interval contains time components, result is Datetime - return types.MustCreateDatetimeType(query.Type_DATETIME, prec) + return types.DatetimeMaxPrecision } else { // otherwise result is Date return types.Date @@ -850,9 +833,9 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ // handle input of Time type if isInputTime { - if isYmdInterval || isMixedInterval { + if isYmd || isMixed { // if interval contains date components, result is Datetime - return types.MustCreateDatetimeType(query.Type_DATETIME, prec) + return types.DatetimeMaxPrecision } else { // otherwise result is Time return types.Time @@ -861,12 +844,12 @@ func dateOffsetType(input sql.Expression, interval *expression.Interval) sql.Typ // handle dynamic input type if types.IsDeferredType(inputType) { - if isYmdInterval && !isHmsInterval { + if isYmd && !isHms { // if interval contains only date components, result is Date return types.Date } else { // otherwise result is Datetime - return types.MustCreateDatetimeType(query.Type_DATETIME, prec) + return types.DatetimeMaxPrecision } } diff --git a/sql/expression/function/date_test.go b/sql/expression/function/date_test.go index 25576625bb..5153cb5304 100644 --- a/sql/expression/function/date_test.go +++ b/sql/expression/function/date_test.go @@ -71,7 +71,7 @@ func TestAddDate(t *testing.T) { require.NoError(err) require.Equal(expected, result) - // If the second argument is NOT an interval, then it's assumed to be a day interval + // If the second argument is NOT an interval, then ADDDATE works exactly like DATE_ADD f, err = NewAddDate( expression.NewLiteral("2018-05-02", types.LongText), expression.NewLiteral(int64(1), types.Int64)) @@ -90,6 +90,70 @@ func TestAddDate(t *testing.T) { require.NoError(err) require.Equal(expected, result) + f, err = NewAddDate( + expression.NewLiteral("2018-05-02", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "DAY")) + require.NoError(err) + expected = "2018-05-03" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewAddDate( + expression.NewLiteral("2018-05-02 12:34:56", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "DAY")) + require.NoError(err) + expected = "2018-05-03 12:34:56" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewAddDate( + expression.NewLiteral("2018-05-02 12:34:56.123", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "DAY")) + require.NoError(err) + expected = "2018-05-03 12:34:56.123000" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewAddDate( + expression.NewLiteral("2018-05-02 12:34:56.123456", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "DAY")) + require.NoError(err) + expected = "2018-05-03 12:34:56.123456" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewAddDate( + expression.NewLiteral("2018-05-02", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "SECOND")) + require.NoError(err) + expected = "2018-05-02 00:00:01" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewAddDate( + expression.NewLiteral("2018-05-02", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(10), types.Int64), "MICROSECOND")) + require.NoError(err) + expected = "2018-05-02 00:00:00.000010" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewAddDate( + expression.NewLiteral("2018-05-02", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "MICROSECOND")) + require.NoError(err) + expected = "2018-05-02 00:00:00.000001" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + // If the interval param is NULL, then NULL is returned f2, err := NewAddDate( expression.NewLiteral("2018-05-02", types.LongText), @@ -98,7 +162,12 @@ func TestAddDate(t *testing.T) { require.NoError(err) require.Nil(result) + f, err = NewAddDate( + expression.NewGetField(0, types.Int64, "foo", true), + expression.NewLiteral(int64(1), types.Int64)) + // If the date param is NULL, then NULL is returned + require.NoError(err) result, err = f.Eval(ctx, sql.Row{nil}) require.NoError(err) require.Nil(result) @@ -149,7 +218,7 @@ func TestDateAdd(t *testing.T) { ) require.NoError(err) - expected := time.Date(2018, time.May, 3, 0, 0, 0, 0, time.UTC) + expected := "2018-05-03" result, err := f.Eval(ctx, sql.Row{"2018-05-02"}) require.NoError(err) @@ -183,13 +252,43 @@ func TestSubDate(t *testing.T) { _, err = NewSubDate(expression.NewLiteral("2018-05-02", types.LongText)) require.Error(err) + var expected, result interface{} + var f sql.Expression + + f, err = NewSubDate( + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 123456000, time.UTC), types.Date), + expression.NewLiteral(int64(1), types.Int64)) + require.NoError(err) + expected = time.Date(2018, 5, 1, 0, 0,0, 0, time.UTC) + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewSubDate( + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 0, time.UTC), types.Datetime), + expression.NewLiteral(int64(1), types.Int64)) + require.NoError(err) + expected = time.Date(2018, 5, 1, 12, 34,56, 0, time.UTC) + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewSubDate( + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 123456000, time.UTC), types.DatetimeMaxPrecision), + expression.NewLiteral(int64(1), types.Int64)) + require.NoError(err) + expected = time.Date(2018, 5, 1, 12, 34,56, 123456000, time.UTC) + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + // If the second argument is NOT an interval, then it's assumed to be a day interval - f, err := NewSubDate( + f, err = NewSubDate( expression.NewLiteral("2018-05-02", types.LongText), expression.NewLiteral(int64(1), types.Int64)) require.NoError(err) - expected := time.Date(2018, time.May, 1, 0, 0, 0, 0, time.UTC) - result, err := f.Eval(ctx, sql.Row{}) + expected = "2018-05-01" + result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) @@ -202,6 +301,70 @@ func TestSubDate(t *testing.T) { require.NoError(err) require.Equal(expected, result) + f, err = NewSubDate( + expression.NewLiteral("2018-05-02", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "DAY")) + require.NoError(err) + expected = "2018-05-01" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewSubDate( + expression.NewLiteral("2018-05-02 12:34:56", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "DAY")) + require.NoError(err) + expected = "2018-05-01 12:34:56" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewSubDate( + expression.NewLiteral("2018-05-02 12:34:56.123", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "DAY")) + require.NoError(err) + expected = "2018-05-01 12:34:56.123000" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewSubDate( + expression.NewLiteral("2018-05-02 12:34:56.123456", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "DAY")) + require.NoError(err) + expected = "2018-05-01 12:34:56.123456" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewSubDate( + expression.NewLiteral("2018-05-02", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "SECOND")) + require.NoError(err) + expected = "2018-05-01 23:59:59" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewSubDate( + expression.NewLiteral("2018-05-02", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(10), types.Int64), "MICROSECOND")) + require.NoError(err) + expected = "2018-05-01 23:59:59.999990" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + f, err = NewSubDate( + expression.NewLiteral("2018-05-02", types.LongText), + expression.NewInterval(expression.NewLiteral(int64(1), types.Int64), "MICROSECOND")) + require.NoError(err) + expected = "2018-05-01 23:59:59.999999" + result, err = f.Eval(ctx, sql.Row{}) + require.NoError(err) + require.Equal(expected, result) + + // If the interval param is NULL, then NULL is returned f2, err := NewSubDate( expression.NewLiteral("2018-05-02", types.LongText), @@ -210,6 +373,10 @@ func TestSubDate(t *testing.T) { require.NoError(err) require.Nil(result) + f, err = NewSubDate( + expression.NewGetField(0, types.Int64, "foo", true), + expression.NewLiteral(int64(1), types.Int64)) + // If the date param is NULL, then NULL is returned result, err = f.Eval(ctx, sql.Row{nil}) require.NoError(err) diff --git a/sql/type.go b/sql/type.go index 1757ef4c99..c5ab25e50e 100644 --- a/sql/type.go +++ b/sql/type.go @@ -48,6 +48,10 @@ const ( // TimestampDatetimeLayout is the formatting string with the layout of the timestamp // using the format of Go "time" package. TimestampDatetimeLayout = "2006-01-02 15:04:05.999999" + + // DatetimeLayoutNoTrim is the formatting string with the layout of the datetime that + // doesn't trim trailing zeros + DatetimeLayoutNoTrim = "2006-01-02 15:04:05.000000" ) const ( From 93e17fc24c6af69b32f2898465307bbb4b5b861c Mon Sep 17 00:00:00 2001 From: jycor Date: Tue, 30 Jul 2024 20:20:31 +0000 Subject: [PATCH 3/4] [ga-format-pr] Run ./format_repo.sh to fix formatting --- sql/expression/function/date.go | 2 +- sql/expression/function/date_test.go | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/sql/expression/function/date.go b/sql/expression/function/date.go index e2aefedde0..7e087380ec 100644 --- a/sql/expression/function/date.go +++ b/sql/expression/function/date.go @@ -26,7 +26,7 @@ import ( "github.com/dolthub/go-mysql-server/sql/expression" "github.com/dolthub/go-mysql-server/sql/transform" "github.com/dolthub/go-mysql-server/sql/types" - ) +) // NewAddDate returns a new function expression, or an error if one couldn't be created. The ADDDATE // function is a synonym for DATE_ADD, with the one exception that if the second argument is NOT an diff --git a/sql/expression/function/date_test.go b/sql/expression/function/date_test.go index 5153cb5304..a9da761dd6 100644 --- a/sql/expression/function/date_test.go +++ b/sql/expression/function/date_test.go @@ -45,28 +45,28 @@ func TestAddDate(t *testing.T) { var f sql.Expression f, err = NewAddDate( - expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 123456000, time.UTC), types.Date), + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34, 56, 123456000, time.UTC), types.Date), expression.NewLiteral(int64(1), types.Int64)) require.NoError(err) - expected = time.Date(2018, 5, 3, 0, 0,0, 0, time.UTC) + expected = time.Date(2018, 5, 3, 0, 0, 0, 0, time.UTC) result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) f, err = NewAddDate( - expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 0, time.UTC), types.Datetime), + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34, 56, 0, time.UTC), types.Datetime), expression.NewLiteral(int64(1), types.Int64)) require.NoError(err) - expected = time.Date(2018, 5, 3, 12, 34,56, 0, time.UTC) + expected = time.Date(2018, 5, 3, 12, 34, 56, 0, time.UTC) result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) f, err = NewAddDate( - expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 123456000, time.UTC), types.DatetimeMaxPrecision), + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34, 56, 123456000, time.UTC), types.DatetimeMaxPrecision), expression.NewLiteral(int64(1), types.Int64)) require.NoError(err) - expected = time.Date(2018, 5, 3, 12, 34,56, 123456000, time.UTC) + expected = time.Date(2018, 5, 3, 12, 34, 56, 123456000, time.UTC) result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) @@ -153,7 +153,6 @@ func TestAddDate(t *testing.T) { require.NoError(err) require.Equal(expected, result) - // If the interval param is NULL, then NULL is returned f2, err := NewAddDate( expression.NewLiteral("2018-05-02", types.LongText), @@ -256,28 +255,28 @@ func TestSubDate(t *testing.T) { var f sql.Expression f, err = NewSubDate( - expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 123456000, time.UTC), types.Date), + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34, 56, 123456000, time.UTC), types.Date), expression.NewLiteral(int64(1), types.Int64)) require.NoError(err) - expected = time.Date(2018, 5, 1, 0, 0,0, 0, time.UTC) + expected = time.Date(2018, 5, 1, 0, 0, 0, 0, time.UTC) result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) f, err = NewSubDate( - expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 0, time.UTC), types.Datetime), + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34, 56, 0, time.UTC), types.Datetime), expression.NewLiteral(int64(1), types.Int64)) require.NoError(err) - expected = time.Date(2018, 5, 1, 12, 34,56, 0, time.UTC) + expected = time.Date(2018, 5, 1, 12, 34, 56, 0, time.UTC) result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) f, err = NewSubDate( - expression.NewLiteral(time.Date(2018, 5, 2, 12, 34,56, 123456000, time.UTC), types.DatetimeMaxPrecision), + expression.NewLiteral(time.Date(2018, 5, 2, 12, 34, 56, 123456000, time.UTC), types.DatetimeMaxPrecision), expression.NewLiteral(int64(1), types.Int64)) require.NoError(err) - expected = time.Date(2018, 5, 1, 12, 34,56, 123456000, time.UTC) + expected = time.Date(2018, 5, 1, 12, 34, 56, 123456000, time.UTC) result, err = f.Eval(ctx, sql.Row{}) require.NoError(err) require.Equal(expected, result) @@ -364,7 +363,6 @@ func TestSubDate(t *testing.T) { require.NoError(err) require.Equal(expected, result) - // If the interval param is NULL, then NULL is returned f2, err := NewSubDate( expression.NewLiteral("2018-05-02", types.LongText), From 0aaef665d4e12e40bd4dc453527363015cf73c51 Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 30 Jul 2024 15:08:23 -0700 Subject: [PATCH 4/4] fix tests --- sql/expression/function/date_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sql/expression/function/date_test.go b/sql/expression/function/date_test.go index a9da761dd6..5231ad9f12 100644 --- a/sql/expression/function/date_test.go +++ b/sql/expression/function/date_test.go @@ -218,7 +218,6 @@ func TestDateAdd(t *testing.T) { require.NoError(err) expected := "2018-05-03" - result, err := f.Eval(ctx, sql.Row{"2018-05-02"}) require.NoError(err) require.Equal(expected, result) @@ -414,8 +413,7 @@ func TestDateSub(t *testing.T) { ) require.NoError(err) - expected := time.Date(2018, time.May, 1, 0, 0, 0, 0, time.UTC) - + expected := "2018-05-01" result, err := f.Eval(ctx, sql.Row{"2018-05-02"}) require.NoError(err) require.Equal(expected, result)