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