diff --git a/src/lib.rs b/src/lib.rs index 4cde86c..d4b445e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -728,6 +728,11 @@ impl<'conn> Statement<'conn> { cols } + /// Return the number of columns in the result set returned by the prepared statement. + pub fn column_count(&self) -> i32 { + self.column_count + } + /// 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 may change from one release of SQLite to the next. /// @@ -1131,6 +1136,11 @@ impl<'stmt> Row<'stmt> { } } } + + /// Return the number of columns in the current row. + pub fn column_count(&self) -> i32 { + self.stmt.column_count() + } } /// A trait implemented by types that can index into columns of a row. @@ -1267,9 +1277,11 @@ mod test { db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap(); let stmt = db.prepare("SELECT * FROM foo").unwrap(); + assert_eq!(stmt.column_count(), 1); assert_eq!(stmt.column_names(), vec!["x"]); let stmt = db.prepare("SELECT x AS a, x AS b FROM foo").unwrap(); + assert_eq!(stmt.column_count(), 2); assert_eq!(stmt.column_names(), vec!["a", "b"]); } @@ -1668,5 +1680,17 @@ mod test { } } + #[test] + #[cfg_attr(rustfmt, rustfmt_skip)] + fn test_dynamic() { + let db = checked_memory_handle(); + let sql = "BEGIN; + CREATE TABLE foo(x INTEGER, y TEXT); + INSERT INTO foo VALUES(4, \"hello\"); + END;"; + db.execute_batch(sql).unwrap(); + + db.query_row("SELECT * FROM foo", &[], |r| assert_eq!(2, r.column_count())).unwrap(); + } } } diff --git a/src/types.rs b/src/types.rs index e80f267..de3d709 100644 --- a/src/types.rs +++ b/src/types.rs @@ -96,7 +96,7 @@ macro_rules! raw_to_impl( ) ); -raw_to_impl!(c_int, sqlite3_bind_int); +raw_to_impl!(c_int, sqlite3_bind_int); // i32 raw_to_impl!(i64, sqlite3_bind_int64); raw_to_impl!(c_double, sqlite3_bind_double); @@ -208,9 +208,9 @@ macro_rules! raw_from_impl( ) ); -raw_from_impl!(c_int, sqlite3_column_int, ffi::SQLITE_INTEGER); +raw_from_impl!(c_int, sqlite3_column_int, ffi::SQLITE_INTEGER); // i32 raw_from_impl!(i64, sqlite3_column_int64, ffi::SQLITE_INTEGER); -raw_from_impl!(c_double, sqlite3_column_double, ffi::SQLITE_FLOAT); +raw_from_impl!(c_double, sqlite3_column_double, ffi::SQLITE_FLOAT); // f64 impl FromSql for bool { unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { @@ -293,6 +293,39 @@ impl FromSql for Option { } } +/// Dynamic type value (http://sqlite.org/datatype3.html) +/// Value's type is dictated by SQLite (not by the caller). +#[derive(Clone,Debug,PartialEq)] +pub enum Value { + /// The value is a `NULL` value. + Null, + /// The value is a signed integer. + Integer(i64), + /// The value is a floating point number. + Real(f64), + /// The value is a text string. + Text(String), + /// The value is a blob of data + Blob(Vec), +} + +impl FromSql for Value { + unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { + match sqlite3_column_type(stmt, col) { + ffi::SQLITE_TEXT => FromSql::column_result(stmt, col).map(|t| Value::Text(t)), + ffi::SQLITE_INTEGER => Ok(Value::Integer(ffi::sqlite3_column_int64(stmt, col))), + ffi::SQLITE_FLOAT => Ok(Value::Real(ffi::sqlite3_column_double(stmt, col))), + ffi::SQLITE_NULL => Ok(Value::Null), + ffi::SQLITE_BLOB => FromSql::column_result(stmt, col).map(|t| Value::Blob(t)), + _ => Err(Error::InvalidColumnType), + } + } + + unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { + true + } +} + #[cfg(test)] mod test { use Connection; @@ -438,4 +471,26 @@ mod test { assert!(is_invalid_column_type(row.get_checked::>(4).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::(4).err().unwrap())); } + + #[test] + fn test_dynamic_type() { + use super::Value; + let db = checked_memory_handle(); + + db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)", + &[]) + .unwrap(); + + let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap(); + let mut rows = stmt.query(&[]).unwrap(); + + let row = rows.next().unwrap().unwrap(); + assert_eq!(Value::Blob(vec![1, 2]), + row.get_checked::(0).unwrap()); + assert_eq!(Value::Text(String::from("text")), + row.get_checked::(1).unwrap()); + assert_eq!(Value::Integer(1), row.get_checked::(2).unwrap()); + assert_eq!(Value::Real(1.5), row.get_checked::(3).unwrap()); + assert_eq!(Value::Null, row.get_checked::(4).unwrap()); + } }