1 use std::str; 2 3 use crate::{Error, Result, Statement}; 4 5 /// Information about a column of a SQLite query. 6 #[cfg(feature = "column_decltype")] 7 #[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))] 8 #[derive(Debug)] 9 pub struct Column<'stmt> { 10 name: &'stmt str, 11 decl_type: Option<&'stmt str>, 12 } 13 14 #[cfg(feature = "column_decltype")] 15 #[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))] 16 impl Column<'_> { 17 /// Returns the name of the column. 18 #[inline] 19 #[must_use] name(&self) -> &str20 pub fn name(&self) -> &str { 21 self.name 22 } 23 24 /// Returns the type of the column (`None` for expression). 25 #[inline] 26 #[must_use] decl_type(&self) -> Option<&str>27 pub fn decl_type(&self) -> Option<&str> { 28 self.decl_type 29 } 30 } 31 32 impl Statement<'_> { 33 /// Get all the column names in the result set of the prepared statement. 34 /// 35 /// If associated DB schema can be altered concurrently, you should make 36 /// sure that current statement has already been stepped once before 37 /// calling this method. column_names(&self) -> Vec<&str>38 pub fn column_names(&self) -> Vec<&str> { 39 let n = self.column_count(); 40 let mut cols = Vec::with_capacity(n); 41 for i in 0..n { 42 let s = self.column_name_unwrap(i); 43 cols.push(s); 44 } 45 cols 46 } 47 48 /// Return the number of columns in the result set returned by the prepared 49 /// statement. 50 /// 51 /// If associated DB schema can be altered concurrently, you should make 52 /// sure that current statement has already been stepped once before 53 /// calling this method. 54 #[inline] column_count(&self) -> usize55 pub fn column_count(&self) -> usize { 56 self.stmt.column_count() 57 } 58 59 /// Check that column name reference lifetime is limited: 60 /// https://www.sqlite.org/c3ref/column_name.html 61 /// > The returned string pointer is valid... 62 /// 63 /// `column_name` reference can become invalid if `stmt` is reprepared 64 /// (because of schema change) when `query_row` is called. So we assert 65 /// that a compilation error happens if this reference is kept alive: 66 /// ```compile_fail 67 /// use rusqlite::{Connection, Result}; 68 /// fn main() -> Result<()> { 69 /// let db = Connection::open_in_memory()?; 70 /// let mut stmt = db.prepare("SELECT 1 as x")?; 71 /// let column_name = stmt.column_name(0)?; 72 /// let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502 73 /// assert_eq!(1, x); 74 /// assert_eq!("x", column_name); 75 /// Ok(()) 76 /// } 77 /// ``` 78 #[inline] column_name_unwrap(&self, col: usize) -> &str79 pub(super) fn column_name_unwrap(&self, col: usize) -> &str { 80 // Just panic if the bounds are wrong for now, we never call this 81 // without checking first. 82 self.column_name(col).expect("Column out of bounds") 83 } 84 85 /// Returns the name assigned to a particular column in the result set 86 /// returned by the prepared statement. 87 /// 88 /// If associated DB schema can be altered concurrently, you should make 89 /// sure that current statement has already been stepped once before 90 /// calling this method. 91 /// 92 /// ## Failure 93 /// 94 /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid 95 /// column range for this row. 96 /// 97 /// # Panics 98 /// 99 /// Panics when column name is not valid UTF-8. 100 #[inline] column_name(&self, col: usize) -> Result<&str>101 pub fn column_name(&self, col: usize) -> Result<&str> { 102 self.stmt 103 .column_name(col) 104 // clippy::or_fun_call (nightly) vs clippy::unnecessary-lazy-evaluations (stable) 105 .ok_or(Error::InvalidColumnIndex(col)) 106 .map(|slice| { 107 slice 108 .to_str() 109 .expect("Invalid UTF-8 sequence in column name") 110 }) 111 } 112 113 /// Returns the column index in the result set for a given column name. 114 /// 115 /// If there is no AS clause then the name of the column is unspecified and 116 /// may change from one release of SQLite to the next. 117 /// 118 /// If associated DB schema can be altered concurrently, you should make 119 /// sure that current statement has already been stepped once before 120 /// calling this method. 121 /// 122 /// # Failure 123 /// 124 /// Will return an `Error::InvalidColumnName` when there is no column with 125 /// the specified `name`. 126 #[inline] column_index(&self, name: &str) -> Result<usize>127 pub fn column_index(&self, name: &str) -> Result<usize> { 128 let bytes = name.as_bytes(); 129 let n = self.column_count(); 130 for i in 0..n { 131 // Note: `column_name` is only fallible if `i` is out of bounds, 132 // which we've already checked. 133 if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) { 134 return Ok(i); 135 } 136 } 137 Err(Error::InvalidColumnName(String::from(name))) 138 } 139 140 /// Returns a slice describing the columns of the result of the query. 141 /// 142 /// If associated DB schema can be altered concurrently, you should make 143 /// sure that current statement has already been stepped once before 144 /// calling this method. 145 #[cfg(feature = "column_decltype")] 146 #[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))] columns(&self) -> Vec<Column>147 pub fn columns(&self) -> Vec<Column> { 148 let n = self.column_count(); 149 let mut cols = Vec::with_capacity(n); 150 for i in 0..n { 151 let name = self.column_name_unwrap(i); 152 let slice = self.stmt.column_decltype(i); 153 let decl_type = slice.map(|s| { 154 s.to_str() 155 .expect("Invalid UTF-8 sequence in column declaration") 156 }); 157 cols.push(Column { name, decl_type }); 158 } 159 cols 160 } 161 } 162 163 #[cfg(test)] 164 mod test { 165 use crate::{Connection, Result}; 166 167 #[test] 168 #[cfg(feature = "column_decltype")] test_columns() -> Result<()>169 fn test_columns() -> Result<()> { 170 use super::Column; 171 172 let db = Connection::open_in_memory()?; 173 let query = db.prepare("SELECT * FROM sqlite_master")?; 174 let columns = query.columns(); 175 let column_names: Vec<&str> = columns.iter().map(Column::name).collect(); 176 assert_eq!( 177 column_names.as_slice(), 178 &["type", "name", "tbl_name", "rootpage", "sql"] 179 ); 180 let column_types: Vec<Option<String>> = columns 181 .iter() 182 .map(|col| col.decl_type().map(str::to_lowercase)) 183 .collect(); 184 assert_eq!( 185 &column_types[..3], 186 &[ 187 Some("text".to_owned()), 188 Some("text".to_owned()), 189 Some("text".to_owned()), 190 ] 191 ); 192 Ok(()) 193 } 194 195 #[test] test_column_name_in_error() -> Result<()>196 fn test_column_name_in_error() -> Result<()> { 197 use crate::{types::Type, Error}; 198 let db = Connection::open_in_memory()?; 199 db.execute_batch( 200 "BEGIN; 201 CREATE TABLE foo(x INTEGER, y TEXT); 202 INSERT INTO foo VALUES(4, NULL); 203 END;", 204 )?; 205 let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?; 206 let mut rows = stmt.query([])?; 207 let row = rows.next()?.unwrap(); 208 match row.get::<_, String>(0).unwrap_err() { 209 Error::InvalidColumnType(idx, name, ty) => { 210 assert_eq!(idx, 0); 211 assert_eq!(name, "renamed"); 212 assert_eq!(ty, Type::Integer); 213 } 214 e => { 215 panic!("Unexpected error type: {e:?}"); 216 } 217 } 218 match row.get::<_, String>("y").unwrap_err() { 219 Error::InvalidColumnType(idx, name, ty) => { 220 assert_eq!(idx, 1); 221 assert_eq!(name, "y"); 222 assert_eq!(ty, Type::Null); 223 } 224 e => { 225 panic!("Unexpected error type: {e:?}"); 226 } 227 } 228 Ok(()) 229 } 230 231 /// `column_name` reference should stay valid until `stmt` is reprepared (or 232 /// reset) even if DB schema is altered (SQLite documentation is 233 /// ambiguous here because it says reference "is valid until (...) the next 234 /// call to sqlite3_column_name() or sqlite3_column_name16() on the same 235 /// column.". We assume that reference is valid if only 236 /// `sqlite3_column_name()` is used): 237 #[test] 238 #[cfg(feature = "modern_sqlite")] test_column_name_reference() -> Result<()>239 fn test_column_name_reference() -> Result<()> { 240 let db = Connection::open_in_memory()?; 241 db.execute_batch("CREATE TABLE y (x);")?; 242 let stmt = db.prepare("SELECT x FROM y;")?; 243 let column_name = stmt.column_name(0)?; 244 assert_eq!("x", column_name); 245 db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?; 246 // column name is not refreshed until statement is re-prepared 247 let same_column_name = stmt.column_name(0)?; 248 assert_eq!(same_column_name, column_name); 249 Ok(()) 250 } 251 } 252