Skip to content

Commit e23900e

Browse files
authored
Merge pull request #2963 from dolthub/from_unixtime
From unixtime
2 parents ec78760 + 997c868 commit e23900e

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
@@ -5575,9 +5575,9 @@ SELECT * FROM cte WHERE d = 2;`,
55755575
{
55765576
Query: "select from_unixtime(i) from mytable order by 1",
55775577
Expected: []sql.Row{
5578-
{time.Unix(1, 0)},
5579-
{time.Unix(2, 0)},
5580-
{time.Unix(3, 0)},
5578+
{UnixTimeInLocal(1, 0)},
5579+
{UnixTimeInLocal(2, 0)},
5580+
{UnixTimeInLocal(3, 0)},
55815581
},
55825582
},
55835583
// TODO: add additional tests for other functions. Every function needs an engine test to ensure it works correctly
@@ -11768,3 +11768,9 @@ func MustParseTime(layout, value string) time.Time {
1176811768
}
1176911769
return parsed
1177011770
}
11771+
11772+
func UnixTimeInLocal(sec, nsec int64) time.Time {
11773+
t := time.Unix(sec, nsec)
11774+
_, offset := t.Zone()
11775+
return t.Add(time.Second * time.Duration(offset)).In(time.UTC)
11776+
}

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
@@ -111,6 +111,7 @@ var _ sql.FunctionExpression = (*UnixTimestamp)(nil)
111111
var _ sql.CollationCoercible = (*UnixTimestamp)(nil)
112112

113113
const MaxUnixTimeMicroSecs = 32536771199999999
114+
const MaxUnixTimeSecs = 32536771199
114115

115116
// canEval returns if the expression contains an expression that cannot be evaluated without sql.Context or sql.Row.
116117
func canEval(expr sql.Expression) bool {
@@ -354,14 +355,20 @@ func (ut *UnixTimestamp) String() string {
354355

355356
// FromUnixtime converts the argument to a datetime.
356357
type FromUnixtime struct {
357-
*UnaryFunc
358+
expression.NaryExpression
358359
}
359360

360361
var _ sql.FunctionExpression = (*FromUnixtime)(nil)
361362
var _ sql.CollationCoercible = (*FromUnixtime)(nil)
362363

363-
func NewFromUnixtime(arg sql.Expression) sql.Expression {
364-
return &FromUnixtime{NewUnaryFunc(arg, "FROM_UNIXTIME", types.DatetimeMaxPrecision)}
364+
// NewFromUnixtime https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html#function_from-unixtime
365+
func NewFromUnixtime(args ...sql.Expression) (sql.Expression, error) {
366+
switch len(args) {
367+
case 1, 2:
368+
return &FromUnixtime{expression.NaryExpression{ChildExpressions: args}}, nil
369+
default:
370+
return nil, sql.ErrInvalidArgumentNumber.New("FROM_UNIXTIME", 2, len(args))
371+
}
365372
}
366373

367374
// Description implements sql.FunctionExpression
@@ -375,28 +382,74 @@ func (*FromUnixtime) CollationCoercibility(ctx *sql.Context) (collation sql.Coll
375382
}
376383

377384
func (r *FromUnixtime) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
378-
val, err := r.EvalChild(ctx, row)
385+
vals := make([]interface{}, len(r.ChildExpressions))
386+
for i, e := range r.ChildExpressions {
387+
val, err := e.Eval(ctx, row)
388+
if err != nil {
389+
return nil, err
390+
}
391+
vals[i] = val
392+
}
393+
394+
// TODO support decimal value in timestamp
395+
n, _, err := types.Int64.Convert(ctx, vals[0])
379396
if err != nil {
380397
return nil, err
381398
}
382-
383-
if val == nil {
399+
if n == nil {
384400
return nil, nil
385401
}
386-
387-
n, _, err := types.Int64.Convert(ctx, val)
402+
sec := n.(int64)
403+
if sec > MaxUnixTimeSecs || sec < 0 {
404+
return nil, nil
405+
}
406+
t := time.Unix(sec, 0).In(time.UTC)
407+
tz, err := SessionTimeZone(ctx)
388408
if err != nil {
389409
return nil, err
390410
}
391-
392-
return time.Unix(n.(int64), 0), nil
411+
t, _ = gmstime.ConvertTimeZone(t, "UTC", tz)
412+
if len(vals) == 1 {
413+
return t, nil // If format is omitted, this function returns a DATETIME value.
414+
}
415+
format, _, err := types.Text.Convert(ctx, vals[1])
416+
if err != nil {
417+
return nil, err
418+
}
419+
if format == nil {
420+
return nil, nil
421+
}
422+
return formatDate(format.(string), t)
393423
}
394424

395425
func (r *FromUnixtime) WithChildren(children ...sql.Expression) (sql.Expression, error) {
396-
if len(children) != 1 {
397-
return nil, sql.ErrInvalidChildrenNumber.New(r, len(children), 1)
426+
return NewFromUnixtime(children...)
427+
}
428+
429+
func (r *FromUnixtime) FunctionName() string {
430+
return "FROM_UNIXTIME"
431+
}
432+
433+
func (r *FromUnixtime) String() string {
434+
switch len(r.ChildExpressions) {
435+
case 1:
436+
return fmt.Sprintf("FROM_UNIXTIME(%s)", r.ChildExpressions[0])
437+
case 2:
438+
return fmt.Sprintf("FROM_UNIXTIME(%s, %s)", r.ChildExpressions[0], r.ChildExpressions[1])
439+
default:
440+
return "FROM_UNIXTIME(INVALID_NUMBER_OF_ARGUMENTS)"
441+
}
442+
}
443+
444+
func (r *FromUnixtime) Type() sql.Type {
445+
switch len(r.ChildExpressions) {
446+
case 1:
447+
return types.DatetimeMaxPrecision
448+
case 2:
449+
return types.Text
450+
default:
451+
return types.Null
398452
}
399-
return NewFromUnixtime(children[0]), nil
400453
}
401454

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