diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index dee00bf7327..973c79b44c3 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,6 +722,56 @@ 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.Sign() < 0 { + neg = true + seconds = seconds.Abs() + } + + sec, frac := seconds.QuoRem(decimal.New(1, 0), 0) + ns := frac.Mul(decimal.New(1, 9)) + + h, sec := sec.QuoRem(decSecondsInHour, 0) + min, sec := sec.QuoRem(decMinutesInHour, 0) + + if h.Cmp(decMaxHours) > 0 { + h := uint16(MaxHours) + if neg { + h |= negMask + } + + return Time{ + hour: h, + minute: 59, + second: 59, + nanosecond: 0, + } + } + + hour, _ := h.Int64() + if neg { + hour |= int64(negMask) + } + + m, _ := min.Int64() + s, _ := sec.Int64() + nsec, _ := ns.Int64() + + return Time{ + hour: uint16(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/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/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go index e7563e8f258..f7aca0509bd 100644 --- a/go/vt/vtgate/evalengine/cached_size.go +++ b/go/vt/vtgate/evalengine/cached_size.go @@ -1547,6 +1547,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 b45ac10b194..07c302ac6ec 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -4103,6 +4103,27 @@ func (asm *assembler) Fn_FROM_DAYS() { }, "FN FROM_DAYS INT64(SP-1)") } +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.NewTimeFromSeconds(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) + + 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)") +} + 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 bf662e436b7..5a253799b7f 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -125,6 +125,10 @@ type ( CallExpr } + builtinSecToTime struct { + CallExpr + } + builtinTimeToSec struct { CallExpr } @@ -197,6 +201,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 = (*builtinToSeconds)(nil) var _ IR = (*builtinQuarter)(nil) @@ -886,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 } @@ -1371,6 +1376,70 @@ 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 + } + + var e *evalDecimal + prec := datetime.DefaultPrecision + + switch { + case sqltypes.IsDecimal(arg.SQLType()): + 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) + } + 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(int(evalDecimalPrecision(e)), prec) + return newEvalTime(datetime.NewTimeFromSeconds(e.dec), 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.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.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 64dbd773a44..c937e7dbbc0 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -143,6 +143,7 @@ var Cases = []TestCase{ {Run: FnLastDay}, {Run: FnToDays}, {Run: FnFromDays}, + {Run: FnSecToTime}, {Run: FnTimeToSec}, {Run: FnToSeconds}, {Run: FnQuarter}, @@ -2060,6 +2061,21 @@ func FnFromDays(yield Query) { } } +func FnSecToTime(yield Query) { + 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) { 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 4f7ba1a451c..d4c6bcdae5a 100644 --- a/go/vt/vtgate/evalengine/translate_builtin.go +++ b/go/vt/vtgate/evalengine/translate_builtin.go @@ -462,6 +462,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)