From ff8e6729ce0628c3da97bd8e85c636f3645c2516 Mon Sep 17 00:00:00 2001
From: Jochen Voss <voss@seehuhn.de>
Date: Thu, 20 Jun 2013 21:52:38 +0100
Subject: [PATCH] Start work on introducing machine-readable error codes.

This commit introduces a new type 'ErrNo', implementing the error
interface.  Constants for all sqlite3 error codes are provided
in the new source file "error.go".
---
 error.go           | 41 +++++++++++++++++++++++++++++++++++++++++
 error_test.go      | 34 ++++++++++++++++++++++++++++++++++
 sqlite3.go         | 26 +++++++++++++++-----------
 sqlite3_other.go   |  2 +-
 sqlite3_test.go    |  2 +-
 sqlite3_windows.go |  2 +-
 6 files changed, 93 insertions(+), 14 deletions(-)
 create mode 100644 error.go
 create mode 100644 error_test.go

diff --git a/error.go b/error.go
new file mode 100644
index 0000000..15c843e
--- /dev/null
+++ b/error.go
@@ -0,0 +1,41 @@
+package sqlite3
+
+import "C"
+
+type ErrNo int
+
+// result codes from http://www.sqlite.org/c3ref/c_abort.html
+var (
+	ErrError      error = ErrNo(1)  /* SQL error or missing database */
+	ErrInternal   error = ErrNo(2)  /* Internal logic error in SQLite */
+	ErrPerm       error = ErrNo(3)  /* Access permission denied */
+	ErrAbort      error = ErrNo(4)  /* Callback routine requested an abort */
+	ErrBusy       error = ErrNo(5)  /* The database file is locked */
+	ErrLocked     error = ErrNo(6)  /* A table in the database is locked */
+	ErrNomem      error = ErrNo(7)  /* A malloc() failed */
+	ErrReadonly   error = ErrNo(8)  /* Attempt to write a readonly database */
+	ErrInterrupt  error = ErrNo(9)  /* Operation terminated by sqlite3_interrupt() */
+	ErrIoErr      error = ErrNo(10) /* Some kind of disk I/O error occurred */
+	ErrCorrupt    error = ErrNo(11) /* The database disk image is malformed */
+	ErrNotFound   error = ErrNo(12) /* Unknown opcode in sqlite3_file_control() */
+	ErrFull       error = ErrNo(13) /* Insertion failed because database is full */
+	ErrCantOpen   error = ErrNo(14) /* Unable to open the database file */
+	ErrProtocol   error = ErrNo(15) /* Database lock protocol error */
+	ErrEmpty      error = ErrNo(16) /* Database is empty */
+	ErrSchema     error = ErrNo(17) /* The database schema changed */
+	ErrTooBig     error = ErrNo(18) /* String or BLOB exceeds size limit */
+	ErrConstraint error = ErrNo(19) /* Abort due to constraint violation */
+	ErrMismatch   error = ErrNo(20) /* Data type mismatch */
+	ErrMisuse     error = ErrNo(21) /* Library used incorrectly */
+	ErrNoLFS      error = ErrNo(22) /* Uses OS features not supported on host */
+	ErrAuth       error = ErrNo(23) /* Authorization denied */
+	ErrFormat     error = ErrNo(24) /* Auxiliary database format error */
+	ErrRange      error = ErrNo(25) /* 2nd parameter to sqlite3_bind out of range */
+	ErrNotADB     error = ErrNo(26) /* File opened that is not a database file */
+	ErrNotice     error = ErrNo(27) /* Notifications from sqlite3_log() */
+	ErrWarning    error = ErrNo(28) /* Warnings from sqlite3_log() */
+)
+
+func (err ErrNo) Error() string {
+	return errorString(err)
+}
diff --git a/error_test.go b/error_test.go
new file mode 100644
index 0000000..197b2f0
--- /dev/null
+++ b/error_test.go
@@ -0,0 +1,34 @@
+package sqlite3
+
+import (
+	"database/sql"
+	"io/ioutil"
+	"os"
+	"path"
+	"testing"
+)
+
+func TestFailures(t *testing.T) {
+	dirName, err := ioutil.TempDir("", "sqlite3")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dirName)
+
+	dbFileName := path.Join(dirName, "test.db")
+	f, err := os.Create(dbFileName)
+	if err != nil {
+		t.Error(err)
+	}
+	f.Write([]byte{1, 2, 3, 4, 5})
+	f.Close()
+
+	db, err := sql.Open("sqlite3", dbFileName)
+	if err == nil {
+		_, err = db.Exec("drop table foo")
+	}
+	if err != ErrNotADB {
+		t.Error("wrong error code for corrupted DB")
+	}
+	db.Close()
+}
diff --git a/sqlite3.go b/sqlite3.go
index 11bfba4..4e390e4 100644
--- a/sqlite3.go
+++ b/sqlite3.go
@@ -132,7 +132,7 @@ func (c *SQLiteConn) exec(cmd string) error {
 	defer C.free(unsafe.Pointer(pcmd))
 	rv := C.sqlite3_exec(c.db, pcmd, nil, nil, nil)
 	if rv != C.SQLITE_OK {
-		return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
+		return ErrNo(rv)
 	}
 	return nil
 }
@@ -145,6 +145,10 @@ func (c *SQLiteConn) Begin() (driver.Tx, error) {
 	return &SQLiteTx{c}, nil
 }
 
+func errorString(err ErrNo) string {
+	return C.GoString(C.sqlite3_errstr(C.int(err)))
+}
+
 // Open database and return a new connection.
 // You can specify DSN string with URI filename.
 //   test.db
@@ -165,7 +169,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 			C.SQLITE_OPEN_CREATE,
 		nil)
 	if rv != 0 {
-		return nil, errors.New(C.GoString(C.sqlite3_errmsg(db)))
+		return nil, ErrNo(rv)
 	}
 	if db == nil {
 		return nil, errors.New("sqlite succeeded without returning a database")
@@ -173,7 +177,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 
 	rv = C.sqlite3_busy_timeout(db, 5000)
 	if rv != C.SQLITE_OK {
-		return nil, errors.New(C.GoString(C.sqlite3_errmsg(db)))
+		return nil, ErrNo(rv)
 	}
 
 	return &SQLiteConn{db}, nil
@@ -188,7 +192,7 @@ func (c *SQLiteConn) Close() error {
 	}
 	rv := C.sqlite3_close(c.db)
 	if rv != C.SQLITE_OK {
-		return errors.New("error while closing sqlite database connection")
+		return ErrNo(rv)
 	}
 	c.db = nil
 	return nil
@@ -202,7 +206,7 @@ func (c *SQLiteConn) Prepare(query string) (driver.Stmt, error) {
 	var perror *C.char
 	rv := C.sqlite3_prepare_v2(c.db, pquery, -1, &s, &perror)
 	if rv != C.SQLITE_OK {
-		return nil, errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
+		return nil, ErrNo(rv)
 	}
 	var t string
 	if perror != nil && C.strlen(perror) > 0 {
@@ -222,7 +226,7 @@ func (s *SQLiteStmt) Close() error {
 	}
 	rv := C.sqlite3_finalize(s.s)
 	if rv != C.SQLITE_OK {
-		return errors.New(C.GoString(C.sqlite3_errmsg(s.c.db)))
+		return ErrNo(rv)
 	}
 	return nil
 }
@@ -235,7 +239,7 @@ func (s *SQLiteStmt) NumInput() int {
 func (s *SQLiteStmt) bind(args []driver.Value) error {
 	rv := C.sqlite3_reset(s.s)
 	if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
-		return errors.New(C.GoString(C.sqlite3_errmsg(s.c.db)))
+		return ErrNo(rv)
 	}
 
 	for i, v := range args {
@@ -280,7 +284,7 @@ func (s *SQLiteStmt) bind(args []driver.Value) error {
 			rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
 		}
 		if rv != C.SQLITE_OK {
-			return errors.New(C.GoString(C.sqlite3_errmsg(s.c.db)))
+			return ErrNo(rv)
 		}
 	}
 	return nil
@@ -311,7 +315,7 @@ func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
 	}
 	rv := C.sqlite3_step(s.s)
 	if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
-		return nil, errors.New(C.GoString(C.sqlite3_errmsg(s.c.db)))
+		return nil, ErrNo(rv)
 	}
 
 	res := &SQLiteResult{
@@ -325,7 +329,7 @@ func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
 func (rc *SQLiteRows) Close() error {
 	rv := C.sqlite3_reset(rc.s.s)
 	if rv != C.SQLITE_OK {
-		return errors.New(C.GoString(C.sqlite3_errmsg(rc.s.c.db)))
+		return ErrNo(rv)
 	}
 	return nil
 }
@@ -348,7 +352,7 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
 		return io.EOF
 	}
 	if rv != C.SQLITE_ROW {
-		return errors.New(C.GoString(C.sqlite3_errmsg(rc.s.c.db)))
+		return ErrNo(rv)
 	}
 
 	if rc.decltype == nil {
diff --git a/sqlite3_other.go b/sqlite3_other.go
index 58030db..96fb194 100644
--- a/sqlite3_other.go
+++ b/sqlite3_other.go
@@ -1,6 +1,6 @@
 // +build !windows
 
-package sqlite
+package sqlite3
 
 /*
 #cgo pkg-config: sqlite3
diff --git a/sqlite3_test.go b/sqlite3_test.go
index be337ae..f614cef 100644
--- a/sqlite3_test.go
+++ b/sqlite3_test.go
@@ -1,4 +1,4 @@
-package sqlite
+package sqlite3
 
 import (
 	"crypto/rand"
diff --git a/sqlite3_windows.go b/sqlite3_windows.go
index 322400f..7193dfb 100644
--- a/sqlite3_windows.go
+++ b/sqlite3_windows.go
@@ -1,4 +1,4 @@
-package sqlite
+package sqlite3
 
 /*
 #cgo CFLAGS: -I. -fno-stack-check -fno-stack-protector -mno-stack-arg-probe