From a530c913093d8e9b62aad644626f653bd73b74bd Mon Sep 17 00:00:00 2001 From: Dan Peterson Date: Wed, 20 Oct 2021 19:40:06 -0300 Subject: [PATCH] driver: return error strings for constraint errors In conn.step, use conn.errstr which gets the error from sqlite instead of looking up the result code in the ErrorCodeString map. This changes the code 5 (SQLITE_BUSY) message slightly, including "database is locked" as returned my errstr. "SQLITE_BUSY" is still added to the message. Fixes #73 --- all_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++- sqlite.go | 11 +++++++---- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/all_test.go b/all_test.go index 4770e3e..9c869bb 100644 --- a/all_test.go +++ b/all_test.go @@ -1880,7 +1880,7 @@ func TestIssue66(t *testing.T) { // think that's correct (jnml). t.Logf("insert 2: %v", err) - if !strings.Contains(err.Error(), "SQLITE_BUSY") { + if !strings.Contains(err.Error(), "database is locked (5) (SQLITE_BUSY)") { t.Fatalf("insert 2: %v", err) } } @@ -2010,3 +2010,58 @@ func testIssue65(t *testing.T, db *sql.DB, canFail bool) { } } } + +// https://gitlab.com/cznic/sqlite/-/issues/73 +func TestConstraintPrimaryKeyError(t *testing.T) { + db, err := sql.Open(driverName, "file::memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS hash (hashval TEXT PRIMARY KEY NOT NULL)`) + if err != nil { + t.Fatal(err) + } + + _, err = db.Exec("INSERT INTO hash (hashval) VALUES (?)", "somehashval") + if err != nil { + t.Fatal(err) + } + + _, err = db.Exec("INSERT INTO hash (hashval) VALUES (?)", "somehashval") + if err == nil { + t.Fatal("wanted error") + } + + if errs, want := err.Error(), "constraint failed: UNIQUE constraint failed: hash.hashval (1555)"; errs != want { + t.Fatalf("got error string %q, want %q", errs, want) + } +} + +func TestConstraintUniqueError(t *testing.T) { + db, err := sql.Open(driverName, "file::memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS hash (hashval TEXT UNIQUE)`) + if err != nil { + t.Fatal(err) + } + + _, err = db.Exec("INSERT INTO hash (hashval) VALUES (?)", "somehashval") + if err != nil { + t.Fatal(err) + } + + _, err = db.Exec("INSERT INTO hash (hashval) VALUES (?)", "somehashval") + if err == nil { + t.Fatal("wanted error") + } + + if errs, want := err.Error(), "constraint failed: UNIQUE constraint failed: hash.hashval (2067)"; errs != want { + t.Fatalf("got error string %q, want %q", errs, want) + } +} diff --git a/sqlite.go b/sqlite.go index ccf079e..5383d12 100644 --- a/sqlite.go +++ b/sqlite.go @@ -11,7 +11,6 @@ import ( "context" "database/sql" "database/sql/driver" - "errors" "fmt" "io" "math" @@ -883,7 +882,7 @@ func (c *conn) step(pstmt uintptr) (int, error) { return int(rc), nil default: - return int(rc), errors.New(ErrorCodeString[int(rc)]) + return int(rc), c.errstr(rc) } } } @@ -1225,11 +1224,15 @@ func (c *conn) errstr(rc int32) error { p := sqlite3.Xsqlite3_errstr(c.tls, rc) str := libc.GoString(p) p = sqlite3.Xsqlite3_errmsg(c.tls, c.db) + var s string + if rc == sqlite3.SQLITE_BUSY { + s = " (SQLITE_BUSY)" + } switch msg := libc.GoString(p); { case msg == str: - return &Error{msg: fmt.Sprintf("%s (%v)", str, rc), code: int(rc)} + return &Error{msg: fmt.Sprintf("%s (%v)%s", str, rc, s), code: int(rc)} default: - return &Error{msg: fmt.Sprintf("%s: %s (%v)", str, msg, rc), code: int(rc)} + return &Error{msg: fmt.Sprintf("%s: %s (%v)%s", str, msg, rc, s), code: int(rc)} } }