From f65db67ea3ece8b3ce3023a59b59519865d7473a Mon Sep 17 00:00:00 2001 From: Micah Stetson Date: Sat, 29 Dec 2012 16:24:53 -0800 Subject: [PATCH 1/3] Refactor timestamp tests --- sqlite3_test.go | 211 ++++++++---------------------------------------- 1 file changed, 33 insertions(+), 178 deletions(-) diff --git a/sqlite3_test.go b/sqlite3_test.go index 5cd61c3..6a508bc 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -245,35 +245,34 @@ func TestTimestamp(t *testing.T) { defer db.Close() _, err = db.Exec("DROP TABLE foo") - _, err = db.Exec("CREATE TABLE foo(id INTEGER, ts timeSTAMP)") + _, err = db.Exec("CREATE TABLE foo(id INTEGER, ts timeSTAMP, dt DATETIME)") if err != nil { t.Fatal("Failed to create table:", err) } timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC) - _, err = db.Exec("INSERT INTO foo(id, ts) VALUES(1, ?)", timestamp1) - if err != nil { - t.Fatal("Failed to insert timestamp:", err) + timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 3000000, time.UTC) + tests := []struct { + value interface{} + expected time.Time + }{ + {timestamp1, timestamp1}, + {timestamp1.Unix(), timestamp1}, + {"nonsense", time.Time{}}, + {"0000-00-00 00:00:00", time.Time{}}, + {timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1}, + {"2012-11-04", time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC)}, + {timestamp2, timestamp2}, + {"2006-01-02 15:04:05.003", timestamp2}, + } + for i := range tests { + _, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value) + if err != nil { + t.Fatal("Failed to insert timestamp:", err) + } } - timestamp2 := time.Date(2012, time.April, 6, 23, 22, 0, 0, time.UTC) - _, err = db.Exec("INSERT INTO foo(id, ts) VALUES(2, ?)", timestamp2.Unix()) - if err != nil { - t.Fatal("Failed to insert timestamp:", err) - } - - _, err = db.Exec("INSERT INTO foo(id, ts) VALUES(3, ?)", "nonsense") - if err != nil { - t.Fatal("Failed to insert nonsense:", err) - } - - timestamp4 := time.Date(2012, time.April, 6, 23, 22, 0, 0, time.FixedZone("TEST", -7*3600)) - _, err = db.Exec("INSERT INTO foo(id, ts) VALUES(4, ?)", timestamp4) - if err != nil { - t.Fatal("Failed to insert timestamp:", err) - } - - rows, err := db.Query("SELECT id, ts FROM foo ORDER BY id ASC") + rows, err := db.Query("SELECT id, ts, dt FROM foo ORDER BY id ASC") if err != nil { t.Fatal("Unable to query foo table:", err) } @@ -282,44 +281,27 @@ func TestTimestamp(t *testing.T) { seen := 0 for rows.Next() { var id int - var ts time.Time + var ts, dt time.Time - if err := rows.Scan(&id, &ts); err != nil { + if err := rows.Scan(&id, &ts, &dt); err != nil { t.Error("Unable to scan results:", err) continue } - - if id == 1 { - seen += 1 - if !timestamp1.Equal(ts) { - t.Errorf("Value for id 1 should be %v, not %v", timestamp1, ts) - } + if id < 0 || id >= len(tests) { + t.Error("Bad row id: ", id) + continue } - - if id == 2 { - seen += 1 - if !timestamp2.Equal(ts) { - t.Errorf("Value for id 2 should be %v, not %v", timestamp2, ts) - } + seen++ + if !tests[id].expected.Equal(ts) { + t.Errorf("Timestamp value for id %v should be %v, not %v", id, tests[id].expected, ts) } - - if id == 3 { - seen += 1 - if !ts.IsZero() { - t.Errorf("Value for id 3 should be the zero time, not %v", ts) - } - } - - if id == 4 { - seen += 1 - if !timestamp4.Equal(ts) { - t.Errorf("Value for id 4 should be %v, not %v", timestamp4, ts) - } + if !tests[id].expected.Equal(dt) { + t.Errorf("Datetime value for id %v should be %v, not %v", id, tests[id].expected, dt) } } - if seen != 4 { - t.Error("Expected to see four timestamps") + if seen != len(tests) { + t.Errorf("Expected to see %d rows", len(tests)) } } @@ -414,130 +396,3 @@ func TestBoolean(t *testing.T) { } } -func TestDateOnlyTimestamp(t *testing.T) { - // make sure that timestamps without times are extractable. these are generated when - // e.g., you use the sqlite `DATE()` built-in. - - db, er := sql.Open("sqlite3", "db.sqlite") - if er != nil { - t.Fatal(er) - } - defer func() { - db.Close() - os.Remove("db.sqlite") - }() - - _, er = db.Exec(` - CREATE TABLE test - ( entry_id INTEGER PRIMARY KEY AUTOINCREMENT - , entry_published TIMESTAMP NOT NULL - ) - `) - if er != nil { - t.Fatal(er) - } - - _, er = db.Exec(` - INSERT INTO test VALUES ( 1, '2012-11-04') - `) - if er != nil { - t.Fatal(er) - } - - rows, er := db.Query("SELECT entry_id, entry_published FROM test") - if er != nil { - t.Fatal(er) - } - defer rows.Close() - - if !rows.Next() { - if er := rows.Err(); er != nil { - t.Fatal(er) - } else { - t.Fatalf("Unable to extract row containing date-only timestamp") - } - } - - var entryId int64 - var entryPublished time.Time - - if er := rows.Scan(&entryId, &entryPublished); er != nil { - t.Fatal(er) - } - - if entryId != 1 { - t.Errorf("EntryId does not match inserted value") - } - - if entryPublished.Year() != 2012 || entryPublished.Month() != 11 || entryPublished.Day() != 4 { - t.Errorf("Extracted time does not match inserted value") - } -} - -func TestDatetime(t *testing.T) { - db, err := sql.Open("sqlite3", "./datetime.db") - if err != nil { - t.Fatal("Failed to open database:", err) - } - - defer func() { - db.Close() - os.Remove("./datetime.db") - }() - - _, err = db.Exec("DROP TABLE datetimetest") - _, err = db.Exec(` - CREATE TABLE datetimetest - ( entry_id INTEGER - , my_datetime DATETIME - ) - `) - - if err != nil { - t.Fatal("Failed to create table:", err) - } - datetime := "2006-01-02 15:04:05.003" - _, err = db.Exec(` - INSERT INTO datetimetest(entry_id, my_datetime) - VALUES(1, ?)`, datetime) - if err != nil { - t.Fatal("Failed to insert datetime:", err) - } - - rows, err := db.Query( - "SELECT entry_id, my_datetime FROM datetimetest ORDER BY entry_id ASC") - if err != nil { - t.Fatal("Unable to query datetimetest table:", err) - } - defer rows.Close() - - if !rows.Next() { - if er := rows.Err(); er != nil { - t.Fatal(er) - } else { - t.Fatalf("Unable to extract row containing datetime") - } - } - - var entryId int - var myDatetime time.Time - - if err := rows.Scan(&entryId, &myDatetime); err != nil { - t.Error("Unable to scan results:", err) - } - - if entryId != 1 { - t.Errorf("EntryId does not match inserted value") - } - - if myDatetime.Year() != 2006 || - myDatetime.Month() != 1 || - myDatetime.Day() != 2 || - myDatetime.Hour() != 15 || - myDatetime.Minute() != 4 || - myDatetime.Second() != 5 || - myDatetime.Nanosecond() != 3000000 { - t.Errorf("Extracted time does not match inserted value") - } - -} From ce139f706b870082071a9d9fd18284c7b7fc9d65 Mon Sep 17 00:00:00 2001 From: Micah Stetson Date: Sat, 29 Dec 2012 16:36:29 -0800 Subject: [PATCH 2/3] Support time values with nanosecond precision --- sqlite3.go | 27 ++++++++++++++------------- sqlite3_test.go | 4 ++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index fb446cc..6c0fc61 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -40,9 +40,15 @@ import ( "unsafe" ) -const SQLiteTimestampFormat = "2006-01-02 15:04:05" -const SQLiteDateFormat = "2006-01-02" -const SQLiteDatetimeFormat = "2006-01-02 15:04:05.000" +// Timestamp formats understood by both this module and SQLite. +// The first format in the slice will be used when saving time values +// into the database. When parsing a string from a timestamp or +// datetime column, the formats are tried in order. +var SQLiteTimestampFormats = []string{ + "2006-01-02 15:04:05.999999999", + "2006-01-02 15:04:05", + "2006-01-02", +} func init() { sql.Register("sqlite3", &SQLiteDriver{}) @@ -213,7 +219,7 @@ func (s *SQLiteStmt) bind(args []driver.Value) error { } rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(p), C.int(len(v))) case time.Time: - b := []byte(v.UTC().Format(SQLiteTimestampFormat)) + b := []byte(v.UTC().Format(SQLiteTimestampFormats[0])) rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b))) } if rv != C.SQLITE_OK { @@ -327,19 +333,14 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error { switch rc.decltype[i] { case "timestamp", "datetime": - for { - if dest[i], err = time.Parse(SQLiteTimestampFormat, s); err == nil { - break - } - if dest[i], err = time.Parse(SQLiteDateFormat, s); err == nil { - break - } - if dest[i], err = time.Parse(SQLiteDatetimeFormat, s); err == nil { + for _, format := range SQLiteTimestampFormats { + if dest[i], err = time.Parse(format, s); err == nil { break } + } + if err != nil { // The column is a time value, so return the zero time on parse failure. dest[i] = time.Time{} - break } default: dest[i] = s diff --git a/sqlite3_test.go b/sqlite3_test.go index 6a508bc..a5f324f 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -251,7 +251,7 @@ func TestTimestamp(t *testing.T) { } timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC) - timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 3000000, time.UTC) + timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC) tests := []struct { value interface{} expected time.Time @@ -263,7 +263,7 @@ func TestTimestamp(t *testing.T) { {timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1}, {"2012-11-04", time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC)}, {timestamp2, timestamp2}, - {"2006-01-02 15:04:05.003", timestamp2}, + {"2006-01-02 15:04:05.123456789", timestamp2}, } for i := range tests { _, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value) From 44496728c24982a76893272b23d0c7e949b5818c Mon Sep 17 00:00:00 2001 From: Micah Stetson Date: Sat, 29 Dec 2012 16:51:15 -0800 Subject: [PATCH 3/3] Support more of the timestamp formats undestood by SQLite --- sqlite3.go | 4 ++++ sqlite3_test.go | 22 +++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index 6c0fc61..0a21d23 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -46,7 +46,11 @@ import ( // datetime column, the formats are tried in order. var SQLiteTimestampFormats = []string{ "2006-01-02 15:04:05.999999999", + "2006-01-02T15:04:05.999999999", "2006-01-02 15:04:05", + "2006-01-02T15:04:05", + "2006-01-02 15:04", + "2006-01-02T15:04", "2006-01-02", } diff --git a/sqlite3_test.go b/sqlite3_test.go index a5f324f..9bcc5f3 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -252,18 +252,30 @@ func TestTimestamp(t *testing.T) { timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC) timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC) + timestamp3 := time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC) tests := []struct { value interface{} expected time.Time }{ - {timestamp1, timestamp1}, - {timestamp1.Unix(), timestamp1}, {"nonsense", time.Time{}}, {"0000-00-00 00:00:00", time.Time{}}, + {timestamp1, timestamp1}, + {timestamp1.Unix(), timestamp1}, {timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1}, - {"2012-11-04", time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC)}, + {timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1}, + {timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1}, + {timestamp1.Format("2006-01-02 15:04:05"), timestamp1}, + {timestamp1.Format("2006-01-02T15:04:05"), timestamp1}, {timestamp2, timestamp2}, {"2006-01-02 15:04:05.123456789", timestamp2}, + {"2006-01-02T15:04:05.123456789", timestamp2}, + {"2012-11-04", timestamp3}, + {"2012-11-04 00:00", timestamp3}, + {"2012-11-04 00:00:00", timestamp3}, + {"2012-11-04 00:00:00.000", timestamp3}, + {"2012-11-04T00:00", timestamp3}, + {"2012-11-04T00:00:00", timestamp3}, + {"2012-11-04T00:00:00.000", timestamp3}, } for i := range tests { _, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value) @@ -293,10 +305,10 @@ func TestTimestamp(t *testing.T) { } seen++ if !tests[id].expected.Equal(ts) { - t.Errorf("Timestamp value for id %v should be %v, not %v", id, tests[id].expected, ts) + t.Errorf("Timestamp value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt) } if !tests[id].expected.Equal(dt) { - t.Errorf("Datetime value for id %v should be %v, not %v", id, tests[id].expected, dt) + t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt) } }