From 93ce586139da959a135293766c2ab33021982318 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Tue, 18 Apr 2023 01:00:59 +0100 Subject: [PATCH] Optimize time. --- driver/driver.go | 6 +++--- driver/time.go | 14 +++++++------- driver/time_test.go | 8 ++++---- stmt.go | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/driver/driver.go b/driver/driver.go index 14acc8c..2a47af4 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -318,7 +318,7 @@ func (s stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (drive case sqlite3.ZeroBlob: err = s.stmt.BindZeroBlob(id, int64(a)) case time.Time: - err = s.stmt.BindText(id, a.Format(time.RFC3339Nano)) + err = s.stmt.BindTime(id, a, sqlite3.TimeFormatDefault) case nil: err = s.stmt.BindNull(id) default: @@ -389,10 +389,10 @@ func (r rows) Next(dest []driver.Value) error { dest[i] = r.stmt.ColumnInt64(i) case sqlite3.FLOAT: dest[i] = r.stmt.ColumnFloat(i) - case sqlite3.TEXT: - dest[i] = maybeTime(r.stmt.ColumnText(i)) case sqlite3.BLOB: dest[i] = r.stmt.ColumnRawBlob(i) + case sqlite3.TEXT: + dest[i] = stringOrTime(r.stmt.ColumnRawText(i)) case sqlite3.NULL: if buf, ok := dest[i].([]byte); ok { dest[i] = buf[0:0] diff --git a/driver/time.go b/driver/time.go index 5c4f3d0..fa3247d 100644 --- a/driver/time.go +++ b/driver/time.go @@ -9,23 +9,23 @@ import ( // if it roundtrips back to the same string. // This way times can be persisted to, and recovered from, the database, // but if a string is needed, [database/sql] will recover the same string. -func maybeTime(text string) driver.Value { +func stringOrTime(text []byte) driver.Value { // Weed out (some) values that can't possibly be // [time.RFC3339Nano] timestamps. if len(text) < len("2006-01-02T15:04:05Z") { - return text + return string(text) } if len(text) > len(time.RFC3339Nano) { - return text + return string(text) } if text[4] != '-' || text[10] != 'T' || text[16] != ':' { - return text + return string(text) } // Slow path. - date, err := time.Parse(time.RFC3339Nano, text) - if err == nil && date.Format(time.RFC3339Nano) == text { + date, err := time.Parse(time.RFC3339Nano, string(text)) + if err == nil && date.Format(time.RFC3339Nano) == string(text) { return date } - return text + return string(text) } diff --git a/driver/time_test.go b/driver/time_test.go index 2690159..e0306ff 100644 --- a/driver/time_test.go +++ b/driver/time_test.go @@ -6,7 +6,7 @@ import ( ) // This checks that any string can be recovered as the same string. -func Fuzz_maybeTime_1(f *testing.F) { +func Fuzz_stringOrTime_1(f *testing.F) { f.Add("") f.Add(" ") f.Add("SQLite") @@ -22,7 +22,7 @@ func Fuzz_maybeTime_1(f *testing.F) { f.Add("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") f.Fuzz(func(t *testing.T, str string) { - value := maybeTime(str) + value := stringOrTime([]byte(str)) switch v := value.(type) { case time.Time: @@ -48,7 +48,7 @@ func Fuzz_maybeTime_1(f *testing.F) { // This checks that any [time.Time] can be recovered as a [time.Time], // with nanosecond accuracy, and preserving any timezone offset. -func Fuzz_maybeTime_2(f *testing.F) { +func Fuzz_stringOrTime_2(f *testing.F) { f.Add(0, 0) f.Add(0, 1) f.Add(0, -1) @@ -59,7 +59,7 @@ func Fuzz_maybeTime_2(f *testing.F) { f.Add(-763421161058, 222_222_222) // twosday, year 22222BC checkTime := func(t *testing.T, date time.Time) { - value := maybeTime(date.Format(time.RFC3339Nano)) + value := stringOrTime([]byte(date.Format(time.RFC3339Nano))) switch v := value.(type) { case time.Time: diff --git a/stmt.go b/stmt.go index d64ddf1..9121d86 100644 --- a/stmt.go +++ b/stmt.go @@ -217,6 +217,9 @@ func (s *Stmt) BindNull(param int) error { // // https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error { + if format == TimeFormatDefault { + return s.bindRFC3339Nano(param, value) + } switch v := format.Encode(value).(type) { case string: s.BindText(param, v) @@ -230,6 +233,20 @@ func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error { return nil } +func (s *Stmt) bindRFC3339Nano(param int, value time.Time) error { + const maxlen = uint64(len(time.RFC3339Nano)) + + ptr := s.c.new(maxlen) + buf := util.View(s.c.mod, ptr, maxlen) + buf = value.AppendFormat(buf[:0], time.RFC3339Nano) + + r := s.c.call(s.c.api.bindText, + uint64(s.handle), uint64(param), + uint64(ptr), uint64(len(buf)), + uint64(s.c.api.destructor), _UTF8) + return s.c.error(r[0]) +} + // ColumnCount returns the number of columns in a result set. // // https://www.sqlite.org/c3ref/column_count.html