diff --git a/src/cache.rs b/src/cache.rs index dfece9e..a6d1727 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -151,9 +151,9 @@ impl StatementCache { #[cfg(test)] mod test { - use fallible_iterator::FallibleIterator; use super::StatementCache; use crate::{Connection, NO_PARAMS}; + use fallible_iterator::FallibleIterator; impl StatementCache { fn clear(&self) { @@ -279,8 +279,7 @@ mod test { let mut stmt = db.prepare_cached(sql).unwrap(); assert_eq!( Ok(Some(1i32)), - stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)) - .next() + stmt.query(NO_PARAMS).unwrap().map(|r| r.get(0)).next() ); } @@ -296,7 +295,9 @@ mod test { let mut stmt = db.prepare_cached(sql).unwrap(); assert_eq!( Ok(Some((1i32, 2i32))), - stmt.query(NO_PARAMS).unwrap().map(|r| Ok((r.get(0)?, r.get(1)?))) + stmt.query(NO_PARAMS) + .unwrap() + .map(|r| Ok((r.get(0)?, r.get(1)?))) .next() ); } diff --git a/src/column.rs b/src/column.rs new file mode 100644 index 0000000..2e08eb2 --- /dev/null +++ b/src/column.rs @@ -0,0 +1,132 @@ +use std::str; + +use crate::{Error, Result, Row, Rows, Statement}; + +/// Information about a column of a SQLite query. +#[derive(Debug)] +pub struct Column<'stmt> { + name: &'stmt str, + decl_type: Option<&'stmt str>, +} + +impl Column<'_> { + /// Returns the name of the column. + pub fn name(&self) -> &str { + self.name + } + + /// Returns the type of the column (`None` for expression). + pub fn decl_type(&self) -> Option<&str> { + self.decl_type + } +} + +impl Statement<'_> { + /// Get all the column names in the result set of the prepared statement. + pub fn column_names(&self) -> Vec<&str> { + let n = self.column_count(); + let mut cols = Vec::with_capacity(n as usize); + for i in 0..n { + let slice = self.stmt.column_name(i); + let s = str::from_utf8(slice.to_bytes()).unwrap(); + cols.push(s); + } + cols + } + + /// Return the number of columns in the result set returned by the prepared + /// statement. + pub fn column_count(&self) -> usize { + self.stmt.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. + /// + /// # Failure + /// + /// Will return an `Error::InvalidColumnName` when there is no column with + /// the specified `name`. + pub fn column_index(&self, name: &str) -> Result { + let bytes = name.as_bytes(); + let n = self.column_count(); + for i in 0..n { + if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) { + return Ok(i); + } + } + Err(Error::InvalidColumnName(String::from(name))) + } + + /// Returns a slice describing the columns of the result of the query. + pub fn columns<'stmt>(&'stmt self) -> Vec> { + let n = self.column_count(); + let mut cols = Vec::with_capacity(n as usize); + for i in 0..n { + let slice = self.stmt.column_name(i); + let name = str::from_utf8(slice.to_bytes()).unwrap(); + let slice = self.stmt.column_decltype(i); + let decl_type = slice.map(|s| str::from_utf8(s.to_bytes()).unwrap()); + cols.push(Column { name, decl_type }); + } + cols + } +} + +impl<'stmt> Rows<'stmt> { + /// Get all the column names. + pub fn column_names(&self) -> Option> { + self.stmt.map(Statement::column_names) + } + + /// Return the number of columns. + pub fn column_count(&self) -> Option { + self.stmt.map(Statement::column_count) + } + + /// Returns a slice describing the columns of the Rows. + pub fn columns(&self) -> Option>> { + self.stmt.map(Statement::columns) + } +} + +impl<'stmt> Row<'stmt> { + /// Return the number of columns in the current row. + pub fn column_count(&self) -> usize { + self.stmt.column_count() + } + + /// Returns a slice describing the columns of the Row. + pub fn columns(&self) -> Vec> { + self.stmt.columns() + } +} + +#[cfg(test)] +mod test { + use super::Column; + use crate::Connection; + + #[test] + fn test_columns() { + let db = Connection::open_in_memory().unwrap(); + let query = db.prepare("SELECT * FROM sqlite_master").unwrap(); + let columns = query.columns(); + let column_names: Vec<&str> = columns.iter().map(Column::name).collect(); + assert_eq!( + column_names.as_slice(), + &["type", "name", "tbl_name", "rootpage", "sql"] + ); + let column_types: Vec> = columns.iter().map(Column::decl_type).collect(); + assert_eq!( + &column_types[..3], + &[ + Some("text"), + Some("text"), + Some("text"), + ] + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index f7c875d..3685349 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ use crate::raw_statement::RawStatement; use crate::types::ValueRef; pub use crate::cache::CachedStatement; +pub use crate::column::Column; pub use crate::error::Error; pub use crate::ffi::ErrorCode; #[cfg(feature = "hooks")] @@ -104,6 +105,7 @@ pub mod backup; pub mod blob; mod busy; mod cache; +mod column; pub mod config; #[cfg(any(feature = "functions", feature = "vtab"))] mod context; @@ -844,10 +846,10 @@ unsafe fn db_filename(_: *mut ffi::sqlite3) -> Option { #[cfg(test)] mod test { - use fallible_iterator::FallibleIterator; use self::tempdir::TempDir; pub use super::*; use crate::ffi; + use fallible_iterator::FallibleIterator; pub use std::error::Error as StdError; pub use std::fmt; use tempdir; @@ -1131,7 +1133,9 @@ mod test { let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap(); let results: Result> = query - .query(NO_PARAMS).unwrap().map(|row| row.get(1)) + .query(NO_PARAMS) + .unwrap() + .map(|row| row.get(1)) .collect(); assert_eq!(results.unwrap().concat(), "hello, world!"); diff --git a/src/raw_statement.rs b/src/raw_statement.rs index 8e3b5b4..f42b1c9 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -26,6 +26,17 @@ impl RawStatement { unsafe { ffi::sqlite3_column_type(self.0, idx as c_int) } } + pub fn column_decltype(&self, idx: usize) -> Option<&CStr> { + unsafe { + let decltype = ffi::sqlite3_column_decltype(self.0, idx as c_int); + if decltype.is_null() { + None + } else { + Some(CStr::from_ptr(decltype)) + } + } + } + pub fn column_name(&self, idx: usize) -> &CStr { unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx as c_int)) } } diff --git a/src/row.rs b/src/row.rs index 9188be4..ac6c33a 100644 --- a/src/row.rs +++ b/src/row.rs @@ -7,7 +7,7 @@ use crate::types::{FromSql, FromSqlError, ValueRef}; /// An handle for the resulting rows of a query. pub struct Rows<'stmt> { - stmt: Option<&'stmt Statement<'stmt>>, + pub(crate) stmt: Option<&'stmt Statement<'stmt>>, row: Option>, } @@ -183,7 +183,7 @@ impl<'stmt> FallibleStreamingIterator for Rows<'stmt> { /// A single result row of a query. pub struct Row<'stmt> { - stmt: &'stmt Statement<'stmt>, + pub(crate) stmt: &'stmt Statement<'stmt>, } impl<'stmt> Row<'stmt> { @@ -275,11 +275,6 @@ impl<'stmt> Row<'stmt> { pub fn get_raw(&self, idx: I) -> ValueRef<'_> { self.get_raw_checked(idx).unwrap() } - - /// Return the number of columns in the current row. - pub fn column_count(&self) -> usize { - self.stmt.column_count() - } } /// A trait implemented by types that can index into columns of a row. diff --git a/src/session.rs b/src/session.rs index 2546520..ac6e9f9 100644 --- a/src/session.rs +++ b/src/session.rs @@ -719,8 +719,8 @@ unsafe extern "C" fn x_output(p_out: *mut c_void, data: *const c_void, len: c_in #[cfg(test)] mod test { - use std::sync::atomic::{AtomicBool, Ordering}; use fallible_streaming_iterator::FallibleStreamingIterator; + use std::sync::atomic::{AtomicBool, Ordering}; use super::{Changeset, ChangesetIter, ConflictAction, ConflictType, Session}; use crate::hooks::Action; diff --git a/src/statement.rs b/src/statement.rs index c2401c3..c8f0293 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -18,48 +18,10 @@ use crate::vtab::array::{free_array, ARRAY_TYPE}; /// A prepared statement. pub struct Statement<'conn> { conn: &'conn Connection, - stmt: RawStatement, + pub(crate) stmt: RawStatement, } impl Statement<'_> { - /// Get all the column names in the result set of the prepared statement. - pub fn column_names(&self) -> Vec<&str> { - let n = self.column_count(); - let mut cols = Vec::with_capacity(n as usize); - for i in 0..n { - let slice = self.stmt.column_name(i); - let s = str::from_utf8(slice.to_bytes()).unwrap(); - cols.push(s); - } - cols - } - - /// Return the number of columns in the result set returned by the prepared - /// statement. - pub fn column_count(&self) -> usize { - self.stmt.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. - /// - /// # Failure - /// - /// Will return an `Error::InvalidColumnName` when there is no column with - /// the specified `name`. - pub fn column_index(&self, name: &str) -> Result { - let bytes = name.as_bytes(); - let n = self.column_count(); - for i in 0..n { - if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) { - return Ok(i); - } - } - Err(Error::InvalidColumnName(String::from(name))) - } - /// Execute the prepared statement. /// /// On success, returns the number of rows that were changed or inserted or diff --git a/src/types/url.rs b/src/types/url.rs index 3028a04..6d96028 100644 --- a/src/types/url.rs +++ b/src/types/url.rs @@ -1,7 +1,7 @@ //! `ToSql` and `FromSql` implementation for [`url::Url`]. -use url::Url; -use crate::Result; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; +use crate::Result; +use url::Url; /// Serialize `Url` to text. impl ToSql for Url { @@ -23,8 +23,8 @@ impl FromSql for Url { #[cfg(test)] mod test { - use url::{Url, ParseError}; - use crate::{Connection, params, Error, Result}; + use crate::{params, Connection, Error, Result}; + use url::{ParseError, Url}; fn checked_memory_handle() -> Connection { let db = Connection::open_in_memory().unwrap(); @@ -34,11 +34,7 @@ mod test { } fn get_url(db: &Connection, id: i64) -> Result { - db.query_row( - "SELECT v FROM urls WHERE i = ?", - params![id], - |r| r.get(0), - ) + db.query_row("SELECT v FROM urls WHERE i = ?", params![id], |r| r.get(0)) } #[test] diff --git a/src/vtab/csvtab.rs b/src/vtab/csvtab.rs index 7e52655..bf092b0 100644 --- a/src/vtab/csvtab.rs +++ b/src/vtab/csvtab.rs @@ -345,9 +345,9 @@ impl From for Error { #[cfg(test)] mod test { - use fallible_iterator::FallibleIterator; use crate::vtab::csvtab; use crate::{Connection, Result, NO_PARAMS}; + use fallible_iterator::FallibleIterator; #[test] fn test_csv_module() { @@ -364,7 +364,9 @@ mod test { } let ids: Result> = s - .query(NO_PARAMS).unwrap().map(|row| row.get::<_, i32>(0)) + .query(NO_PARAMS) + .unwrap() + .map(|row| row.get::<_, i32>(0)) .collect(); let sum = ids.unwrap().iter().sum::(); assert_eq!(sum, 15);