From e456e48f0935cdd256391600a60e881510596afc Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 28 Jan 2024 02:28:05 +0530 Subject: [PATCH 1/8] eval: Implement FROM_DAYS Signed-off-by: Noble Mittal --- go/vt/vtgate/evalengine/cached_size.go | 12 +++++ go/vt/vtgate/evalengine/compiler_asm.go | 17 +++++++ go/vt/vtgate/evalengine/fn_time.go | 52 ++++++++++++++++++++ go/vt/vtgate/evalengine/testcases/cases.go | 20 ++++++++ go/vt/vtgate/evalengine/translate_builtin.go | 5 ++ 5 files changed, 106 insertions(+) diff --git a/go/vt/vtgate/evalengine/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go index 077fcf0c2da..42ab2daeb17 100644 --- a/go/vt/vtgate/evalengine/cached_size.go +++ b/go/vt/vtgate/evalengine/cached_size.go @@ -835,6 +835,18 @@ func (cached *builtinFromBase64) CachedSize(alloc bool) int64 { size += cached.CallExpr.CachedSize(false) return size } +func (cached *builtinFromDays) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(48) + } + // field CallExpr vitess.io/vitess/go/vt/vtgate/evalengine.CallExpr + size += cached.CallExpr.CachedSize(false) + return size +} func (cached *builtinFromUnixtime) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index 1a750ed7d0e..a162b4a3468 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -3795,6 +3795,23 @@ func (asm *assembler) Fn_LAST_DAY() { }, "FN LAST_DAY DATETIME(SP-1)") } +func (asm *assembler) Fn_FROM_DAYS() { + asm.emit(func(env *ExpressionEnv) int { + arg := env.vm.stack[env.vm.sp-1].(*evalInt64) + if arg == nil { + return 1 + } + + d := fromDays(env.currentTimezone(), arg.i) + if d == nil || d.IsZero() { + env.vm.stack[env.vm.sp-1] = nil + } else { + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(*d) + } + return 1 + }, "FN FROM_DAYS INT64(SP-1)") +} + func (asm *assembler) Fn_QUARTER() { asm.emit(func(env *ExpressionEnv) int { if env.vm.stack[env.vm.sp-1] == nil { diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index de1b7259aff..24697ce0dbe 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -111,6 +111,10 @@ type ( CallExpr } + builtinFromDays struct { + CallExpr + } + builtinQuarter struct { CallExpr } @@ -173,6 +177,7 @@ var _ IR = (*builtinMinute)(nil) var _ IR = (*builtinMonth)(nil) var _ IR = (*builtinMonthName)(nil) var _ IR = (*builtinLastDay)(nil) +var _ IR = (*builtinFromDays)(nil) var _ IR = (*builtinQuarter)(nil) var _ IR = (*builtinSecond)(nil) var _ IR = (*builtinTime)(nil) @@ -1249,6 +1254,53 @@ func (call *builtinLastDay) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil } +func fromDays(loc *time.Location, d int64) *datetime.Date { + // 3652424 days corresponds to maximum date i.e. 9999-12-31 + // mysql returns 0000-00-00 for days below 366 + if d > 3652424 || d < 366 { + return nil + } + + t := time.Date(1, time.January, 1, 0, 0, 0, 0, loc).AddDate(0, 0, int(d-366)) + dt := datetime.NewDateFromStd(t) + return &dt +} + +func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) { + d, err := b.arg1(env) + if err != nil { + return nil, err + } + if d == nil { + return nil, nil + } + days := evalToInt64(d).i + dt := fromDays(env.currentTimezone(), days) + + if dt == nil { + return nil, nil + } + return newEvalDate(*dt, env.sqlmode.AllowZeroDate()), nil +} + +func (call *builtinFromDays) compile(c *compiler) (ctype, error) { + arg, err := call.Arguments[0].compile(c) + if err != nil { + return ctype{}, err + } + + skip := c.compileNullCheck1(arg) + switch arg.Type { + case sqltypes.Int64: + default: + c.asm.Convert_xi(1) + } + + c.asm.Fn_FROM_DAYS() + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil +} + func (b *builtinQuarter) eval(env *ExpressionEnv) (eval, error) { date, err := b.arg1(env) if err != nil { diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 59ba6591a6d..47d097f345c 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -131,6 +131,7 @@ var Cases = []TestCase{ {Run: FnMonth}, {Run: FnMonthName}, {Run: FnLastDay}, + {Run: FnFromDays}, {Run: FnQuarter}, {Run: FnSecond}, {Run: FnTime}, @@ -1766,6 +1767,25 @@ func FnLastDay(yield Query) { } } +func FnFromDays(yield Query) { + for _, d := range inputConversions { + yield(fmt.Sprintf("FROM_DAYS(%s)", d), nil) + } + + days := []string{ + "0", + "1", + "366", + "365242", + "3652424", + "3652425", + } + + for _, d := range days { + yield(fmt.Sprintf("FROM_DAYS(%s)", d), nil) + } +} + func FnQuarter(yield Query) { for _, d := range inputConversions { yield(fmt.Sprintf("QUARTER(%s)", d), nil) diff --git a/go/vt/vtgate/evalengine/translate_builtin.go b/go/vt/vtgate/evalengine/translate_builtin.go index 1546a6e2610..250205a26d2 100644 --- a/go/vt/vtgate/evalengine/translate_builtin.go +++ b/go/vt/vtgate/evalengine/translate_builtin.go @@ -419,6 +419,11 @@ func (ast *astCompiler) translateFuncExpr(fn *sqlparser.FuncExpr) (IR, error) { return nil, argError(method) } return &builtinLastDay{CallExpr: call}, nil + case "from_days": + if len(args) != 1 { + return nil, argError(method) + } + return &builtinFromDays{CallExpr: call}, nil case "quarter": if len(args) != 1 { return nil, argError(method) From d8453079b59f2f179c0446a96f3caf09310f04d9 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 28 Jan 2024 04:36:23 +0530 Subject: [PATCH 2/8] Rename mysqlDateFromDayNumber to export Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 4 ++-- go/mysql/datetime/mydate.go | 8 ++++---- go/mysql/datetime/mydate_test.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index 67191e5c48e..1bd5d9ce39d 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -615,13 +615,13 @@ func (dt *DateTime) addInterval(itv *Interval) bool { return false } - dt.Date.year, dt.Date.month, dt.Date.day = mysqlDateFromDayNumber(daynum) + dt.Date.year, dt.Date.month, dt.Date.day = MysqlDateFromDayNumber(daynum) return true case itv.unit.HasDayParts(): daynum := mysqlDayNumber(dt.Date.Year(), dt.Date.Month(), dt.Date.Day()) daynum += itv.day - dt.Date.year, dt.Date.month, dt.Date.day = mysqlDateFromDayNumber(daynum) + dt.Date.year, dt.Date.month, dt.Date.day = MysqlDateFromDayNumber(daynum) return true case itv.unit.HasMonthParts(): diff --git a/go/mysql/datetime/mydate.go b/go/mysql/datetime/mydate.go index 1d4a2eaf958..c172ff7de6f 100644 --- a/go/mysql/datetime/mydate.go +++ b/go/mysql/datetime/mydate.go @@ -23,8 +23,8 @@ package datetime // day count that traditional datetime systems use (e.g. the daycount // algorithm in the Go standard library). It is often off by one, possibly // because of incorrect leap year handling, but the inverse of the algorithm -// in mysqlDateFromDayNumber takes this into account. Hence, the results -// of this function can ONLY be passed to mysqlDateFromDayNumber; using +// in MysqlDateFromDayNumber takes this into account. Hence, the results +// of this function can ONLY be passed to MysqlDateFromDayNumber; using // a day number with one of Go's datetime APIs will return incorrect results. // This API should only be used when performing datetime calculations (addition // and subtraction), so that the results match MySQL's. All other date handling @@ -46,7 +46,7 @@ func mysqlDayNumber(year, month, day int) int { return days + year/4 - leapAdjust } -// mysqlDateFromDayNumber converts an absolute day number into a date (a year, month, day triplet). +// MysqlDateFromDayNumber converts an absolute day number into a date (a year, month, day triplet). // This is an algorithm that has been reverse engineered from MySQL; // the tables used as a reference can be found in `testdata/daynr_to_date.json`. // See the warning from mysqlDayNumber: the day number used as an argument to @@ -54,7 +54,7 @@ func mysqlDayNumber(year, month, day int) int { // This API should only be used when performing datetime calculations (addition // and subtraction), so that the results match MySQL's. All other date handling // operations must use our helpers based on Go's standard library. -func mysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) { +func MysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) { if daynr <= 365 || daynr >= 3652500 { return 0, 0, 0 } diff --git a/go/mysql/datetime/mydate_test.go b/go/mysql/datetime/mydate_test.go index 29ecd2df9d2..d3662df9c59 100644 --- a/go/mysql/datetime/mydate_test.go +++ b/go/mysql/datetime/mydate_test.go @@ -49,7 +49,7 @@ func TestDayNumberFields(t *testing.T) { require.NoError(t, err) for _, tc := range expected { - y, m, d := mysqlDateFromDayNumber(tc[0]) + y, m, d := MysqlDateFromDayNumber(tc[0]) assert.Equal(t, tc[1], int(y)) assert.Equal(t, tc[2], int(m)) assert.Equal(t, tc[3], int(d)) From eb184fee01e982945cf524ffe8efeb882e0a6ca9 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 28 Jan 2024 04:38:53 +0530 Subject: [PATCH 3/8] Make FROM_DAYS return zero date Signed-off-by: Noble Mittal --- go/vt/vtgate/evalengine/compiler_asm.go | 11 ++++++-- go/vt/vtgate/evalengine/fn_time.go | 33 +++++++++------------- go/vt/vtgate/evalengine/testcases/cases.go | 1 + 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index a162b4a3468..c14bed15f33 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -37,6 +37,7 @@ import ( "vitess.io/vitess/go/mysql/collations/charset/types" "vitess.io/vitess/go/mysql/collations/colldata" + mysqldt "vitess.io/vitess/go/mysql/datetime" "vitess.io/vitess/go/hack" "vitess.io/vitess/go/mysql/collations" @@ -3799,14 +3800,18 @@ func (asm *assembler) Fn_FROM_DAYS() { asm.emit(func(env *ExpressionEnv) int { arg := env.vm.stack[env.vm.sp-1].(*evalInt64) if arg == nil { + env.vm.stack[env.vm.sp-1] = nil return 1 } - d := fromDays(env.currentTimezone(), arg.i) - if d == nil || d.IsZero() { + y, m, d := mysqldt.MysqlDateFromDayNumber(int(arg.i)) + if y == 0 && m == 0 && d == 0 { + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(datetime.Date{}) + } else if y > 9999 { env.vm.stack[env.vm.sp-1] = nil } else { - env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(*d) + dt := datetime.NewDateFromStd(time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, env.currentTimezone())) + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(dt) } return 1 }, "FN FROM_DAYS INT64(SP-1)") diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index 24697ce0dbe..6eb8db8c721 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -23,6 +23,7 @@ import ( "vitess.io/vitess/go/hack" "vitess.io/vitess/go/mysql/collations" "vitess.io/vitess/go/mysql/datetime" + mysqldt "vitess.io/vitess/go/mysql/datetime" "vitess.io/vitess/go/mysql/decimal" "vitess.io/vitess/go/sqltypes" ) @@ -1254,33 +1255,27 @@ func (call *builtinLastDay) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil } -func fromDays(loc *time.Location, d int64) *datetime.Date { - // 3652424 days corresponds to maximum date i.e. 9999-12-31 - // mysql returns 0000-00-00 for days below 366 - if d > 3652424 || d < 366 { - return nil +func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) { + arg, err := b.arg1(env) + if arg == nil || err != nil { + return nil, nil } - t := time.Date(1, time.January, 1, 0, 0, 0, 0, loc).AddDate(0, 0, int(d-366)) - dt := datetime.NewDateFromStd(t) - return &dt -} + days := evalToInt64(arg).i + y, m, d := mysqldt.MysqlDateFromDayNumber(int(days)) -func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) { - d, err := b.arg1(env) - if err != nil { - return nil, err - } - if d == nil { + // mysql returns 0000-00-00 for days below 366 and above 3652499 + if y == 0 && m == 0 && d == 0 { return nil, nil } - days := evalToInt64(d).i - dt := fromDays(env.currentTimezone(), days) - if dt == nil { + // mysql returns NULL if y is greater than 9999 + if y > 9999 { return nil, nil } - return newEvalDate(*dt, env.sqlmode.AllowZeroDate()), nil + + dt := datetime.NewDateFromStd(time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, env.currentTimezone())) + return newEvalDate(dt, true), nil } func (call *builtinFromDays) compile(c *compiler) (ctype, error) { diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 47d097f345c..8da716a219f 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -1779,6 +1779,7 @@ func FnFromDays(yield Query) { "365242", "3652424", "3652425", + "730669", } for _, d := range days { From c4eba9d6aa39d9e8ddd031bff32cc92d3a4b00db Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 28 Jan 2024 04:40:44 +0530 Subject: [PATCH 4/8] Remove unnecessary check from Fn_FROM_DAYS Signed-off-by: Noble Mittal --- go/vt/vtgate/evalengine/compiler_asm.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index c14bed15f33..b0dd2adf16c 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -3799,11 +3799,6 @@ func (asm *assembler) Fn_LAST_DAY() { func (asm *assembler) Fn_FROM_DAYS() { asm.emit(func(env *ExpressionEnv) int { arg := env.vm.stack[env.vm.sp-1].(*evalInt64) - if arg == nil { - env.vm.stack[env.vm.sp-1] = nil - return 1 - } - y, m, d := mysqldt.MysqlDateFromDayNumber(int(arg.i)) if y == 0 && m == 0 && d == 0 { env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(datetime.Date{}) From 88d7b765a02fda789878bf0724408ef1d4441cc3 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 28 Jan 2024 04:53:49 +0530 Subject: [PATCH 5/8] Add more edge cases for FROM_DAYS test Signed-off-by: Noble Mittal --- go/vt/vtgate/evalengine/testcases/cases.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 8da716a219f..682d6134c7e 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -1779,6 +1779,8 @@ func FnFromDays(yield Query) { "365242", "3652424", "3652425", + "3652500", + "3652499", "730669", } From 871191f198e742901d2a00d2ea5188dccd5f3b12 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 28 Jan 2024 13:34:57 +0530 Subject: [PATCH 6/8] Remove double export and return zerodate for nil arg Signed-off-by: Noble Mittal --- go/vt/vtgate/evalengine/compiler_asm.go | 3 +-- go/vt/vtgate/evalengine/fn_time.go | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index b0dd2adf16c..8b44cd69f1c 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -37,7 +37,6 @@ import ( "vitess.io/vitess/go/mysql/collations/charset/types" "vitess.io/vitess/go/mysql/collations/colldata" - mysqldt "vitess.io/vitess/go/mysql/datetime" "vitess.io/vitess/go/hack" "vitess.io/vitess/go/mysql/collations" @@ -3799,7 +3798,7 @@ func (asm *assembler) Fn_LAST_DAY() { func (asm *assembler) Fn_FROM_DAYS() { asm.emit(func(env *ExpressionEnv) int { arg := env.vm.stack[env.vm.sp-1].(*evalInt64) - y, m, d := mysqldt.MysqlDateFromDayNumber(int(arg.i)) + y, m, d := datetime.MysqlDateFromDayNumber(int(arg.i)) if y == 0 && m == 0 && d == 0 { env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(datetime.Date{}) } else if y > 9999 { diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index 6eb8db8c721..2c615002684 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -23,7 +23,6 @@ import ( "vitess.io/vitess/go/hack" "vitess.io/vitess/go/mysql/collations" "vitess.io/vitess/go/mysql/datetime" - mysqldt "vitess.io/vitess/go/mysql/datetime" "vitess.io/vitess/go/mysql/decimal" "vitess.io/vitess/go/sqltypes" ) @@ -1257,16 +1256,19 @@ func (call *builtinLastDay) compile(c *compiler) (ctype, error) { func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) { arg, err := b.arg1(env) - if arg == nil || err != nil { + if arg == nil { return nil, nil } + if err != nil { + return nil, err + } days := evalToInt64(arg).i - y, m, d := mysqldt.MysqlDateFromDayNumber(int(days)) + y, m, d := datetime.MysqlDateFromDayNumber(int(days)) // mysql returns 0000-00-00 for days below 366 and above 3652499 if y == 0 && m == 0 && d == 0 { - return nil, nil + return newEvalDate(datetime.Date{}, true), nil } // mysql returns NULL if y is greater than 9999 From 9a73127b7877135def929baeab321d601af92bce Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 28 Jan 2024 14:31:06 +0530 Subject: [PATCH 7/8] Add DateFromDayNumber func to return Date from day number Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 4 ++-- go/mysql/datetime/mydate.go | 16 ++++++++++++---- go/mysql/datetime/mydate_test.go | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index 1bd5d9ce39d..67191e5c48e 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -615,13 +615,13 @@ func (dt *DateTime) addInterval(itv *Interval) bool { return false } - dt.Date.year, dt.Date.month, dt.Date.day = MysqlDateFromDayNumber(daynum) + dt.Date.year, dt.Date.month, dt.Date.day = mysqlDateFromDayNumber(daynum) return true case itv.unit.HasDayParts(): daynum := mysqlDayNumber(dt.Date.Year(), dt.Date.Month(), dt.Date.Day()) daynum += itv.day - dt.Date.year, dt.Date.month, dt.Date.day = MysqlDateFromDayNumber(daynum) + dt.Date.year, dt.Date.month, dt.Date.day = mysqlDateFromDayNumber(daynum) return true case itv.unit.HasMonthParts(): diff --git a/go/mysql/datetime/mydate.go b/go/mysql/datetime/mydate.go index c172ff7de6f..5a566fc36c1 100644 --- a/go/mysql/datetime/mydate.go +++ b/go/mysql/datetime/mydate.go @@ -23,8 +23,8 @@ package datetime // day count that traditional datetime systems use (e.g. the daycount // algorithm in the Go standard library). It is often off by one, possibly // because of incorrect leap year handling, but the inverse of the algorithm -// in MysqlDateFromDayNumber takes this into account. Hence, the results -// of this function can ONLY be passed to MysqlDateFromDayNumber; using +// in mysqlDateFromDayNumber takes this into account. Hence, the results +// of this function can ONLY be passed to mysqlDateFromDayNumber; using // a day number with one of Go's datetime APIs will return incorrect results. // This API should only be used when performing datetime calculations (addition // and subtraction), so that the results match MySQL's. All other date handling @@ -46,7 +46,7 @@ func mysqlDayNumber(year, month, day int) int { return days + year/4 - leapAdjust } -// MysqlDateFromDayNumber converts an absolute day number into a date (a year, month, day triplet). +// mysqlDateFromDayNumber converts an absolute day number into a date (a year, month, day triplet). // This is an algorithm that has been reverse engineered from MySQL; // the tables used as a reference can be found in `testdata/daynr_to_date.json`. // See the warning from mysqlDayNumber: the day number used as an argument to @@ -54,7 +54,7 @@ func mysqlDayNumber(year, month, day int) int { // This API should only be used when performing datetime calculations (addition // and subtraction), so that the results match MySQL's. All other date handling // operations must use our helpers based on Go's standard library. -func MysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) { +func mysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) { if daynr <= 365 || daynr >= 3652500 { return 0, 0, 0 } @@ -81,3 +81,11 @@ func MysqlDateFromDayNumber(daynr int) (uint16, uint8, uint8) { panic("unreachable: yday is too large?") } + +// DateFromDayNumber converts an absolute day number into a Date. +// Returns zero date if day number exceeds 3652499 or is less than 366. +func DateFromDayNumber(daynr int) Date { + var d Date + d.year, d.month, d.day = mysqlDateFromDayNumber(daynr) + return d +} diff --git a/go/mysql/datetime/mydate_test.go b/go/mysql/datetime/mydate_test.go index d3662df9c59..29ecd2df9d2 100644 --- a/go/mysql/datetime/mydate_test.go +++ b/go/mysql/datetime/mydate_test.go @@ -49,7 +49,7 @@ func TestDayNumberFields(t *testing.T) { require.NoError(t, err) for _, tc := range expected { - y, m, d := MysqlDateFromDayNumber(tc[0]) + y, m, d := mysqlDateFromDayNumber(tc[0]) assert.Equal(t, tc[1], int(y)) assert.Equal(t, tc[2], int(m)) assert.Equal(t, tc[3], int(d)) From e44193af322d12543036ff8f3101259f991dd690 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 28 Jan 2024 14:32:53 +0530 Subject: [PATCH 8/8] Make use of DateFromDayNumber in FROM_DAYS func Signed-off-by: Noble Mittal --- go/vt/vtgate/evalengine/compiler_asm.go | 12 +++++------- go/vt/vtgate/evalengine/fn_time.go | 16 ++++------------ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index 8b44cd69f1c..91c9915186c 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -3798,15 +3798,13 @@ func (asm *assembler) Fn_LAST_DAY() { func (asm *assembler) Fn_FROM_DAYS() { asm.emit(func(env *ExpressionEnv) int { arg := env.vm.stack[env.vm.sp-1].(*evalInt64) - y, m, d := datetime.MysqlDateFromDayNumber(int(arg.i)) - if y == 0 && m == 0 && d == 0 { - env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(datetime.Date{}) - } else if y > 9999 { + d := datetime.DateFromDayNumber(int(arg.i)) + if d.Year() > 9999 { env.vm.stack[env.vm.sp-1] = nil - } else { - dt := datetime.NewDateFromStd(time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, env.currentTimezone())) - env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(dt) + return 1 } + + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalDate(d) return 1 }, "FN FROM_DAYS INT64(SP-1)") } diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index 2c615002684..bf4bfb71934 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -1263,21 +1263,13 @@ func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) { return nil, err } - days := evalToInt64(arg).i - y, m, d := datetime.MysqlDateFromDayNumber(int(days)) + d := datetime.DateFromDayNumber(int(evalToInt64(arg).i)) - // mysql returns 0000-00-00 for days below 366 and above 3652499 - if y == 0 && m == 0 && d == 0 { - return newEvalDate(datetime.Date{}, true), nil - } - - // mysql returns NULL if y is greater than 9999 - if y > 9999 { + // mysql returns NULL if year is greater than 9999 + if d.Year() > 9999 { return nil, nil } - - dt := datetime.NewDateFromStd(time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, env.currentTimezone())) - return newEvalDate(dt, true), nil + return newEvalDate(d, true), nil } func (call *builtinFromDays) compile(c *compiler) (ctype, error) {