From 35afdd14fb8ecd07262a26a329fcf86c69e7efc6 Mon Sep 17 00:00:00 2001 From: Frank Braun Date: Sun, 27 Sep 2015 08:30:08 +0200 Subject: [PATCH] update go-sqlite3 & fix tests --- .travis.yml | 1 + MAINTENANCE | 4 +- sqlite3.go | 2 +- sqlite3_test.go | 498 ++++++++++++++++++++++++++++++++++------ sqlite3_test/sqltest.go | 6 +- sqlite3_windows.go | 14 ++ 6 files changed, 449 insertions(+), 76 deletions(-) create mode 100644 sqlite3_windows.go diff --git a/.travis.yml b/.travis.yml index 5b77ae6..424009e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,3 @@ language: go go: 1.5 +script: go test -v . diff --git a/MAINTENANCE b/MAINTENANCE index 6681974..642e507 100644 --- a/MAINTENANCE +++ b/MAINTENANCE @@ -9,7 +9,7 @@ tracked for changes (maintenance details below): Update code from https://github.com/mattn/go-sqlite3 ---------------------------------------------------- -Current HEAD: 3b3f1d01b2696af5501697c35629048c227586ab +Current HEAD: b808f01f660667b1510acf216aa2b9fe7f555ddb Track files: @@ -17,6 +17,8 @@ error.go error_test.go sqlite3.go (dead code and extension loading coding removed) sqlite3_other.go +sqlite3_test.go (adjust path) +sqlite3_windows.go _example/simple/simple.go sqlite3_test/sqltest.go diff --git a/sqlite3.go b/sqlite3.go index b62a9f5..c42924a 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -8,7 +8,7 @@ package sqlite3 /* #cgo CFLAGS: -std=gnu99 #cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE -#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS +#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4_UNICODE61 #include #include #include diff --git a/sqlite3_test.go b/sqlite3_test.go index bcbba44..4517883 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -1,14 +1,25 @@ +// Copyright (C) 2014 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + package sqlite3 import ( - "./sqltest" "crypto/rand" "database/sql" + "database/sql/driver" "encoding/hex" + "errors" + "fmt" + "net/url" "os" "path/filepath" + "strings" "testing" "time" + + "github.com/mutecomm/go-sqlcipher/sqlite3_test" ) func TempFilename() string { @@ -17,11 +28,17 @@ func TempFilename() string { return filepath.Join(os.TempDir(), "foo"+hex.EncodeToString(randBytes)+".db") } -func TestOpen(t *testing.T) { +func doTestOpen(t *testing.T, option string) (string, error) { + var url string tempFilename := TempFilename() - db, err := sql.Open("sqlite3", tempFilename) + if option != "" { + url = tempFilename + option + } else { + url = tempFilename + } + db, err := sql.Open("sqlite3", url) if err != nil { - t.Fatal("Failed to open database:", err) + return "Failed to open database:", err } defer os.Remove(tempFilename) defer db.Close() @@ -29,11 +46,38 @@ func TestOpen(t *testing.T) { _, err = db.Exec("drop table foo") _, err = db.Exec("create table foo (id integer)") if err != nil { - t.Fatal("Failed to create table:", err) + return "Failed to create table:", err } if stat, err := os.Stat(tempFilename); err != nil || stat.IsDir() { - t.Error("Failed to create ./foo.db") + return "Failed to create ./foo.db", nil + } + + return "", nil +} + +func TestOpen(t *testing.T) { + cases := map[string]bool{ + "": true, + "?_txlock=immediate": true, + "?_txlock=deferred": true, + "?_txlock=exclusive": true, + "?_txlock=bogus": false, + } + for option, expectedPass := range cases { + result, err := doTestOpen(t, option) + if result == "" { + if !expectedPass { + errmsg := fmt.Sprintf("_txlock error not caught at dbOpen with option: %s", option) + t.Fatal(errmsg) + } + } else if expectedPass { + if err == nil { + t.Fatal(result) + } else { + t.Fatal(result, err) + } + } } } @@ -303,6 +347,7 @@ func TestTimestamp(t *testing.T) { {"0000-00-00 00:00:00", time.Time{}}, {timestamp1, timestamp1}, {timestamp1.Unix(), timestamp1}, + {timestamp1.UnixNano() / int64(time.Millisecond), timestamp1}, {timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1}, {timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1}, {timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1}, @@ -627,78 +672,389 @@ func TestWAL(t *testing.T) { } } +func TestTimezoneConversion(t *testing.T) { + zones := []string{"UTC", "US/Central", "US/Pacific", "Local"} + for _, tz := range zones { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename+"?_loc="+url.QueryEscape(tz)) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer os.Remove(tempFilename) + defer db.Close() + + _, err = db.Exec("DROP TABLE foo") + _, err = db.Exec("CREATE TABLE foo(id INTEGER, ts TIMESTAMP, dt DATETIME)") + if err != nil { + t.Fatal("Failed to create table:", err) + } + + loc, err := time.LoadLocation(tz) + if err != nil { + t.Fatal("Failed to load location:", err) + } + + 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 + }{ + {"nonsense", time.Time{}.In(loc)}, + {"0000-00-00 00:00:00", time.Time{}.In(loc)}, + {timestamp1, timestamp1.In(loc)}, + {timestamp1.Unix(), timestamp1.In(loc)}, + {timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1.In(loc)}, + {timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1.In(loc)}, + {timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1.In(loc)}, + {timestamp1.Format("2006-01-02 15:04:05"), timestamp1.In(loc)}, + {timestamp1.Format("2006-01-02T15:04:05"), timestamp1.In(loc)}, + {timestamp2, timestamp2.In(loc)}, + {"2006-01-02 15:04:05.123456789", timestamp2.In(loc)}, + {"2006-01-02T15:04:05.123456789", timestamp2.In(loc)}, + {"2012-11-04", timestamp3.In(loc)}, + {"2012-11-04 00:00", timestamp3.In(loc)}, + {"2012-11-04 00:00:00", timestamp3.In(loc)}, + {"2012-11-04 00:00:00.000", timestamp3.In(loc)}, + {"2012-11-04T00:00", timestamp3.In(loc)}, + {"2012-11-04T00:00:00", timestamp3.In(loc)}, + {"2012-11-04T00:00:00.000", timestamp3.In(loc)}, + } + 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) + } + } + + 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) + } + defer rows.Close() + + seen := 0 + for rows.Next() { + var id int + var ts, dt time.Time + + if err := rows.Scan(&id, &ts, &dt); err != nil { + t.Error("Unable to scan results:", err) + continue + } + if id < 0 || id >= len(tests) { + t.Error("Bad row id: ", id) + continue + } + seen++ + if !tests[id].expected.Equal(ts) { + t.Errorf("Timestamp value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, ts) + } + if !tests[id].expected.Equal(dt) { + t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt) + } + if tests[id].expected.Location().String() != ts.Location().String() { + t.Errorf("Location for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected.Location().String(), ts.Location().String()) + } + if tests[id].expected.Location().String() != dt.Location().String() { + t.Errorf("Location for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected.Location().String(), dt.Location().String()) + } + } + + if seen != len(tests) { + t.Errorf("Expected to see %d rows", len(tests)) + } + } +} + func TestSuite(t *testing.T) { - db, err := sql.Open("sqlite3", ":memory:") + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename+"?_busy_timeout=99999") if err != nil { t.Fatal(err) } + defer os.Remove(tempFilename) defer db.Close() - sqltest.RunTests(t, db, sqltest.SQLITE) + sqlite3_test.RunTests(t, db, sqlite3_test.SQLITE) } // TODO: Execer & Queryer currently disabled // https://github.com/mattn/go-sqlite3/issues/82 -//func TestExecer(t *testing.T) { -// tempFilename := TempFilename() -// db, err := sql.Open("sqlite3", tempFilename) -// if err != nil { -// t.Fatal("Failed to open database:", err) -// } -// defer os.Remove(tempFilename) -// defer db.Close() -// -// _, err = db.Exec(` -// create table foo (id integer); -// insert into foo(id) values(?); -// insert into foo(id) values(?); -// insert into foo(id) values(?); -// `, 1, 2, 3) -// if err != nil { -// t.Error("Failed to call db.Exec:", err) -// } -// if err != nil { -// t.Error("Failed to call res.RowsAffected:", err) -// } -//} -// -//func TestQueryer(t *testing.T) { -// tempFilename := TempFilename() -// db, err := sql.Open("sqlite3", tempFilename) -// if err != nil { -// t.Fatal("Failed to open database:", err) -// } -// defer os.Remove(tempFilename) -// defer db.Close() -// -// _, err = db.Exec(` -// create table foo (id integer); -// `) -// if err != nil { -// t.Error("Failed to call db.Query:", err) -// } -// -// rows, err := db.Query(` -// insert into foo(id) values(?); -// insert into foo(id) values(?); -// insert into foo(id) values(?); -// select id from foo order by id; -// `, 3, 2, 1) -// if err != nil { -// t.Error("Failed to call db.Query:", err) -// } -// defer rows.Close() -// n := 1 -// if rows != nil { -// for rows.Next() { -// var id int -// err = rows.Scan(&id) -// if err != nil { -// t.Error("Failed to db.Query:", err) -// } -// if id != n { -// t.Error("Failed to db.Query: not matched results") -// } -// } -// } -//} +func TestExecer(t *testing.T) { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer os.Remove(tempFilename) + defer db.Close() + + _, err = db.Exec(` + create table foo (id integer); -- one comment + insert into foo(id) values(?); + insert into foo(id) values(?); + insert into foo(id) values(?); -- another comment + `, 1, 2, 3) + if err != nil { + t.Error("Failed to call db.Exec:", err) + } +} + +func TestQueryer(t *testing.T) { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer os.Remove(tempFilename) + defer db.Close() + + _, err = db.Exec(` + create table foo (id integer); + `) + if err != nil { + t.Error("Failed to call db.Query:", err) + } + + rows, err := db.Query(` + insert into foo(id) values(?); + insert into foo(id) values(?); + insert into foo(id) values(?); + select id from foo order by id; + `, 3, 2, 1) + if err != nil { + t.Error("Failed to call db.Query:", err) + } + defer rows.Close() + n := 1 + if rows != nil { + for rows.Next() { + var id int + err = rows.Scan(&id) + if err != nil { + t.Error("Failed to db.Query:", err) + } + if id != n { + t.Error("Failed to db.Query: not matched results") + } + } + } +} + +func TestStress(t *testing.T) { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + db.Exec("CREATE TABLE foo (id int);") + db.Exec("INSERT INTO foo VALUES(1);") + db.Exec("INSERT INTO foo VALUES(2);") + db.Close() + + for i := 0; i < 10000; i++ { + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + + for j := 0; j < 3; j++ { + rows, err := db.Query("select * from foo where id=1;") + if err != nil { + t.Error("Failed to call db.Query:", err) + } + for rows.Next() { + var i int + if err := rows.Scan(&i); err != nil { + t.Errorf("Scan failed: %v\n", err) + } + } + if err := rows.Err(); err != nil { + t.Errorf("Post-scan failed: %v\n", err) + } + rows.Close() + } + db.Close() + } +} + +func TestDateTimeLocal(t *testing.T) { + zone := "Asia/Tokyo" + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename+"?_loc="+zone) + if err != nil { + t.Fatal("Failed to open database:", err) + } + db.Exec("CREATE TABLE foo (dt datetime);") + db.Exec("INSERT INTO foo VALUES('2015-03-05 15:16:17');") + + row := db.QueryRow("select * from foo") + var d time.Time + err = row.Scan(&d) + if err != nil { + t.Fatal("Failed to scan datetime:", err) + } + if d.Hour() == 15 || !strings.Contains(d.String(), "JST") { + t.Fatal("Result should have timezone", d) + } + db.Close() + + db, err = sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + + row = db.QueryRow("select * from foo") + err = row.Scan(&d) + if err != nil { + t.Fatal("Failed to scan datetime:", err) + } + if d.UTC().Hour() != 15 || !strings.Contains(d.String(), "UTC") { + t.Fatalf("Result should not have timezone %v %v", zone, d.String()) + } + + _, err = db.Exec("DELETE FROM foo") + if err != nil { + t.Fatal("Failed to delete table:", err) + } + dt, err := time.Parse("2006/1/2 15/4/5 -0700 MST", "2015/3/5 15/16/17 +0900 JST") + if err != nil { + t.Fatal("Failed to parse datetime:", err) + } + db.Exec("INSERT INTO foo VALUES(?);", dt) + + db.Close() + db, err = sql.Open("sqlite3", tempFilename+"?_loc="+zone) + if err != nil { + t.Fatal("Failed to open database:", err) + } + + row = db.QueryRow("select * from foo") + err = row.Scan(&d) + if err != nil { + t.Fatal("Failed to scan datetime:", err) + } + if d.Hour() != 15 || !strings.Contains(d.String(), "JST") { + t.Fatalf("Result should have timezone %v %v", zone, d.String()) + } +} + +func TestVersion(t *testing.T) { + s, n, id := Version() + if s == "" || n == 0 || id == "" { + t.Errorf("Version failed %q, %d, %q\n", s, n, id) + } +} + +func TestNumberNamedParams(t *testing.T) { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer os.Remove(tempFilename) + defer db.Close() + + _, err = db.Exec(` + create table foo (id integer, name text, extra text); + `) + if err != nil { + t.Error("Failed to call db.Query:", err) + } + + _, err = db.Exec(`insert into foo(id, name, extra) values($1, $2, $2)`, 1, "foo") + if err != nil { + t.Error("Failed to call db.Exec:", err) + } + + row := db.QueryRow(`select id, extra from foo where id = $1 and extra = $2`, 1, "foo") + if row == nil { + t.Error("Failed to call db.QueryRow") + } + var id int + var extra string + err = row.Scan(&id, &extra) + if err != nil { + t.Error("Failed to db.Scan:", err) + } + if id != 1 || extra != "foo" { + t.Error("Failed to db.QueryRow: not matched results") + } +} + +func TestStringContainingZero(t *testing.T) { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer os.Remove(tempFilename) + defer db.Close() + + _, err = db.Exec(` + create table foo (id integer, name, extra text); + `) + if err != nil { + t.Error("Failed to call db.Query:", err) + } + + const text = "foo\x00bar" + + _, err = db.Exec(`insert into foo(id, name, extra) values($1, $2, $2)`, 1, text) + if err != nil { + t.Error("Failed to call db.Exec:", err) + } + + row := db.QueryRow(`select id, extra from foo where id = $1 and extra = $2`, 1, text) + if row == nil { + t.Error("Failed to call db.QueryRow") + } + + var id int + var extra string + err = row.Scan(&id, &extra) + if err != nil { + t.Error("Failed to db.Scan:", err) + } + if id != 1 || extra != text { + t.Error("Failed to db.QueryRow: not matched results") + } +} + +const CurrentTimeStamp = "2006-01-02 15:04:05" + +type TimeStamp struct{ *time.Time } + +func (t TimeStamp) Scan(value interface{}) error { + var err error + switch v := value.(type) { + case string: + *t.Time, err = time.Parse(CurrentTimeStamp, v) + case []byte: + *t.Time, err = time.Parse(CurrentTimeStamp, string(v)) + default: + err = errors.New("invalid type for current_timestamp") + } + return err +} + +func (t TimeStamp) Value() (driver.Value, error) { + return t.Time.Format(CurrentTimeStamp), nil +} + +func TestDateTimeNow(t *testing.T) { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + var d time.Time + err = db.QueryRow("SELECT datetime('now')").Scan(TimeStamp{&d}) + if err != nil { + t.Fatal("Failed to scan datetime:", err) + } +} diff --git a/sqlite3_test/sqltest.go b/sqlite3_test/sqltest.go index fc82782..782e15f 100644 --- a/sqlite3_test/sqltest.go +++ b/sqlite3_test/sqltest.go @@ -318,7 +318,7 @@ func BenchmarkQuery(b *testing.B) { var i int var f float64 var s string -// var t time.Time + // var t time.Time if err := db.QueryRow("select null, 1, 1.1, 'foo'").Scan(&n, &i, &f, &s); err != nil { panic(err) } @@ -331,7 +331,7 @@ func BenchmarkParams(b *testing.B) { var i int var f float64 var s string -// var t time.Time + // var t time.Time if err := db.QueryRow("select ?, ?, ?, ?", nil, 1, 1.1, "foo").Scan(&n, &i, &f, &s); err != nil { panic(err) } @@ -350,7 +350,7 @@ func BenchmarkStmt(b *testing.B) { var i int var f float64 var s string -// var t time.Time + // var t time.Time if err := st.QueryRow(nil, 1, 1.1, "foo").Scan(&n, &i, &f, &s); err != nil { panic(err) } diff --git a/sqlite3_windows.go b/sqlite3_windows.go new file mode 100644 index 0000000..abc8384 --- /dev/null +++ b/sqlite3_windows.go @@ -0,0 +1,14 @@ +// Copyright (C) 2014 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +build windows + +package sqlite3 + +/* +#cgo CFLAGS: -I. -fno-stack-check -fno-stack-protector -mno-stack-arg-probe +#cgo windows,386 CFLAGS: -D_localtime32=localtime +#cgo LDFLAGS: -lmingwex -lmingw32 +*/ +import "C"