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}