Skip to content

Commit b4f5cc7

Browse files
rittnejemattn
authored andcommitted
add SystemErrno to Error (#740)
* adding SystemErrno to Error, and fixing error logic when open fails * fix for old versions of libsqlite3 that do not have sqlite3_system_errno defined * fixing pre-processor logic
1 parent 590d44c commit b4f5cc7

File tree

4 files changed

+77
-12
lines changed

4 files changed

+77
-12
lines changed

error.go

+17-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55

66
package sqlite3
77

8+
/*
9+
#ifndef USE_LIBSQLITE3
10+
#include <sqlite3-binding.h>
11+
#else
12+
#include <sqlite3.h>
13+
#endif
14+
*/
815
import "C"
16+
import "syscall"
917

1018
// ErrNo inherit errno.
1119
type ErrNo int
@@ -20,6 +28,7 @@ type ErrNoExtended int
2028
type Error struct {
2129
Code ErrNo /* The error code returned by SQLite */
2230
ExtendedCode ErrNoExtended /* The extended error code returned by SQLite */
31+
SystemErrno syscall.Errno /* The system errno returned by the OS through SQLite, if applicable */
2332
err string /* The error string returned by sqlite3_errmsg(),
2433
this usually contains more specific details. */
2534
}
@@ -72,10 +81,16 @@ func (err ErrNoExtended) Error() string {
7281
}
7382

7483
func (err Error) Error() string {
84+
var str string
7585
if err.err != "" {
76-
return err.err
86+
str = err.err
87+
} else {
88+
str = C.GoString(C.sqlite3_errstr(C.int(err.Code)))
7789
}
78-
return errorString(err)
90+
if err.SystemErrno != 0 {
91+
str += ": " + err.SystemErrno.Error()
92+
}
93+
return str
7994
}
8095

8196
// result codes from http://www.sqlite.org/c3ref/c_abort_rollback.html

error_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,36 @@ func TestExtendedErrorCodes_Unique(t *testing.T) {
240240
extended, expected)
241241
}
242242
}
243+
}
244+
245+
func TestError_SystemErrno(t *testing.T) {
246+
_, n, _ := Version()
247+
if n < 3012000 {
248+
t.Skip("sqlite3_system_errno requires sqlite3 >= 3.12.0")
249+
}
250+
251+
// open a non-existent database in read-only mode so we get an IO error.
252+
db, err := sql.Open("sqlite3", "file:nonexistent.db?mode=ro")
253+
if err != nil {
254+
t.Fatal(err)
255+
}
256+
defer db.Close()
243257

258+
err = db.Ping()
259+
if err == nil {
260+
t.Fatal("expected error pinging read-only non-existent database, but got nil")
261+
}
262+
263+
serr, ok := err.(Error)
264+
if !ok {
265+
t.Fatalf("expected error to be of type Error, but got %[1]T %[1]v", err)
266+
}
267+
268+
if serr.SystemErrno == 0 {
269+
t.Fatal("expected SystemErrno to be set")
270+
}
271+
272+
if !os.IsNotExist(serr.SystemErrno) {
273+
t.Errorf("expected SystemErrno to be a not exists error, but got %v", serr.SystemErrno)
274+
}
244275
}

sqlite3.go

+27-8
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ static int _sqlite3_limit(sqlite3* db, int limitId, int newLimit) {
183183
return sqlite3_limit(db, limitId, newLimit);
184184
#endif
185185
}
186+
187+
#if SQLITE_VERSION_NUMBER < 3012000
188+
static int sqlite3_system_errno(sqlite3 *db) {
189+
return 0;
190+
}
191+
#endif
186192
*/
187193
import "C"
188194
import (
@@ -198,6 +204,7 @@ import (
198204
"strconv"
199205
"strings"
200206
"sync"
207+
"syscall"
201208
"time"
202209
"unsafe"
203210
)
@@ -749,15 +756,28 @@ func (c *SQLiteConn) lastError() error {
749756
return lastError(c.db)
750757
}
751758

759+
// Note: may be called with db == nil
752760
func lastError(db *C.sqlite3) error {
753-
rv := C.sqlite3_errcode(db)
761+
rv := C.sqlite3_errcode(db) // returns SQLITE_NOMEM if db == nil
754762
if rv == C.SQLITE_OK {
755763
return nil
756764
}
765+
extrv := C.sqlite3_extended_errcode(db) // returns SQLITE_NOMEM if db == nil
766+
errStr := C.GoString(C.sqlite3_errmsg(db)) // returns "out of memory" if db == nil
767+
768+
// https://www.sqlite.org/c3ref/system_errno.html
769+
// sqlite3_system_errno is only meaningful if the error code was SQLITE_CANTOPEN,
770+
// or it was SQLITE_IOERR and the extended code was not SQLITE_IOERR_NOMEM
771+
var systemErrno syscall.Errno
772+
if rv == C.SQLITE_CANTOPEN || (rv == C.SQLITE_IOERR && extrv != C.SQLITE_IOERR_NOMEM) {
773+
systemErrno = syscall.Errno(C.sqlite3_system_errno(db))
774+
}
775+
757776
return Error{
758777
Code: ErrNo(rv),
759-
ExtendedCode: ErrNoExtended(C.sqlite3_extended_errcode(db)),
760-
err: C.GoString(C.sqlite3_errmsg(db)),
778+
ExtendedCode: ErrNoExtended(extrv),
779+
SystemErrno: systemErrno,
780+
err: errStr,
761781
}
762782
}
763783

@@ -869,10 +889,6 @@ func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) {
869889
return &SQLiteTx{c}, nil
870890
}
871891

872-
func errorString(err Error) string {
873-
return C.GoString(C.sqlite3_errstr(C.int(err.Code)))
874-
}
875-
876892
// Open database and return a new connection.
877893
//
878894
// A pragma can take either zero or one argument.
@@ -1342,10 +1358,13 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
13421358
mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
13431359
nil)
13441360
if rv != 0 {
1361+
// Save off the error _before_ closing the database.
1362+
// This is safe even if db is nil.
1363+
err := lastError(db)
13451364
if db != nil {
13461365
C.sqlite3_close_v2(db)
13471366
}
1348-
return nil, Error{Code: ErrNo(rv)}
1367+
return nil, err
13491368
}
13501369
if db == nil {
13511370
return nil, errors.New("sqlite succeeded without returning a database")

sqlite3_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,8 @@ func TestInsert(t *testing.T) {
305305

306306
func TestUpsert(t *testing.T) {
307307
_, n, _ := Version()
308-
if !(n >= 3024000) {
309-
t.Skip("UPSERT requires sqlite3 => 3.24.0")
308+
if n < 3024000 {
309+
t.Skip("UPSERT requires sqlite3 >= 3.24.0")
310310
}
311311
tempFilename := TempFilename(t)
312312
defer os.Remove(tempFilename)

0 commit comments

Comments
 (0)