• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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