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.
This commit is contained in:
John Gallagher
2015-12-13 00:54:08 -05:00
parent b385ae002b
commit aac4d59fcc
7 changed files with 241 additions and 241 deletions

View File

@@ -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<String>),
FromSqlConversionFailure(Box<error::Error>),
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<std::error::Error>),
}
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 {
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<String>) -> 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> {
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<CString> {
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<Statement<'a>> {
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<Row<'stmt>> {
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<T: FromSql>(&self, idx: c_int) -> Result<T> {
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::<i64>(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::<i32>(0);
assert!(result.unwrap_err().message.contains("advancing to next row"));
match first.get_checked::<i32>(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<Vec<f64>> = query.query_and_then(&[],
|row| row.get_checked(1))
let bad_type: Result<Vec<f64>> = 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<Vec<String>> = query.query_and_then(&[],
|row| row.get_checked(3))
let bad_idx: Result<Vec<String>> = 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<Vec<String>> = 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<Vec<String>> = 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<String> = 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<String> = 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),
}
}
}