diff --git a/src/error.rs b/src/error.rs index 1b41935..f0ee5b3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,11 @@ pub enum Error { /// the requested Rust type. FromSqlConversionFailure(usize, Type, Box), + /// Error when SQLite gives us an integral value outside the range of the requested type (e.g., + /// trying to get the value 1000 into a `u8`). The associated `c_int` is the column index, and + /// the associated `i64` is the value returned by SQLite. + IntegralValueOutOfRange(c_int, i64), + /// Error converting a string to UTF-8. Utf8Error(str::Utf8Error), @@ -99,6 +104,9 @@ impl fmt::Display for Error { i, err) } + Error::IntegralValueOutOfRange(col, val) => { + write!(f, "Integer {} out of range at index {}", val, col) + } Error::Utf8Error(ref err) => err.fmt(f), Error::NulError(ref err) => err.fmt(f), Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name), @@ -133,6 +141,9 @@ impl error::Error for Error { "SQLite was compiled or configured for single-threaded use only" } Error::FromSqlConversionFailure(_, _, ref err) => err.description(), + Error::IntegralValueOutOfRange(_, _) => { + "integral value out of range of requested type" + } Error::Utf8Error(ref err) => err.description(), Error::InvalidParameterName(_) => "invalid parameter name", Error::NulError(ref err) => err.description(), @@ -160,6 +171,7 @@ impl error::Error for Error { Error::Utf8Error(ref err) => Some(err), Error::NulError(ref err) => Some(err), + Error::IntegralValueOutOfRange(_, _) | Error::SqliteSingleThreadedMode | Error::InvalidParameterName(_) | Error::ExecuteReturnedResults | diff --git a/src/lib.rs b/src/lib.rs index b23d19c..d7016e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1129,6 +1129,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> { let value = unsafe { ValueRef::new(&self.stmt.stmt, idx) }; FromSql::column_result(value).map_err(|err| match err { FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()), + FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), FromSqlError::Other(err) => { Error::FromSqlConversionFailure(idx as usize, value.data_type(), err) } diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index 3c78d7c..da4588f 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -5,9 +5,13 @@ use std::fmt; /// Enum listing possible errors from `FromSql` trait. #[derive(Debug)] pub enum FromSqlError { - /// Error when an SQLite value is requested, but the type of the result cannot be converted to the - /// requested Rust type. + /// Error when an SQLite value is requested, but the type of the result cannot be converted to + /// the requested Rust type. InvalidType, + + /// Error when the i64 value returned by SQLite cannot be stored into the requested type. + OutOfRange(i64), + /// An error case available for implementors of the `FromSql` trait. Other(Box), } @@ -16,6 +20,7 @@ impl fmt::Display for FromSqlError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { FromSqlError::InvalidType => write!(f, "Invalid type"), + FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i), FromSqlError::Other(ref err) => err.fmt(f), } } @@ -25,6 +30,7 @@ impl Error for FromSqlError { fn description(&self) -> &str { match *self { FromSqlError::InvalidType => "invalid type", + FromSqlError::OutOfRange(_) => "value out of range", FromSqlError::Other(ref err) => err.description(), } } @@ -33,6 +39,7 @@ impl Error for FromSqlError { fn cause(&self) -> Option<&Error> { match *self { FromSqlError::InvalidType => None, + FromSqlError::OutOfRange(_) => None, FromSqlError::Other(ref err) => err.cause(), } } @@ -48,7 +55,13 @@ pub trait FromSql: Sized { impl FromSql for i32 { fn column_result(value: ValueRef) -> FromSqlResult { - i64::column_result(value).map(|i| i as i32) + i64::column_result(value).and_then(|i| { + if i < i32::min_value() as i64 || i > i32::max_value() as i64 { + Err(FromSqlError::OutOfRange(i)) + } else { + Ok(i as i32) + } + }) } } @@ -103,3 +116,36 @@ impl FromSql for Value { Ok(value.into()) } } + +#[cfg(test)] +mod test { + use {Connection, Error}; + + fn checked_memory_handle() -> Connection { + Connection::open_in_memory().unwrap() + } + + #[test] + fn test_integral_ranges() { + let db = checked_memory_handle(); + + fn assert_out_of_range_error(err: Error, value: i64) { + match err { + Error::IntegralValueOutOfRange(_, bad) => assert_eq!(bad, value), + _ => panic!("unexpected error {}", err), + } + } + + // i32 + for bad in &[-2147483649, 2147483648] { + let err = db.query_row("SELECT ?", &[bad], |r| r.get_checked::<_, i32>(0)) + .unwrap() + .unwrap_err(); + assert_out_of_range_error(err, *bad); + } + for good in &[-2147483648, 2147483647] { + assert_eq!(*good, + db.query_row("SELECT ?", &[good], |r| r.get::<_, i32>(0)).unwrap()); + } + } +}