From 99a072ddfadf2dd3a3b91ae5aea0d0352a36a90b Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sun, 31 Mar 2024 19:41:14 +0530 Subject: [PATCH 1/6] evalengine: Add initial implementation for SEC_TO_TIME Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 30 +++++++++++++ go/vt/vtgate/evalengine/compiler_asm.go | 16 +++++++ go/vt/vtgate/evalengine/fn_time.go | 45 ++++++++++++++++++++ go/vt/vtgate/evalengine/testcases/cases.go | 37 ++++++++++++++++ go/vt/vtgate/evalengine/translate_builtin.go | 5 +++ 5 files changed, 133 insertions(+) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index 1673a71d662..da8fe9a3bdc 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -714,6 +714,36 @@ func NewTimeFromStd(t time.Time) Time { } } +func NewTimeFromSeconds(seconds float64) Time { + var neg bool + if seconds < 0 { + neg = true + seconds = -seconds + } + + s := int64(seconds) + nsec := int(1e9 * (seconds - float64(s))) + + h, s := s/3600, s%3600 + m, s := s/60, s%60 + + if h >= 839 { + h, m, s, nsec = 838, 59, 59, 0 + } + + hour := uint16(h) + if neg { + hour |= negMask + } + + return Time{ + hour: hour, + minute: uint8(m), + second: uint8(s), + nanosecond: uint32(nsec), + } +} + func NewDateTimeFromStd(t time.Time) DateTime { return DateTime{ Date: NewDateFromStd(t), diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index 5eeb9a6300d..8e103a0fcd4 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -3914,6 +3914,22 @@ func (asm *assembler) Fn_FROM_DAYS() { }, "FN FROM_DAYS INT64(SP-1)") } +func (asm *assembler) Fn_SEC_TO_TIME(tt sqltypes.Type) { + asm.emit(func(env *ExpressionEnv) int { + e := env.vm.stack[env.vm.sp-1].(*evalDecimal) + prec := min(evalDecimalPrecision(e), datetime.DefaultPrecision) + sec, _ := e.toFloat0() + // if tt == sqltypes.TypeJSON { + // env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(sec), datetime.DefaultPrecision) + // } else if sqltypes.IsDateOrTime(tt) { + // env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(sec), int(prec)) + // } else { + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(sec), int(prec)) + // } + return 1 + }, "FN SEC_TO_TIME DECIMAL(SP-1)") +} + func (asm *assembler) Fn_TIME_TO_SEC() { 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 57edf29e2a4..307c5009d17 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -17,6 +17,7 @@ limitations under the License. package evalengine import ( + "fmt" "math" "time" @@ -119,6 +120,10 @@ type ( CallExpr } + builtinSecToTime struct { + CallExpr + } + builtinTimeToSec struct { CallExpr } @@ -187,6 +192,7 @@ var _ IR = (*builtinMonthName)(nil) var _ IR = (*builtinLastDay)(nil) var _ IR = (*builtinToDays)(nil) var _ IR = (*builtinFromDays)(nil) +var _ IR = (*builtinSecToTime)(nil) var _ IR = (*builtinTimeToSec)(nil) var _ IR = (*builtinQuarter)(nil) var _ IR = (*builtinSecond)(nil) @@ -1336,6 +1342,45 @@ func (call *builtinFromDays) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Date, Flag: arg.Flag | flagNullable}, nil } +func (b *builtinSecToTime) eval(env *ExpressionEnv) (eval, error) { + arg, err := b.arg1(env) + if arg == nil { + return nil, nil + } + if err != nil { + return nil, err + } + + e := evalToDecimal(arg, 0, 0) + prec := min(evalDecimalPrecision(e), datetime.DefaultPrecision) + sec, _ := e.toFloat0() + return newEvalTime(datetime.NewTimeFromSeconds(sec), int(prec)), nil +} + +func (call *builtinSecToTime) compile(c *compiler) (ctype, error) { + arg, err := call.Arguments[0].compile(c) + if err != nil { + return ctype{}, err + } + + skip := c.compileNullCheck1(arg) + + switch { + case sqltypes.IsIntegral(arg.Type): + c.asm.Convert_xd(1, 0, 0) + case sqltypes.IsDateOrTime(arg.Type): + fmt.Println("time is this") + c.asm.Convert_xd(1, 0, 0) + case arg.Type == sqltypes.Decimal: + default: + c.asm.Convert_xd(1, 0, datetime.DefaultPrecision) + } + + c.asm.Fn_SEC_TO_TIME(arg.Type) + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.Time, Flag: arg.Flag}, nil +} + func (b *builtinTimeToSec) eval(env *ExpressionEnv) (eval, error) { arg, err := b.arg1(env) if arg == nil { diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index b55f6c2c18d..d2c49c6a75d 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -141,6 +141,7 @@ var Cases = []TestCase{ {Run: FnLastDay}, {Run: FnToDays}, {Run: FnFromDays}, + {Run: FnSecToTime}, {Run: FnTimeToSec}, {Run: FnQuarter}, {Run: FnSecond}, @@ -1973,6 +1974,42 @@ func FnFromDays(yield Query) { } } +func FnSecToTime(yield Query) { + // for _, s := range inputConversions { + // yield(fmt.Sprintf("SEC_TO_TIME(%s)", s), nil) + // } + + seconds := []string{ + "0", + "0.99", + "1", + "-1.45632", + "366", + "365242", + "3652424", + "3652425", + "3652500", + "3652499", + "'3652499'", + "730669", + "730669.213", + "730669.99999999999", + "730669.213123321213", + "730669.213123", + "730669.213123", + "'12.12'", + "'730669.213123'", + "time '130:34:58.6'", + "time '-31:34:58'", + "cast(0b001 as json)", + "cast(0xFF666F6F626172FF as json)", + } + + for _, s := range seconds { + yield(fmt.Sprintf("SEC_TO_TIME(%s)", s), nil) + } +} + func FnTimeToSec(yield Query) { for _, d := range inputConversions { yield(fmt.Sprintf("TIME_TO_SEC(%s)", d), nil) diff --git a/go/vt/vtgate/evalengine/translate_builtin.go b/go/vt/vtgate/evalengine/translate_builtin.go index 97900c98e57..1b0e8e8694c 100644 --- a/go/vt/vtgate/evalengine/translate_builtin.go +++ b/go/vt/vtgate/evalengine/translate_builtin.go @@ -452,6 +452,11 @@ func (ast *astCompiler) translateFuncExpr(fn *sqlparser.FuncExpr) (IR, error) { return nil, argError(method) } return &builtinFromDays{CallExpr: call}, nil + case "sec_to_time": + if len(args) != 1 { + return nil, argError(method) + } + return &builtinSecToTime{CallExpr: call}, nil case "time_to_sec": if len(args) != 1 { return nil, argError(method) From beb06f23cc1c55c310520109b7b6efff6d8962d0 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Mon, 15 Apr 2024 02:53:36 +0530 Subject: [PATCH 2/6] evalengine:Handle time and decimal inputs for SEC_TO_TIME Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 10 +++--- go/vt/vtgate/evalengine/cached_size.go | 12 +++++++ go/vt/vtgate/evalengine/compiler_asm.go | 17 +++++---- go/vt/vtgate/evalengine/fn_time.go | 34 +++++++++++++++--- go/vt/vtgate/evalengine/testcases/cases.go | 41 ++++++---------------- 5 files changed, 68 insertions(+), 46 deletions(-) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index da8fe9a3bdc..3abbb775108 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -18,6 +18,7 @@ package datetime import ( "encoding/binary" + "math" "time" "vitess.io/vitess/go/mysql/decimal" @@ -721,14 +722,15 @@ func NewTimeFromSeconds(seconds float64) Time { seconds = -seconds } - s := int64(seconds) - nsec := int(1e9 * (seconds - float64(s))) + sec, nsec := math.Modf(seconds) + s := int(sec) + ns := nsec * (1e9) h, s := s/3600, s%3600 m, s := s/60, s%60 if h >= 839 { - h, m, s, nsec = 838, 59, 59, 0 + h, m, s, ns = 838, 59, 59, 0 } hour := uint16(h) @@ -740,7 +742,7 @@ func NewTimeFromSeconds(seconds float64) Time { hour: hour, minute: uint8(m), second: uint8(s), - nanosecond: uint32(nsec), + nanosecond: uint32(ns), } } diff --git a/go/vt/vtgate/evalengine/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go index 72bfbafed73..e4cbafbbb25 100644 --- a/go/vt/vtgate/evalengine/cached_size.go +++ b/go/vt/vtgate/evalengine/cached_size.go @@ -1523,6 +1523,18 @@ func (cached *builtinSHA2) CachedSize(alloc bool) int64 { size += cached.CallExpr.CachedSize(false) return size } +func (cached *builtinSecToTime) 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 *builtinSecond) 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 8e103a0fcd4..fc0b14d8a69 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -31,6 +31,7 @@ import ( "math/bits" "net/netip" "strconv" + "strings" "time" "github.com/google/uuid" @@ -3918,14 +3919,18 @@ func (asm *assembler) Fn_SEC_TO_TIME(tt sqltypes.Type) { asm.emit(func(env *ExpressionEnv) int { e := env.vm.stack[env.vm.sp-1].(*evalDecimal) prec := min(evalDecimalPrecision(e), datetime.DefaultPrecision) + + if sqltypes.IsDateOrTime(tt) { + parts := strings.Split(e.dec.String(), ".") + if len(parts) == 1 { + prec = 0 + } else { + prec = min(int32(len(parts[1])), datetime.DefaultPrecision) + } + } + sec, _ := e.toFloat0() - // if tt == sqltypes.TypeJSON { - // env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(sec), datetime.DefaultPrecision) - // } else if sqltypes.IsDateOrTime(tt) { - // env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(sec), int(prec)) - // } else { env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(sec), int(prec)) - // } return 1 }, "FN SEC_TO_TIME DECIMAL(SP-1)") } diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index 307c5009d17..0bf74f5f685 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -17,8 +17,8 @@ limitations under the License. package evalengine import ( - "fmt" "math" + "strings" "time" "vitess.io/vitess/go/hack" @@ -1351,9 +1351,34 @@ func (b *builtinSecToTime) eval(env *ExpressionEnv) (eval, error) { return nil, err } - e := evalToDecimal(arg, 0, 0) + var e *evalDecimal + switch { + case arg.SQLType() == sqltypes.Decimal: + e = arg.(*evalDecimal) + case sqltypes.IsIntegral(arg.SQLType()): + e = evalToDecimal(arg, 0, 0) + case sqltypes.IsTextOrBinary(arg.SQLType()): + b := arg.(*evalBytes) + if b.isHexOrBitLiteral() { + e = evalToDecimal(arg, 0, 0) + } else { + e = evalToDecimal(arg, 0, datetime.DefaultPrecision) + } + default: + e = evalToDecimal(arg, 0, datetime.DefaultPrecision) + } + prec := min(evalDecimalPrecision(e), datetime.DefaultPrecision) sec, _ := e.toFloat0() + + if sqltypes.IsDateOrTime(arg.SQLType()) { + parts := strings.Split(e.dec.String(), ".") + if len(parts) == 1 { + prec = 0 + } else { + prec = min(int32(len(parts[1])), datetime.DefaultPrecision) + } + } return newEvalTime(datetime.NewTimeFromSeconds(sec), int(prec)), nil } @@ -1366,12 +1391,11 @@ func (call *builtinSecToTime) compile(c *compiler) (ctype, error) { skip := c.compileNullCheck1(arg) switch { + case arg.Type == sqltypes.Decimal: case sqltypes.IsIntegral(arg.Type): c.asm.Convert_xd(1, 0, 0) - case sqltypes.IsDateOrTime(arg.Type): - fmt.Println("time is this") + case sqltypes.IsTextOrBinary(arg.Type) && arg.isHexOrBitLiteral(): c.asm.Convert_xd(1, 0, 0) - case arg.Type == sqltypes.Decimal: default: c.asm.Convert_xd(1, 0, datetime.DefaultPrecision) } diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index d2c49c6a75d..aab5987dcc6 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -1975,39 +1975,18 @@ func FnFromDays(yield Query) { } func FnSecToTime(yield Query) { - // for _, s := range inputConversions { - // yield(fmt.Sprintf("SEC_TO_TIME(%s)", s), nil) - // } - - seconds := []string{ - "0", - "0.99", - "1", - "-1.45632", - "366", - "365242", - "3652424", - "3652425", - "3652500", - "3652499", - "'3652499'", - "730669", - "730669.213", - "730669.99999999999", - "730669.213123321213", - "730669.213123", - "730669.213123", - "'12.12'", - "'730669.213123'", - "time '130:34:58.6'", - "time '-31:34:58'", - "cast(0b001 as json)", - "cast(0xFF666F6F626172FF as json)", - } - - for _, s := range seconds { + for _, s := range inputConversions { yield(fmt.Sprintf("SEC_TO_TIME(%s)", s), nil) } + + mysqlDocSamples := []string{ + `SEC_TO_TIME(2378)`, + `SEC_TO_TIME(2378) + 0`, + } + + for _, q := range mysqlDocSamples { + yield(q, nil) + } } func FnTimeToSec(yield Query) { From 93bdb29f85fedce3f37a9a27e951ceafa4119a2f Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Mon, 15 Apr 2024 11:56:15 +0530 Subject: [PATCH 3/6] Add failing cases in SEC_TO_TIME Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 32 ++++++++++++++++++++++ go/vt/vtgate/evalengine/testcases/cases.go | 5 ++++ 2 files changed, 37 insertions(+) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index 3abbb775108..f5e4918ed30 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -715,6 +715,38 @@ func NewTimeFromStd(t time.Time) Time { } } +func NewTimeFromSecondsDecimal(seconds decimal.Decimal) Time { + var neg bool + if seconds.Cmp(decimal.NewFromInt(0)) >= 0 { + neg = true + seconds = seconds.Mul(decimal.NewFromInt(-1)) + } + + id, frac := seconds.QuoRem(decimal.New(1, 0), 0) + ns := frac.Mul(decimal.New(1, 9)) + + s, _ := id.Int64() + h, s := s/3600, s%3600 + m, s := s/60, s%60 + + if h >= 839 { + h, m, s = 838, 59, 59 + } + + hour := uint16(h) + if neg { + hour |= negMask + } + + nsec, _ := ns.Int64() + return Time{ + hour: hour, + minute: uint8(m), + second: uint8(s), + nanosecond: uint32(nsec), + } +} + func NewTimeFromSeconds(seconds float64) Time { var neg bool if seconds < 0 { diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index aab5987dcc6..49644554ae5 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -1982,6 +1982,11 @@ func FnSecToTime(yield Query) { mysqlDocSamples := []string{ `SEC_TO_TIME(2378)`, `SEC_TO_TIME(2378) + 0`, + `SEC_TO_TIME(time'12:11:12.0000')`, + `SEC_TO_TIME(12321233.00000)`, + `SEC_TO_TIME(103458)`, + `SEC_TO_TIME(20000101103458.123456)`, + `SEC_TO_TIME(time'00:00:01.0000')`, } for _, q := range mysqlDocSamples { From 86930d4bf1a14ed455c8e4e24b4f6ed54c366240 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Fri, 19 Apr 2024 03:07:27 +0530 Subject: [PATCH 4/6] evalengine: Improve SEC_TO_TIME logic Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 66 ++++++++-------------- go/vt/vtgate/evalengine/compiler_asm.go | 26 ++++----- go/vt/vtgate/evalengine/fn_time.go | 32 +++++------ go/vt/vtgate/evalengine/testcases/cases.go | 5 -- 4 files changed, 53 insertions(+), 76 deletions(-) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index f5e4918ed30..7c3f57caebc 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -18,7 +18,6 @@ package datetime import ( "encoding/binary" - "math" "time" "vitess.io/vitess/go/mysql/decimal" @@ -717,64 +716,47 @@ func NewTimeFromStd(t time.Time) Time { func NewTimeFromSecondsDecimal(seconds decimal.Decimal) Time { var neg bool - if seconds.Cmp(decimal.NewFromInt(0)) >= 0 { + if seconds.Cmp(decimal.NewFromInt(0)) < 0 { neg = true seconds = seconds.Mul(decimal.NewFromInt(-1)) } - id, frac := seconds.QuoRem(decimal.New(1, 0), 0) + sec, frac := seconds.QuoRem(decimal.New(1, 0), 0) ns := frac.Mul(decimal.New(1, 9)) - s, _ := id.Int64() - h, s := s/3600, s%3600 - m, s := s/60, s%60 + h := sec.Div(decimal.NewFromInt(3600), 0) + _, sec = sec.QuoRem(decimal.NewFromInt(3600), 0) + min := sec.Div(decimal.NewFromInt(60), 0) + _, sec = sec.QuoRem(decimal.NewFromInt(60), 0) - if h >= 839 { - h, m, s = 838, 59, 59 + if h.Cmp(decimal.NewFromInt(839)) >= 0 { + h := uint16(838) + if neg { + h |= negMask + } + + return Time{ + hour: h, + minute: 59, + second: 59, + nanosecond: 0, + } } - hour := uint16(h) + hour, _ := h.Int64() if neg { - hour |= negMask + hour |= int64(negMask) } + m, _ := min.Int64() + s, _ := sec.Int64() nsec, _ := ns.Int64() - return Time{ - hour: hour, - minute: uint8(m), - second: uint8(s), - nanosecond: uint32(nsec), - } -} - -func NewTimeFromSeconds(seconds float64) Time { - var neg bool - if seconds < 0 { - neg = true - seconds = -seconds - } - - sec, nsec := math.Modf(seconds) - s := int(sec) - ns := nsec * (1e9) - - h, s := s/3600, s%3600 - m, s := s/60, s%60 - - if h >= 839 { - h, m, s, ns = 838, 59, 59, 0 - } - - hour := uint16(h) - if neg { - hour |= negMask - } return Time{ - hour: hour, + hour: uint16(hour), minute: uint8(m), second: uint8(s), - nanosecond: uint32(ns), + nanosecond: uint32(nsec), } } diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index fc0b14d8a69..62327fcb522 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -31,7 +31,6 @@ import ( "math/bits" "net/netip" "strconv" - "strings" "time" "github.com/google/uuid" @@ -3915,22 +3914,23 @@ func (asm *assembler) Fn_FROM_DAYS() { }, "FN FROM_DAYS INT64(SP-1)") } -func (asm *assembler) Fn_SEC_TO_TIME(tt sqltypes.Type) { +func (asm *assembler) Fn_SEC_TO_TIME_D() { + asm.emit(func(env *ExpressionEnv) int { + e := env.vm.stack[env.vm.sp-1].(*evalTemporal) + prec := int(e.prec) + + sec := newEvalDecimalWithPrec(e.toDecimal(), int32(prec)) + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSecondsDecimal(sec.dec), prec) + return 1 + }, "FN SEC_TO_TIME TEMPORAL(SP-1)") +} + +func (asm *assembler) Fn_SEC_TO_TIME_d() { asm.emit(func(env *ExpressionEnv) int { e := env.vm.stack[env.vm.sp-1].(*evalDecimal) prec := min(evalDecimalPrecision(e), datetime.DefaultPrecision) - if sqltypes.IsDateOrTime(tt) { - parts := strings.Split(e.dec.String(), ".") - if len(parts) == 1 { - prec = 0 - } else { - prec = min(int32(len(parts[1])), datetime.DefaultPrecision) - } - } - - sec, _ := e.toFloat0() - env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(sec), int(prec)) + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSecondsDecimal(e.dec), int(prec)) return 1 }, "FN SEC_TO_TIME DECIMAL(SP-1)") } diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index 0bf74f5f685..b82a1c7d69f 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -18,7 +18,6 @@ package evalengine import ( "math" - "strings" "time" "vitess.io/vitess/go/hack" @@ -1352,8 +1351,10 @@ func (b *builtinSecToTime) eval(env *ExpressionEnv) (eval, error) { } var e *evalDecimal + prec := datetime.DefaultPrecision + switch { - case arg.SQLType() == sqltypes.Decimal: + case sqltypes.IsDecimal(arg.SQLType()): e = arg.(*evalDecimal) case sqltypes.IsIntegral(arg.SQLType()): e = evalToDecimal(arg, 0, 0) @@ -1364,22 +1365,16 @@ func (b *builtinSecToTime) eval(env *ExpressionEnv) (eval, error) { } else { e = evalToDecimal(arg, 0, datetime.DefaultPrecision) } + case sqltypes.IsDateOrTime(arg.SQLType()): + d := arg.(*evalTemporal) + e = evalToDecimal(d, 0, int32(d.prec)) + prec = int(d.prec) default: e = evalToDecimal(arg, 0, datetime.DefaultPrecision) } - prec := min(evalDecimalPrecision(e), datetime.DefaultPrecision) - sec, _ := e.toFloat0() - - if sqltypes.IsDateOrTime(arg.SQLType()) { - parts := strings.Split(e.dec.String(), ".") - if len(parts) == 1 { - prec = 0 - } else { - prec = min(int32(len(parts[1])), datetime.DefaultPrecision) - } - } - return newEvalTime(datetime.NewTimeFromSeconds(sec), int(prec)), nil + prec = min(int(evalDecimalPrecision(e)), prec) + return newEvalTime(datetime.NewTimeFromSecondsDecimal(e.dec), prec), nil } func (call *builtinSecToTime) compile(c *compiler) (ctype, error) { @@ -1391,16 +1386,21 @@ func (call *builtinSecToTime) compile(c *compiler) (ctype, error) { skip := c.compileNullCheck1(arg) switch { - case arg.Type == sqltypes.Decimal: + case sqltypes.IsDecimal(arg.Type): + c.asm.Fn_SEC_TO_TIME_d() case sqltypes.IsIntegral(arg.Type): c.asm.Convert_xd(1, 0, 0) + c.asm.Fn_SEC_TO_TIME_d() case sqltypes.IsTextOrBinary(arg.Type) && arg.isHexOrBitLiteral(): c.asm.Convert_xd(1, 0, 0) + c.asm.Fn_SEC_TO_TIME_d() + case sqltypes.IsDateOrTime(arg.Type): + c.asm.Fn_SEC_TO_TIME_D() default: c.asm.Convert_xd(1, 0, datetime.DefaultPrecision) + c.asm.Fn_SEC_TO_TIME_d() } - c.asm.Fn_SEC_TO_TIME(arg.Type) c.asm.jumpDestination(skip) return ctype{Type: sqltypes.Time, Flag: arg.Flag}, nil } diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 49644554ae5..aab5987dcc6 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -1982,11 +1982,6 @@ func FnSecToTime(yield Query) { mysqlDocSamples := []string{ `SEC_TO_TIME(2378)`, `SEC_TO_TIME(2378) + 0`, - `SEC_TO_TIME(time'12:11:12.0000')`, - `SEC_TO_TIME(12321233.00000)`, - `SEC_TO_TIME(103458)`, - `SEC_TO_TIME(20000101103458.123456)`, - `SEC_TO_TIME(time'00:00:01.0000')`, } for _, q := range mysqlDocSamples { From 442c248c4c9a1e193a93f5af1b0a11459535d6ec Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Sat, 20 Apr 2024 17:02:00 +0530 Subject: [PATCH 5/6] Rename NewTimeFromSeconds func and define datetime.MaxHours Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 11 +++++++---- go/mysql/datetime/parse.go | 2 +- go/vt/vtgate/evalengine/compiler_asm.go | 4 ++-- go/vt/vtgate/evalengine/fn_time.go | 8 ++++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index 053bdde0d7d..e3357a39c7a 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -44,7 +44,10 @@ type DateTime struct { Time Time } -const DefaultPrecision = 6 +const ( + DefaultPrecision = 6 + MaxHours = 838 +) func (t Time) AppendFormat(b []byte, prec uint8) []byte { if t.Neg() { @@ -719,7 +722,7 @@ func NewTimeFromStd(t time.Time) Time { } } -func NewTimeFromSecondsDecimal(seconds decimal.Decimal) Time { +func NewTimeFromSeconds(seconds decimal.Decimal) Time { var neg bool if seconds.Cmp(decimal.NewFromInt(0)) < 0 { neg = true @@ -734,8 +737,8 @@ func NewTimeFromSecondsDecimal(seconds decimal.Decimal) Time { min := sec.Div(decimal.NewFromInt(60), 0) _, sec = sec.QuoRem(decimal.NewFromInt(60), 0) - if h.Cmp(decimal.NewFromInt(839)) >= 0 { - h := uint16(838) + if h.Cmp(decimal.NewFromInt(MaxHours)) > 0 { + h := uint16(MaxHours) if neg { h |= negMask } diff --git a/go/mysql/datetime/parse.go b/go/mysql/datetime/parse.go index 0d9e6bb2326..b3673cbcd42 100644 --- a/go/mysql/datetime/parse.go +++ b/go/mysql/datetime/parse.go @@ -339,7 +339,7 @@ func ParseTimeInt64(i int64) (t Time, ok bool) { return t, false } - if i > 838 { + if i > MaxHours { return t, false } t.hour = uint16(i) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index 7a323e52005..07c302ac6ec 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -4109,7 +4109,7 @@ func (asm *assembler) Fn_SEC_TO_TIME_D() { prec := int(e.prec) sec := newEvalDecimalWithPrec(e.toDecimal(), int32(prec)) - env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSecondsDecimal(sec.dec), prec) + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(sec.dec), prec) return 1 }, "FN SEC_TO_TIME TEMPORAL(SP-1)") } @@ -4119,7 +4119,7 @@ func (asm *assembler) Fn_SEC_TO_TIME_d() { e := env.vm.stack[env.vm.sp-1].(*evalDecimal) prec := min(evalDecimalPrecision(e), datetime.DefaultPrecision) - env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSecondsDecimal(e.dec), int(prec)) + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalTime(datetime.NewTimeFromSeconds(e.dec), int(prec)) return 1 }, "FN SEC_TO_TIME DECIMAL(SP-1)") } diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index 7f7c588bca4..5a253799b7f 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -891,12 +891,12 @@ func (call *builtinMakedate) compile(c *compiler) (ctype, error) { func clampHourMinute(h, m int64) (int64, int64, bool, bool) { var clamped bool - if h > 838 || h < -838 { + if h > datetime.MaxHours || h < -datetime.MaxHours { clamped = true if h > 0 { - h = 838 + h = datetime.MaxHours } else { - h = -838 + h = -datetime.MaxHours } m = 59 } @@ -1409,7 +1409,7 @@ func (b *builtinSecToTime) eval(env *ExpressionEnv) (eval, error) { } prec = min(int(evalDecimalPrecision(e)), prec) - return newEvalTime(datetime.NewTimeFromSecondsDecimal(e.dec), prec), nil + return newEvalTime(datetime.NewTimeFromSeconds(e.dec), prec), nil } func (call *builtinSecToTime) compile(c *compiler) (ctype, error) { From b9e929df82e249c5bd45e5e2be7d47fe29ac2327 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Tue, 23 Apr 2024 15:20:20 +0530 Subject: [PATCH 6/6] Make efficient use of decimal APIs Signed-off-by: Noble Mittal --- go/mysql/datetime/datetime.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index e3357a39c7a..973c79b44c3 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -722,22 +722,26 @@ func NewTimeFromStd(t time.Time) Time { } } +var ( + decSecondsInHour = decimal.NewFromInt(3600) + decMinutesInHour = decimal.NewFromInt(60) + decMaxHours = decimal.NewFromInt(MaxHours) +) + func NewTimeFromSeconds(seconds decimal.Decimal) Time { var neg bool - if seconds.Cmp(decimal.NewFromInt(0)) < 0 { + if seconds.Sign() < 0 { neg = true - seconds = seconds.Mul(decimal.NewFromInt(-1)) + seconds = seconds.Abs() } sec, frac := seconds.QuoRem(decimal.New(1, 0), 0) ns := frac.Mul(decimal.New(1, 9)) - h := sec.Div(decimal.NewFromInt(3600), 0) - _, sec = sec.QuoRem(decimal.NewFromInt(3600), 0) - min := sec.Div(decimal.NewFromInt(60), 0) - _, sec = sec.QuoRem(decimal.NewFromInt(60), 0) + h, sec := sec.QuoRem(decSecondsInHour, 0) + min, sec := sec.QuoRem(decMinutesInHour, 0) - if h.Cmp(decimal.NewFromInt(MaxHours)) > 0 { + if h.Cmp(decMaxHours) > 0 { h := uint16(MaxHours) if neg { h |= negMask