rusqlite/
column.rs

1use std::str;
2
3use 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)]
9pub 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")))]
16impl Column<'_> {
17    /// Returns the name of the column.
18    #[inline]
19    #[must_use]
20    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]
27    pub fn decl_type(&self) -> Option<&str> {
28        self.decl_type
29    }
30}
31
32impl 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.
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]
55    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]
79    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]
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]
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")))]
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)]
164mod test {
165    use crate::{Connection, Result};
166
167    #[test]
168    #[cfg(feature = "column_decltype")]
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]
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")]
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}