-
-
Notifications
You must be signed in to change notification settings - Fork 935
Proof of concept for supporting temporal values as Scanners #344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
I used |
|
I've looked at this again, and I'm liking a lot of what I'm seeing. However, I'm not sure that only getting the values out of the database is going to be enough. Have you considered at all whether any additional functionality should be provided, and if so, what kind? For an example, the first thing that comes to mind is how difficult (AFAIK, at least) it is to currently do the equivalent of "timestamp AT TIME ZONE .." in Go when the timestamp (without time zone) comes from the database.
I'm confused about what you mean by this. Can you elaborate on this point a bit? |
In my experience, any kind of arithmetic should happen in the full context of a particular location/locale. Go's I thought about providing methods that make it a little easier for users to convert these values to // Time builds a time.Time object representing the value of this clock
// at a specific date and location.
func (c Clock) Time(year int, month time.Month, day int, loc *time.Location) time.Time {
return time.Date(year, month, day, c.Hour, c.Minute, c.Second, c.Nanosecond, loc)
}But we would have to decide how to handle infinite values for date and timestamp types.
"Currently" with this proposal or without? With this proposal, one need only pass the fields of type TimeInMyAppLocation time.Time
func (t *TimeInMyAppLocation) Scan(src interface{}) (err error) {
var ts pq.Timestamp
if err = ts.Scan(src); err == nil {
*t = TimeInMyAppLocation(time.Date(
ts.Year, ts.Month, ts.Day,
ts.Hour, ts.Minute, ts.Second, ts.Nanosecond,
time.UTC))
}
return
}
I mean that we can allow the user to choose, through some configuration, whether temporal values are returned as |
|
#391 indicates that not all canonical representations of |
|
I agree that a convenience method to extract the time would be useful. The current behavior when scanning a And a DefaultTime could be implemented as |
|
That also means we can pretty easily do Or we could write a convenience function: |
The features provided by the temporal types of Postgres differ from those of Go's
time.Time. We should read and write values of these types in such a way as to:This PR proposes to use a handful of Go types that implement
sql.Scanneranddriver.Valuerto achieve these goals.Preserve meaning
The
date,timestampandtimestamptztypes support infinite values which are explicitly outside the range of non-infinite (i.e. normal) values. The proposedpq.Date,pq.Timestampandpq.TimestampTZtypes have anInfinityfield to represent these special values.The Postgres temporal types that lack a date or a time zone contain only as much information as a calendar or a wall clock. The proposed types contain the same amount of information (e.g. no default time zone.)
Maximize usability
When not concerned about infinite values, a user can scan a
timestamptzvalue directly into atime.Time.Multiple proposed types can be used as scan destinations for multiple Postgres temporal values. For example, a
timestampcan be scanned into apq.Date.When using
pq.Clock,pq.Dateorpq.Timestamp, a user need not consider the behavior of thetimepackage at all. Even so, these types are congruous with thetimepackage. For example, one may implement ansql.Scannerthat uses these types as intermediaries, passing their fields directly totime.Date().Minimize maintenance
Since there is little to no guidance for how a driver should return
time.Timevalues, it is important to clearly document how temporal values are handled. The proposed, exported types provide a natural place for such documentation, including the disparities between Postgres temporal types and thetimepackage.The composition of these types should result in a few small units of shared code, and these units will likely return
errorto match thesql.Scannerinterface. The related tests can be focused, succinct and thorough.Maximize performance
For most temporal types, parsing can be deferred until the scan destination is known. In these cases, it is possible to limit processing to only the fields requested by the user. For example, only the year, month and day of a
timestampvalue need to be parsed when populating apq.Date.For these same types, the driver is responsible only for parsing the various Postgres output formats into numeric fields. The user decides if/when to incur the cost of calculating a
time.Timevalue.Mappings to/from Go and Postgres
From Backend
timestamp[]bytepq.Date,pq.Timestamptimestamptz[]byte,1time.Timepq.Date,pq.Timestamp,pq.TimestampTZdate[]bytepq.Datetime[]bytepq.Clocktimetz[]byteTo Backend
time.Timedate,2time,2timetz,timestamp,2timestamptzpq.Timestamp[]bytedate,timestamp,timestamptz3pq.TimestampTZ[]bytedate,2timestamp,2timestamptzpq.Date[]bytedate,timestamp,timestamptz3pq.Clock[]bytetime,timetz31 When
"infinity"or"-infinity"2 Time zone is silently ignored
3 Interpreted as being in the session time zone
Transition
The proposed types work correctly without any changes to the existing driver encode/decode process. The existing (driver) parsing and formatting code can be refactored toward this interface, eliminating duplicated functionality.
The (expected) performance gains from returning
[]bytefor most types can be activated with a package-level configuration.If this interface is preferred by maintainers, the existing/default behavior can be deprecated and eventually eliminated.