From 4c23faaca7f590e025650097a8d548fc1b3972ec Mon Sep 17 00:00:00 2001 From: Noble Mittal <62551163+beingnoble03@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:05:56 +0530 Subject: [PATCH] evalengine: Implement FROM_DAYS (#15058) Signed-off-by: Noble Mittal --- go/mysql/datetime/mydate.go | 8 ++++ go/vt/vtgate/evalengine/cached_size.go | 12 ++++++ go/vt/vtgate/evalengine/compiler_asm.go | 14 +++++++ go/vt/vtgate/evalengine/fn_time.go | 41 ++++++++++++++++++++ go/vt/vtgate/evalengine/testcases/cases.go | 23 +++++++++++ go/vt/vtgate/evalengine/translate_builtin.go | 5 +++ 6 files changed, 103 insertions(+) diff --git a/go/mysql/datetime/mydate.go b/go/mysql/datetime/mydate.go index 1d4a2eaf958..5a566fc36c1 100644 --- a/go/mysql/datetime/mydate.go +++ b/go/mysql/datetime/mydate.go @@ -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/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..91c9915186c 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -3795,6 +3795,20 @@ 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) + d := datetime.DateFromDayNumber(int(arg.i)) + if d.Year() > 9999 { + env.vm.stack[env.vm.sp-1] = nil + return 1 + } + + 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..bf4bfb71934 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,42 @@ func (call *builtinLastDay) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil } +func (b *builtinFromDays) eval(env *ExpressionEnv) (eval, error) { + arg, err := b.arg1(env) + if arg == nil { + return nil, nil + } + if err != nil { + return nil, err + } + + d := datetime.DateFromDayNumber(int(evalToInt64(arg).i)) + + // mysql returns NULL if year is greater than 9999 + if d.Year() > 9999 { + return nil, nil + } + return newEvalDate(d, true), 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..682d6134c7e 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,28 @@ 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", + "3652500", + "3652499", + "730669", + } + + 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)