Include the name of the column in InvalidColumnType errors

This commit is contained in:
Thom Chiovoloni 2019-06-25 12:09:44 -07:00
parent 454899f612
commit 4356f5a176
7 changed files with 73 additions and 22 deletions

View File

@ -27,8 +27,7 @@ impl Statement<'_> {
let n = self.column_count(); let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize); let mut cols = Vec::with_capacity(n as usize);
for i in 0..n { for i in 0..n {
let slice = self.stmt.column_name(i); let s = self.column_name(i);
let s = str::from_utf8(slice.to_bytes()).unwrap();
cols.push(s); cols.push(s);
} }
cols cols
@ -40,6 +39,13 @@ impl Statement<'_> {
self.stmt.column_count() self.stmt.column_count()
} }
pub(crate) fn column_name(&self, col: usize) -> &str {
// Just panic if the bounds are wrong for now, we never call this
// without checking first.
let slice = self.stmt.column_name(col).expect("Column out of bounds");
str::from_utf8(slice.to_bytes()).unwrap()
}
/// Returns the column index in the result set for a given column name. /// Returns the column index in the result set for a given column name.
/// ///
/// If there is no AS clause then the name of the column is unspecified and /// If there is no AS clause then the name of the column is unspecified and
@ -53,7 +59,9 @@ impl Statement<'_> {
let bytes = name.as_bytes(); let bytes = name.as_bytes();
let n = self.column_count(); let n = self.column_count();
for i in 0..n { for i in 0..n {
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) { // Note: `column_name` is only fallible if `i` is out of bounds,
// which we've already checked.
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
return Ok(i); return Ok(i);
} }
} }
@ -65,8 +73,7 @@ impl Statement<'_> {
let n = self.column_count(); let n = self.column_count();
let mut cols = Vec::with_capacity(n as usize); let mut cols = Vec::with_capacity(n as usize);
for i in 0..n { for i in 0..n {
let slice = self.stmt.column_name(i); let name = self.column_name(i);
let name = str::from_utf8(slice.to_bytes()).unwrap();
let slice = self.stmt.column_decltype(i); let slice = self.stmt.column_decltype(i);
let decl_type = slice.map(|s| str::from_utf8(s.to_bytes()).unwrap()); let decl_type = slice.map(|s| str::from_utf8(s.to_bytes()).unwrap());
cols.push(Column { name, decl_type }); cols.push(Column { name, decl_type });
@ -125,4 +132,38 @@ mod test {
&[Some("text"), Some("text"), Some("text"),] &[Some("text"), Some("text"), Some("text"),]
); );
} }
#[test]
fn test_column_name_in_error() {
use crate::{types::Type, Error};
let db = Connection::open_in_memory().unwrap();
db.execute_batch(
"BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, NULL);
END;").unwrap();
let mut stmt = db.prepare("SELECT x as renamed, y FROM foo").unwrap();
let mut rows = stmt.query(crate::NO_PARAMS).unwrap();
let row = rows.next().unwrap().unwrap();
match row.get::<_, String>(0).unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 0);
assert_eq!(name, "renamed");
assert_eq!(ty, Type::Integer);
}
e => {
panic!("Unexpected error type: {:?}", e);
}
}
match row.get::<_, String>("y").unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 1);
assert_eq!(name, "y");
assert_eq!(ty, Type::Null);
}
e => {
panic!("Unexpected error type: {:?}", e);
}
}
}
} }

View File

@ -59,7 +59,7 @@ pub enum Error {
/// Error when the value of a particular column is requested, but the type /// 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 /// of the result in that column cannot be converted to the requested
/// Rust type. /// Rust type.
InvalidColumnType(usize, Type), InvalidColumnType(usize, String, Type),
/// Error when a query that was expected to insert one row did not insert /// Error when a query that was expected to insert one row did not insert
/// any or insert many. /// any or insert many.
@ -117,8 +117,8 @@ impl PartialEq for Error {
(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true, (Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
(Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2, (Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
(Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2, (Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
(Error::InvalidColumnType(i1, t1), Error::InvalidColumnType(i2, t2)) => { (Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
i1 == i2 && t1 == t2 i1 == i2 && t1 == t2 && n1 == n2
} }
(Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2, (Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
@ -182,8 +182,8 @@ impl fmt::Display for Error {
Error::QueryReturnedNoRows => write!(f, "Query returned no rows"), Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i), Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name), Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {}", name),
Error::InvalidColumnType(i, ref t) => { Error::InvalidColumnType(i, ref name, ref t) => {
write!(f, "Invalid column type {} at index: {}", t, i) write!(f, "Invalid column type {} at index: {}, name: {}", t, i, name)
} }
Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i), Error::StatementChangedRows(i) => write!(f, "Query changed {} rows", i),
@ -229,7 +229,7 @@ impl error::Error for Error {
Error::QueryReturnedNoRows => "query returned no rows", Error::QueryReturnedNoRows => "query returned no rows",
Error::InvalidColumnIndex(_) => "invalid column index", Error::InvalidColumnIndex(_) => "invalid column index",
Error::InvalidColumnName(_) => "invalid column name", Error::InvalidColumnName(_) => "invalid column name",
Error::InvalidColumnType(_, _) => "invalid column type", Error::InvalidColumnType(_, _, _) => "invalid column type",
Error::StatementChangedRows(_) => "query inserted zero or more than one row", Error::StatementChangedRows(_) => "query inserted zero or more than one row",
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
@ -262,7 +262,7 @@ impl error::Error for Error {
| Error::QueryReturnedNoRows | Error::QueryReturnedNoRows
| Error::InvalidColumnIndex(_) | Error::InvalidColumnIndex(_)
| Error::InvalidColumnName(_) | Error::InvalidColumnName(_)
| Error::InvalidColumnType(_, _) | Error::InvalidColumnType(_, _, _)
| Error::InvalidPath(_) | Error::InvalidPath(_)
| Error::StatementChangedRows(_) | Error::StatementChangedRows(_)
| Error::InvalidQuery => None, | Error::InvalidQuery => None,

View File

@ -1476,7 +1476,7 @@ mod test {
.collect(); .collect();
match bad_type.unwrap_err() { match bad_type.unwrap_err() {
Error::InvalidColumnType(_, _) => (), Error::InvalidColumnType(_, _, _) => (),
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
@ -1531,7 +1531,7 @@ mod test {
.collect(); .collect();
match bad_type.unwrap_err() { match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (), CustomError::Sqlite(Error::InvalidColumnType(_, _, _)) => (),
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }
@ -1588,7 +1588,7 @@ mod test {
}); });
match bad_type.unwrap_err() { match bad_type.unwrap_err() {
CustomError::Sqlite(Error::InvalidColumnType(_, _)) => (), CustomError::Sqlite(Error::InvalidColumnType(_, _, _)) => (),
err => panic!("Unexpected error {}", err), err => panic!("Unexpected error {}", err),
} }

View File

@ -37,8 +37,18 @@ impl RawStatement {
} }
} }
pub fn column_name(&self, idx: usize) -> &CStr { pub fn column_name(&self, idx: usize) -> Option<&CStr> {
unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) } let idx = idx as c_int;
if idx < 0 || idx >= self.column_count() as c_int {
return None;
}
unsafe {
let ptr = ffi::sqlite3_column_name(self.0, idx);
// If ptr is null here, it's an OOM, so there's probably nothing
// meaningful we can do. Just assert instead of returning None.
assert!(!ptr.is_null(), "Null pointer from sqlite3_column_name: Out of memory?");
Some(CStr::from_ptr(ptr))
}
} }
pub fn step(&self) -> c_int { pub fn step(&self) -> c_int {

View File

@ -223,15 +223,15 @@ impl<'stmt> Row<'stmt> {
let idx = idx.idx(self.stmt)?; let idx = idx.idx(self.stmt)?;
let value = self.stmt.value_ref(idx); let value = self.stmt.value_ref(idx);
FromSql::column_result(value).map_err(|err| match err { FromSql::column_result(value).map_err(|err| match err {
FromSqlError::InvalidType => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidType => Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type()),
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
FromSqlError::Other(err) => { FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err) Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
} }
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type()),
#[cfg(feature = "uuid")] #[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidUuidSize(_) => Error::InvalidColumnType(idx, self.stmt.column_name(idx).into(), value.data_type()),
}) })
} }

View File

@ -229,7 +229,7 @@ mod test {
fn test_mismatched_types() { fn test_mismatched_types() {
fn is_invalid_column_type(err: Error) -> bool { fn is_invalid_column_type(err: Error) -> bool {
match err { match err {
Error::InvalidColumnType(_, _) => true, Error::InvalidColumnType(_, _, _) => true,
_ => false, _ => false,
} }
} }

View File

@ -472,7 +472,7 @@ impl Values<'_> {
} }
FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i), FromSqlError::OutOfRange(i) => Error::IntegralValueOutOfRange(idx, i),
#[cfg(feature = "i128_blob")] #[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, value.data_type()), FromSqlError::InvalidI128Size(_) => Error::InvalidColumnType(idx, idx.to_string(), value.data_type()),
#[cfg(feature = "uuid")] #[cfg(feature = "uuid")]
FromSqlError::InvalidUuidSize(_) => { FromSqlError::InvalidUuidSize(_) => {
Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err)) Error::FromSqlConversionFailure(idx, value.data_type(), Box::new(err))