From 6ac159679671fbe88b6981573933bb9b1809defc Mon Sep 17 00:00:00 2001 From: Noble Mittal <62551163+beingnoble03@users.noreply.github.com> Date: Mon, 29 Jan 2024 22:28:09 +0530 Subject: [PATCH] evalengine: Implement TO_DAYS (#15065) Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 4 +- go/mysql/datetime/mydate.go | 8 ++-- go/mysql/datetime/mydate_test.go | 4 +- go/vt/vtgate/evalengine/cached_size.go | 12 ++++++ go/vt/vtgate/evalengine/compiler_asm.go | 16 ++++++++ go/vt/vtgate/evalengine/fn_time.go | 41 ++++++++++++++++++++ go/vt/vtgate/evalengine/testcases/cases.go | 25 ++++++++++++ go/vt/vtgate/evalengine/translate_builtin.go | 5 +++ 8 files changed, 107 insertions(+), 8 deletions(-) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index 67191e5c48e..0ca96787962 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -610,7 +610,7 @@ func (dt *DateTime) addInterval(itv *Interval) bool { dt.Time.minute = uint8((dur % time.Hour) / time.Minute) dt.Time.hour = uint16(dur / time.Hour) - daynum := mysqlDayNumber(dt.Date.Year(), dt.Date.Month(), 1) + int(days) + daynum := MysqlDayNumber(dt.Date.Year(), dt.Date.Month(), 1) + int(days) if daynum < 0 || daynum > maxDay { return false } @@ -619,7 +619,7 @@ func (dt *DateTime) addInterval(itv *Interval) bool { return true case itv.unit.HasDayParts(): - daynum := mysqlDayNumber(dt.Date.Year(), dt.Date.Month(), dt.Date.Day()) + 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) return true diff --git a/go/mysql/datetime/mydate.go b/go/mysql/datetime/mydate.go index 5a566fc36c1..62cbb3f2524 100644 --- a/go/mysql/datetime/mydate.go +++ b/go/mysql/datetime/mydate.go @@ -16,7 +16,7 @@ limitations under the License. package datetime -// mysqlDayNumber converts a date into an absolute day number. +// MysqlDayNumber converts a date into an absolute day number. // This is an algorithm that has been reverse engineered from MySQL; // the tables used as a reference can be found in `testdata/year_to_daynr.json`. // It is worth noting that this absolute day number does not match the @@ -29,7 +29,7 @@ package datetime // 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 mysqlDayNumber(year, month, day int) int { +func MysqlDayNumber(year, month, day int) int { if year == 0 && month == 0 { return 0 } @@ -49,8 +49,8 @@ func mysqlDayNumber(year, month, day int) int { // 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 -// this function must come from mysqlDayNumber or the results won't be correct. +// See the warning from MysqlDayNumber: the day number used as an argument to +// this function must come from MysqlDayNumber or the results won't be correct. // 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. diff --git a/go/mysql/datetime/mydate_test.go b/go/mysql/datetime/mydate_test.go index 29ecd2df9d2..ba575ed4e05 100644 --- a/go/mysql/datetime/mydate_test.go +++ b/go/mysql/datetime/mydate_test.go @@ -35,7 +35,7 @@ func TestDayNumber(t *testing.T) { require.NoError(t, err) for year, daynr := range expected { - assert.Equal(t, daynr, mysqlDayNumber(year, 1, 1)) + assert.Equal(t, daynr, MysqlDayNumber(year, 1, 1)) } } @@ -54,6 +54,6 @@ func TestDayNumberFields(t *testing.T) { assert.Equal(t, tc[2], int(m)) assert.Equal(t, tc[3], int(d)) - assert.Equalf(t, tc[0], mysqlDayNumber(tc[1], tc[2], tc[3]), "date %d-%d-%d", tc[1], tc[2], tc[3]) + assert.Equalf(t, tc[0], MysqlDayNumber(tc[1], tc[2], tc[3]), "date %d-%d-%d", tc[1], tc[2], tc[3]) } } diff --git a/go/vt/vtgate/evalengine/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go index 42ab2daeb17..8c22ff4ecd9 100644 --- a/go/vt/vtgate/evalengine/cached_size.go +++ b/go/vt/vtgate/evalengine/cached_size.go @@ -1579,6 +1579,18 @@ func (cached *builtinToBase64) CachedSize(alloc bool) int64 { size += cached.CallExpr.CachedSize(false) return size } +func (cached *builtinToDays) 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 *builtinTrim) 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 91c9915186c..653c0fa3bf4 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -3795,6 +3795,22 @@ func (asm *assembler) Fn_LAST_DAY() { }, "FN LAST_DAY DATETIME(SP-1)") } +func (asm *assembler) Fn_TO_DAYS() { + asm.emit(func(env *ExpressionEnv) int { + if env.vm.stack[env.vm.sp-1] == nil { + return 1 + } + arg := env.vm.stack[env.vm.sp-1].(*evalTemporal) + if arg.dt.Date.IsZero() { + env.vm.stack[env.vm.sp-1] = nil + } else { + numDays := datetime.MysqlDayNumber(arg.dt.Date.Year(), arg.dt.Date.Month(), arg.dt.Date.Day()) + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalInt64(int64(numDays)) + } + return 1 + }, "FN TO_DAYS DATE(SP-1)") +} + func (asm *assembler) Fn_FROM_DAYS() { asm.emit(func(env *ExpressionEnv) int { arg := env.vm.stack[env.vm.sp-1].(*evalInt64) diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index bf4bfb71934..538dc59138c 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -111,6 +111,10 @@ type ( CallExpr } + builtinToDays struct { + CallExpr + } + builtinFromDays struct { CallExpr } @@ -177,6 +181,7 @@ var _ IR = (*builtinMinute)(nil) var _ IR = (*builtinMonth)(nil) var _ IR = (*builtinMonthName)(nil) var _ IR = (*builtinLastDay)(nil) +var _ IR = (*builtinToDays)(nil) var _ IR = (*builtinFromDays)(nil) var _ IR = (*builtinQuarter)(nil) var _ IR = (*builtinSecond)(nil) @@ -1254,6 +1259,41 @@ func (call *builtinLastDay) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil } +func (b *builtinToDays) eval(env *ExpressionEnv) (eval, error) { + date, err := b.arg1(env) + if err != nil { + return nil, err + } + if date == nil { + return nil, nil + } + dt := evalToDate(date, env.now, env.sqlmode.AllowZeroDate()) + if dt == nil || dt.isZero() { + return nil, nil + } + + numDays := datetime.MysqlDayNumber(dt.dt.Date.Year(), dt.dt.Date.Month(), dt.dt.Date.Day()) + return newEvalInt64(int64(numDays)), nil +} + +func (call *builtinToDays) 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.Date, sqltypes.Datetime: + default: + c.asm.Convert_xD(1, true) + } + c.asm.Fn_TO_DAYS() + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.Int64, Col: collationNumeric, Flag: arg.Flag | flagNullable}, nil +} + func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) { arg, err := b.arg1(env) if arg == nil { @@ -1279,6 +1319,7 @@ func (call *builtinFromDays) compile(c *compiler) (ctype, error) { } skip := c.compileNullCheck1(arg) + switch arg.Type { case sqltypes.Int64: default: diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 51d95722e46..f9036c1afca 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: FnToDays}, {Run: FnFromDays}, {Run: FnQuarter}, {Run: FnSecond}, @@ -1769,6 +1770,30 @@ func FnLastDay(yield Query) { } } +func FnToDays(yield Query) { + for _, d := range inputConversions { + yield(fmt.Sprintf("TO_DAYS(%s)", d), nil) + } + + dates := []string{ + `DATE'0000-00-00'`, + `0`, + `'0000-00-00'`, + `DATE'2023-09-03 00:00:00'`, + `DATE'2023-09-03 07:00:00'`, + `DATE'0000-00-00 00:00:00'`, + `950501`, + `'2007-10-07'`, + `'2008-10-07'`, + `'08-10-07'`, + `'0000-01-01'`, + } + + for _, d := range dates { + yield(fmt.Sprintf("TO_DAYS(%s)", d), nil) + } +} + func FnFromDays(yield Query) { for _, d := range inputConversions { yield(fmt.Sprintf("FROM_DAYS(%s)", d), nil) diff --git a/go/vt/vtgate/evalengine/translate_builtin.go b/go/vt/vtgate/evalengine/translate_builtin.go index 250205a26d2..b44c17f6f9b 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 "to_days": + if len(args) != 1 { + return nil, argError(method) + } + return &builtinToDays{CallExpr: call}, nil case "from_days": if len(args) != 1 { return nil, argError(method)