From ac0129617fd4293e7c341a48bba2adfc85f5afe1 Mon Sep 17 00:00:00 2001 From: Egon Elbre Date: Sun, 12 Apr 2015 14:59:29 +0300 Subject: [PATCH 1/5] Fix NULs in text. NUL character is a valid symbols in UTF8. Fixes #195 --- sqlite3.go | 4 +++- sqlite3_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/sqlite3.go b/sqlite3.go index f4de3fd..99924bf 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -624,7 +624,9 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error { case C.SQLITE_TEXT: var err error var timeVal time.Time - s := C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_text(rc.s.s, C.int(i))))) + + n := int(C.sqlite3_column_bytes(rc.s.s, C.int(i))) + s := C.GoStringN((*C.char)(unsafe.Pointer(C.sqlite3_column_text(rc.s.s, C.int(i)))), C.int(n)) switch rc.decltype[i] { case "timestamp", "datetime", "date": diff --git a/sqlite3_test.go b/sqlite3_test.go index aa86011..44e00f1 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -945,3 +945,42 @@ func TestNumberNamedParams(t *testing.T) { 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") + } +} From f91a09fb506e7543841b4a9725e16da84fc82b72 Mon Sep 17 00:00:00 2001 From: Serge Hallyn Date: Fri, 10 Apr 2015 11:32:18 -0500 Subject: [PATCH 2/5] Add a txlock option when opening databases (v2) When specified, changes the default locking at a tx.Begin. Changelog (v2): Add a testcase to ensure _txlock is properly handled. Closes #189 Signed-off-by: Serge Hallyn --- sqlite3.go | 27 +++++++++++++++++++++++---- sqlite3_test.go | 44 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index f4de3fd..c7a87b7 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -117,8 +117,9 @@ type SQLiteDriver struct { // Conn struct. type SQLiteConn struct { - db *C.sqlite3 - loc *time.Location + db *C.sqlite3 + loc *time.Location + txlock string } // Tx struct. @@ -252,7 +253,7 @@ func (c *SQLiteConn) exec(cmd string) (driver.Result, error) { // Begin transaction. func (c *SQLiteConn) Begin() (driver.Tx, error) { - if _, err := c.exec("BEGIN"); err != nil { + if _, err := c.exec(c.txlock); err != nil { return nil, err } return &SQLiteTx{c}, nil @@ -273,12 +274,16 @@ func errorString(err Error) string { // Specify location of time format. It's possible to specify "auto". // _busy_timeout=XXX // Specify value for sqlite3_busy_timeout. +// _txlock=XXX +// Specify locking behavior for transactions. XXX can be "immediate", +// "deferred", "exclusive". func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { if C.sqlite3_threadsafe() == 0 { return nil, errors.New("sqlite library was not compiled for thread-safe operation") } var loc *time.Location + txlock := "BEGIN" busy_timeout := 5000 pos := strings.IndexRune(dsn, '?') if pos >= 1 { @@ -308,6 +313,20 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { busy_timeout = int(iv) } + // _txlock + if val := params.Get("_txlock"); val != "" { + switch val { + case "immediate": + txlock = "BEGIN IMMEDIATE" + case "exclusive": + txlock = "BEGIN EXCLUSIVE" + case "deferred": + txlock = "BEGIN" + default: + return nil, fmt.Errorf("Invalid _txlock: %v", val) + } + } + if !strings.HasPrefix(dsn, "file:") { dsn = dsn[:pos] } @@ -333,7 +352,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { return nil, Error{Code: ErrNo(rv)} } - conn := &SQLiteConn{db: db, loc: loc} + conn := &SQLiteConn{db: db, loc: loc, txlock: txlock} if len(d.Extensions) > 0 { rv = C.sqlite3_enable_load_extension(db, 1) diff --git a/sqlite3_test.go b/sqlite3_test.go index aa86011..deb9706 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -9,6 +9,7 @@ import ( "crypto/rand" "database/sql" "encoding/hex" + "fmt" "net/url" "os" "path/filepath" @@ -25,11 +26,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() @@ -37,11 +44,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) + } + } } } From dee1a37fe11067e97971fac64fa5bff4ef0934f4 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 15 Apr 2015 16:26:27 +0900 Subject: [PATCH 3/5] Z suffix should be no-op --- sqlite3.go | 1 + sqlite3_test.go | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/sqlite3.go b/sqlite3.go index 5620b88..233e7e9 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -650,6 +650,7 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error { switch rc.decltype[i] { case "timestamp", "datetime", "date": var t time.Time + s = strings.TrimSuffix(s, "Z") for _, format := range SQLiteTimestampFormats { if timeVal, err = time.ParseInLocation(format, s, time.UTC); err == nil { t = timeVal diff --git a/sqlite3_test.go b/sqlite3_test.go index 74cafa8..3a7d162 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -8,7 +8,9 @@ package sqlite3 import ( "crypto/rand" "database/sql" + "database/sql/driver" "encoding/hex" + "errors" "fmt" "net/url" "os" @@ -65,7 +67,7 @@ func TestOpen(t *testing.T) { for option, expectedPass := range cases { result, err := doTestOpen(t, option) if result == "" { - if ! expectedPass { + if !expectedPass { errmsg := fmt.Sprintf("_txlock error not caught at dbOpen with option: %s", option) t.Fatal(errmsg) } @@ -1018,3 +1020,41 @@ func TestStringContainingZero(t *testing.T) { 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 { + fmt.Printf("%T\n", value) + + 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) + } +} From f136f0c8dcab5adb705d1d356685c33983b5210b Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 15 Apr 2015 16:27:00 +0900 Subject: [PATCH 4/5] Remove debug code --- sqlite3_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/sqlite3_test.go b/sqlite3_test.go index 3a7d162..423f30e 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -1026,8 +1026,6 @@ const CurrentTimeStamp = "2006-01-02 15:04:05" type TimeStamp struct{ *time.Time } func (t TimeStamp) Scan(value interface{}) error { - fmt.Printf("%T\n", value) - var err error switch v := value.(type) { case string: From 542ae647f8601bafd96233961b150cae198e0295 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Tue, 28 Apr 2015 08:57:25 +0900 Subject: [PATCH 5/5] remove -lpthread. related issue #201 --- sqlite3_other.go | 1 - 1 file changed, 1 deletion(-) diff --git a/sqlite3_other.go b/sqlite3_other.go index 8d98b4a..a20d02c 100644 --- a/sqlite3_other.go +++ b/sqlite3_other.go @@ -9,6 +9,5 @@ package sqlite3 /* #cgo CFLAGS: -I. #cgo linux LDFLAGS: -ldl -#cgo LDFLAGS: -lpthread */ import "C"