rusqlite/
column.rs

1use std::str;
2
3use crate::{Error, Result, Statement};
4
5/// Information about a column of a SQLite query.
6#[derive(Debug)]
7pub struct Column<'stmt> {
8    name: &'stmt str,
9    decl_type: Option<&'stmt str>,
10}
11
12impl Column<'_> {
13    /// Returns the name of the column.
14    #[inline]
15    #[must_use]
16    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]
23    pub fn decl_type(&self) -> Option<&str> {
24        self.decl_type
25    }
26}
27
28impl 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.
34    pub fn column_names(&self) -> Vec<&str> {
35        let n = self.column_count();
36        let mut cols = Vec::with_capacity(n);
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]
51    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]
75    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]
95    pub fn column_name(&self, col: usize) -> Result<&str> {
96        self.stmt
97            .column_name(col)
98            // clippy::or_fun_call (nightly) vs clippy::unnecessary-lazy-evaluations (stable)
99            .ok_or(Error::InvalidColumnIndex(col))
100            .map(|slice| {
101                str::from_utf8(slice.to_bytes()).expect("Invalid UTF-8 sequence in column name")
102            })
103    }
104
105    /// Returns the column index in the result set for a given column name.
106    ///
107    /// If there is no AS clause then the name of the column is unspecified and
108    /// may change from one release of SQLite to the next.
109    ///
110    /// If associated DB schema can be altered concurrently, you should make
111    /// sure that current statement has already been stepped once before
112    /// calling this method.
113    ///
114    /// # Failure
115    ///
116    /// Will return an `Error::InvalidColumnName` when there is no column with
117    /// the specified `name`.
118    #[inline]
119    pub fn column_index(&self, name: &str) -> Result<usize> {
120        let bytes = name.as_bytes();
121        let n = self.column_count();
122        for i in 0..n {
123            // Note: `column_name` is only fallible if `i` is out of bounds,
124            // which we've already checked.
125            if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
126                return Ok(i);
127            }
128        }
129        Err(Error::InvalidColumnName(String::from(name)))
130    }
131
132    /// Returns a slice describing the columns of the result of the query.
133    ///
134    /// If associated DB schema can be altered concurrently, you should make
135    /// sure that current statement has already been stepped once before
136    /// calling this method.
137    #[cfg(feature = "column_decltype")]
138    #[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
139    pub fn columns(&self) -> Vec<Column> {
140        let n = self.column_count();
141        let mut cols = Vec::with_capacity(n);
142        for i in 0..n {
143            let name = self.column_name_unwrap(i);
144            let slice = self.stmt.column_decltype(i);
145            let decl_type = slice.map(|s| {
146                str::from_utf8(s.to_bytes()).expect("Invalid UTF-8 sequence in column declaration")
147            });
148            cols.push(Column { name, decl_type });
149        }
150        cols
151    }
152}
153
154#[cfg(test)]
155mod test {
156    use crate::{Connection, Result};
157
158    #[test]
159    #[cfg(feature = "column_decltype")]
160    fn test_columns() -> Result<()> {
161        use super::Column;
162
163        let db = Connection::open_in_memory()?;
164        let query = db.prepare("SELECT * FROM sqlite_master")?;
165        let columns = query.columns();
166        let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
167        assert_eq!(
168            column_names.as_slice(),
169            &["type", "name", "tbl_name", "rootpage", "sql"]
170        );
171        let column_types: Vec<Option<String>> = columns
172            .iter()
173            .map(|col| col.decl_type().map(str::to_lowercase))
174            .collect();
175        assert_eq!(
176            &column_types[..3],
177            &[
178                Some("text".to_owned()),
179                Some("text".to_owned()),
180                Some("text".to_owned()),
181            ]
182        );
183        Ok(())
184    }
185
186    #[test]
187    fn test_column_name_in_error() -> Result<()> {
188        use crate::{types::Type, Error};
189        let db = Connection::open_in_memory()?;
190        db.execute_batch(
191            "BEGIN;
192             CREATE TABLE foo(x INTEGER, y TEXT);
193             INSERT INTO foo VALUES(4, NULL);
194             END;",
195        )?;
196        let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
197        let mut rows = stmt.query([])?;
198        let row = rows.next()?.unwrap();
199        match row.get::<_, String>(0).unwrap_err() {
200            Error::InvalidColumnType(idx, name, ty) => {
201                assert_eq!(idx, 0);
202                assert_eq!(name, "renamed");
203                assert_eq!(ty, Type::Integer);
204            }
205            e => {
206                panic!("Unexpected error type: {:?}", e);
207            }
208        }
209        match row.get::<_, String>("y").unwrap_err() {
210            Error::InvalidColumnType(idx, name, ty) => {
211                assert_eq!(idx, 1);
212                assert_eq!(name, "y");
213                assert_eq!(ty, Type::Null);
214            }
215            e => {
216                panic!("Unexpected error type: {:?}", e);
217            }
218        }
219        Ok(())
220    }
221
222    /// `column_name` reference should stay valid until `stmt` is reprepared (or
223    /// reset) even if DB schema is altered (SQLite documentation is
224    /// ambiguous here because it says reference "is valid until (...) the next
225    /// call to sqlite3_column_name() or sqlite3_column_name16() on the same
226    /// column.". We assume that reference is valid if only
227    /// `sqlite3_column_name()` is used):
228    #[test]
229    #[cfg(feature = "modern_sqlite")]
230    fn test_column_name_reference() -> Result<()> {
231        let db = Connection::open_in_memory()?;
232        db.execute_batch("CREATE TABLE y (x);")?;
233        let stmt = db.prepare("SELECT x FROM y;")?;
234        let column_name = stmt.column_name(0)?;
235        assert_eq!("x", column_name);
236        db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?;
237        // column name is not refreshed until statement is re-prepared
238        let same_column_name = stmt.column_name(0)?;
239        assert_eq!(same_column_name, column_name);
240        Ok(())
241    }
242}