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}