From 0e61905a8ba3e23d27c8d8c8840f67dec6238eda Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 16 Dec 2015 15:54:31 -0500 Subject: [PATCH 01/14] Remove doc publishing from .travis.yml --- .travis.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2bd2336..ba5124f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,12 +13,3 @@ script: - cargo test --features trace - cargo test --features functions - cargo test --features "backup functions load_extension trace" - - cargo doc --no-deps --features "backup functions load_extension trace" - -after_success: | - [ $TRAVIS_BRANCH = master ] && - [ $TRAVIS_PULL_REQUEST = false ] && - echo '' > target/doc/index.html && - sudo pip install ghp-import && - ghp-import -n target/doc && - git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages From c7c1c88c1c3c3822dd672ffccea61ddaffb5ad7d Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 16 Dec 2015 15:54:40 -0500 Subject: [PATCH 02/14] Add shell script to publish docs --- publish-ghp-docs.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 publish-ghp-docs.sh diff --git a/publish-ghp-docs.sh b/publish-ghp-docs.sh new file mode 100755 index 0000000..f9eeb17 --- /dev/null +++ b/publish-ghp-docs.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +git describe --exact-match --tags $(git log -n1 --pretty='%h') >/dev/null 2>&1 +if [[ $? != 0 ]]; then + echo "Should not publish tags from an untagged commit!" + exit 1 +fi + +cd $(git rev-parse --show-toplevel) +rm -rf target/doc/ +multirust run nightly cargo doc --no-deps --features "load_extension trace" +echo '' > target/doc/index.html +ghp-import target/doc +git push origin gh-pages:gh-pages From a1a1a4d2f010a87be5fa9e2f4496ce844d8da310 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 16 Dec 2015 15:56:05 -0500 Subject: [PATCH 03/14] Add to CONTRIBUTORS --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 3fbb310..6d0dd29 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -12,3 +12,4 @@ rusqlite contributors (sorted alphabetically) * [Ben Striegel](https://github.com/bstrie) * [Andrew Straw](https://github.com/astraw) * [Ronald Kinard](https://github.com/Furyhunter) +* [maciejkula](https://github.com/maciejkula) From 17e54eb6f65bab3ae3fe84a11904085c1d732c7f Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sat, 12 Dec 2015 23:10:21 -0500 Subject: [PATCH 04/14] Add extended result codes to libsqlite3-sys --- libsqlite3-sys/src/lib.rs | 117 +++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/libsqlite3-sys/src/lib.rs b/libsqlite3-sys/src/lib.rs index ca49efb..5a79516 100644 --- a/libsqlite3-sys/src/lib.rs +++ b/libsqlite3-sys/src/lib.rs @@ -11,6 +11,8 @@ use libc::c_int; mod bindgen; +// Result codes. + pub const SQLITE_OK : c_int = 0; pub const SQLITE_ERROR : c_int = 1; pub const SQLITE_INTERNAL : c_int = 2; @@ -43,6 +45,63 @@ pub const SQLITE_WARNING : c_int = 28; pub const SQLITE_ROW : c_int = 100; pub const SQLITE_DONE : c_int = 101; +// Extended result codes. + +pub const SQLITE_IOERR_READ : c_int = (SQLITE_IOERR | (1<<8)); +pub const SQLITE_IOERR_SHORT_READ : c_int = (SQLITE_IOERR | (2<<8)); +pub const SQLITE_IOERR_WRITE : c_int = (SQLITE_IOERR | (3<<8)); +pub const SQLITE_IOERR_FSYNC : c_int = (SQLITE_IOERR | (4<<8)); +pub const SQLITE_IOERR_DIR_FSYNC : c_int = (SQLITE_IOERR | (5<<8)); +pub const SQLITE_IOERR_TRUNCATE : c_int = (SQLITE_IOERR | (6<<8)); +pub const SQLITE_IOERR_FSTAT : c_int = (SQLITE_IOERR | (7<<8)); +pub const SQLITE_IOERR_UNLOCK : c_int = (SQLITE_IOERR | (8<<8)); +pub const SQLITE_IOERR_RDLOCK : c_int = (SQLITE_IOERR | (9<<8)); +pub const SQLITE_IOERR_DELETE : c_int = (SQLITE_IOERR | (10<<8)); +pub const SQLITE_IOERR_BLOCKED : c_int = (SQLITE_IOERR | (11<<8)); +pub const SQLITE_IOERR_NOMEM : c_int = (SQLITE_IOERR | (12<<8)); +pub const SQLITE_IOERR_ACCESS : c_int = (SQLITE_IOERR | (13<<8)); +pub const SQLITE_IOERR_CHECKRESERVEDLOCK : c_int = (SQLITE_IOERR | (14<<8)); +pub const SQLITE_IOERR_LOCK : c_int = (SQLITE_IOERR | (15<<8)); +pub const SQLITE_IOERR_CLOSE : c_int = (SQLITE_IOERR | (16<<8)); +pub const SQLITE_IOERR_DIR_CLOSE : c_int = (SQLITE_IOERR | (17<<8)); +pub const SQLITE_IOERR_SHMOPEN : c_int = (SQLITE_IOERR | (18<<8)); +pub const SQLITE_IOERR_SHMSIZE : c_int = (SQLITE_IOERR | (19<<8)); +pub const SQLITE_IOERR_SHMLOCK : c_int = (SQLITE_IOERR | (20<<8)); +pub const SQLITE_IOERR_SHMMAP : c_int = (SQLITE_IOERR | (21<<8)); +pub const SQLITE_IOERR_SEEK : c_int = (SQLITE_IOERR | (22<<8)); +pub const SQLITE_IOERR_DELETE_NOENT : c_int = (SQLITE_IOERR | (23<<8)); +pub const SQLITE_IOERR_MMAP : c_int = (SQLITE_IOERR | (24<<8)); +pub const SQLITE_IOERR_GETTEMPPATH : c_int = (SQLITE_IOERR | (25<<8)); +pub const SQLITE_IOERR_CONVPATH : c_int = (SQLITE_IOERR | (26<<8)); +pub const SQLITE_IOERR_VNODE : c_int = (SQLITE_IOERR | (27<<8)); +pub const SQLITE_LOCKED_SHAREDCACHE : c_int = (SQLITE_LOCKED | (1<<8)); +pub const SQLITE_BUSY_RECOVERY : c_int = (SQLITE_BUSY | (1<<8)); +pub const SQLITE_BUSY_SNAPSHOT : c_int = (SQLITE_BUSY | (2<<8)); +pub const SQLITE_CANTOPEN_NOTEMPDIR : c_int = (SQLITE_CANTOPEN | (1<<8)); +pub const SQLITE_CANTOPEN_ISDIR : c_int = (SQLITE_CANTOPEN | (2<<8)); +pub const SQLITE_CANTOPEN_FULLPATH : c_int = (SQLITE_CANTOPEN | (3<<8)); +pub const SQLITE_CANTOPEN_CONVPATH : c_int = (SQLITE_CANTOPEN | (4<<8)); +pub const SQLITE_CORRUPT_VTAB : c_int = (SQLITE_CORRUPT | (1<<8)); +pub const SQLITE_READONLY_RECOVERY : c_int = (SQLITE_READONLY | (1<<8)); +pub const SQLITE_READONLY_CANTLOCK : c_int = (SQLITE_READONLY | (2<<8)); +pub const SQLITE_READONLY_ROLLBACK : c_int = (SQLITE_READONLY | (3<<8)); +pub const SQLITE_READONLY_DBMOVED : c_int = (SQLITE_READONLY | (4<<8)); +pub const SQLITE_ABORT_ROLLBACK : c_int = (SQLITE_ABORT | (2<<8)); +pub const SQLITE_CONSTRAINT_CHECK : c_int = (SQLITE_CONSTRAINT | (1<<8)); +pub const SQLITE_CONSTRAINT_COMMITHOOK : c_int = (SQLITE_CONSTRAINT | (2<<8)); +pub const SQLITE_CONSTRAINT_FOREIGNKEY : c_int = (SQLITE_CONSTRAINT | (3<<8)); +pub const SQLITE_CONSTRAINT_FUNCTION : c_int = (SQLITE_CONSTRAINT | (4<<8)); +pub const SQLITE_CONSTRAINT_NOTNULL : c_int = (SQLITE_CONSTRAINT | (5<<8)); +pub const SQLITE_CONSTRAINT_PRIMARYKEY : c_int = (SQLITE_CONSTRAINT | (6<<8)); +pub const SQLITE_CONSTRAINT_TRIGGER : c_int = (SQLITE_CONSTRAINT | (7<<8)); +pub const SQLITE_CONSTRAINT_UNIQUE : c_int = (SQLITE_CONSTRAINT | (8<<8)); +pub const SQLITE_CONSTRAINT_VTAB : c_int = (SQLITE_CONSTRAINT | (9<<8)); +pub const SQLITE_CONSTRAINT_ROWID : c_int = (SQLITE_CONSTRAINT |(10<<8)); +pub const SQLITE_NOTICE_RECOVER_WAL : c_int = (SQLITE_NOTICE | (1<<8)); +pub const SQLITE_NOTICE_RECOVER_ROLLBACK : c_int = (SQLITE_NOTICE | (2<<8)); +pub const SQLITE_WARNING_AUTOINDEX : c_int = (SQLITE_WARNING | (1<<8)); +pub const SQLITE_AUTH_USER : c_int = (SQLITE_AUTH | (1<<8)); + // SQLite datatype constants. pub const SQLITE_INTEGER : c_int = 1; pub const SQLITE_FLOAT : c_int = 2; @@ -91,7 +150,63 @@ pub fn code_to_str(code: c_int) -> &'static str { SQLITE_WARNING => "Warnings from sqlite3_log()", SQLITE_ROW => "sqlite3_step() has another row ready", SQLITE_DONE => "sqlite3_step() has finished executing", - _ => "Unknown error code", + + SQLITE_IOERR_READ => "Error reading from disk", + SQLITE_IOERR_SHORT_READ => "Unable to obtain number of requested bytes (file truncated?)", + SQLITE_IOERR_WRITE => "Error writing to disk", + SQLITE_IOERR_FSYNC => "Error flushing data to persistent storage (fsync)", + SQLITE_IOERR_DIR_FSYNC => "Error calling fsync on a directory", + SQLITE_IOERR_TRUNCATE => "Error attempting to truncate file", + SQLITE_IOERR_FSTAT => "Error invoking fstat to get file metadata", + SQLITE_IOERR_UNLOCK => "I/O error within xUnlock of a VFS object", + SQLITE_IOERR_RDLOCK => "I/O error within xLock of a VFS object (trying to obtain a read lock)", + SQLITE_IOERR_DELETE => "I/O error within xDelete of a VFS object", + SQLITE_IOERR_BLOCKED => "SQLITE_IOERR_BLOCKED", // no longer used + SQLITE_IOERR_NOMEM => "Out of memory in I/O layer", + SQLITE_IOERR_ACCESS => "I/O error within xAccess of a VFS object", + SQLITE_IOERR_CHECKRESERVEDLOCK => "I/O error within then xCheckReservedLock method", + SQLITE_IOERR_LOCK => "I/O error in the advisory file locking layer", + SQLITE_IOERR_CLOSE => "I/O error within the xClose method", + SQLITE_IOERR_DIR_CLOSE => "SQLITE_IOERR_DIR_CLOSE", // no longer used + SQLITE_IOERR_SHMOPEN => "I/O error within the xShmMap method (trying to open a new shared-memory segment)", + SQLITE_IOERR_SHMSIZE => "I/O error within the xShmMap method (trying to resize an existing shared-memory segment)", + SQLITE_IOERR_SHMLOCK => "SQLITE_IOERR_SHMLOCK", // no longer used + SQLITE_IOERR_SHMMAP => "I/O error within the xShmMap method (trying to map a shared-memory segment into process address space)", + SQLITE_IOERR_SEEK => "I/O error within the xRead or xWrite (trying to seek within a file)", + SQLITE_IOERR_DELETE_NOENT => "File being deleted does not exist", + SQLITE_IOERR_MMAP => "I/O error while trying to map or unmap part of the database file into process address space", + SQLITE_IOERR_GETTEMPPATH => "VFS is unable to determine a suitable directory for temporary files", + SQLITE_IOERR_CONVPATH => "cygwin_conv_path() system call failed", + SQLITE_IOERR_VNODE => "SQLITE_IOERR_VNODE", // not documented? + SQLITE_LOCKED_SHAREDCACHE => "Locking conflict due to another connection with a shared cache", + SQLITE_BUSY_RECOVERY => "Another process is recovering a WAL mode database file", + SQLITE_BUSY_SNAPSHOT => "Cannot promote read transaction to write transaction because of writes by another connection", + SQLITE_CANTOPEN_NOTEMPDIR => "SQLITE_CANTOPEN_NOTEMPDIR", // no longer used + SQLITE_CANTOPEN_ISDIR => "Attempted to open directory as file", + SQLITE_CANTOPEN_FULLPATH => "Unable to convert filename into full pathname", + SQLITE_CANTOPEN_CONVPATH => "cygwin_conv_path() system call failed", + SQLITE_CORRUPT_VTAB => "Content in the virtual table is corrupt", + SQLITE_READONLY_RECOVERY => "WAL mode database file needs recovery (requires write access)", + SQLITE_READONLY_CANTLOCK => "Shared-memory file associated with WAL mode database is read-only", + SQLITE_READONLY_ROLLBACK => "Database has hot journal that must be rolled back (requires write access)", + SQLITE_READONLY_DBMOVED => "Database cannot be modified because database file has moved", + SQLITE_ABORT_ROLLBACK => "Transaction was rolled back", + SQLITE_CONSTRAINT_CHECK => "A CHECK constraint failed", + SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback", + SQLITE_CONSTRAINT_FOREIGNKEY => "Foreign key constraint failed", + SQLITE_CONSTRAINT_FUNCTION => "Error returned from extension function", + SQLITE_CONSTRAINT_NOTNULL => "A NOT NULL constraint failed", + SQLITE_CONSTRAINT_PRIMARYKEY => "A PRIMARY KEY constraint failed", + SQLITE_CONSTRAINT_TRIGGER => "A RAISE function within a trigger fired", + SQLITE_CONSTRAINT_UNIQUE => "A UNIQUE constraint failed", + SQLITE_CONSTRAINT_VTAB => "An application-defined virtual table error occurred", + SQLITE_CONSTRAINT_ROWID => "A non-unique rowid occurred", + SQLITE_NOTICE_RECOVER_WAL => "A WAL mode database file was recovered", + SQLITE_NOTICE_RECOVER_ROLLBACK => "Hot journal was rolled back", + SQLITE_WARNING_AUTOINDEX => "Automatic indexing used - database might benefit from additional indexes", + SQLITE_AUTH_USER => "SQLITE_AUTH_USER", // not documented? + + _ => "Unknown error code", } } From bf859a8008b5c66e6b3fb4cfca754bd9b369831e Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sat, 12 Dec 2015 23:10:35 -0500 Subject: [PATCH 05/14] Attempt to enable extended result codes for all connections --- src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e41dc91..875cfb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -616,6 +616,10 @@ impl InnerConnection { ffi::sqlite3_close(db); return Err(e); } + + // attempt to turn on extended results code; don't fail if we can't. + ffi::sqlite3_extended_result_codes(db, 1); + Ok(InnerConnection { db: db }) } } @@ -1421,6 +1425,18 @@ mod test { assert!(format!("{:?}", stmt).contains(query)); } + #[test] + fn test_notnull_constraint_error() { + let db = checked_memory_handle(); + db.execute_batch("CREATE TABLE foo(x NOT NULL)").unwrap(); + + let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", &[]); + assert!(result.is_err()); + + let err = result.err().unwrap(); + assert_eq!(err.code, ffi::SQLITE_CONSTRAINT_NOTNULL); + } + mod query_and_then_tests { extern crate libsqlite3_sys as ffi; use super::*; From 1f26093fc6bd457f3ccd2470c35b6594950f8b82 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sat, 12 Dec 2015 23:13:48 -0500 Subject: [PATCH 06/14] Move error codes into module (internal change only - public API stays the same). --- libsqlite3-sys/src/error.rs | 185 +++++++++++++++++++++++++++++++++++ libsqlite3-sys/src/lib.rs | 186 +----------------------------------- 2 files changed, 187 insertions(+), 184 deletions(-) create mode 100644 libsqlite3-sys/src/error.rs diff --git a/libsqlite3-sys/src/error.rs b/libsqlite3-sys/src/error.rs new file mode 100644 index 0000000..9990064 --- /dev/null +++ b/libsqlite3-sys/src/error.rs @@ -0,0 +1,185 @@ +use libc::c_int; + +// Result codes. + +pub const SQLITE_OK : c_int = 0; +pub const SQLITE_ERROR : c_int = 1; +pub const SQLITE_INTERNAL : c_int = 2; +pub const SQLITE_PERM : c_int = 3; +pub const SQLITE_ABORT : c_int = 4; +pub const SQLITE_BUSY : c_int = 5; +pub const SQLITE_LOCKED : c_int = 6; +pub const SQLITE_NOMEM : c_int = 7; +pub const SQLITE_READONLY : c_int = 8; +pub const SQLITE_INTERRUPT : c_int = 9; +pub const SQLITE_IOERR : c_int = 10; +pub const SQLITE_CORRUPT : c_int = 11; +pub const SQLITE_NOTFOUND : c_int = 12; +pub const SQLITE_FULL : c_int = 13; +pub const SQLITE_CANTOPEN : c_int = 14; +pub const SQLITE_PROTOCOL : c_int = 15; +pub const SQLITE_EMPTY : c_int = 16; +pub const SQLITE_SCHEMA : c_int = 17; +pub const SQLITE_TOOBIG : c_int = 18; +pub const SQLITE_CONSTRAINT: c_int = 19; +pub const SQLITE_MISMATCH : c_int = 20; +pub const SQLITE_MISUSE : c_int = 21; +pub const SQLITE_NOLFS : c_int = 22; +pub const SQLITE_AUTH : c_int = 23; +pub const SQLITE_FORMAT : c_int = 24; +pub const SQLITE_RANGE : c_int = 25; +pub const SQLITE_NOTADB : c_int = 26; +pub const SQLITE_NOTICE : c_int = 27; +pub const SQLITE_WARNING : c_int = 28; +pub const SQLITE_ROW : c_int = 100; +pub const SQLITE_DONE : c_int = 101; + +// Extended result codes. + +pub const SQLITE_IOERR_READ : c_int = (SQLITE_IOERR | (1<<8)); +pub const SQLITE_IOERR_SHORT_READ : c_int = (SQLITE_IOERR | (2<<8)); +pub const SQLITE_IOERR_WRITE : c_int = (SQLITE_IOERR | (3<<8)); +pub const SQLITE_IOERR_FSYNC : c_int = (SQLITE_IOERR | (4<<8)); +pub const SQLITE_IOERR_DIR_FSYNC : c_int = (SQLITE_IOERR | (5<<8)); +pub const SQLITE_IOERR_TRUNCATE : c_int = (SQLITE_IOERR | (6<<8)); +pub const SQLITE_IOERR_FSTAT : c_int = (SQLITE_IOERR | (7<<8)); +pub const SQLITE_IOERR_UNLOCK : c_int = (SQLITE_IOERR | (8<<8)); +pub const SQLITE_IOERR_RDLOCK : c_int = (SQLITE_IOERR | (9<<8)); +pub const SQLITE_IOERR_DELETE : c_int = (SQLITE_IOERR | (10<<8)); +pub const SQLITE_IOERR_BLOCKED : c_int = (SQLITE_IOERR | (11<<8)); +pub const SQLITE_IOERR_NOMEM : c_int = (SQLITE_IOERR | (12<<8)); +pub const SQLITE_IOERR_ACCESS : c_int = (SQLITE_IOERR | (13<<8)); +pub const SQLITE_IOERR_CHECKRESERVEDLOCK : c_int = (SQLITE_IOERR | (14<<8)); +pub const SQLITE_IOERR_LOCK : c_int = (SQLITE_IOERR | (15<<8)); +pub const SQLITE_IOERR_CLOSE : c_int = (SQLITE_IOERR | (16<<8)); +pub const SQLITE_IOERR_DIR_CLOSE : c_int = (SQLITE_IOERR | (17<<8)); +pub const SQLITE_IOERR_SHMOPEN : c_int = (SQLITE_IOERR | (18<<8)); +pub const SQLITE_IOERR_SHMSIZE : c_int = (SQLITE_IOERR | (19<<8)); +pub const SQLITE_IOERR_SHMLOCK : c_int = (SQLITE_IOERR | (20<<8)); +pub const SQLITE_IOERR_SHMMAP : c_int = (SQLITE_IOERR | (21<<8)); +pub const SQLITE_IOERR_SEEK : c_int = (SQLITE_IOERR | (22<<8)); +pub const SQLITE_IOERR_DELETE_NOENT : c_int = (SQLITE_IOERR | (23<<8)); +pub const SQLITE_IOERR_MMAP : c_int = (SQLITE_IOERR | (24<<8)); +pub const SQLITE_IOERR_GETTEMPPATH : c_int = (SQLITE_IOERR | (25<<8)); +pub const SQLITE_IOERR_CONVPATH : c_int = (SQLITE_IOERR | (26<<8)); +pub const SQLITE_IOERR_VNODE : c_int = (SQLITE_IOERR | (27<<8)); +pub const SQLITE_LOCKED_SHAREDCACHE : c_int = (SQLITE_LOCKED | (1<<8)); +pub const SQLITE_BUSY_RECOVERY : c_int = (SQLITE_BUSY | (1<<8)); +pub const SQLITE_BUSY_SNAPSHOT : c_int = (SQLITE_BUSY | (2<<8)); +pub const SQLITE_CANTOPEN_NOTEMPDIR : c_int = (SQLITE_CANTOPEN | (1<<8)); +pub const SQLITE_CANTOPEN_ISDIR : c_int = (SQLITE_CANTOPEN | (2<<8)); +pub const SQLITE_CANTOPEN_FULLPATH : c_int = (SQLITE_CANTOPEN | (3<<8)); +pub const SQLITE_CANTOPEN_CONVPATH : c_int = (SQLITE_CANTOPEN | (4<<8)); +pub const SQLITE_CORRUPT_VTAB : c_int = (SQLITE_CORRUPT | (1<<8)); +pub const SQLITE_READONLY_RECOVERY : c_int = (SQLITE_READONLY | (1<<8)); +pub const SQLITE_READONLY_CANTLOCK : c_int = (SQLITE_READONLY | (2<<8)); +pub const SQLITE_READONLY_ROLLBACK : c_int = (SQLITE_READONLY | (3<<8)); +pub const SQLITE_READONLY_DBMOVED : c_int = (SQLITE_READONLY | (4<<8)); +pub const SQLITE_ABORT_ROLLBACK : c_int = (SQLITE_ABORT | (2<<8)); +pub const SQLITE_CONSTRAINT_CHECK : c_int = (SQLITE_CONSTRAINT | (1<<8)); +pub const SQLITE_CONSTRAINT_COMMITHOOK : c_int = (SQLITE_CONSTRAINT | (2<<8)); +pub const SQLITE_CONSTRAINT_FOREIGNKEY : c_int = (SQLITE_CONSTRAINT | (3<<8)); +pub const SQLITE_CONSTRAINT_FUNCTION : c_int = (SQLITE_CONSTRAINT | (4<<8)); +pub const SQLITE_CONSTRAINT_NOTNULL : c_int = (SQLITE_CONSTRAINT | (5<<8)); +pub const SQLITE_CONSTRAINT_PRIMARYKEY : c_int = (SQLITE_CONSTRAINT | (6<<8)); +pub const SQLITE_CONSTRAINT_TRIGGER : c_int = (SQLITE_CONSTRAINT | (7<<8)); +pub const SQLITE_CONSTRAINT_UNIQUE : c_int = (SQLITE_CONSTRAINT | (8<<8)); +pub const SQLITE_CONSTRAINT_VTAB : c_int = (SQLITE_CONSTRAINT | (9<<8)); +pub const SQLITE_CONSTRAINT_ROWID : c_int = (SQLITE_CONSTRAINT |(10<<8)); +pub const SQLITE_NOTICE_RECOVER_WAL : c_int = (SQLITE_NOTICE | (1<<8)); +pub const SQLITE_NOTICE_RECOVER_ROLLBACK : c_int = (SQLITE_NOTICE | (2<<8)); +pub const SQLITE_WARNING_AUTOINDEX : c_int = (SQLITE_WARNING | (1<<8)); +pub const SQLITE_AUTH_USER : c_int = (SQLITE_AUTH | (1<<8)); + +pub fn code_to_str(code: c_int) -> &'static str { + match code { + SQLITE_OK => "Successful result", + SQLITE_ERROR => "SQL error or missing database", + SQLITE_INTERNAL => "Internal logic error in SQLite", + SQLITE_PERM => "Access permission denied", + SQLITE_ABORT => "Callback routine requested an abort", + SQLITE_BUSY => "The database file is locked", + SQLITE_LOCKED => "A table in the database is locked", + SQLITE_NOMEM => "A malloc() failed", + SQLITE_READONLY => "Attempt to write a readonly database", + SQLITE_INTERRUPT => "Operation terminated by sqlite3_interrupt()", + SQLITE_IOERR => "Some kind of disk I/O error occurred", + SQLITE_CORRUPT => "The database disk image is malformed", + SQLITE_NOTFOUND => "Unknown opcode in sqlite3_file_control()", + SQLITE_FULL => "Insertion failed because database is full", + SQLITE_CANTOPEN => "Unable to open the database file", + SQLITE_PROTOCOL => "Database lock protocol error", + SQLITE_EMPTY => "Database is empty", + SQLITE_SCHEMA => "The database schema changed", + SQLITE_TOOBIG => "String or BLOB exceeds size limit", + SQLITE_CONSTRAINT=> "Abort due to constraint violation", + SQLITE_MISMATCH => "Data type mismatch", + SQLITE_MISUSE => "Library used incorrectly", + SQLITE_NOLFS => "Uses OS features not supported on host", + SQLITE_AUTH => "Authorization denied", + SQLITE_FORMAT => "Auxiliary database format error", + SQLITE_RANGE => "2nd parameter to sqlite3_bind out of range", + SQLITE_NOTADB => "File opened that is not a database file", + SQLITE_NOTICE => "Notifications from sqlite3_log()", + SQLITE_WARNING => "Warnings from sqlite3_log()", + SQLITE_ROW => "sqlite3_step() has another row ready", + SQLITE_DONE => "sqlite3_step() has finished executing", + + SQLITE_IOERR_READ => "Error reading from disk", + SQLITE_IOERR_SHORT_READ => "Unable to obtain number of requested bytes (file truncated?)", + SQLITE_IOERR_WRITE => "Error writing to disk", + SQLITE_IOERR_FSYNC => "Error flushing data to persistent storage (fsync)", + SQLITE_IOERR_DIR_FSYNC => "Error calling fsync on a directory", + SQLITE_IOERR_TRUNCATE => "Error attempting to truncate file", + SQLITE_IOERR_FSTAT => "Error invoking fstat to get file metadata", + SQLITE_IOERR_UNLOCK => "I/O error within xUnlock of a VFS object", + SQLITE_IOERR_RDLOCK => "I/O error within xLock of a VFS object (trying to obtain a read lock)", + SQLITE_IOERR_DELETE => "I/O error within xDelete of a VFS object", + SQLITE_IOERR_BLOCKED => "SQLITE_IOERR_BLOCKED", // no longer used + SQLITE_IOERR_NOMEM => "Out of memory in I/O layer", + SQLITE_IOERR_ACCESS => "I/O error within xAccess of a VFS object", + SQLITE_IOERR_CHECKRESERVEDLOCK => "I/O error within then xCheckReservedLock method", + SQLITE_IOERR_LOCK => "I/O error in the advisory file locking layer", + SQLITE_IOERR_CLOSE => "I/O error within the xClose method", + SQLITE_IOERR_DIR_CLOSE => "SQLITE_IOERR_DIR_CLOSE", // no longer used + SQLITE_IOERR_SHMOPEN => "I/O error within the xShmMap method (trying to open a new shared-memory segment)", + SQLITE_IOERR_SHMSIZE => "I/O error within the xShmMap method (trying to resize an existing shared-memory segment)", + SQLITE_IOERR_SHMLOCK => "SQLITE_IOERR_SHMLOCK", // no longer used + SQLITE_IOERR_SHMMAP => "I/O error within the xShmMap method (trying to map a shared-memory segment into process address space)", + SQLITE_IOERR_SEEK => "I/O error within the xRead or xWrite (trying to seek within a file)", + SQLITE_IOERR_DELETE_NOENT => "File being deleted does not exist", + SQLITE_IOERR_MMAP => "I/O error while trying to map or unmap part of the database file into process address space", + SQLITE_IOERR_GETTEMPPATH => "VFS is unable to determine a suitable directory for temporary files", + SQLITE_IOERR_CONVPATH => "cygwin_conv_path() system call failed", + SQLITE_IOERR_VNODE => "SQLITE_IOERR_VNODE", // not documented? + SQLITE_LOCKED_SHAREDCACHE => "Locking conflict due to another connection with a shared cache", + SQLITE_BUSY_RECOVERY => "Another process is recovering a WAL mode database file", + SQLITE_BUSY_SNAPSHOT => "Cannot promote read transaction to write transaction because of writes by another connection", + SQLITE_CANTOPEN_NOTEMPDIR => "SQLITE_CANTOPEN_NOTEMPDIR", // no longer used + SQLITE_CANTOPEN_ISDIR => "Attempted to open directory as file", + SQLITE_CANTOPEN_FULLPATH => "Unable to convert filename into full pathname", + SQLITE_CANTOPEN_CONVPATH => "cygwin_conv_path() system call failed", + SQLITE_CORRUPT_VTAB => "Content in the virtual table is corrupt", + SQLITE_READONLY_RECOVERY => "WAL mode database file needs recovery (requires write access)", + SQLITE_READONLY_CANTLOCK => "Shared-memory file associated with WAL mode database is read-only", + SQLITE_READONLY_ROLLBACK => "Database has hot journal that must be rolled back (requires write access)", + SQLITE_READONLY_DBMOVED => "Database cannot be modified because database file has moved", + SQLITE_ABORT_ROLLBACK => "Transaction was rolled back", + SQLITE_CONSTRAINT_CHECK => "A CHECK constraint failed", + SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback", + SQLITE_CONSTRAINT_FOREIGNKEY => "Foreign key constraint failed", + SQLITE_CONSTRAINT_FUNCTION => "Error returned from extension function", + SQLITE_CONSTRAINT_NOTNULL => "A NOT NULL constraint failed", + SQLITE_CONSTRAINT_PRIMARYKEY => "A PRIMARY KEY constraint failed", + SQLITE_CONSTRAINT_TRIGGER => "A RAISE function within a trigger fired", + SQLITE_CONSTRAINT_UNIQUE => "A UNIQUE constraint failed", + SQLITE_CONSTRAINT_VTAB => "An application-defined virtual table error occurred", + SQLITE_CONSTRAINT_ROWID => "A non-unique rowid occurred", + SQLITE_NOTICE_RECOVER_WAL => "A WAL mode database file was recovered", + SQLITE_NOTICE_RECOVER_ROLLBACK => "Hot journal was rolled back", + SQLITE_WARNING_AUTOINDEX => "Automatic indexing used - database might benefit from additional indexes", + SQLITE_AUTH_USER => "SQLITE_AUTH_USER", // not documented? + + _ => "Unknown error code", + } +} diff --git a/libsqlite3-sys/src/lib.rs b/libsqlite3-sys/src/lib.rs index 5a79516..fb2e110 100644 --- a/libsqlite3-sys/src/lib.rs +++ b/libsqlite3-sys/src/lib.rs @@ -5,102 +5,13 @@ extern crate libc; pub use self::bindgen::*; +pub use self::error::*; use std::mem; use libc::c_int; mod bindgen; - -// Result codes. - -pub const SQLITE_OK : c_int = 0; -pub const SQLITE_ERROR : c_int = 1; -pub const SQLITE_INTERNAL : c_int = 2; -pub const SQLITE_PERM : c_int = 3; -pub const SQLITE_ABORT : c_int = 4; -pub const SQLITE_BUSY : c_int = 5; -pub const SQLITE_LOCKED : c_int = 6; -pub const SQLITE_NOMEM : c_int = 7; -pub const SQLITE_READONLY : c_int = 8; -pub const SQLITE_INTERRUPT : c_int = 9; -pub const SQLITE_IOERR : c_int = 10; -pub const SQLITE_CORRUPT : c_int = 11; -pub const SQLITE_NOTFOUND : c_int = 12; -pub const SQLITE_FULL : c_int = 13; -pub const SQLITE_CANTOPEN : c_int = 14; -pub const SQLITE_PROTOCOL : c_int = 15; -pub const SQLITE_EMPTY : c_int = 16; -pub const SQLITE_SCHEMA : c_int = 17; -pub const SQLITE_TOOBIG : c_int = 18; -pub const SQLITE_CONSTRAINT: c_int = 19; -pub const SQLITE_MISMATCH : c_int = 20; -pub const SQLITE_MISUSE : c_int = 21; -pub const SQLITE_NOLFS : c_int = 22; -pub const SQLITE_AUTH : c_int = 23; -pub const SQLITE_FORMAT : c_int = 24; -pub const SQLITE_RANGE : c_int = 25; -pub const SQLITE_NOTADB : c_int = 26; -pub const SQLITE_NOTICE : c_int = 27; -pub const SQLITE_WARNING : c_int = 28; -pub const SQLITE_ROW : c_int = 100; -pub const SQLITE_DONE : c_int = 101; - -// Extended result codes. - -pub const SQLITE_IOERR_READ : c_int = (SQLITE_IOERR | (1<<8)); -pub const SQLITE_IOERR_SHORT_READ : c_int = (SQLITE_IOERR | (2<<8)); -pub const SQLITE_IOERR_WRITE : c_int = (SQLITE_IOERR | (3<<8)); -pub const SQLITE_IOERR_FSYNC : c_int = (SQLITE_IOERR | (4<<8)); -pub const SQLITE_IOERR_DIR_FSYNC : c_int = (SQLITE_IOERR | (5<<8)); -pub const SQLITE_IOERR_TRUNCATE : c_int = (SQLITE_IOERR | (6<<8)); -pub const SQLITE_IOERR_FSTAT : c_int = (SQLITE_IOERR | (7<<8)); -pub const SQLITE_IOERR_UNLOCK : c_int = (SQLITE_IOERR | (8<<8)); -pub const SQLITE_IOERR_RDLOCK : c_int = (SQLITE_IOERR | (9<<8)); -pub const SQLITE_IOERR_DELETE : c_int = (SQLITE_IOERR | (10<<8)); -pub const SQLITE_IOERR_BLOCKED : c_int = (SQLITE_IOERR | (11<<8)); -pub const SQLITE_IOERR_NOMEM : c_int = (SQLITE_IOERR | (12<<8)); -pub const SQLITE_IOERR_ACCESS : c_int = (SQLITE_IOERR | (13<<8)); -pub const SQLITE_IOERR_CHECKRESERVEDLOCK : c_int = (SQLITE_IOERR | (14<<8)); -pub const SQLITE_IOERR_LOCK : c_int = (SQLITE_IOERR | (15<<8)); -pub const SQLITE_IOERR_CLOSE : c_int = (SQLITE_IOERR | (16<<8)); -pub const SQLITE_IOERR_DIR_CLOSE : c_int = (SQLITE_IOERR | (17<<8)); -pub const SQLITE_IOERR_SHMOPEN : c_int = (SQLITE_IOERR | (18<<8)); -pub const SQLITE_IOERR_SHMSIZE : c_int = (SQLITE_IOERR | (19<<8)); -pub const SQLITE_IOERR_SHMLOCK : c_int = (SQLITE_IOERR | (20<<8)); -pub const SQLITE_IOERR_SHMMAP : c_int = (SQLITE_IOERR | (21<<8)); -pub const SQLITE_IOERR_SEEK : c_int = (SQLITE_IOERR | (22<<8)); -pub const SQLITE_IOERR_DELETE_NOENT : c_int = (SQLITE_IOERR | (23<<8)); -pub const SQLITE_IOERR_MMAP : c_int = (SQLITE_IOERR | (24<<8)); -pub const SQLITE_IOERR_GETTEMPPATH : c_int = (SQLITE_IOERR | (25<<8)); -pub const SQLITE_IOERR_CONVPATH : c_int = (SQLITE_IOERR | (26<<8)); -pub const SQLITE_IOERR_VNODE : c_int = (SQLITE_IOERR | (27<<8)); -pub const SQLITE_LOCKED_SHAREDCACHE : c_int = (SQLITE_LOCKED | (1<<8)); -pub const SQLITE_BUSY_RECOVERY : c_int = (SQLITE_BUSY | (1<<8)); -pub const SQLITE_BUSY_SNAPSHOT : c_int = (SQLITE_BUSY | (2<<8)); -pub const SQLITE_CANTOPEN_NOTEMPDIR : c_int = (SQLITE_CANTOPEN | (1<<8)); -pub const SQLITE_CANTOPEN_ISDIR : c_int = (SQLITE_CANTOPEN | (2<<8)); -pub const SQLITE_CANTOPEN_FULLPATH : c_int = (SQLITE_CANTOPEN | (3<<8)); -pub const SQLITE_CANTOPEN_CONVPATH : c_int = (SQLITE_CANTOPEN | (4<<8)); -pub const SQLITE_CORRUPT_VTAB : c_int = (SQLITE_CORRUPT | (1<<8)); -pub const SQLITE_READONLY_RECOVERY : c_int = (SQLITE_READONLY | (1<<8)); -pub const SQLITE_READONLY_CANTLOCK : c_int = (SQLITE_READONLY | (2<<8)); -pub const SQLITE_READONLY_ROLLBACK : c_int = (SQLITE_READONLY | (3<<8)); -pub const SQLITE_READONLY_DBMOVED : c_int = (SQLITE_READONLY | (4<<8)); -pub const SQLITE_ABORT_ROLLBACK : c_int = (SQLITE_ABORT | (2<<8)); -pub const SQLITE_CONSTRAINT_CHECK : c_int = (SQLITE_CONSTRAINT | (1<<8)); -pub const SQLITE_CONSTRAINT_COMMITHOOK : c_int = (SQLITE_CONSTRAINT | (2<<8)); -pub const SQLITE_CONSTRAINT_FOREIGNKEY : c_int = (SQLITE_CONSTRAINT | (3<<8)); -pub const SQLITE_CONSTRAINT_FUNCTION : c_int = (SQLITE_CONSTRAINT | (4<<8)); -pub const SQLITE_CONSTRAINT_NOTNULL : c_int = (SQLITE_CONSTRAINT | (5<<8)); -pub const SQLITE_CONSTRAINT_PRIMARYKEY : c_int = (SQLITE_CONSTRAINT | (6<<8)); -pub const SQLITE_CONSTRAINT_TRIGGER : c_int = (SQLITE_CONSTRAINT | (7<<8)); -pub const SQLITE_CONSTRAINT_UNIQUE : c_int = (SQLITE_CONSTRAINT | (8<<8)); -pub const SQLITE_CONSTRAINT_VTAB : c_int = (SQLITE_CONSTRAINT | (9<<8)); -pub const SQLITE_CONSTRAINT_ROWID : c_int = (SQLITE_CONSTRAINT |(10<<8)); -pub const SQLITE_NOTICE_RECOVER_WAL : c_int = (SQLITE_NOTICE | (1<<8)); -pub const SQLITE_NOTICE_RECOVER_ROLLBACK : c_int = (SQLITE_NOTICE | (2<<8)); -pub const SQLITE_WARNING_AUTOINDEX : c_int = (SQLITE_WARNING | (1<<8)); -pub const SQLITE_AUTH_USER : c_int = (SQLITE_AUTH | (1<<8)); +mod error; // SQLite datatype constants. pub const SQLITE_INTEGER : c_int = 1; @@ -117,99 +28,6 @@ pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type { Some(unsafe { mem::transmute(-1isize) }) } -pub fn code_to_str(code: c_int) -> &'static str { - match code { - SQLITE_OK => "Successful result", - SQLITE_ERROR => "SQL error or missing database", - SQLITE_INTERNAL => "Internal logic error in SQLite", - SQLITE_PERM => "Access permission denied", - SQLITE_ABORT => "Callback routine requested an abort", - SQLITE_BUSY => "The database file is locked", - SQLITE_LOCKED => "A table in the database is locked", - SQLITE_NOMEM => "A malloc() failed", - SQLITE_READONLY => "Attempt to write a readonly database", - SQLITE_INTERRUPT => "Operation terminated by sqlite3_interrupt()", - SQLITE_IOERR => "Some kind of disk I/O error occurred", - SQLITE_CORRUPT => "The database disk image is malformed", - SQLITE_NOTFOUND => "Unknown opcode in sqlite3_file_control()", - SQLITE_FULL => "Insertion failed because database is full", - SQLITE_CANTOPEN => "Unable to open the database file", - SQLITE_PROTOCOL => "Database lock protocol error", - SQLITE_EMPTY => "Database is empty", - SQLITE_SCHEMA => "The database schema changed", - SQLITE_TOOBIG => "String or BLOB exceeds size limit", - SQLITE_CONSTRAINT=> "Abort due to constraint violation", - SQLITE_MISMATCH => "Data type mismatch", - SQLITE_MISUSE => "Library used incorrectly", - SQLITE_NOLFS => "Uses OS features not supported on host", - SQLITE_AUTH => "Authorization denied", - SQLITE_FORMAT => "Auxiliary database format error", - SQLITE_RANGE => "2nd parameter to sqlite3_bind out of range", - SQLITE_NOTADB => "File opened that is not a database file", - SQLITE_NOTICE => "Notifications from sqlite3_log()", - SQLITE_WARNING => "Warnings from sqlite3_log()", - SQLITE_ROW => "sqlite3_step() has another row ready", - SQLITE_DONE => "sqlite3_step() has finished executing", - - SQLITE_IOERR_READ => "Error reading from disk", - SQLITE_IOERR_SHORT_READ => "Unable to obtain number of requested bytes (file truncated?)", - SQLITE_IOERR_WRITE => "Error writing to disk", - SQLITE_IOERR_FSYNC => "Error flushing data to persistent storage (fsync)", - SQLITE_IOERR_DIR_FSYNC => "Error calling fsync on a directory", - SQLITE_IOERR_TRUNCATE => "Error attempting to truncate file", - SQLITE_IOERR_FSTAT => "Error invoking fstat to get file metadata", - SQLITE_IOERR_UNLOCK => "I/O error within xUnlock of a VFS object", - SQLITE_IOERR_RDLOCK => "I/O error within xLock of a VFS object (trying to obtain a read lock)", - SQLITE_IOERR_DELETE => "I/O error within xDelete of a VFS object", - SQLITE_IOERR_BLOCKED => "SQLITE_IOERR_BLOCKED", // no longer used - SQLITE_IOERR_NOMEM => "Out of memory in I/O layer", - SQLITE_IOERR_ACCESS => "I/O error within xAccess of a VFS object", - SQLITE_IOERR_CHECKRESERVEDLOCK => "I/O error within then xCheckReservedLock method", - SQLITE_IOERR_LOCK => "I/O error in the advisory file locking layer", - SQLITE_IOERR_CLOSE => "I/O error within the xClose method", - SQLITE_IOERR_DIR_CLOSE => "SQLITE_IOERR_DIR_CLOSE", // no longer used - SQLITE_IOERR_SHMOPEN => "I/O error within the xShmMap method (trying to open a new shared-memory segment)", - SQLITE_IOERR_SHMSIZE => "I/O error within the xShmMap method (trying to resize an existing shared-memory segment)", - SQLITE_IOERR_SHMLOCK => "SQLITE_IOERR_SHMLOCK", // no longer used - SQLITE_IOERR_SHMMAP => "I/O error within the xShmMap method (trying to map a shared-memory segment into process address space)", - SQLITE_IOERR_SEEK => "I/O error within the xRead or xWrite (trying to seek within a file)", - SQLITE_IOERR_DELETE_NOENT => "File being deleted does not exist", - SQLITE_IOERR_MMAP => "I/O error while trying to map or unmap part of the database file into process address space", - SQLITE_IOERR_GETTEMPPATH => "VFS is unable to determine a suitable directory for temporary files", - SQLITE_IOERR_CONVPATH => "cygwin_conv_path() system call failed", - SQLITE_IOERR_VNODE => "SQLITE_IOERR_VNODE", // not documented? - SQLITE_LOCKED_SHAREDCACHE => "Locking conflict due to another connection with a shared cache", - SQLITE_BUSY_RECOVERY => "Another process is recovering a WAL mode database file", - SQLITE_BUSY_SNAPSHOT => "Cannot promote read transaction to write transaction because of writes by another connection", - SQLITE_CANTOPEN_NOTEMPDIR => "SQLITE_CANTOPEN_NOTEMPDIR", // no longer used - SQLITE_CANTOPEN_ISDIR => "Attempted to open directory as file", - SQLITE_CANTOPEN_FULLPATH => "Unable to convert filename into full pathname", - SQLITE_CANTOPEN_CONVPATH => "cygwin_conv_path() system call failed", - SQLITE_CORRUPT_VTAB => "Content in the virtual table is corrupt", - SQLITE_READONLY_RECOVERY => "WAL mode database file needs recovery (requires write access)", - SQLITE_READONLY_CANTLOCK => "Shared-memory file associated with WAL mode database is read-only", - SQLITE_READONLY_ROLLBACK => "Database has hot journal that must be rolled back (requires write access)", - SQLITE_READONLY_DBMOVED => "Database cannot be modified because database file has moved", - SQLITE_ABORT_ROLLBACK => "Transaction was rolled back", - SQLITE_CONSTRAINT_CHECK => "A CHECK constraint failed", - SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback", - SQLITE_CONSTRAINT_FOREIGNKEY => "Foreign key constraint failed", - SQLITE_CONSTRAINT_FUNCTION => "Error returned from extension function", - SQLITE_CONSTRAINT_NOTNULL => "A NOT NULL constraint failed", - SQLITE_CONSTRAINT_PRIMARYKEY => "A PRIMARY KEY constraint failed", - SQLITE_CONSTRAINT_TRIGGER => "A RAISE function within a trigger fired", - SQLITE_CONSTRAINT_UNIQUE => "A UNIQUE constraint failed", - SQLITE_CONSTRAINT_VTAB => "An application-defined virtual table error occurred", - SQLITE_CONSTRAINT_ROWID => "A non-unique rowid occurred", - SQLITE_NOTICE_RECOVER_WAL => "A WAL mode database file was recovered", - SQLITE_NOTICE_RECOVER_ROLLBACK => "Hot journal was rolled back", - SQLITE_WARNING_AUTOINDEX => "Automatic indexing used - database might benefit from additional indexes", - SQLITE_AUTH_USER => "SQLITE_AUTH_USER", // not documented? - - _ => "Unknown error code", - } -} - pub const SQLITE_CONFIG_LOG : c_int = 16; pub const SQLITE_UTF8 : c_int = 1; pub const SQLITE_DETERMINISTIC : c_int = 0x800; From b385ae002b778433c1114a32627dfce60790f712 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sat, 12 Dec 2015 23:32:37 -0500 Subject: [PATCH 07/14] Add `ErrorCode` enum and `Error` struct to libsqlite3-sys. --- libsqlite3-sys/src/error.rs | 77 +++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/libsqlite3-sys/src/error.rs b/libsqlite3-sys/src/error.rs index 9990064..5c4bace 100644 --- a/libsqlite3-sys/src/error.rs +++ b/libsqlite3-sys/src/error.rs @@ -1,4 +1,81 @@ use libc::c_int; +use std::fmt; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ErrorCode { + InternalMalfunction, + PermissionDenied, + OperationAborted, + DatabaseBusy, + DatabaseLocked, + OutOfMemory, + ReadOnly, + OperationInterrupted, + SystemIOFailure, + DatabaseCorrupt, + NotFound, + DiskFull, + CannotOpen, + FileLockingProtocolFailed, + SchemaChanged, + TooBig, + ConstraintViolation, + TypeMismatch, + APIMisuse, + NoLargeFileSupport, + AuthorizationForStatementDenied, + ParameterOutOfRange, + NotADatabase, + Unknown, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Error { + pub code: ErrorCode, + pub extended_code: c_int, +} + +impl Error { + pub fn new(result_code: c_int) -> Error { + let code = match result_code & 0xff { + SQLITE_INTERNAL => ErrorCode::InternalMalfunction, + SQLITE_PERM => ErrorCode::PermissionDenied, + SQLITE_ABORT => ErrorCode::OperationAborted, + SQLITE_BUSY => ErrorCode::DatabaseBusy, + SQLITE_LOCKED => ErrorCode::DatabaseLocked, + SQLITE_NOMEM => ErrorCode::OutOfMemory, + SQLITE_READONLY => ErrorCode::ReadOnly, + SQLITE_INTERRUPT => ErrorCode::OperationInterrupted, + SQLITE_IOERR => ErrorCode::SystemIOFailure, + SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt, + SQLITE_NOTFOUND => ErrorCode::NotFound, + SQLITE_FULL => ErrorCode::DiskFull, + SQLITE_CANTOPEN => ErrorCode::CannotOpen, + SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed, + SQLITE_SCHEMA => ErrorCode::SchemaChanged, + SQLITE_TOOBIG => ErrorCode::TooBig, + SQLITE_CONSTRAINT=> ErrorCode::ConstraintViolation, + SQLITE_MISMATCH => ErrorCode::TypeMismatch, + SQLITE_MISUSE => ErrorCode::APIMisuse, + SQLITE_NOLFS => ErrorCode::NoLargeFileSupport, + SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied, + SQLITE_RANGE => ErrorCode::ParameterOutOfRange, + SQLITE_NOTADB => ErrorCode::NotADatabase, + _ => ErrorCode::Unknown, + }; + + Error { + code: code, + extended_code: result_code, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Error code {}: {}", self.extended_code, code_to_str(self.extended_code)) + } +} // Result codes. From aac4d59fcc4af0cc30dbc6ceeadf052a3002045b Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 13 Dec 2015 00:54:08 -0500 Subject: [PATCH 08/14] Change `Error` from a struct to an enum (BREAKING CHANGE). This allows us to separate out the underlying SQLite error codes from errors that occur on the Rust side. --- libsqlite3-sys/src/error.rs | 7 + src/backup.rs | 7 +- src/functions.rs | 58 ++++---- src/lib.rs | 278 +++++++++++++++++++++--------------- src/named_params.rs | 5 +- src/trace.rs | 11 +- src/types.rs | 116 ++++++--------- 7 files changed, 241 insertions(+), 241 deletions(-) diff --git a/libsqlite3-sys/src/error.rs b/libsqlite3-sys/src/error.rs index 5c4bace..cf6f1b0 100644 --- a/libsqlite3-sys/src/error.rs +++ b/libsqlite3-sys/src/error.rs @@ -1,4 +1,5 @@ use libc::c_int; +use std::error; use std::fmt; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -77,6 +78,12 @@ impl fmt::Display for Error { } } +impl error::Error for Error { + fn description(&self) -> &str { + code_to_str(self.extended_code) + } +} + // Result codes. pub const SQLITE_OK : c_int = 0; diff --git a/src/backup.rs b/src/backup.rs index 8eb5625..0f724cb 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -244,12 +244,7 @@ impl<'a, 'b> Backup<'a, 'b> { ffi::SQLITE_OK => Ok(More), ffi::SQLITE_BUSY => Ok(Busy), ffi::SQLITE_LOCKED => Ok(Locked), - rc => { - Err(Error { - code: rc, - message: ffi::code_to_str(rc).into(), - }) - } + _ => Err(Error::from_sqlite_code(rc, None)), } } diff --git a/src/functions.rs b/src/functions.rs index 196e639..1def91c 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -26,11 +26,10 @@ //! match entry { //! Occupied(occ) => occ.into_mut(), //! Vacant(vac) => { -//! let r = try!(Regex::new(®ex_s).map_err(|e| Error { -//! code: libsqlite3_sys::SQLITE_ERROR, -//! message: format!("Invalid regular expression: {}", e), -//! })); -//! vac.insert(r) +//! match Regex::new(®ex_s) { +//! Ok(r) => vac.insert(r), +//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))), +//! } //! } //! } //! }; @@ -50,6 +49,7 @@ //! assert!(is_match); //! } //! ``` +use std::error::Error as StdError; use std::ffi::CStr; use std::mem; use std::ptr; @@ -225,14 +225,8 @@ impl FromValue for String { Ok("".to_string()) } else { let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes(); - let utf8_str = str::from_utf8(c_slice); - utf8_str.map(|s| s.to_string()) - .map_err(|e| { - Error { - code: 0, - message: e.to_string(), - } - }) + let utf8_str = try!(str::from_utf8(c_slice)); + Ok(utf8_str.into()) } } @@ -302,10 +296,7 @@ impl<'a> Context<'a> { if T::parameter_has_valid_sqlite_type(arg) { T::parameter_value(arg) } else { - Err(Error { - code: ffi::SQLITE_MISMATCH, - message: "Invalid value type".to_string(), - }) + Err(Error::InvalidFunctionParameterType) } } } @@ -421,9 +412,15 @@ impl InnerConnection { assert!(!boxed_f.is_null(), "Internal error - null function pointer"); match (*boxed_f)(&ctx) { Ok(r) => r.set_result(ctx.ctx), - Err(e) => { - ffi::sqlite3_result_error_code(ctx.ctx, e.code); - if let Ok(cstr) = str_to_cstring(&e.message) { + Err(Error::SqliteFailure(err, s)) => { + ffi::sqlite3_result_error_code(ctx.ctx, err.extended_code); + if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) { + ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1); + } + }, + Err(err) => { + ffi::sqlite3_result_error_code(ctx.ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); + if let Ok(cstr) = str_to_cstring(err.description()) { ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1); } } @@ -476,7 +473,6 @@ mod test { use self::regex::Regex; use {Connection, Error, Result}; - use ffi; use functions::Context; fn half(ctx: &Context) -> Result { @@ -516,13 +512,10 @@ mod test { let new_re = match saved_re { None => { let s = try!(ctx.get::(0)); - let r = try!(Regex::new(&s).map_err(|e| { - Error { - code: ffi::SQLITE_ERROR, - message: format!("Invalid regular expression: {}", e), - } - })); - Some(r) + match Regex::new(&s) { + Ok(r) => Some(r), + Err(err) => return Err(Error::UserFunctionError(Box::new(err))), + } } Some(_) => None, }; @@ -591,11 +584,10 @@ mod test { match entry { Occupied(occ) => occ.into_mut(), Vacant(vac) => { - let r = try!(Regex::new(®ex_s).map_err(|e| Error { - code: ffi::SQLITE_ERROR, - message: format!("Invalid regular expression: {}", e), - })); - vac.insert(r) + match Regex::new(®ex_s) { + Ok(r) => vac.insert(r), + Err(err) => return Err(Error::UserFunctionError(Box::new(err))), + } } } }; diff --git a/src/lib.rs b/src/lib.rs index 875cfb1..c4dcfa7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,59 +103,129 @@ unsafe fn errmsg_to_string(errmsg: *const c_char) -> String { /// Old name for `Error`. `SqliteError` is deprecated. pub type SqliteError = Error; -/// Encompasses an error result from a call to the SQLite C API. -#[derive(Debug, PartialEq)] -pub struct Error { - /// The error code returned by a SQLite C API call. See [SQLite Result - /// Codes](http://www.sqlite.org/rescode.html) for details. - pub code: c_int, +#[derive(Debug)] +pub enum Error { + SqliteFailure(ffi::Error, Option), + FromSqlConversionFailure(Box), + Utf8Error(str::Utf8Error), + NulError(std::ffi::NulError), + InvalidParameterName(String), + InvalidPath(PathBuf), + ExecuteReturnedResults, + QueryReturnedNoRows, + GetFromStaleRow, + InvalidColumnIndex(c_int), + InvalidColumnType, - /// The error message provided by [sqlite3_errmsg](http://www.sqlite.org/c3ref/errcode.html), - /// if possible, or a generic error message based on `code` otherwise. - pub message: String, + #[cfg(feature = "functions")] + InvalidFunctionParameterType, + #[cfg(feature = "functions")] + #[allow(dead_code)] + UserFunctionError(Box), +} + +impl From for Error { + fn from(err: str::Utf8Error) -> Error { + Error::Utf8Error(err) + } +} + +impl From for Error { + fn from(err: std::ffi::NulError) -> Error { + Error::NulError(err) + } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} (SQLite error {})", self.message, self.code) + match self { + &Error::SqliteFailure(ref err, None) => err.fmt(f), + &Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), + &Error::FromSqlConversionFailure(ref err) => err.fmt(f), + &Error::Utf8Error(ref err) => err.fmt(f), + &Error::NulError(ref err) => err.fmt(f), + &Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name), + &Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()), + &Error::ExecuteReturnedResults => write!(f, "Execute returned results - did you mean to call query?"), + &Error::QueryReturnedNoRows => write!(f, "Query returned no rows"), + &Error::GetFromStaleRow => write!(f, "Attempted to get a value from a stale row"), + &Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i), + &Error::InvalidColumnType => write!(f, "Invalid column type"), + + #[cfg(feature = "functions")] + &Error::InvalidFunctionParameterType => write!(f, "Invalid function parameter type"), + #[cfg(feature = "functions")] + &Error::UserFunctionError(ref err) => err.fmt(f), + } } } impl error::Error for Error { fn description(&self) -> &str { - &self.message + match self { + &Error::SqliteFailure(ref err, None) => err.description(), + &Error::SqliteFailure(_, Some(ref s)) => s, + &Error::FromSqlConversionFailure(ref err) => err.description(), + &Error::Utf8Error(ref err) => err.description(), + &Error::InvalidParameterName(_) => "invalid parameter name", + &Error::NulError(ref err) => err.description(), + &Error::InvalidPath(_) => "invalid path", + &Error::ExecuteReturnedResults => "execute returned results - did you mean to call query?", + &Error::QueryReturnedNoRows => "query returned no rows", + &Error::GetFromStaleRow => "attempted to get a value from a stale row", + &Error::InvalidColumnIndex(_) => "invalid column index", + &Error::InvalidColumnType => "invalid column type", + + #[cfg(feature = "functions")] + &Error::InvalidFunctionParameterType => "invalid function parameter type", + #[cfg(feature = "functions")] + &Error::UserFunctionError(ref err) => err.description(), + } + } + + fn cause(&self) -> Option<&error::Error> { + match self { + &Error::SqliteFailure(ref err, _) => Some(err), + &Error::FromSqlConversionFailure(ref err) => Some(&**err), + &Error::Utf8Error(ref err) => Some(err), + &Error::NulError(ref err) => Some(err), + &Error::InvalidParameterName(_) => None, + &Error::InvalidPath(_) => None, + &Error::ExecuteReturnedResults => None, + &Error::QueryReturnedNoRows => None, + &Error::GetFromStaleRow => None, + &Error::InvalidColumnIndex(_) => None, + &Error::InvalidColumnType => None, + + #[cfg(feature = "functions")] + &Error::InvalidFunctionParameterType => None, + #[cfg(feature = "functions")] + &Error::UserFunctionError(ref err) => Some(&**err), + } } } impl Error { + fn from_sqlite_code(code: c_int, message: Option) -> Error { + Error::SqliteFailure(ffi::Error::new(code), message) + } + fn from_handle(db: *mut ffi::Struct_sqlite3, code: c_int) -> Error { let message = if db.is_null() { - ffi::code_to_str(code).to_string() + None } else { - unsafe { errmsg_to_string(ffi::sqlite3_errmsg(db)) } + Some(unsafe { errmsg_to_string(ffi::sqlite3_errmsg(db)) }) }; - Error { - code: code, - message: message, - } + Error::from_sqlite_code(code, message) } } fn str_to_cstring(s: &str) -> Result { - CString::new(s).map_err(|_| { - Error { - code: ffi::SQLITE_MISUSE, - message: format!("Could not convert string {} to C-combatible string", s), - } - }) + Ok(try!(CString::new(s))) } fn path_to_cstring(p: &Path) -> Result { - let s = try!(p.to_str().ok_or(Error { - code: ffi::SQLITE_MISUSE, - message: format!("Could not convert path {} to UTF-8 string", - p.to_string_lossy()), - })); + let s = try!(p.to_str().ok_or(Error::InvalidPath(p.to_owned()))); str_to_cstring(s) } @@ -598,10 +668,7 @@ impl InnerConnection { let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null()); if r != ffi::SQLITE_OK { let e = if db.is_null() { - Error { - code: r, - message: ffi::code_to_str(r).to_string(), - } + Error::from_sqlite_code(r, None) } else { let e = Error::from_handle(db, r); ffi::sqlite3_close(db); @@ -681,10 +748,7 @@ impl InnerConnection { } else { let message = errmsg_to_string(&*errmsg); ffi::sqlite3_free(errmsg as *mut libc::c_void); - Err(Error { - code: r, - message: message, - }) + Err(Error::from_sqlite_code(r, Some(message))) } } } @@ -698,10 +762,7 @@ impl InnerConnection { sql: &str) -> Result> { if sql.len() >= ::std::i32::MAX as usize { - return Err(Error { - code: ffi::SQLITE_TOOBIG, - message: "statement too long".to_string(), - }); + return Err(Error::from_sqlite_code(ffi::SQLITE_TOOBIG, None)); } let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let c_sql = try!(str_to_cstring(sql)); @@ -797,21 +858,12 @@ impl<'conn> Statement<'conn> { match r { ffi::SQLITE_DONE => { if self.column_count != 0 { - Err(Error { - code: ffi::SQLITE_MISUSE, - message: "Unexpected column count - did you mean to call query?" - .to_string(), - }) + Err(Error::ExecuteReturnedResults) } else { Ok(self.conn.changes()) } - } - ffi::SQLITE_ROW => { - Err(Error { - code: r, - message: "Unexpected row result - did you mean to call query?".to_string(), - }) - } + }, + ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults), _ => Err(self.conn.decode_result(r).unwrap_err()), } } @@ -1052,12 +1104,7 @@ impl<'stmt> Rows<'stmt> { fn get_expected_row(&mut self) -> Result> { match self.next() { Some(row) => row, - None => { - Err(Error { - code: ffi::SQLITE_NOTICE, - message: "Query did not return a row".to_string(), - }) - } + None => Err(Error::QueryReturnedNoRows), } } } @@ -1142,26 +1189,17 @@ impl<'stmt> Row<'stmt> { /// for this row or if this row is stale. pub fn get_checked(&self, idx: c_int) -> Result { if self.row_idx != self.current_row.get() { - return Err(Error { - code: ffi::SQLITE_MISUSE, - message: "Cannot get values from a row after advancing to next row".to_string(), - }); + return Err(Error::GetFromStaleRow); } unsafe { if idx < 0 || idx >= self.stmt.column_count { - return Err(Error { - code: ffi::SQLITE_MISUSE, - message: "Invalid column index".to_string(), - }); + return Err(Error::InvalidColumnIndex(idx)); } if T::column_has_valid_sqlite_type(self.stmt.stmt, idx) { FromSql::column_result(self.stmt.stmt, idx) } else { - Err(Error { - code: ffi::SQLITE_MISMATCH, - message: "Invalid column type".to_string(), - }) + Err(Error::InvalidColumnType) } } } @@ -1264,8 +1302,10 @@ mod test { fn test_execute_select() { let db = checked_memory_handle(); let err = db.execute("SELECT 1 WHERE 1 < ?", &[&1i32]).unwrap_err(); - assert!(err.code == ffi::SQLITE_MISUSE); - assert!(err.message == "Unexpected column count - did you mean to call query?"); + match err { + Error::ExecuteReturnedResults => (), + _ => panic!("Unexpected error: {}", err), + } } #[test] @@ -1364,10 +1404,10 @@ mod test { .unwrap()); let result = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get::(0)); - let error = result.unwrap_err(); - - assert!(error.code == ffi::SQLITE_NOTICE); - assert!(error.message == "Query did not return a row"); + match result.unwrap_err() { + Error::QueryReturnedNoRows => (), + err => panic!("Unexpected error {}", err), + } let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", &[], |_| ()); @@ -1380,7 +1420,7 @@ mod test { db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap(); let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err(); - assert!(err.message.contains("does_not_exist")); + assert!(format!("{}", err).contains("does_not_exist")); } #[test] @@ -1397,8 +1437,10 @@ mod test { assert_eq!(2i32, second.get(0)); - let result = first.get_checked::(0); - assert!(result.unwrap_err().message.contains("advancing to next row")); + match first.get_checked::(0).unwrap_err() { + Error::GetFromStaleRow => (), + err => panic!("Unexpected error {}", err), + } } #[test] @@ -1433,15 +1475,17 @@ mod test { let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", &[]); assert!(result.is_err()); - let err = result.err().unwrap(); - assert_eq!(err.code, ffi::SQLITE_CONSTRAINT_NOTNULL); + match result.unwrap_err() { + Error::SqliteFailure(err, _) => assert_eq!(err.extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL), + err => panic!("Unexpected error {}", err), + } } mod query_and_then_tests { extern crate libsqlite3_sys as ffi; use super::*; - #[derive(Debug, PartialEq)] + #[derive(Debug)] enum CustomError { SomeError, Sqlite(Error), @@ -1512,27 +1556,23 @@ mod test { db.execute_batch(sql).unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); - let bad_type: Result> = query.query_and_then(&[], - |row| row.get_checked(1)) + let bad_type: Result> = query.query_and_then(&[], |row| row.get_checked(1)) .unwrap() .collect(); - assert_eq!(bad_type, - Err(Error { - code: ffi::SQLITE_MISMATCH, - message: "Invalid column type".to_owned(), - })); + match bad_type.unwrap_err() { + Error::InvalidColumnType => (), + err => panic!("Unexpected error {}", err), + } - let bad_idx: Result> = query.query_and_then(&[], - |row| row.get_checked(3)) + let bad_idx: Result> = query.query_and_then(&[], |row| row.get_checked(3)) .unwrap() .collect(); - assert_eq!(bad_idx, - Err(Error { - code: ffi::SQLITE_MISUSE, - message: "Invalid column index".to_owned(), - })); + match bad_idx.unwrap_err() { + Error::InvalidColumnIndex(_) => (), + err => panic!("Unexpected error {}", err), + } } #[test] @@ -1580,11 +1620,10 @@ mod test { .unwrap() .collect(); - assert_eq!(bad_type, - Err(CustomError::Sqlite(Error { - code: ffi::SQLITE_MISMATCH, - message: "Invalid column type".to_owned(), - }))); + match bad_type.unwrap_err() { + CustomError::Sqlite(Error::InvalidColumnType) => (), + err => panic!("Unexpected error {}", err), + } let bad_idx: CustomResult> = query.query_and_then(&[], |row| { row.get_checked(3) @@ -1593,11 +1632,10 @@ mod test { .unwrap() .collect(); - assert_eq!(bad_idx, - Err(CustomError::Sqlite(Error { - code: ffi::SQLITE_MISUSE, - message: "Invalid column index".to_owned(), - }))); + match bad_idx.unwrap_err() { + CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (), + err => panic!("Unexpected error {}", err), + } let non_sqlite_err: CustomResult> = query.query_and_then(&[], |_| { Err(CustomError::SomeError) @@ -1605,7 +1643,10 @@ mod test { .unwrap() .collect(); - assert_eq!(non_sqlite_err, Err(CustomError::SomeError)); + match non_sqlite_err.unwrap_err() { + CustomError::SomeError => (), + err => panic!("Unexpected error {}", err), + } } #[test] @@ -1641,27 +1682,28 @@ mod test { row.get_checked(1).map_err(CustomError::Sqlite) }); - assert_eq!(bad_type, - Err(CustomError::Sqlite(Error { - code: ffi::SQLITE_MISMATCH, - message: "Invalid column type".to_owned(), - }))); + match bad_type.unwrap_err() { + CustomError::Sqlite(Error::InvalidColumnType) => (), + err => panic!("Unexpected error {}", err), + } let bad_idx: CustomResult = db.query_row_and_then(query, &[], |row| { row.get_checked(3).map_err(CustomError::Sqlite) }); - assert_eq!(bad_idx, - Err(CustomError::Sqlite(Error { - code: ffi::SQLITE_MISUSE, - message: "Invalid column index".to_owned(), - }))); + match bad_idx.unwrap_err() { + CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (), + err => panic!("Unexpected error {}", err), + } let non_sqlite_err: CustomResult = db.query_row_and_then(query, &[], |_| { Err(CustomError::SomeError) }); - assert_eq!(non_sqlite_err, Err(CustomError::SomeError)); + match non_sqlite_err.unwrap_err() { + CustomError::SomeError => (), + err => panic!("Unexpected error {}", err), + } } } diff --git a/src/named_params.rs b/src/named_params.rs index d1dcdc9..dc880ca 100644 --- a/src/named_params.rs +++ b/src/named_params.rs @@ -133,10 +133,7 @@ impl<'conn> Statement<'conn> { if let Some(i) = try!(self.parameter_index(name)) { try!(self.conn.decode_result(unsafe { value.bind_parameter(self.stmt, i) })); } else { - return Err(Error { - code: ffi::SQLITE_MISUSE, - message: format!("Invalid parameter name: {}", name), - }); + return Err(Error::InvalidParameterName(name.into())); } } Ok(()) diff --git a/src/trace.rs b/src/trace.rs index 0c49ce5..c11534c 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -42,14 +42,11 @@ pub unsafe fn config_log(callback: Option) -> Result<()> { } }; - if rc != ffi::SQLITE_OK { - return Err(Error { - code: rc, - message: "sqlite3_config(SQLITE_CONFIG_LOG, ...)".to_string(), - }); + if rc == ffi::SQLITE_OK { + Ok(()) + } else { + Err(Error::from_sqlite_code(rc, None)) } - - Ok(()) } /// Write a message into the error log established by `config_log`. diff --git a/src/types.rs b/src/types.rs index f0852ab..c33afa8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -232,14 +232,8 @@ impl FromSql for String { Ok("".to_string()) } else { let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes(); - let utf8_str = str::from_utf8(c_slice); - utf8_str.map(|s| s.to_string()) - .map_err(|e| { - Error { - code: 0, - message: e.to_string(), - } - }) + let utf8_str = try!(str::from_utf8(c_slice)); + Ok(utf8_str.into()) } } @@ -272,14 +266,10 @@ impl FromSql for time::Timespec { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { let col_str = FromSql::column_result(stmt, col); col_str.and_then(|txt: String| { - time::strptime(&txt, SQLITE_DATETIME_FMT) - .map(|tm| tm.to_timespec()) - .map_err(|parse_error| { - Error { - code: ffi::SQLITE_MISMATCH, - message: format!("{}", parse_error), - } - }) + match time::strptime(&txt, SQLITE_DATETIME_FMT) { + Ok(tm) => Ok(tm.to_timespec()), + Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), + } }) } @@ -306,8 +296,8 @@ impl FromSql for Option { #[cfg(test)] mod test { use Connection; - use ffi; use super::time; + use Error; use libc::{c_int, c_double}; fn checked_memory_handle() -> Connection { @@ -380,6 +370,13 @@ mod test { #[test] fn test_mismatched_types() { + fn is_invalid_column_type(err: Error) -> bool { + match err { + Error::InvalidColumnType => true, + _ => false, + } + } + let db = checked_memory_handle(); db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)", @@ -403,69 +400,42 @@ mod test { // check some invalid types // 0 is actually a blob (Vec) - assert_eq!(row.get_checked::(0).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(0).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(0).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(0).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(0).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::>(0).err().unwrap().code, - ffi::SQLITE_MISMATCH); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(0).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(0).err().unwrap())); // 1 is actually a text (String) - assert_eq!(row.get_checked::(1).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(1).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(1).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::>(1).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::>(1).err().unwrap().code, - ffi::SQLITE_MISMATCH); + assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(1).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(1).err().unwrap())); // 2 is actually an integer - assert_eq!(row.get_checked::(2).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(2).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::>(2).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(2).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::>(2).err().unwrap().code, - ffi::SQLITE_MISMATCH); + assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); // 3 is actually a float (c_double) - assert_eq!(row.get_checked::(3).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(3).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(3).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::>(3).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(3).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::>(3).err().unwrap().code, - ffi::SQLITE_MISMATCH); + assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(3).err().unwrap())); // 4 is actually NULL - assert_eq!(row.get_checked::(4).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(4).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(4).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(4).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::>(4).err().unwrap().code, - ffi::SQLITE_MISMATCH); - assert_eq!(row.get_checked::(4).err().unwrap().code, - ffi::SQLITE_MISMATCH); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(4).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); } } From 047861b928f159d1c6c5f77c32a4e968d6b69edb Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sun, 13 Dec 2015 01:04:09 -0500 Subject: [PATCH 09/14] Move `Error` into its own module (internal organization only - public API remains). --- src/backup.rs | 15 +++--- src/error.rs | 126 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 136 ++++---------------------------------------------- src/trace.rs | 5 +- 4 files changed, 146 insertions(+), 136 deletions(-) create mode 100644 src/error.rs diff --git a/src/backup.rs b/src/backup.rs index 0f724cb..047f4fd 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -36,7 +36,8 @@ use std::time::Duration; use ffi; -use {DatabaseName, Connection, Error, Result}; +use {DatabaseName, Connection, Result}; +use error::{error_from_sqlite_code, error_from_handle}; impl Connection { /// Back up the `name` database to the given destination path. @@ -70,8 +71,8 @@ impl Connection { match r { Done => Ok(()), - Busy => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)), - Locked => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)), + Busy => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)), + Locked => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)), More => unreachable!(), } } @@ -115,8 +116,8 @@ impl Connection { match r { Done => Ok(()), - Busy => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)), - Locked => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)), + Busy => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)), + Locked => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)), More => unreachable!(), } } @@ -201,7 +202,7 @@ impl<'a, 'b> Backup<'a, 'b> { from.db.borrow_mut().db, from_name.as_ptr()); if b.is_null() { - return Err(Error::from_handle(to_db, ffi::sqlite3_errcode(to_db))); + return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db))); } b }; @@ -244,7 +245,7 @@ impl<'a, 'b> Backup<'a, 'b> { ffi::SQLITE_OK => Ok(More), ffi::SQLITE_BUSY => Ok(Busy), ffi::SQLITE_LOCKED => Ok(Locked), - _ => Err(Error::from_sqlite_code(rc, None)), + _ => Err(error_from_sqlite_code(rc, None)), } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..245f2ea --- /dev/null +++ b/src/error.rs @@ -0,0 +1,126 @@ +use std::error; +use std::fmt; +use std::path::PathBuf; +use std::str; +use libc::c_int; +use {ffi, errmsg_to_string}; + +/// Old name for `Error`. `SqliteError` is deprecated. +pub type SqliteError = Error; + +#[derive(Debug)] +pub enum Error { + SqliteFailure(ffi::Error, Option), + FromSqlConversionFailure(Box), + Utf8Error(str::Utf8Error), + NulError(::std::ffi::NulError), + InvalidParameterName(String), + InvalidPath(PathBuf), + ExecuteReturnedResults, + QueryReturnedNoRows, + GetFromStaleRow, + InvalidColumnIndex(c_int), + InvalidColumnType, + + #[cfg(feature = "functions")] + InvalidFunctionParameterType, + #[cfg(feature = "functions")] + #[allow(dead_code)] + UserFunctionError(Box), +} + +impl From for Error { + fn from(err: str::Utf8Error) -> Error { + Error::Utf8Error(err) + } +} + +impl From<::std::ffi::NulError> for Error { + fn from(err: ::std::ffi::NulError) -> Error { + Error::NulError(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Error::SqliteFailure(ref err, None) => err.fmt(f), + &Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), + &Error::FromSqlConversionFailure(ref err) => err.fmt(f), + &Error::Utf8Error(ref err) => err.fmt(f), + &Error::NulError(ref err) => err.fmt(f), + &Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name), + &Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()), + &Error::ExecuteReturnedResults => write!(f, "Execute returned results - did you mean to call query?"), + &Error::QueryReturnedNoRows => write!(f, "Query returned no rows"), + &Error::GetFromStaleRow => write!(f, "Attempted to get a value from a stale row"), + &Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i), + &Error::InvalidColumnType => write!(f, "Invalid column type"), + + #[cfg(feature = "functions")] + &Error::InvalidFunctionParameterType => write!(f, "Invalid function parameter type"), + #[cfg(feature = "functions")] + &Error::UserFunctionError(ref err) => err.fmt(f), + } + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + match self { + &Error::SqliteFailure(ref err, None) => err.description(), + &Error::SqliteFailure(_, Some(ref s)) => s, + &Error::FromSqlConversionFailure(ref err) => err.description(), + &Error::Utf8Error(ref err) => err.description(), + &Error::InvalidParameterName(_) => "invalid parameter name", + &Error::NulError(ref err) => err.description(), + &Error::InvalidPath(_) => "invalid path", + &Error::ExecuteReturnedResults => "execute returned results - did you mean to call query?", + &Error::QueryReturnedNoRows => "query returned no rows", + &Error::GetFromStaleRow => "attempted to get a value from a stale row", + &Error::InvalidColumnIndex(_) => "invalid column index", + &Error::InvalidColumnType => "invalid column type", + + #[cfg(feature = "functions")] + &Error::InvalidFunctionParameterType => "invalid function parameter type", + #[cfg(feature = "functions")] + &Error::UserFunctionError(ref err) => err.description(), + } + } + + fn cause(&self) -> Option<&error::Error> { + match self { + &Error::SqliteFailure(ref err, _) => Some(err), + &Error::FromSqlConversionFailure(ref err) => Some(&**err), + &Error::Utf8Error(ref err) => Some(err), + &Error::NulError(ref err) => Some(err), + &Error::InvalidParameterName(_) => None, + &Error::InvalidPath(_) => None, + &Error::ExecuteReturnedResults => None, + &Error::QueryReturnedNoRows => None, + &Error::GetFromStaleRow => None, + &Error::InvalidColumnIndex(_) => None, + &Error::InvalidColumnType => None, + + #[cfg(feature = "functions")] + &Error::InvalidFunctionParameterType => None, + #[cfg(feature = "functions")] + &Error::UserFunctionError(ref err) => Some(&**err), + } + } +} + +// These are public but not re-exported by lib.rs, so only visible within crate. + +pub fn error_from_sqlite_code(code: c_int, message: Option) -> Error { + Error::SqliteFailure(ffi::Error::new(code), message) +} + +pub fn error_from_handle(db: *mut ffi::Struct_sqlite3, code: c_int) -> Error { + let message = if db.is_null() { + None + } else { + Some(unsafe { errmsg_to_string(ffi::sqlite3_errmsg(db)) }) + }; + error_from_sqlite_code(code, message) +} diff --git a/src/lib.rs b/src/lib.rs index c4dcfa7..564aca5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,6 @@ use std::mem; use std::ptr; use std::fmt; use std::path::{Path, PathBuf}; -use std::error; use std::rc::Rc; use std::cell::{RefCell, Cell}; use std::ffi::{CStr, CString}; @@ -73,8 +72,10 @@ use std::str; use libc::{c_int, c_char}; use types::{ToSql, FromSql}; +use error::{error_from_sqlite_code, error_from_handle}; pub use transaction::{SqliteTransaction, Transaction, TransactionBehavior}; +pub use error::{SqliteError, Error}; #[cfg(feature = "load_extension")] pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard}; @@ -82,6 +83,7 @@ pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard}; pub mod types; mod transaction; mod named_params; +mod error; #[cfg(feature = "load_extension")]mod load_extension_guard; #[cfg(feature = "trace")]pub mod trace; #[cfg(feature = "backup")]pub mod backup; @@ -100,126 +102,6 @@ unsafe fn errmsg_to_string(errmsg: *const c_char) -> String { utf8_str.unwrap_or("Invalid string encoding").to_string() } -/// Old name for `Error`. `SqliteError` is deprecated. -pub type SqliteError = Error; - -#[derive(Debug)] -pub enum Error { - SqliteFailure(ffi::Error, Option), - FromSqlConversionFailure(Box), - Utf8Error(str::Utf8Error), - NulError(std::ffi::NulError), - InvalidParameterName(String), - InvalidPath(PathBuf), - ExecuteReturnedResults, - QueryReturnedNoRows, - GetFromStaleRow, - InvalidColumnIndex(c_int), - InvalidColumnType, - - #[cfg(feature = "functions")] - InvalidFunctionParameterType, - #[cfg(feature = "functions")] - #[allow(dead_code)] - UserFunctionError(Box), -} - -impl From for Error { - fn from(err: str::Utf8Error) -> Error { - Error::Utf8Error(err) - } -} - -impl From for Error { - fn from(err: std::ffi::NulError) -> Error { - Error::NulError(err) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &Error::SqliteFailure(ref err, None) => err.fmt(f), - &Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), - &Error::FromSqlConversionFailure(ref err) => err.fmt(f), - &Error::Utf8Error(ref err) => err.fmt(f), - &Error::NulError(ref err) => err.fmt(f), - &Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name), - &Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()), - &Error::ExecuteReturnedResults => write!(f, "Execute returned results - did you mean to call query?"), - &Error::QueryReturnedNoRows => write!(f, "Query returned no rows"), - &Error::GetFromStaleRow => write!(f, "Attempted to get a value from a stale row"), - &Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i), - &Error::InvalidColumnType => write!(f, "Invalid column type"), - - #[cfg(feature = "functions")] - &Error::InvalidFunctionParameterType => write!(f, "Invalid function parameter type"), - #[cfg(feature = "functions")] - &Error::UserFunctionError(ref err) => err.fmt(f), - } - } -} - -impl error::Error for Error { - fn description(&self) -> &str { - match self { - &Error::SqliteFailure(ref err, None) => err.description(), - &Error::SqliteFailure(_, Some(ref s)) => s, - &Error::FromSqlConversionFailure(ref err) => err.description(), - &Error::Utf8Error(ref err) => err.description(), - &Error::InvalidParameterName(_) => "invalid parameter name", - &Error::NulError(ref err) => err.description(), - &Error::InvalidPath(_) => "invalid path", - &Error::ExecuteReturnedResults => "execute returned results - did you mean to call query?", - &Error::QueryReturnedNoRows => "query returned no rows", - &Error::GetFromStaleRow => "attempted to get a value from a stale row", - &Error::InvalidColumnIndex(_) => "invalid column index", - &Error::InvalidColumnType => "invalid column type", - - #[cfg(feature = "functions")] - &Error::InvalidFunctionParameterType => "invalid function parameter type", - #[cfg(feature = "functions")] - &Error::UserFunctionError(ref err) => err.description(), - } - } - - fn cause(&self) -> Option<&error::Error> { - match self { - &Error::SqliteFailure(ref err, _) => Some(err), - &Error::FromSqlConversionFailure(ref err) => Some(&**err), - &Error::Utf8Error(ref err) => Some(err), - &Error::NulError(ref err) => Some(err), - &Error::InvalidParameterName(_) => None, - &Error::InvalidPath(_) => None, - &Error::ExecuteReturnedResults => None, - &Error::QueryReturnedNoRows => None, - &Error::GetFromStaleRow => None, - &Error::InvalidColumnIndex(_) => None, - &Error::InvalidColumnType => None, - - #[cfg(feature = "functions")] - &Error::InvalidFunctionParameterType => None, - #[cfg(feature = "functions")] - &Error::UserFunctionError(ref err) => Some(&**err), - } - } -} - -impl Error { - fn from_sqlite_code(code: c_int, message: Option) -> Error { - Error::SqliteFailure(ffi::Error::new(code), message) - } - - fn from_handle(db: *mut ffi::Struct_sqlite3, code: c_int) -> Error { - let message = if db.is_null() { - None - } else { - Some(unsafe { errmsg_to_string(ffi::sqlite3_errmsg(db)) }) - }; - Error::from_sqlite_code(code, message) - } -} - fn str_to_cstring(s: &str) -> Result { Ok(try!(CString::new(s))) } @@ -668,9 +550,9 @@ impl InnerConnection { let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null()); if r != ffi::SQLITE_OK { let e = if db.is_null() { - Error::from_sqlite_code(r, None) + error_from_sqlite_code(r, None) } else { - let e = Error::from_handle(db, r); + let e = error_from_handle(db, r); ffi::sqlite3_close(db); e }; @@ -679,7 +561,7 @@ impl InnerConnection { } let r = ffi::sqlite3_busy_timeout(db, 5000); if r != ffi::SQLITE_OK { - let e = Error::from_handle(db, r); + let e = error_from_handle(db, r); ffi::sqlite3_close(db); return Err(e); } @@ -699,7 +581,7 @@ impl InnerConnection { if code == ffi::SQLITE_OK { Ok(()) } else { - Err(Error::from_handle(self.db(), code)) + Err(error_from_handle(self.db(), code)) } } @@ -748,7 +630,7 @@ impl InnerConnection { } else { let message = errmsg_to_string(&*errmsg); ffi::sqlite3_free(errmsg as *mut libc::c_void); - Err(Error::from_sqlite_code(r, Some(message))) + Err(error_from_sqlite_code(r, Some(message))) } } } @@ -762,7 +644,7 @@ impl InnerConnection { sql: &str) -> Result> { if sql.len() >= ::std::i32::MAX as usize { - return Err(Error::from_sqlite_code(ffi::SQLITE_TOOBIG, None)); + return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None)); } let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let c_sql = try!(str_to_cstring(sql)); diff --git a/src/trace.rs b/src/trace.rs index c11534c..bd3b537 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -8,7 +8,8 @@ use std::str; use std::time::Duration; use super::ffi; -use {Error, Result, Connection}; +use {Result, Connection}; +use error::error_from_sqlite_code; /// Set up the process-wide SQLite error logging callback. /// This function is marked unsafe for two reasons: @@ -45,7 +46,7 @@ pub unsafe fn config_log(callback: Option) -> Result<()> { if rc == ffi::SQLITE_OK { Ok(()) } else { - Err(Error::from_sqlite_code(rc, None)) + Err(error_from_sqlite_code(rc, None)) } } From 2129cdb0f2b9896352a2052cf7224c3f3344cc4e Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 15 Dec 2015 14:34:42 -0500 Subject: [PATCH 10/14] Add Send and Sync bounds to boxed errors to be comaptible with io::Error. --- src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 245f2ea..88a9232 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,7 +11,7 @@ pub type SqliteError = Error; #[derive(Debug)] pub enum Error { SqliteFailure(ffi::Error, Option), - FromSqlConversionFailure(Box), + FromSqlConversionFailure(Box), Utf8Error(str::Utf8Error), NulError(::std::ffi::NulError), InvalidParameterName(String), @@ -26,7 +26,7 @@ pub enum Error { InvalidFunctionParameterType, #[cfg(feature = "functions")] #[allow(dead_code)] - UserFunctionError(Box), + UserFunctionError(Box), } impl From for Error { From 7920dbc5ffcdeecdff407f5105370d4d40586ba8 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 15 Dec 2015 14:41:54 -0500 Subject: [PATCH 11/14] Only check for SQLITE_CONSTRAINT_NOTNULL on new enough versions of SQLite. --- src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 564aca5..7ca67e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1358,7 +1358,16 @@ mod test { assert!(result.is_err()); match result.unwrap_err() { - Error::SqliteFailure(err, _) => assert_eq!(err.extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL), + Error::SqliteFailure(err, _) => { + assert_eq!(err.code, ffi::ErrorCode::ConstraintViolation); + + // extended error codes for constraints were added in SQLite 3.7.16; if we're + // running on a version at least that new, check for the extended code + let version = unsafe { ffi::sqlite3_libversion_number() }; + if version >= 3007016 { + assert_eq!(err.extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL) + } + }, err => panic!("Unexpected error {}", err), } } From 2e082d7f9494739c95d5fca4db90263fbd3c57c5 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 16 Dec 2015 23:46:39 -0500 Subject: [PATCH 12/14] Document new Error enum. --- Changelog.md | 6 ++++++ src/error.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/Changelog.md b/Changelog.md index e47480a..f041681 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,11 @@ # Version UPCOMING (TBD) +* BREAKING CHANGE: `SqliteError` is now an enum instead of a struct. Previously, we were (ab)using + the error code and message to send back both underlying SQLite errors and errors that occurred + at the Rust level. Now those have been separated out; SQLite errors are returned as + `SqliteFailure` cases (which still include the error code but also include a Rust-friendlier + enum as well), and rusqlite-level errors are captured in other cases. Because of this change, + `SqliteError` no longer implements `PartialEq`. * BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and `SqliteTransactionExclusive` are no longer exported. Instead, use `TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and diff --git a/src/error.rs b/src/error.rs index 88a9232..810fb65 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,22 +8,53 @@ use {ffi, errmsg_to_string}; /// Old name for `Error`. `SqliteError` is deprecated. pub type SqliteError = Error; +/// Enum listing possible errors from rusqlite. #[derive(Debug)] pub enum Error { + /// An error from an underlying SQLite call. SqliteFailure(ffi::Error, Option), + + /// An error case available for implementors of the `FromSql` trait. FromSqlConversionFailure(Box), + + /// Error converting a string to UTF-8. Utf8Error(str::Utf8Error), + + /// Error converting a string to a C-compatible string because it contained an embedded nul. NulError(::std::ffi::NulError), + + /// Error when using SQL named parameters and passing a parameter name not present in the SQL. InvalidParameterName(String), + + /// Error converting a file path to a string. InvalidPath(PathBuf), + + /// Error returned when an `execute` call returns rowss. ExecuteReturnedResults, + + /// Error when a query that was expected to return at least one row (e.g., for `query_row`) + /// did not return any. QueryReturnedNoRows, + + /// Error when trying to access a `Row` after stepping past it. See the discussion on + /// the `Rows` type for more details. GetFromStaleRow, + + /// Error when the value of a particular column is requested, but the index is out of range + /// for the statement. InvalidColumnIndex(c_int), + + /// Error when the value of a particular column is requested, but the type of the result in + /// that column cannot be converted to the requested Rust type. InvalidColumnType, + /// Error returned by `functions::Context::get` when the function argument cannot be converted + /// to the requested type. #[cfg(feature = "functions")] InvalidFunctionParameterType, + + /// An error case available for implementors of custom user functions (e.g., + /// `create_scalar_function`). #[cfg(feature = "functions")] #[allow(dead_code)] UserFunctionError(Box), From b241f98920162b67b436ad0ab9f4bf7dc7958822 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 16 Dec 2015 23:33:56 -0500 Subject: [PATCH 13/14] Add test and check for SQLite being in single-threaded mode --- Cargo.toml | 3 +++ Changelog.md | 3 +++ src/error.rs | 7 ++++++ src/lib.rs | 24 +++++++++++++++++++++ tests/deny_single_threaded_sqlite_config.rs | 20 +++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 tests/deny_single_threaded_sqlite_config.rs diff --git a/Cargo.toml b/Cargo.toml index 38200cb..d112f86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,6 @@ version = "0.3.0" [[test]] name = "config_log" harness = false + +[[test]] +name = "deny_single_threaded_sqlite_config" diff --git a/Changelog.md b/Changelog.md index f041681..490a62c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,9 @@ `SqliteFailure` cases (which still include the error code but also include a Rust-friendlier enum as well), and rusqlite-level errors are captured in other cases. Because of this change, `SqliteError` no longer implements `PartialEq`. +* BREAKING CHANGE: When opening a new detection, rusqlite now detects if SQLite was compiled or + configured for single-threaded use only; if it was, connection attempts will fail. If this + affects you, please open an issue. * BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and `SqliteTransactionExclusive` are no longer exported. Instead, use `TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and diff --git a/src/error.rs b/src/error.rs index 810fb65..9670800 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,10 @@ pub enum Error { /// An error from an underlying SQLite call. SqliteFailure(ffi::Error, Option), + /// Error reported when attempting to open a connection when SQLite was configured to + /// allow single-threaded use only. + SqliteSingleThreadedMode, + /// An error case available for implementors of the `FromSql` trait. FromSqlConversionFailure(Box), @@ -77,6 +81,7 @@ impl fmt::Display for Error { match self { &Error::SqliteFailure(ref err, None) => err.fmt(f), &Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), + &Error::SqliteSingleThreadedMode => write!(f, "SQLite was compiled or configured for single-threaded use only"), &Error::FromSqlConversionFailure(ref err) => err.fmt(f), &Error::Utf8Error(ref err) => err.fmt(f), &Error::NulError(ref err) => err.fmt(f), @@ -101,6 +106,7 @@ impl error::Error for Error { match self { &Error::SqliteFailure(ref err, None) => err.description(), &Error::SqliteFailure(_, Some(ref s)) => s, + &Error::SqliteSingleThreadedMode => "SQLite was compiled or configured for single-threaded use only", &Error::FromSqlConversionFailure(ref err) => err.description(), &Error::Utf8Error(ref err) => err.description(), &Error::InvalidParameterName(_) => "invalid parameter name", @@ -122,6 +128,7 @@ impl error::Error for Error { fn cause(&self) -> Option<&error::Error> { match self { &Error::SqliteFailure(ref err, _) => Some(err), + &Error::SqliteSingleThreadedMode => None, &Error::FromSqlConversionFailure(ref err) => Some(&**err), &Error::Utf8Error(ref err) => Some(err), &Error::NulError(ref err) => Some(err), diff --git a/src/lib.rs b/src/lib.rs index 7ca67e1..0b77690 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -546,6 +546,30 @@ impl InnerConnection { flags: OpenFlags) -> Result { unsafe { + // Before opening the database, we need to check that SQLite hasn't been + // compiled or configured to be in single-threaded mode. If it has, we're + // exposing a very unsafe API to Rust, so refuse to open connections at all. + // Unfortunately, the check for this is quite gross. sqlite3_threadsafe() only + // returns how SQLite was _compiled_; there is no public API to check whether + // someone called sqlite3_config() to set single-threaded mode. We can cheat + // by trying to allocate a mutex, though; in single-threaded mode due to + // compilation settings, the magic value 8 is returned (see the definition of + // sqlite3_mutex_alloc at https://github.com/mackyle/sqlite/blob/master/src/mutex.h); + // in single-threaded mode due to sqlite3_config(), the magic value 8 is also + // returned (see the definition of noopMutexAlloc at + // https://github.com/mackyle/sqlite/blob/master/src/mutex_noop.c). + const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8; + let mutex_ptr = ffi::sqlite3_mutex_alloc(0); + let is_singlethreaded = if mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC { + true + } else { + false + }; + ffi::sqlite3_mutex_free(mutex_ptr); + if is_singlethreaded { + return Err(Error::SqliteSingleThreadedMode); + } + let mut db: *mut ffi::sqlite3 = mem::uninitialized(); let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null()); if r != ffi::SQLITE_OK { diff --git a/tests/deny_single_threaded_sqlite_config.rs b/tests/deny_single_threaded_sqlite_config.rs new file mode 100644 index 0000000..8178739 --- /dev/null +++ b/tests/deny_single_threaded_sqlite_config.rs @@ -0,0 +1,20 @@ +//! Ensure we reject connections when SQLite is in single-threaded mode, as it +//! would violate safety if multiple Rust threads tried to use connections. + +extern crate rusqlite; +extern crate libsqlite3_sys as ffi; + +use rusqlite::Connection; + +#[test] +fn test_error_when_singlethread_mode() { + // put SQLite into single-threaded mode + unsafe { + // 1 == SQLITE_CONFIG_SINGLETHREAD + assert_eq!(ffi::sqlite3_config(1), ffi::SQLITE_OK); + println!("{}", ffi::sqlite3_mutex_alloc(0) as u64); + } + + let result = Connection::open_in_memory(); + assert!(result.is_err()); +} From 38cf8d597bf0e992bbd9bbf7a68b77b267b9d7c6 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Thu, 17 Dec 2015 00:16:30 -0500 Subject: [PATCH 14/14] Bump to rusqlite 0.6.0 and libsqlite3-sys 0.4.0. --- Cargo.toml | 4 ++-- Changelog.md | 2 +- libsqlite3-sys/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d112f86..3555180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rusqlite" -version = "0.5.0" +version = "0.6.0" authors = ["John Gallagher "] description = "Ergonomic wrapper for SQLite" repository = "https://github.com/jgallagher/rusqlite" @@ -31,7 +31,7 @@ regex = "~0.1.41" [dependencies.libsqlite3-sys] path = "libsqlite3-sys" -version = "0.3.0" +version = "0.4.0" [[test]] name = "config_log" diff --git a/Changelog.md b/Changelog.md index 490a62c..157a3fc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,4 @@ -# Version UPCOMING (TBD) +# Version 0.6.0 (2015-12-17) * BREAKING CHANGE: `SqliteError` is now an enum instead of a struct. Previously, we were (ab)using the error code and message to send back both underlying SQLite errors and errors that occurred diff --git a/libsqlite3-sys/Cargo.toml b/libsqlite3-sys/Cargo.toml index 6be5973..b199cfc 100644 --- a/libsqlite3-sys/Cargo.toml +++ b/libsqlite3-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libsqlite3-sys" -version = "0.3.0" +version = "0.4.0" authors = ["John Gallagher "] repository = "https://github.com/jgallagher/rusqlite" description = "Native bindings to the libsqlite3 library"