Merge branch 'master' of https://github.com/jgallagher/rusqlite into aggregate

This commit is contained in:
gwenn 2015-12-18 20:39:08 +01:00
commit 468ded3e08
16 changed files with 686 additions and 356 deletions

View File

@ -13,12 +13,3 @@ script:
- cargo test --features trace - cargo test --features trace
- cargo test --features functions - cargo test --features functions
- cargo test --features "backup functions load_extension trace" - 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 '<meta http-equiv=refresh content=0;url=rusqlite/index.html>' > 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

View File

@ -12,3 +12,4 @@ rusqlite contributors (sorted alphabetically)
* [Ben Striegel](https://github.com/bstrie) * [Ben Striegel](https://github.com/bstrie)
* [Andrew Straw](https://github.com/astraw) * [Andrew Straw](https://github.com/astraw)
* [Ronald Kinard](https://github.com/Furyhunter) * [Ronald Kinard](https://github.com/Furyhunter)
* [maciejkula](https://github.com/maciejkula)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rusqlite" name = "rusqlite"
version = "0.5.0" version = "0.6.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
description = "Ergonomic wrapper for SQLite" description = "Ergonomic wrapper for SQLite"
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"
@ -31,8 +31,11 @@ regex = "~0.1.41"
[dependencies.libsqlite3-sys] [dependencies.libsqlite3-sys]
path = "libsqlite3-sys" path = "libsqlite3-sys"
version = "0.3.0" version = "0.4.0"
[[test]] [[test]]
name = "config_log" name = "config_log"
harness = false harness = false
[[test]]
name = "deny_single_threaded_sqlite_config"

View File

@ -1,5 +1,14 @@
# 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
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: 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 * BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and
`SqliteTransactionExclusive` are no longer exported. Instead, use `SqliteTransactionExclusive` are no longer exported. Instead, use
`TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and `TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and

View File

@ -1,6 +1,6 @@
[package] [package]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.3.0" version = "0.4.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"] authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
repository = "https://github.com/jgallagher/rusqlite" repository = "https://github.com/jgallagher/rusqlite"
description = "Native bindings to the libsqlite3 library" description = "Native bindings to the libsqlite3 library"

269
libsqlite3-sys/src/error.rs Normal file
View File

@ -0,0 +1,269 @@
use libc::c_int;
use std::error;
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))
}
}
impl error::Error for Error {
fn description(&self) -> &str {
code_to_str(self.extended_code)
}
}
// 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",
}
}

View File

@ -5,43 +5,13 @@
extern crate libc; extern crate libc;
pub use self::bindgen::*; pub use self::bindgen::*;
pub use self::error::*;
use std::mem; use std::mem;
use libc::c_int; use libc::c_int;
mod bindgen; mod bindgen;
mod error;
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;
// SQLite datatype constants. // SQLite datatype constants.
pub const SQLITE_INTEGER : c_int = 1; pub const SQLITE_INTEGER : c_int = 1;
@ -58,43 +28,6 @@ pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
Some(unsafe { mem::transmute(-1isize) }) 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",
_ => "Unknown error code",
}
}
pub const SQLITE_CONFIG_LOG : c_int = 16; pub const SQLITE_CONFIG_LOG : c_int = 16;
pub const SQLITE_UTF8 : c_int = 1; pub const SQLITE_UTF8 : c_int = 1;
pub const SQLITE_DETERMINISTIC : c_int = 0x800; pub const SQLITE_DETERMINISTIC : c_int = 0x800;

14
publish-ghp-docs.sh Executable file
View File

@ -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 '<meta http-equiv=refresh content=0;url=rusqlite/index.html>' > target/doc/index.html
ghp-import target/doc
git push origin gh-pages:gh-pages

View File

@ -36,7 +36,8 @@ use std::time::Duration;
use ffi; use ffi;
use {DatabaseName, Connection, Error, Result}; use {DatabaseName, Connection, Result};
use error::{error_from_sqlite_code, error_from_handle};
impl Connection { impl Connection {
/// Back up the `name` database to the given destination path. /// Back up the `name` database to the given destination path.
@ -70,8 +71,8 @@ impl Connection {
match r { match r {
Done => Ok(()), Done => Ok(()),
Busy => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)), Busy => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
Locked => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)), Locked => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
More => unreachable!(), More => unreachable!(),
} }
} }
@ -115,8 +116,8 @@ impl Connection {
match r { match r {
Done => Ok(()), Done => Ok(()),
Busy => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)), Busy => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
Locked => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)), Locked => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
More => unreachable!(), More => unreachable!(),
} }
} }
@ -201,7 +202,7 @@ impl<'a, 'b> Backup<'a, 'b> {
from.db.borrow_mut().db, from.db.borrow_mut().db,
from_name.as_ptr()); from_name.as_ptr());
if b.is_null() { 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 b
}; };
@ -244,12 +245,7 @@ impl<'a, 'b> Backup<'a, 'b> {
ffi::SQLITE_OK => Ok(More), ffi::SQLITE_OK => Ok(More),
ffi::SQLITE_BUSY => Ok(Busy), ffi::SQLITE_BUSY => Ok(Busy),
ffi::SQLITE_LOCKED => Ok(Locked), ffi::SQLITE_LOCKED => Ok(Locked),
rc => { _ => Err(error_from_sqlite_code(rc, None)),
Err(Error {
code: rc,
message: ffi::code_to_str(rc).into(),
})
}
} }
} }

164
src/error.rs Normal file
View File

@ -0,0 +1,164 @@
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;
/// Enum listing possible errors from rusqlite.
#[derive(Debug)]
pub enum Error {
/// An error from an underlying SQLite call.
SqliteFailure(ffi::Error, Option<String>),
/// 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<error::Error + Send + Sync>),
/// 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<error::Error + Send + Sync>),
}
impl From<str::Utf8Error> 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::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),
&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::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",
&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::SqliteSingleThreadedMode => None,
&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<String>) -> 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)
}

View File

@ -26,11 +26,10 @@
//! match entry { //! match entry {
//! Occupied(occ) => occ.into_mut(), //! Occupied(occ) => occ.into_mut(),
//! Vacant(vac) => { //! Vacant(vac) => {
//! let r = try!(Regex::new(&regex_s).map_err(|e| Error { //! match Regex::new(&regex_s) {
//! code: libsqlite3_sys::SQLITE_ERROR, //! Ok(r) => vac.insert(r),
//! message: format!("Invalid regular expression: {}", e), //! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
//! })); //! }
//! vac.insert(r)
//! } //! }
//! } //! }
//! }; //! };
@ -50,6 +49,7 @@
//! assert!(is_match); //! assert!(is_match);
//! } //! }
//! ``` //! ```
use std::error::Error as StdError;
use std::ffi::CStr; use std::ffi::CStr;
use std::mem; use std::mem;
use std::ptr; use std::ptr;
@ -225,14 +225,8 @@ impl FromValue for String {
Ok("".to_string()) Ok("".to_string())
} else { } else {
let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes(); let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes();
let utf8_str = str::from_utf8(c_slice); let utf8_str = try!(str::from_utf8(c_slice));
utf8_str.map(|s| s.to_string()) Ok(utf8_str.into())
.map_err(|e| {
Error {
code: 0,
message: e.to_string(),
}
})
} }
} }
@ -302,10 +296,7 @@ impl<'a> Context<'a> {
if T::parameter_has_valid_sqlite_type(arg) { if T::parameter_has_valid_sqlite_type(arg) {
T::parameter_value(arg) T::parameter_value(arg)
} else { } else {
Err(Error { Err(Error::InvalidFunctionParameterType)
code: ffi::SQLITE_MISMATCH,
message: "Invalid value type".to_string(),
})
} }
} }
} }
@ -445,9 +436,15 @@ impl InnerConnection {
assert!(!boxed_f.is_null(), "Internal error - null function pointer"); assert!(!boxed_f.is_null(), "Internal error - null function pointer");
match (*boxed_f)(&ctx) { match (*boxed_f)(&ctx) {
Ok(r) => r.set_result(ctx.ctx), Ok(r) => r.set_result(ctx.ctx),
Err(e) => { Err(Error::SqliteFailure(err, s)) => {
ffi::sqlite3_result_error_code(ctx.ctx, e.code); ffi::sqlite3_result_error_code(ctx.ctx, err.extended_code);
if let Ok(cstr) = str_to_cstring(&e.message) { 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); ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1);
} }
} }
@ -584,7 +581,6 @@ mod test {
use self::regex::Regex; use self::regex::Regex;
use {Connection, Error, Result}; use {Connection, Error, Result};
use ffi;
use functions::Context; use functions::Context;
fn half(ctx: &Context) -> Result<c_double> { fn half(ctx: &Context) -> Result<c_double> {
@ -624,13 +620,10 @@ mod test {
let new_re = match saved_re { let new_re = match saved_re {
None => { None => {
let s = try!(ctx.get::<String>(0)); let s = try!(ctx.get::<String>(0));
let r = try!(Regex::new(&s).map_err(|e| { match Regex::new(&s) {
Error { Ok(r) => Some(r),
code: ffi::SQLITE_ERROR, Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
message: format!("Invalid regular expression: {}", e),
} }
}));
Some(r)
} }
Some(_) => None, Some(_) => None,
}; };
@ -699,11 +692,10 @@ mod test {
match entry { match entry {
Occupied(occ) => occ.into_mut(), Occupied(occ) => occ.into_mut(),
Vacant(vac) => { Vacant(vac) => {
let r = try!(Regex::new(&regex_s).map_err(|e| Error { match Regex::new(&regex_s) {
code: ffi::SQLITE_ERROR, Ok(r) => vac.insert(r),
message: format!("Invalid regular expression: {}", e), Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
})); }
vac.insert(r)
} }
} }
}; };

View File

@ -64,7 +64,6 @@ use std::mem;
use std::ptr; use std::ptr;
use std::fmt; use std::fmt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::error;
use std::rc::Rc; use std::rc::Rc;
use std::cell::{RefCell, Cell}; use std::cell::{RefCell, Cell};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
@ -73,8 +72,10 @@ use std::str;
use libc::{c_int, c_char}; use libc::{c_int, c_char};
use types::{ToSql, FromSql}; use types::{ToSql, FromSql};
use error::{error_from_sqlite_code, error_from_handle};
pub use transaction::{SqliteTransaction, Transaction, TransactionBehavior}; pub use transaction::{SqliteTransaction, Transaction, TransactionBehavior};
pub use error::{SqliteError, Error};
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard}; pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard};
@ -82,6 +83,7 @@ pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard};
pub mod types; pub mod types;
mod transaction; mod transaction;
mod named_params; mod named_params;
mod error;
#[cfg(feature = "load_extension")]mod load_extension_guard; #[cfg(feature = "load_extension")]mod load_extension_guard;
#[cfg(feature = "trace")]pub mod trace; #[cfg(feature = "trace")]pub mod trace;
#[cfg(feature = "backup")]pub mod backup; #[cfg(feature = "backup")]pub mod backup;
@ -100,62 +102,12 @@ unsafe fn errmsg_to_string(errmsg: *const c_char) -> String {
utf8_str.unwrap_or("Invalid string encoding").to_string() utf8_str.unwrap_or("Invalid string encoding").to_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,
/// 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,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} (SQLite error {})", self.message, self.code)
}
}
impl error::Error for Error {
fn description(&self) -> &str {
&self.message
}
}
impl Error {
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()
} else {
unsafe { errmsg_to_string(ffi::sqlite3_errmsg(db)) }
};
Error {
code: code,
message: message,
}
}
}
fn str_to_cstring(s: &str) -> Result<CString> { fn str_to_cstring(s: &str) -> Result<CString> {
CString::new(s).map_err(|_| { Ok(try!(CString::new(s)))
Error {
code: ffi::SQLITE_MISUSE,
message: format!("Could not convert string {} to C-combatible string", s),
}
})
} }
fn path_to_cstring(p: &Path) -> Result<CString> { fn path_to_cstring(p: &Path) -> Result<CString> {
let s = try!(p.to_str().ok_or(Error { let s = try!(p.to_str().ok_or(Error::InvalidPath(p.to_owned())));
code: ffi::SQLITE_MISUSE,
message: format!("Could not convert path {} to UTF-8 string",
p.to_string_lossy()),
}));
str_to_cstring(s) str_to_cstring(s)
} }
@ -594,16 +546,37 @@ impl InnerConnection {
flags: OpenFlags) flags: OpenFlags)
-> Result<InnerConnection> { -> Result<InnerConnection> {
unsafe { 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 mut db: *mut ffi::sqlite3 = mem::uninitialized();
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null()); let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
if r != ffi::SQLITE_OK { if r != ffi::SQLITE_OK {
let e = if db.is_null() { let e = if db.is_null() {
Error { error_from_sqlite_code(r, None)
code: r,
message: ffi::code_to_str(r).to_string(),
}
} else { } else {
let e = Error::from_handle(db, r); let e = error_from_handle(db, r);
ffi::sqlite3_close(db); ffi::sqlite3_close(db);
e e
}; };
@ -612,10 +585,14 @@ impl InnerConnection {
} }
let r = ffi::sqlite3_busy_timeout(db, 5000); let r = ffi::sqlite3_busy_timeout(db, 5000);
if r != ffi::SQLITE_OK { if r != ffi::SQLITE_OK {
let e = Error::from_handle(db, r); let e = error_from_handle(db, r);
ffi::sqlite3_close(db); ffi::sqlite3_close(db);
return Err(e); 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 }) Ok(InnerConnection { db: db })
} }
} }
@ -628,7 +605,7 @@ impl InnerConnection {
if code == ffi::SQLITE_OK { if code == ffi::SQLITE_OK {
Ok(()) Ok(())
} else { } else {
Err(Error::from_handle(self.db(), code)) Err(error_from_handle(self.db(), code))
} }
} }
@ -677,10 +654,7 @@ impl InnerConnection {
} else { } else {
let message = errmsg_to_string(&*errmsg); let message = errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut libc::c_void); ffi::sqlite3_free(errmsg as *mut libc::c_void);
Err(Error { Err(error_from_sqlite_code(r, Some(message)))
code: r,
message: message,
})
} }
} }
} }
@ -694,10 +668,7 @@ impl InnerConnection {
sql: &str) sql: &str)
-> Result<Statement<'a>> { -> Result<Statement<'a>> {
if sql.len() >= ::std::i32::MAX as usize { if sql.len() >= ::std::i32::MAX as usize {
return Err(Error { return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
code: ffi::SQLITE_TOOBIG,
message: "statement too long".to_string(),
});
} }
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let c_sql = try!(str_to_cstring(sql)); let c_sql = try!(str_to_cstring(sql));
@ -793,21 +764,12 @@ impl<'conn> Statement<'conn> {
match r { match r {
ffi::SQLITE_DONE => { ffi::SQLITE_DONE => {
if self.column_count != 0 { if self.column_count != 0 {
Err(Error { Err(Error::ExecuteReturnedResults)
code: ffi::SQLITE_MISUSE,
message: "Unexpected column count - did you mean to call query?"
.to_string(),
})
} else { } else {
Ok(self.conn.changes()) Ok(self.conn.changes())
} }
} },
ffi::SQLITE_ROW => { ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults),
Err(Error {
code: r,
message: "Unexpected row result - did you mean to call query?".to_string(),
})
}
_ => Err(self.conn.decode_result(r).unwrap_err()), _ => Err(self.conn.decode_result(r).unwrap_err()),
} }
} }
@ -1048,12 +1010,7 @@ impl<'stmt> Rows<'stmt> {
fn get_expected_row(&mut self) -> Result<Row<'stmt>> { fn get_expected_row(&mut self) -> Result<Row<'stmt>> {
match self.next() { match self.next() {
Some(row) => row, Some(row) => row,
None => { None => Err(Error::QueryReturnedNoRows),
Err(Error {
code: ffi::SQLITE_NOTICE,
message: "Query did not return a row".to_string(),
})
}
} }
} }
} }
@ -1138,26 +1095,17 @@ impl<'stmt> Row<'stmt> {
/// for this row or if this row is stale. /// for this row or if this row is stale.
pub fn get_checked<T: FromSql>(&self, idx: c_int) -> Result<T> { pub fn get_checked<T: FromSql>(&self, idx: c_int) -> Result<T> {
if self.row_idx != self.current_row.get() { if self.row_idx != self.current_row.get() {
return Err(Error { return Err(Error::GetFromStaleRow);
code: ffi::SQLITE_MISUSE,
message: "Cannot get values from a row after advancing to next row".to_string(),
});
} }
unsafe { unsafe {
if idx < 0 || idx >= self.stmt.column_count { if idx < 0 || idx >= self.stmt.column_count {
return Err(Error { return Err(Error::InvalidColumnIndex(idx));
code: ffi::SQLITE_MISUSE,
message: "Invalid column index".to_string(),
});
} }
if T::column_has_valid_sqlite_type(self.stmt.stmt, idx) { if T::column_has_valid_sqlite_type(self.stmt.stmt, idx) {
FromSql::column_result(self.stmt.stmt, idx) FromSql::column_result(self.stmt.stmt, idx)
} else { } else {
Err(Error { Err(Error::InvalidColumnType)
code: ffi::SQLITE_MISMATCH,
message: "Invalid column type".to_string(),
})
} }
} }
} }
@ -1260,8 +1208,10 @@ mod test {
fn test_execute_select() { fn test_execute_select() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let err = db.execute("SELECT 1 WHERE 1 < ?", &[&1i32]).unwrap_err(); let err = db.execute("SELECT 1 WHERE 1 < ?", &[&1i32]).unwrap_err();
assert!(err.code == ffi::SQLITE_MISUSE); match err {
assert!(err.message == "Unexpected column count - did you mean to call query?"); Error::ExecuteReturnedResults => (),
_ => panic!("Unexpected error: {}", err),
}
} }
#[test] #[test]
@ -1360,10 +1310,10 @@ mod test {
.unwrap()); .unwrap());
let result = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get::<i64>(0)); let result = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get::<i64>(0));
let error = result.unwrap_err(); match result.unwrap_err() {
Error::QueryReturnedNoRows => (),
assert!(error.code == ffi::SQLITE_NOTICE); err => panic!("Unexpected error {}", err),
assert!(error.message == "Query did not return a row"); }
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", &[], |_| ()); let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", &[], |_| ());
@ -1376,7 +1326,7 @@ mod test {
db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap(); db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err(); 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] #[test]
@ -1393,8 +1343,10 @@ mod test {
assert_eq!(2i32, second.get(0)); assert_eq!(2i32, second.get(0));
let result = first.get_checked::<i32>(0); match first.get_checked::<i32>(0).unwrap_err() {
assert!(result.unwrap_err().message.contains("advancing to next row")); Error::GetFromStaleRow => (),
err => panic!("Unexpected error {}", err),
}
} }
#[test] #[test]
@ -1421,11 +1373,34 @@ mod test {
assert!(format!("{:?}", stmt).contains(query)); 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());
match result.unwrap_err() {
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),
}
}
mod query_and_then_tests { mod query_and_then_tests {
extern crate libsqlite3_sys as ffi; extern crate libsqlite3_sys as ffi;
use super::*; use super::*;
#[derive(Debug, PartialEq)] #[derive(Debug)]
enum CustomError { enum CustomError {
SomeError, SomeError,
Sqlite(Error), Sqlite(Error),
@ -1496,27 +1471,23 @@ mod test {
db.execute_batch(sql).unwrap(); db.execute_batch(sql).unwrap();
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
let bad_type: Result<Vec<f64>> = query.query_and_then(&[], let bad_type: Result<Vec<f64>> = query.query_and_then(&[], |row| row.get_checked(1))
|row| row.get_checked(1))
.unwrap() .unwrap()
.collect(); .collect();
assert_eq!(bad_type, match bad_type.unwrap_err() {
Err(Error { Error::InvalidColumnType => (),
code: ffi::SQLITE_MISMATCH, err => panic!("Unexpected error {}", err),
message: "Invalid column type".to_owned(), }
}));
let bad_idx: Result<Vec<String>> = query.query_and_then(&[], let bad_idx: Result<Vec<String>> = query.query_and_then(&[], |row| row.get_checked(3))
|row| row.get_checked(3))
.unwrap() .unwrap()
.collect(); .collect();
assert_eq!(bad_idx, match bad_idx.unwrap_err() {
Err(Error { Error::InvalidColumnIndex(_) => (),
code: ffi::SQLITE_MISUSE, err => panic!("Unexpected error {}", err),
message: "Invalid column index".to_owned(), }
}));
} }
#[test] #[test]
@ -1564,11 +1535,10 @@ mod test {
.unwrap() .unwrap()
.collect(); .collect();
assert_eq!(bad_type, match bad_type.unwrap_err() {
Err(CustomError::Sqlite(Error { CustomError::Sqlite(Error::InvalidColumnType) => (),
code: ffi::SQLITE_MISMATCH, err => panic!("Unexpected error {}", err),
message: "Invalid column type".to_owned(), }
})));
let bad_idx: CustomResult<Vec<String>> = query.query_and_then(&[], |row| { let bad_idx: CustomResult<Vec<String>> = query.query_and_then(&[], |row| {
row.get_checked(3) row.get_checked(3)
@ -1577,11 +1547,10 @@ mod test {
.unwrap() .unwrap()
.collect(); .collect();
assert_eq!(bad_idx, match bad_idx.unwrap_err() {
Err(CustomError::Sqlite(Error { CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
code: ffi::SQLITE_MISUSE, err => panic!("Unexpected error {}", err),
message: "Invalid column index".to_owned(), }
})));
let non_sqlite_err: CustomResult<Vec<String>> = query.query_and_then(&[], |_| { let non_sqlite_err: CustomResult<Vec<String>> = query.query_and_then(&[], |_| {
Err(CustomError::SomeError) Err(CustomError::SomeError)
@ -1589,7 +1558,10 @@ mod test {
.unwrap() .unwrap()
.collect(); .collect();
assert_eq!(non_sqlite_err, Err(CustomError::SomeError)); match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
err => panic!("Unexpected error {}", err),
}
} }
#[test] #[test]
@ -1625,27 +1597,28 @@ mod test {
row.get_checked(1).map_err(CustomError::Sqlite) row.get_checked(1).map_err(CustomError::Sqlite)
}); });
assert_eq!(bad_type, match bad_type.unwrap_err() {
Err(CustomError::Sqlite(Error { CustomError::Sqlite(Error::InvalidColumnType) => (),
code: ffi::SQLITE_MISMATCH, err => panic!("Unexpected error {}", err),
message: "Invalid column type".to_owned(), }
})));
let bad_idx: CustomResult<String> = db.query_row_and_then(query, &[], |row| { let bad_idx: CustomResult<String> = db.query_row_and_then(query, &[], |row| {
row.get_checked(3).map_err(CustomError::Sqlite) row.get_checked(3).map_err(CustomError::Sqlite)
}); });
assert_eq!(bad_idx, match bad_idx.unwrap_err() {
Err(CustomError::Sqlite(Error { CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
code: ffi::SQLITE_MISUSE, err => panic!("Unexpected error {}", err),
message: "Invalid column index".to_owned(), }
})));
let non_sqlite_err: CustomResult<String> = db.query_row_and_then(query, &[], |_| { let non_sqlite_err: CustomResult<String> = db.query_row_and_then(query, &[], |_| {
Err(CustomError::SomeError) Err(CustomError::SomeError)
}); });
assert_eq!(non_sqlite_err, Err(CustomError::SomeError)); match non_sqlite_err.unwrap_err() {
CustomError::SomeError => (),
err => panic!("Unexpected error {}", err),
}
} }
} }

View File

@ -133,10 +133,7 @@ impl<'conn> Statement<'conn> {
if let Some(i) = try!(self.parameter_index(name)) { if let Some(i) = try!(self.parameter_index(name)) {
try!(self.conn.decode_result(unsafe { value.bind_parameter(self.stmt, i) })); try!(self.conn.decode_result(unsafe { value.bind_parameter(self.stmt, i) }));
} else { } else {
return Err(Error { return Err(Error::InvalidParameterName(name.into()));
code: ffi::SQLITE_MISUSE,
message: format!("Invalid parameter name: {}", name),
});
} }
} }
Ok(()) Ok(())

View File

@ -8,7 +8,8 @@ use std::str;
use std::time::Duration; use std::time::Duration;
use super::ffi; 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. /// Set up the process-wide SQLite error logging callback.
/// This function is marked unsafe for two reasons: /// This function is marked unsafe for two reasons:
@ -42,14 +43,11 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
} }
}; };
if rc != ffi::SQLITE_OK { if rc == ffi::SQLITE_OK {
return Err(Error {
code: rc,
message: "sqlite3_config(SQLITE_CONFIG_LOG, ...)".to_string(),
});
}
Ok(()) Ok(())
} else {
Err(error_from_sqlite_code(rc, None))
}
} }
/// Write a message into the error log established by `config_log`. /// Write a message into the error log established by `config_log`.

View File

@ -232,14 +232,8 @@ impl FromSql for String {
Ok("".to_string()) Ok("".to_string())
} else { } else {
let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes(); let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes();
let utf8_str = str::from_utf8(c_slice); let utf8_str = try!(str::from_utf8(c_slice));
utf8_str.map(|s| s.to_string()) Ok(utf8_str.into())
.map_err(|e| {
Error {
code: 0,
message: e.to_string(),
}
})
} }
} }
@ -272,15 +266,11 @@ impl FromSql for time::Timespec {
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<time::Timespec> { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<time::Timespec> {
let col_str = FromSql::column_result(stmt, col); let col_str = FromSql::column_result(stmt, col);
col_str.and_then(|txt: String| { col_str.and_then(|txt: String| {
time::strptime(&txt, SQLITE_DATETIME_FMT) match time::strptime(&txt, SQLITE_DATETIME_FMT) {
.map(|tm| tm.to_timespec()) Ok(tm) => Ok(tm.to_timespec()),
.map_err(|parse_error| { Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
Error {
code: ffi::SQLITE_MISMATCH,
message: format!("{}", parse_error),
} }
}) })
})
} }
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
@ -306,8 +296,8 @@ impl<T: FromSql> FromSql for Option<T> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use Connection; use Connection;
use ffi;
use super::time; use super::time;
use Error;
use libc::{c_int, c_double}; use libc::{c_int, c_double};
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
@ -380,6 +370,13 @@ mod test {
#[test] #[test]
fn test_mismatched_types() { fn test_mismatched_types() {
fn is_invalid_column_type(err: Error) -> bool {
match err {
Error::InvalidColumnType => true,
_ => false,
}
}
let db = checked_memory_handle(); let db = checked_memory_handle();
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)", 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 // check some invalid types
// 0 is actually a blob (Vec<u8>) // 0 is actually a blob (Vec<u8>)
assert_eq!(row.get_checked::<c_int>(0).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<c_int>(0).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<c_int>(0).err().unwrap()));
assert_eq!(row.get_checked::<i64>(0).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<i64>(0).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<c_double>(0).err().unwrap()));
assert_eq!(row.get_checked::<c_double>(0).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<String>(0).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<time::Timespec>(0).err().unwrap()));
assert_eq!(row.get_checked::<String>(0).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<Option<c_int>>(0).err().unwrap()));
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(0).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_int>>(0).err().unwrap().code,
ffi::SQLITE_MISMATCH);
// 1 is actually a text (String) // 1 is actually a text (String)
assert_eq!(row.get_checked::<c_int>(1).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<c_int>(1).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<i64>(1).err().unwrap()));
assert_eq!(row.get_checked::<i64>(1).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<c_double>(1).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<Vec<u8>>(1).err().unwrap()));
assert_eq!(row.get_checked::<c_double>(1).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<Option<c_int>>(1).err().unwrap()));
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Vec<u8>>(1).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_int>>(1).err().unwrap().code,
ffi::SQLITE_MISMATCH);
// 2 is actually an integer // 2 is actually an integer
assert_eq!(row.get_checked::<c_double>(2).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<c_double>(2).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<String>(2).err().unwrap()));
assert_eq!(row.get_checked::<String>(2).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<Vec<u8>>(2).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<time::Timespec>(2).err().unwrap()));
assert_eq!(row.get_checked::<Vec<u8>>(2).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<Option<c_double>>(2).err().unwrap()));
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(2).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_double>>(2).err().unwrap().code,
ffi::SQLITE_MISMATCH);
// 3 is actually a float (c_double) // 3 is actually a float (c_double)
assert_eq!(row.get_checked::<c_int>(3).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<c_int>(3).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<i64>(3).err().unwrap()));
assert_eq!(row.get_checked::<i64>(3).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<String>(3).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<Vec<u8>>(3).err().unwrap()));
assert_eq!(row.get_checked::<String>(3).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<time::Timespec>(3).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<Option<c_int>>(3).err().unwrap()));
assert_eq!(row.get_checked::<Vec<u8>>(3).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(3).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Option<c_int>>(3).err().unwrap().code,
ffi::SQLITE_MISMATCH);
// 4 is actually NULL // 4 is actually NULL
assert_eq!(row.get_checked::<c_int>(4).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<c_int>(4).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<i64>(4).err().unwrap()));
assert_eq!(row.get_checked::<i64>(4).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<c_double>(4).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<String>(4).err().unwrap()));
assert_eq!(row.get_checked::<c_double>(4).err().unwrap().code, assert!(is_invalid_column_type(row.get_checked::<Vec<u8>>(4).err().unwrap()));
ffi::SQLITE_MISMATCH); assert!(is_invalid_column_type(row.get_checked::<time::Timespec>(4).err().unwrap()));
assert_eq!(row.get_checked::<String>(4).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<Vec<u8>>(4).err().unwrap().code,
ffi::SQLITE_MISMATCH);
assert_eq!(row.get_checked::<time::Timespec>(4).err().unwrap().code,
ffi::SQLITE_MISMATCH);
} }
} }

View File

@ -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());
}