Skip to content

Commit fffaf2a

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

File tree

4 files changed

+105
-18
lines changed

4 files changed

+105
-18
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3055,6 +3055,42 @@ CREATE TABLE tab3 (
30553055
},
30563056
},
30573057
},
3058+
{
3059+
Name: "from_unixtime",
3060+
Dialect: "mysql",
3061+
Assertions: []ScriptTestAssertion{
3062+
{
3063+
Query: "select from_unixtime(null)",
3064+
Expected: []sql.Row{{types.Null}},
3065+
},
3066+
// in +8:00
3067+
{
3068+
Query: "set @@session.time_zone='+08:00'",
3069+
Expected: []sql.Row{{}},
3070+
},
3071+
{
3072+
Query: "select from_unixtime(1)",
3073+
Expected: []sql.Row{{time.Unix(1, 0).Add(time.Hour * 8).In(time.UTC)}},
3074+
},
3075+
{
3076+
Query: "SELECT FROM_UNIXTIME(1,'%Y %D %M %H:%i:%s %x')",
3077+
Expected: []sql.Row{{"1970 1st January 08:00:01 1970"}},
3078+
},
3079+
// in utc
3080+
{
3081+
Query: "set @@session.time_zone='UTC'",
3082+
Expected: []sql.Row{{}},
3083+
},
3084+
{
3085+
Query: "select from_unixtime(1)",
3086+
Expected: []sql.Row{{time.Unix(1, 0).In(time.UTC)}},
3087+
},
3088+
{
3089+
Query: "SELECT FROM_UNIXTIME(1,'%Y %D %M %H:%i:%s %x')",
3090+
Expected: []sql.Row{{"1970 1st January 00:00:01 1970"}},
3091+
},
3092+
},
3093+
},
30583094
{
30593095
Name: "unix_timestamp with non UTC timezone",
30603096
Dialect: "mysql",

sql/expression/function/date.go

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -643,14 +643,20 @@ func (ut *UnixTimestamp) String() string {
643643

644644
// FromUnixtime converts the argument to a datetime.
645645
type FromUnixtime struct {
646-
*UnaryFunc
646+
expression.NaryExpression
647647
}
648648

649649
var _ sql.FunctionExpression = (*FromUnixtime)(nil)
650650
var _ sql.CollationCoercible = (*FromUnixtime)(nil)
651651

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

656662
// Description implements sql.FunctionExpression
@@ -664,28 +670,67 @@ func (*FromUnixtime) CollationCoercibility(ctx *sql.Context) (collation sql.Coll
664670
}
665671

666672
func (r *FromUnixtime) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
667-
val, err := r.EvalChild(ctx, row)
673+
vals := make([]interface{}, len(r.ChildExpressions))
674+
for i, e := range r.ChildExpressions {
675+
val, err := e.Eval(ctx, row)
676+
if err != nil {
677+
return nil, err
678+
}
679+
vals[i] = val
680+
}
681+
682+
// TODO support decimal value in timestamp
683+
n, _, err := types.Int64.Convert(vals[0])
668684
if err != nil {
669685
return nil, err
670686
}
671-
672-
if val == nil {
673-
return nil, nil
687+
if n == nil {
688+
return types.Null, nil
674689
}
675-
676-
n, _, err := types.Int64.Convert(val)
690+
t := time.Unix(n.(int64), 0).In(time.UTC)
691+
tz, err := SessionTimeZone(ctx)
677692
if err != nil {
678693
return nil, err
679694
}
680-
681-
return time.Unix(n.(int64), 0), nil
695+
t, _ = gmstime.ConvertTimeZone(t, "UTC", tz)
696+
if len(vals) == 1 {
697+
return t, nil // If format is omitted, this function returns a DATETIME value.
698+
}
699+
format, _, err := types.Text.Convert(vals[1])
700+
if err != nil {
701+
return nil, err
702+
}
703+
return formatDate(format.(string), t)
682704
}
683705

684706
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)
707+
return NewFromUnixtime(children...)
708+
}
709+
710+
func (r *FromUnixtime) FunctionName() string {
711+
return "FROM_UNIXTIME"
712+
}
713+
714+
func (r *FromUnixtime) String() string {
715+
switch len(r.ChildExpressions) {
716+
case 1:
717+
return fmt.Sprintf("FROM_UNIXTIME(%s)", r.ChildExpressions[0])
718+
case 2:
719+
return fmt.Sprintf("FROM_UNIXTIME(%s, %s)", r.ChildExpressions[0], r.ChildExpressions[1])
720+
default:
721+
return "FROM_UNIXTIME(INVALID_NUMBER_OF_ARGUMENTS)"
722+
}
723+
}
724+
725+
func (r *FromUnixtime) Type() sql.Type {
726+
switch len(r.ChildExpressions) {
727+
case 1:
728+
return types.DatetimeMaxPrecision
729+
case 2:
730+
return types.Text
731+
default:
732+
return types.Null
687733
}
688-
return NewFromUnixtime(children[0]), nil
689734
}
690735

691736
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)