Skip to content

Commit 793f52d

Browse files
committed
Support format parameter and timezone in from_unixtime
1 parent 45c4c4c commit 793f52d

File tree

4 files changed

+134
-17
lines changed

4 files changed

+134
-17
lines changed

enginetest/queries/queries.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5539,9 +5539,9 @@ SELECT * FROM cte WHERE d = 2;`,
55395539
{
55405540
Query: "select from_unixtime(i) from mytable order by 1",
55415541
Expected: []sql.Row{
5542-
{time.Unix(1, 0)},
5543-
{time.Unix(2, 0)},
5544-
{time.Unix(3, 0)},
5542+
{UnixTimeInLocal(1, 0)},
5543+
{UnixTimeInLocal(2, 0)},
5544+
{UnixTimeInLocal(3, 0)},
55455545
},
55465546
},
55475547
// TODO: add additional tests for other functions. Every function needs an engine test to ensure it works correctly
@@ -11712,3 +11712,9 @@ func MustParseTime(layout, value string) time.Time {
1171211712
}
1171311713
return parsed
1171411714
}
11715+
11716+
func UnixTimeInLocal(sec, nsec int64) time.Time {
11717+
t := time.Unix(sec, nsec)
11718+
_, offset := t.Zone()
11719+
return t.Add(time.Second * time.Duration(offset)).In(time.UTC)
11720+
}

enginetest/queries/script_queries.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3055,6 +3055,64 @@ CREATE TABLE tab3 (
30553055
},
30563056
},
30573057
},
3058+
{
3059+
Name: "from_unixtime",
3060+
Dialect: "mysql",
3061+
Assertions: []ScriptTestAssertion{
3062+
// null parameter
3063+
{
3064+
Query: "select from_unixtime(null)",
3065+
Expected: []sql.Row{{nil}},
3066+
},
3067+
{
3068+
Query: "select from_unixtime(1, null)",
3069+
Expected: []sql.Row{{nil}},
3070+
},
3071+
// out of range
3072+
{
3073+
Query: "select from_unixtime(-1)",
3074+
Expected: []sql.Row{{nil}},
3075+
},
3076+
{
3077+
Query: "select from_unixtime(32536771200)",
3078+
Expected: []sql.Row{{nil}},
3079+
},
3080+
// in +8:00
3081+
{
3082+
Query: "set @@session.time_zone='+08:00'",
3083+
Expected: []sql.Row{{}},
3084+
},
3085+
{
3086+
Query: "select from_unixtime(1)",
3087+
Expected: []sql.Row{{time.Unix(1, 0).Add(time.Hour * 8).In(time.UTC)}},
3088+
},
3089+
{
3090+
Query: "select from_unixtime(32536771199)",
3091+
Expected: []sql.Row{{time.Unix(32536771199, 0).Add(time.Hour * 8).In(time.UTC)}},
3092+
},
3093+
{
3094+
Query: "SELECT FROM_UNIXTIME(1,'%Y %D %M %H:%i:%s %x')",
3095+
Expected: []sql.Row{{"1970 1st January 08:00:01 1970"}},
3096+
},
3097+
// in utc
3098+
{
3099+
Query: "set @@session.time_zone='UTC'",
3100+
Expected: []sql.Row{{}},
3101+
},
3102+
{
3103+
Query: "select from_unixtime(1)",
3104+
Expected: []sql.Row{{time.Unix(1, 0).In(time.UTC)}},
3105+
},
3106+
{
3107+
Query: "select from_unixtime(32536771199)",
3108+
Expected: []sql.Row{{time.Unix(32536771199, 0).In(time.UTC)}},
3109+
},
3110+
{
3111+
Query: "SELECT FROM_UNIXTIME(1,'%Y %D %M %H:%i:%s %x')",
3112+
Expected: []sql.Row{{"1970 1st January 00:00:01 1970"}},
3113+
},
3114+
},
3115+
},
30583116
{
30593117
Name: "unix_timestamp with non UTC timezone",
30603118
Dialect: "mysql",

sql/expression/function/date.go

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ var _ sql.FunctionExpression = (*UnixTimestamp)(nil)
404404
var _ sql.CollationCoercible = (*UnixTimestamp)(nil)
405405

406406
const MaxUnixTimeMicroSecs = 32536771199999999
407+
const MaxUnixTimeSecs = 32536771199
407408

408409
// noEval returns true if the expression contains an expression that cannot be evaluated without sql.Context or sql.Row.
409410
func noEval(expr sql.Expression) bool {
@@ -643,14 +644,20 @@ func (ut *UnixTimestamp) String() string {
643644

644645
// FromUnixtime converts the argument to a datetime.
645646
type FromUnixtime struct {
646-
*UnaryFunc
647+
expression.NaryExpression
647648
}
648649

649650
var _ sql.FunctionExpression = (*FromUnixtime)(nil)
650651
var _ sql.CollationCoercible = (*FromUnixtime)(nil)
651652

652-
func NewFromUnixtime(arg sql.Expression) sql.Expression {
653-
return &FromUnixtime{NewUnaryFunc(arg, "FROM_UNIXTIME", types.DatetimeMaxPrecision)}
653+
// NewFromUnixtime https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html#function_from-unixtime
654+
func NewFromUnixtime(args ...sql.Expression) (sql.Expression, error) {
655+
switch len(args) {
656+
case 1, 2:
657+
return &FromUnixtime{expression.NaryExpression{ChildExpressions: args}}, nil
658+
default:
659+
return nil, sql.ErrInvalidArgumentNumber.New("FROM_UNIXTIME", 2, len(args))
660+
}
654661
}
655662

656663
// Description implements sql.FunctionExpression
@@ -664,28 +671,74 @@ func (*FromUnixtime) CollationCoercibility(ctx *sql.Context) (collation sql.Coll
664671
}
665672

666673
func (r *FromUnixtime) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
667-
val, err := r.EvalChild(ctx, row)
674+
vals := make([]interface{}, len(r.ChildExpressions))
675+
for i, e := range r.ChildExpressions {
676+
val, err := e.Eval(ctx, row)
677+
if err != nil {
678+
return nil, err
679+
}
680+
vals[i] = val
681+
}
682+
683+
// TODO support decimal value in timestamp
684+
n, _, err := types.Int64.Convert(vals[0])
668685
if err != nil {
669686
return nil, err
670687
}
671-
672-
if val == nil {
688+
if n == nil {
673689
return nil, nil
674690
}
675-
676-
n, _, err := types.Int64.Convert(val)
691+
sec := n.(int64)
692+
if sec > MaxUnixTimeSecs || sec < 0 {
693+
return nil, nil
694+
}
695+
t := time.Unix(sec, 0).In(time.UTC)
696+
tz, err := SessionTimeZone(ctx)
677697
if err != nil {
678698
return nil, err
679699
}
680-
681-
return time.Unix(n.(int64), 0), nil
700+
t, _ = gmstime.ConvertTimeZone(t, "UTC", tz)
701+
if len(vals) == 1 {
702+
return t, nil // If format is omitted, this function returns a DATETIME value.
703+
}
704+
format, _, err := types.Text.Convert(vals[1])
705+
if err != nil {
706+
return nil, err
707+
}
708+
if format == nil {
709+
return nil, nil
710+
}
711+
return formatDate(format.(string), t)
682712
}
683713

684714
func (r *FromUnixtime) WithChildren(children ...sql.Expression) (sql.Expression, error) {
685-
if len(children) != 1 {
686-
return nil, sql.ErrInvalidChildrenNumber.New(r, len(children), 1)
715+
return NewFromUnixtime(children...)
716+
}
717+
718+
func (r *FromUnixtime) FunctionName() string {
719+
return "FROM_UNIXTIME"
720+
}
721+
722+
func (r *FromUnixtime) String() string {
723+
switch len(r.ChildExpressions) {
724+
case 1:
725+
return fmt.Sprintf("FROM_UNIXTIME(%s)", r.ChildExpressions[0])
726+
case 2:
727+
return fmt.Sprintf("FROM_UNIXTIME(%s, %s)", r.ChildExpressions[0], r.ChildExpressions[1])
728+
default:
729+
return "FROM_UNIXTIME(INVALID_NUMBER_OF_ARGUMENTS)"
730+
}
731+
}
732+
733+
func (r *FromUnixtime) Type() sql.Type {
734+
switch len(r.ChildExpressions) {
735+
case 1:
736+
return types.DatetimeMaxPrecision
737+
case 2:
738+
return types.Text
739+
default:
740+
return types.Null
687741
}
688-
return NewFromUnixtime(children[0]), nil
689742
}
690743

691744
type CurrDate struct {

sql/expression/function/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ var BuiltIns = []sql.Function{
9898
sql.FunctionN{Name: "format", Fn: NewFormat},
9999
sql.Function1{Name: "from_base64", Fn: NewFromBase64},
100100
sql.Function1{Name: "from_days", Fn: NewFromDays},
101-
sql.Function1{Name: "from_unixtime", Fn: NewFromUnixtime},
101+
sql.FunctionN{Name: "from_unixtime", Fn: NewFromUnixtime},
102102
sql.Function2{Name: "get_format", Fn: NewGetFormat},
103103
sql.FunctionN{Name: "greatest", Fn: NewGreatest},
104104
sql.Function0{Name: "group_concat", Fn: aggregation.NewEmptyGroupConcat},

0 commit comments

Comments
 (0)