Skip to content
This repository has been archived by the owner on Jan 28, 2021. It is now read-only.

Commit

Permalink
sql: convert dates outside of supported time range to correct date
Browse files Browse the repository at this point in the history
According to MySQL, dates should range between 1000-01-01 00:00:00
and 9999-12-31 23:59:59. This PR changes the behaviour of Convert
methods of Timestamp and Date types so that values returned by this
method are always within that range.

It also adds an exposed utility function named `ToSupportedTimeRange`
that converts a time to the supported time range for clients to use.

The readme now includes instructions for datasource implementors to
always return their dates within the supported range using the
provided utility function.
The reason go-mysql-server does not handle that for data sources by
default is because there is no obvious place to do so in a generic
way. Tables should be the ones returning correct values.

Signed-off-by: Miguel Molina <miguel@erizocosmi.co>
  • Loading branch information
erizocosmico committed Apr 24, 2019
1 parent c4a4af4 commit 4c888c4
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ To be able to create your own data source implementation you need to implement t
- `sql.PushdownProjectionAndFiltersTable` interface will provide the same functionality described before, but also will push down the filters used in the executed query. It allows to filter data in advance, and speed up queries.
- `sql.Indexable` add index capabilities to your table. By implementing this interface you can create and use indexes on this table.
- `sql.Inserter` can be implemented if your data source tables allow insertions.
**IMPORTANT:** when you implement your tables, your columns of timestamp and datetime types **must** return dates in the following range: `1000-01-01 00:00:00` to `9999-12-31 23:59:59`. To do so, you can use the builtin function `sql.ToSupportedTimeRange`.

- If you need some custom tree modifications, you can also implement your own `analyzer.Rules`.

Expand Down
32 changes: 26 additions & 6 deletions sql/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,11 @@ var TimestampLayouts = []string{
"20060102",
}

var (
maxTimestamp = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
minTimestamp = time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC)
)

// SQL implements Type interface.
func (t timestampT) SQL(v interface{}) sqltypes.Value {
if _, ok := v.(nullT); ok {
Expand All @@ -422,11 +427,25 @@ func (t timestampT) SQL(v interface{}) sqltypes.Value {
)
}

// ToSupportedTimeRange returns the time if it's within the supported datetime
// range or either the lower or upper bound.
func ToSupportedTimeRange(t time.Time) time.Time {
if t.Before(minTimestamp) {
return minTimestamp
}

if t.After(maxTimestamp) {
return maxTimestamp
}

return t
}

// Convert implements Type interface.
func (t timestampT) Convert(v interface{}) (interface{}, error) {
switch value := v.(type) {
case time.Time:
return value.UTC(), nil
return ToSupportedTimeRange(value.UTC()), nil
case string:
t, err := time.Parse(TimestampLayout, value)
if err != nil {
Expand All @@ -443,14 +462,14 @@ func (t timestampT) Convert(v interface{}) (interface{}, error) {
return nil, ErrConvertingToTime.Wrap(err, v)
}
}
return t.UTC(), nil
return ToSupportedTimeRange(t.UTC()), nil
default:
ts, err := Int64.Convert(v)
if err != nil {
return nil, ErrInvalidType.New(reflect.TypeOf(v))
}

return time.Unix(ts.(int64), 0).UTC(), nil
return ToSupportedTimeRange(time.Unix(ts.(int64), 0).UTC()), nil
}
}

Expand Down Expand Up @@ -497,20 +516,21 @@ func (t dateT) SQL(v interface{}) sqltypes.Value {
func (t dateT) Convert(v interface{}) (interface{}, error) {
switch value := v.(type) {
case time.Time:
return truncateDate(value).UTC(), nil
return truncateDate(ToSupportedTimeRange(value.UTC())), nil
case string:
t, err := time.Parse(DateLayout, value)
if err != nil {
return nil, ErrConvertingToTime.Wrap(err, v)
}
return truncateDate(t).UTC(), nil
return truncateDate(ToSupportedTimeRange(t.UTC())), nil
default:
ts, err := Int64.Convert(v)
if err != nil {
return nil, ErrInvalidType.New(reflect.TypeOf(v))
}

return truncateDate(time.Unix(ts.(int64), 0)).UTC(), nil
t := time.Unix(ts.(int64), 0)
return truncateDate(ToSupportedTimeRange(t.UTC())), nil
}
}

Expand Down

0 comments on commit 4c888c4

Please sign in to comment.