diff --git a/error.go b/error.go index 2537a55..52017bf 100644 --- a/error.go +++ b/error.go @@ -4,51 +4,120 @@ import "C" type ErrNo int +const ErrNoMask C.int = 0xff + +type ErrNoExtended int + type Error struct { - Code ErrNo /* The error code returned by SQLite */ - err string /* The error string returned by sqlite3_errmsg(), + Code ErrNo /* The error code returned by SQLite */ + ExtendedCode ErrNoExtended /* The extended error code returned by SQLite */ + err string /* The error string returned by sqlite3_errmsg(), this usually contains more specific details. */ } // 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() */ + ErrError = ErrNo(1) /* SQL error or missing database */ + ErrInternal = ErrNo(2) /* Internal logic error in SQLite */ + ErrPerm = ErrNo(3) /* Access permission denied */ + ErrAbort = ErrNo(4) /* Callback routine requested an abort */ + ErrBusy = ErrNo(5) /* The database file is locked */ + ErrLocked = ErrNo(6) /* A table in the database is locked */ + ErrNomem = ErrNo(7) /* A malloc() failed */ + ErrReadonly = ErrNo(8) /* Attempt to write a readonly database */ + ErrInterrupt = ErrNo(9) /* Operation terminated by sqlite3_interrupt() */ + ErrIoErr = ErrNo(10) /* Some kind of disk I/O error occurred */ + ErrCorrupt = ErrNo(11) /* The database disk image is malformed */ + ErrNotFound = ErrNo(12) /* Unknown opcode in sqlite3_file_control() */ + ErrFull = ErrNo(13) /* Insertion failed because database is full */ + ErrCantOpen = ErrNo(14) /* Unable to open the database file */ + ErrProtocol = ErrNo(15) /* Database lock protocol error */ + ErrEmpty = ErrNo(16) /* Database is empty */ + ErrSchema = ErrNo(17) /* The database schema changed */ + ErrTooBig = ErrNo(18) /* String or BLOB exceeds size limit */ + ErrConstraint = ErrNo(19) /* Abort due to constraint violation */ + ErrMismatch = ErrNo(20) /* Data type mismatch */ + ErrMisuse = ErrNo(21) /* Library used incorrectly */ + ErrNoLFS = ErrNo(22) /* Uses OS features not supported on host */ + ErrAuth = ErrNo(23) /* Authorization denied */ + ErrFormat = ErrNo(24) /* Auxiliary database format error */ + ErrRange = ErrNo(25) /* 2nd parameter to sqlite3_bind out of range */ + ErrNotADB = ErrNo(26) /* File opened that is not a database file */ + ErrNotice = ErrNo(27) /* Notifications from sqlite3_log() */ + ErrWarning = ErrNo(28) /* Warnings from sqlite3_log() */ ) func (err ErrNo) Error() string { return Error{Code: err}.Error() } +func (err ErrNo) Extend(by int) ErrNoExtended { + return ErrNoExtended(int(err) | (by << 8)) +} + +func (err ErrNoExtended) Error() string { + return Error{Code: ErrNo(C.int(err) & ErrNoMask), ExtendedCode: err}.Error() +} + func (err Error) Error() string { if err.err != "" { return err.err } return errorString(err) } + +// result codes from http://www.sqlite.org/c3ref/c_abort_rollback.html +var ( + ErrIoErrRead = ErrIoErr.Extend(1) + ErrIoErrShortRead = ErrIoErr.Extend(2) + ErrIoErrWrite = ErrIoErr.Extend(3) + ErrIoErrFsync = ErrIoErr.Extend(4) + ErrIoErrDirFsync = ErrIoErr.Extend(5) + ErrIoErrTruncate = ErrIoErr.Extend(6) + ErrIoErrFstat = ErrIoErr.Extend(7) + ErrIoErrUnlock = ErrIoErr.Extend(8) + ErrIoErrRDlock = ErrIoErr.Extend(9) + ErrIoErrDelete = ErrIoErr.Extend(10) + ErrIoErrBlocked = ErrIoErr.Extend(11) + ErrIoErrNoMem = ErrIoErr.Extend(12) + ErrIoErrAccess = ErrIoErr.Extend(13) + ErrIoErrCheckReservedLock = ErrIoErr.Extend(14) + ErrIoErrLock = ErrIoErr.Extend(15) + ErrIoErrClose = ErrIoErr.Extend(16) + ErrIoErrDirClose = ErrIoErr.Extend(17) + ErrIoErrSHMOpen = ErrIoErr.Extend(18) + ErrIoErrSHMSize = ErrIoErr.Extend(19) + ErrIoErrSHMLock = ErrIoErr.Extend(20) + ErrIoErrSHMMap = ErrIoErr.Extend(21) + ErrIoErrSeek = ErrIoErr.Extend(22) + ErrIoErrDeleteNoent = ErrIoErr.Extend(23) + ErrIoErrMMap = ErrIoErr.Extend(24) + ErrIoErrGetTempPath = ErrIoErr.Extend(25) + ErrIoErrConvPath = ErrIoErr.Extend(26) + ErrLockedSharedCache = ErrLocked.Extend(1) + ErrBusyRecovery = ErrBusy.Extend(1) + ErrBusySnapshot = ErrBusy.Extend(2) + ErrCantOpenNoTempDir = ErrCantOpen.Extend(1) + ErrCantOpenIsDir = ErrCantOpen.Extend(2) + ErrCantOpenFullPath = ErrCantOpen.Extend(3) + ErrCantOpenConvPath = ErrCantOpen.Extend(4) + ErrCorruptVTab = ErrCorrupt.Extend(1) + ErrReadonlyRecovery = ErrReadonly.Extend(1) + ErrReadonlyCantLock = ErrReadonly.Extend(2) + ErrReadonlyRollback = ErrReadonly.Extend(3) + ErrReadonlyDbMoved = ErrReadonly.Extend(4) + ErrAbortRollback = ErrAbort.Extend(2) + ErrConstraintCheck = ErrConstraint.Extend(1) + ErrConstraintCommitHook = ErrConstraint.Extend(2) + ErrConstraintForeignKey = ErrConstraint.Extend(3) + ErrConstraintFunction = ErrConstraint.Extend(4) + ErrConstraintNotNull = ErrConstraint.Extend(5) + ErrConstraintPrimaryKey = ErrConstraint.Extend(6) + ErrConstraintTrigger = ErrConstraint.Extend(7) + ErrConstraintUnique = ErrConstraint.Extend(8) + ErrConstraintVTab = ErrConstraint.Extend(9) + ErrConstraintRowId = ErrConstraint.Extend(10) + ErrNoticeRecoverWAL = ErrNotice.Extend(1) + ErrNoticeRecoverRollback = ErrNotice.Extend(2) + ErrWarningAutoIndex = ErrWarning.Extend(1) +) diff --git a/error_test.go b/error_test.go index a47025a..e21f215 100644 --- a/error_test.go +++ b/error_test.go @@ -57,15 +57,175 @@ func TestSqlLogicErrors(t *testing.T) { if err != nil { t.Error(err) } + defer db.Close() - _, err = db.Exec("CREATE TABLE Foo (id INT PRIMARY KEY)") + _, err = db.Exec("CREATE TABLE Foo (id INTEGER PRIMARY KEY)") if err != nil { t.Error(err) } const expectedErr = "table Foo already exists" - _, err = db.Exec("CREATE TABLE Foo (id INT PRIMARY KEY)") + _, err = db.Exec("CREATE TABLE Foo (id INTEGER PRIMARY KEY)") if err.Error() != expectedErr { t.Errorf("Unexpected error: %s, expected %s", err.Error(), expectedErr) } + +} + +func TestExtendedErrorCodes_ForeignKey(t *testing.T) { + dirName, err := ioutil.TempDir("", "sqlite3-err") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dirName) + + dbFileName := path.Join(dirName, "test.db") + db, err := sql.Open("sqlite3", dbFileName) + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec("PRAGMA foreign_keys=ON;") + if err != nil { + t.Errorf("PRAGMA foreign_keys=ON: %v", err) + } + + _, err = db.Exec(`CREATE TABLE Foo ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + value INTEGER NOT NULL, + ref INTEGER NULL REFERENCES Foo (id), + UNIQUE(value) + );`) + if err != nil { + t.Error(err) + } + + _, err = db.Exec("INSERT INTO Foo (ref, value) VALUES (100, 100);") + if err == nil { + t.Error("No error!") + } else { + sqliteErr := err.(Error) + if sqliteErr.Code != ErrConstraint { + t.Errorf("Wrong basic error code: %d != %d", + sqliteErr.Code, ErrConstraint) + } + if sqliteErr.ExtendedCode != ErrConstraintForeignKey { + t.Errorf("Wrong extended error code: %d != %d", + sqliteErr.ExtendedCode, ErrConstraintForeignKey) + } + } + +} + +func TestExtendedErrorCodes_NotNull(t *testing.T) { + dirName, err := ioutil.TempDir("", "sqlite3-err") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dirName) + + dbFileName := path.Join(dirName, "test.db") + db, err := sql.Open("sqlite3", dbFileName) + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec("PRAGMA foreign_keys=ON;") + if err != nil { + t.Errorf("PRAGMA foreign_keys=ON: %v", err) + } + + _, err = db.Exec(`CREATE TABLE Foo ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + value INTEGER NOT NULL, + ref INTEGER NULL REFERENCES Foo (id), + UNIQUE(value) + );`) + if err != nil { + t.Error(err) + } + + res, err := db.Exec("INSERT INTO Foo (value) VALUES (100);") + if err != nil { + t.Fatalf("Creating first row: %v", err) + } + + id, err := res.LastInsertId() + if err != nil { + t.Fatalf("Retrieving last insert id: %v", err) + } + + _, err = db.Exec("INSERT INTO Foo (ref) VALUES (?);", id) + if err == nil { + t.Error("No error!") + } else { + sqliteErr := err.(Error) + if sqliteErr.Code != ErrConstraint { + t.Errorf("Wrong basic error code: %d != %d", + sqliteErr.Code, ErrConstraint) + } + if sqliteErr.ExtendedCode != ErrConstraintNotNull { + t.Errorf("Wrong extended error code: %d != %d", + sqliteErr.ExtendedCode, ErrConstraintNotNull) + } + } + +} + +func TestExtendedErrorCodes_Unique(t *testing.T) { + dirName, err := ioutil.TempDir("", "sqlite3-err") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dirName) + + dbFileName := path.Join(dirName, "test.db") + db, err := sql.Open("sqlite3", dbFileName) + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec("PRAGMA foreign_keys=ON;") + if err != nil { + t.Errorf("PRAGMA foreign_keys=ON: %v", err) + } + + _, err = db.Exec(`CREATE TABLE Foo ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + value INTEGER NOT NULL, + ref INTEGER NULL REFERENCES Foo (id), + UNIQUE(value) + );`) + if err != nil { + t.Error(err) + } + + res, err := db.Exec("INSERT INTO Foo (value) VALUES (100);") + if err != nil { + t.Fatalf("Creating first row: %v", err) + } + + id, err := res.LastInsertId() + if err != nil { + t.Fatalf("Retrieving last insert id: %v", err) + } + + _, err = db.Exec("INSERT INTO Foo (ref, value) VALUES (?, 100);", id) + if err == nil { + t.Error("No error!") + } else { + sqliteErr := err.(Error) + if sqliteErr.Code != ErrConstraint { + t.Errorf("Wrong basic error code: %d != %d", + sqliteErr.Code, ErrConstraint) + } + if sqliteErr.ExtendedCode != ErrConstraintUnique { + t.Errorf("Wrong extended error code: %d != %d", + sqliteErr.ExtendedCode, ErrConstraintUnique) + } + } + } diff --git a/sqlite3.go b/sqlite3.go index 8df3ef2..3d5110b 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -139,8 +139,10 @@ func (c *SQLiteConn) AutoCommit() bool { } func (c *SQLiteConn) lastError() Error { - return Error{Code: ErrNo(C.sqlite3_errcode(c.db)), - err: C.GoString(C.sqlite3_errmsg(c.db)), + return Error{ + Code: ErrNo(C.sqlite3_errcode(c.db)), + ExtendedCode: ErrNoExtended(C.sqlite3_extended_errcode(c.db)), + err: C.GoString(C.sqlite3_errmsg(c.db)), } }