Skip to content

Commit

Permalink
feat(serial): add support for passing time.Duration
Browse files Browse the repository at this point in the history
  • Loading branch information
joris-bright committed Sep 12, 2024
1 parent 806af86 commit f2181ae
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ types:
passed to Trino as a time with a time zone
* the result of `trino.Timestamp(year, month, day, hour, minute, second,
nanosecond)` - passed to Trino as a timestamp without a time zone
* `time.Duration` - passed to Trino as an interval day to second (precision can be lost for very large durations)

It's not yet possible to pass:
* `float32` or `float64`
* `byte`
* `time.Duration`
* `json.RawMessage`
* maps

Expand Down
62 changes: 61 additions & 1 deletion trino/serial.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package trino
import (
"encoding/json"
"fmt"
"math"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -163,7 +164,7 @@ func Serial(v interface{}) (string, error) {
return "TIMESTAMP " + time.Time(x).Format("'2006-01-02 15:04:05.999999999 Z07:00'"), nil

case time.Duration:
return "", UnsupportedArgError{"time.Duration"}
return serialDuration(x), nil

// TODO - json.RawMesssage should probably be matched to 'JSON' in Trino
case json.RawMessage:
Expand Down Expand Up @@ -208,3 +209,62 @@ func serialSlice(v []interface{}) (string, error) {

return "ARRAY[" + strings.Join(ss, ", ") + "]", nil
}

const (
// For seconds with milliseconds there seems to be a max nr of digits
maxIntervalStrLenWithDot = 11 // 123456789.1 and 12345678.91 are valid
maxIntervalStrLenWithDotAndSign = 12 // -123456789.1 and -12345678.91 are valid
)

func serialDuration(dur time.Duration) string {
millisDur := dur.Milliseconds()
if millisDur%(60*60*1000) == 0 { // hours
return serialHoursInterval(millisDur / 60 / 60 / 1000)
} else if millisDur%(60*1000) == 0 { // minutes
return serialMinutesInterval(millisDur / 60 / 1000)
} else if millisDur%(1000) == 0 { // seconds
return serialSecondsInterval(millisDur / 1000)
} else { // milliseconds
return serialMillisecondsInterval(millisDur)
}
}

func serialHoursInterval(nrOfHours int64) string {
// no need for check, nrOfHours is always within int32 range
return "INTERVAL '" + strconv.FormatInt(nrOfHours, 10) + "' HOUR"
}

func serialMinutesInterval(nrOfMinutes int64) string {
// no need for check, nrOfMinutes is always within int32 range
return "INTERVAL '" + strconv.FormatInt(nrOfMinutes, 10) + "' MINUTE"
}

func serialSecondsInterval(nrOfSeconds int64) string {
if nrOfSeconds < -math.MaxInt32 || nrOfSeconds > math.MaxInt32 { // math.MinInt32 (-2147483648) doesn't work for Trino vs -math.MaxInt (-2147483647) does work
if nrOfSeconds >= 0 {
return serialMinutesInterval((nrOfSeconds + 30) / 60) // round to minutes
} else {
return serialMinutesInterval((nrOfSeconds - 30) / 60)
}
}
return "INTERVAL '" + strconv.FormatInt(nrOfSeconds, 10) + "' SECOND"
}

func serialMillisecondsInterval(nrOfMillis int64) string {
intervalNr := strconv.FormatInt(nrOfMillis/1000, 10) + "."
if nrOfMillis < 0 {
intervalNr += fmt.Sprintf("%03d", -(nrOfMillis % 1000))
} else {
intervalNr += fmt.Sprintf("%03d", nrOfMillis%1000)
}
intervalNr = strings.TrimRight(intervalNr, "0")
if nrOfMillis < 0 && len(intervalNr) > maxIntervalStrLenWithDotAndSign ||
nrOfMillis > 0 && len(intervalNr) > maxIntervalStrLenWithDot {
if nrOfMillis >= 0 {
return serialSecondsInterval((nrOfMillis + 500) / 1000) // round to seconds
} else {
return serialSecondsInterval((nrOfMillis - 500) / 1000)
}
}
return "INTERVAL '" + intervalNr + "' SECOND"
}
91 changes: 91 additions & 0 deletions trino/serial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package trino

import (
"math"
"testing"
"time"

Expand Down Expand Up @@ -160,6 +161,96 @@ func TestSerial(t *testing.T) {
value: time.Date(2017, 7, 10, 11, 34, 25, 123456, time.UTC),
expectedSerial: "TIMESTAMP '2017-07-10 11:34:25.000123456 Z'",
},
{
name: "duration",
value: 10*time.Second + 5*time.Millisecond,
expectedSerial: "INTERVAL '10.005' SECOND",
},
{
name: "duration with negative value",
value: -(10*time.Second + 5*time.Millisecond),
expectedSerial: "INTERVAL '-10.005' SECOND",
},
{
name: "minute duration",
value: 10 * time.Minute,
expectedSerial: "INTERVAL '10' MINUTE",
},
{
name: "hour duration",
value: 23 * time.Hour,
expectedSerial: "INTERVAL '23' HOUR",
},
{
name: "max hour duration",
value: (math.MaxInt64 / time.Hour) * time.Hour,
expectedSerial: "INTERVAL '2562047' HOUR",
},
{
name: "min hour duration",
value: (math.MinInt64 / time.Hour) * time.Hour,
expectedSerial: "INTERVAL '-2562047' HOUR",
},
{
name: "max minute duration",
value: (math.MaxInt64 / time.Minute) * time.Minute,
expectedSerial: "INTERVAL '153722867' MINUTE",
},
{
name: "min minute duration",
value: (math.MinInt64 / time.Minute) * time.Minute,
expectedSerial: "INTERVAL '-153722867' MINUTE",
},
{
name: "too big second duration",
value: (math.MaxInt64 / time.Second) * time.Second,
expectedSerial: "INTERVAL '153722867' MINUTE",
},
{
name: "too small second duration",
value: (math.MinInt64 / time.Second) * time.Second,
expectedSerial: "INTERVAL '-153722867' MINUTE",
},
{
name: "too big millisecond duration rounds to seconds",
value: time.Millisecond*912 + time.Second*12345678,
expectedSerial: "INTERVAL '12345679' SECOND",
},
{
name: "too small millisecond duration rounds to seconds",
value: -(time.Millisecond*910 + time.Second*123456789),
expectedSerial: "INTERVAL '-123456790' SECOND",
},
{
name: "max duration",
value: time.Duration(math.MaxInt64),
expectedSerial: "INTERVAL '153722867' MINUTE",
},
{
name: "min duration",
value: time.Duration(math.MinInt64),
expectedSerial: "INTERVAL '-153722867' MINUTE",
},
{
name: "max allowed second duration",
value: math.MaxInt32 * time.Second,
expectedSerial: "INTERVAL '2147483647' SECOND",
},
{
name: "min allowed second duration",
value: -math.MaxInt32 * time.Second,
expectedSerial: "INTERVAL '-2147483647' SECOND",
},
{
name: "max allowed second with milliseconds duration",
value: 999999999*time.Second + 900*time.Millisecond,
expectedSerial: "INTERVAL '999999999.9' SECOND",
},
{
name: "min allowed second with milliseconds duration",
value: -999999999*time.Second - 900*time.Millisecond,
expectedSerial: "INTERVAL '-999999999.9' SECOND",
},
{
name: "nil",
value: nil,
Expand Down

0 comments on commit f2181ae

Please sign in to comment.