diff --git a/go/mysql/datetime/datetime.go b/go/mysql/datetime/datetime.go index 1673a71d662..dee00bf7327 100644 --- a/go/mysql/datetime/datetime.go +++ b/go/mysql/datetime/datetime.go @@ -599,6 +599,11 @@ func (dt DateTime) toDuration() time.Duration { return dur } +func (dt DateTime) ToSeconds() int64 { + numDays := MysqlDayNumber(dt.Date.Year(), dt.Date.Month(), dt.Date.Day()) + return int64(numDays*24*3600) + dt.Time.ToSeconds() +} + func (dt *DateTime) addInterval(itv *Interval) bool { switch { case itv.unit.HasTimeParts(): diff --git a/go/mysql/datetime/datetime_test.go b/go/mysql/datetime/datetime_test.go index 565d2803fe2..697f967cc3c 100644 --- a/go/mysql/datetime/datetime_test.go +++ b/go/mysql/datetime/datetime_test.go @@ -552,10 +552,21 @@ func TestToSeconds(t *testing.T) { assert.Equal(t, 45020, int(res)) // Neg Time Case - tt.hour = 1<<15 | tt.hour + tt.hour |= 1 << 15 res = tt.ToSeconds() assert.Equal(t, -45020, int(res)) + + dt := NewDateTimeFromStd(testGoTime) + + res = dt.ToSeconds() + assert.Equal(t, 63877465820, int(res)) + + // Neg Time Case + dt.Time.hour |= 1 << 15 + res = dt.ToSeconds() + + assert.Equal(t, 63877375780, int(res)) } func TestToStdTime(t *testing.T) { diff --git a/go/vt/vtgate/evalengine/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go index 72bfbafed73..f2d21af1111 100644 --- a/go/vt/vtgate/evalengine/cached_size.go +++ b/go/vt/vtgate/evalengine/cached_size.go @@ -1679,6 +1679,18 @@ func (cached *builtinToDays) CachedSize(alloc bool) int64 { size += cached.CallExpr.CachedSize(false) return size } +func (cached *builtinToSeconds) 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 5eeb9a6300d..2bda9826be9 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -3927,6 +3927,17 @@ func (asm *assembler) Fn_TIME_TO_SEC() { }, "FN TIME_TO_SEC TIME(SP-1)") } +func (asm *assembler) Fn_TO_SECONDS() { + 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) + env.vm.stack[env.vm.sp-1] = env.vm.arena.newEvalInt64(arg.dt.ToSeconds()) + return 1 + }, "FN TO_SECONDS DATETIME(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 57edf29e2a4..ad8ea68c6a2 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -123,6 +123,10 @@ type ( CallExpr } + builtinToSeconds struct { + CallExpr + } + builtinQuarter struct { CallExpr } @@ -188,6 +192,7 @@ var _ IR = (*builtinLastDay)(nil) var _ IR = (*builtinToDays)(nil) var _ IR = (*builtinFromDays)(nil) var _ IR = (*builtinTimeToSec)(nil) +var _ IR = (*builtinToSeconds)(nil) var _ IR = (*builtinQuarter)(nil) var _ IR = (*builtinSecond)(nil) var _ IR = (*builtinTime)(nil) @@ -1373,6 +1378,40 @@ func (call *builtinTimeToSec) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Int64, Col: collationNumeric, Flag: arg.Flag | flagNullable}, nil } +func (b *builtinToSeconds) eval(env *ExpressionEnv) (eval, error) { + date, err := b.arg1(env) + if err != nil { + return nil, err + } + if date == nil { + return nil, nil + } + dt := evalToDateTime(date, -1, env.now, false) + if dt == nil { + return nil, nil + } + + return newEvalInt64(dt.dt.ToSeconds()), nil +} + +func (call *builtinToSeconds) 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_xDT(1, -1, false) + } + c.asm.Fn_TO_SECONDS() + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.Int64, Col: collationNumeric, 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 b55f6c2c18d..7d0139e6bbb 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -142,6 +142,7 @@ var Cases = []TestCase{ {Run: FnToDays}, {Run: FnFromDays}, {Run: FnTimeToSec}, + {Run: FnToSeconds}, {Run: FnQuarter}, {Run: FnSecond}, {Run: FnTime}, @@ -1997,6 +1998,40 @@ func FnTimeToSec(yield Query) { } } +func FnToSeconds(yield Query) { + for _, t := range inputConversions { + yield(fmt.Sprintf("TO_SECONDS(%s)", t), nil) + } + + timeInputs := []string{ + `DATE'0000-00-00'`, + `0`, + `'0000-00-00'`, + `'00:00:00'`, + `DATE'2023-09-03 00:00:00'`, + `DATE'0000-00-00 00:00:00'`, + `950501`, + `'2007-10-07'`, + `'0000-01-01'`, + `TIME'00:00:00'`, + `TIME'120:01:12'`, + } + + for _, t := range timeInputs { + yield(fmt.Sprintf("TO_SECONDS(%s)", t), nil) + } + + mysqlDocSamples := []string{ + `TO_SECONDS(950501)`, + `TO_SECONDS('2009-11-29')`, + `TO_SECONDS('2009-11-29 13:43:32')`, + } + + for _, q := range mysqlDocSamples { + yield(q, 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 97900c98e57..710245257ed 100644 --- a/go/vt/vtgate/evalengine/translate_builtin.go +++ b/go/vt/vtgate/evalengine/translate_builtin.go @@ -457,6 +457,11 @@ func (ast *astCompiler) translateFuncExpr(fn *sqlparser.FuncExpr) (IR, error) { return nil, argError(method) } return &builtinTimeToSec{CallExpr: call}, nil + case "to_seconds": + if len(args) != 1 { + return nil, argError(method) + } + return &builtinToSeconds{CallExpr: call}, nil case "quarter": if len(args) != 1 { return nil, argError(method)