duckdb/column.rs
1use std::str;
2
3use arrow::datatypes::DataType;
4
5use crate::{Error, Result, Statement};
6
7/// Information about a column of a DuckDB query.
8#[derive(Debug)]
9pub struct Column<'stmt> {
10 name: &'stmt str,
11 decl_type: Option<&'stmt str>,
12}
13
14impl Column<'_> {
15 /// Returns the name of the column.
16 #[inline]
17 pub fn name(&self) -> &str {
18 self.name
19 }
20
21 /// Returns the type of the column (`None` for expression).
22 #[inline]
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 ///
35 /// # Caveats
36 /// Panics if the query has not been [`execute`](Statement::execute)d yet.
37 pub fn column_names(&self) -> Vec<String> {
38 self.stmt
39 .schema()
40 .fields()
41 .iter()
42 .map(|f| f.name().to_owned())
43 .collect()
44 }
45
46 /// Return the number of columns in the result set returned by the prepared
47 /// statement.
48 ///
49 /// If associated DB schema can be altered concurrently, you should make
50 /// sure that current statement has already been stepped once before
51 /// calling this method.
52 #[inline]
53 pub fn column_count(&self) -> usize {
54 self.stmt.column_count()
55 }
56
57 /// Check that column name reference lifetime is limited:
58 /// https://www.sqlite.org/c3ref/column_name.html
59 /// > The returned string pointer is valid...
60 ///
61 /// `column_name` reference can become invalid if `stmt` is reprepared
62 /// (because of schema change) when `query_row` is called. So we assert
63 /// that a compilation error happens if this reference is kept alive:
64 /// ```compile_fail
65 /// use duckdb::{Connection, Result};
66 /// fn main() -> Result<()> {
67 /// let db = Connection::open_in_memory()?;
68 /// let mut stmt = db.prepare("SELECT 1 as x")?;
69 /// let column_name = stmt.column_name(0)?;
70 /// let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502
71 /// assert_eq!(1, x);
72 /// assert_eq!("x", column_name);
73 /// Ok(())
74 /// }
75 /// ```
76 #[inline]
77 pub(super) fn column_name_unwrap(&self, col: usize) -> &String {
78 // Just panic if the bounds are wrong for now, we never call this
79 // without checking first.
80 self.column_name(col).expect("Column out of bounds")
81 }
82
83 /// Returns the name assigned to a particular column in the result set
84 /// returned by the prepared statement.
85 ///
86 /// If associated DB schema can be altered concurrently, you should make
87 /// sure that current statement has already been stepped once before
88 /// calling this method.
89 ///
90 /// ## Failure
91 ///
92 /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
93 /// column range for this row.
94 ///
95 /// # Caveats
96 /// Panics if the query has not been [`execute`](Statement::execute)d yet
97 /// or when column name is not valid UTF-8.
98 #[inline]
99 pub fn column_name(&self, col: usize) -> Result<&String> {
100 self.stmt.column_name(col).ok_or(Error::InvalidColumnIndex(col))
101 }
102
103 /// Returns the column index in the result set for a given column name.
104 ///
105 /// If there is no AS clause then the name of the column is unspecified and
106 /// may change from one release of DuckDB to the next.
107 ///
108 /// If associated DB schema can be altered concurrently, you should make
109 /// sure that current statement has already been stepped once before
110 /// calling this method.
111 ///
112 /// # Failure
113 ///
114 /// Will return an `Error::InvalidColumnName` when there is no column with
115 /// the specified `name`.
116 ///
117 /// # Caveats
118 /// Panics if the query has not been [`execute`](Statement::execute)d yet.
119 #[inline]
120 pub fn column_index(&self, name: &str) -> Result<usize> {
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 name.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap()) {
126 return Ok(i);
127 }
128 }
129 Err(Error::InvalidColumnName(String::from(name)))
130 }
131
132 /// Returns the declared data type of the column.
133 ///
134 /// # Caveats
135 /// Panics if the query has not been [`execute`](Statement::execute)d yet.
136 #[inline]
137 pub fn column_type(&self, idx: usize) -> DataType {
138 self.stmt.column_type(idx)
139 }
140
141 /// Returns a slice describing the columns of the result of the query.
142 ///
143 /// If associated DB schema can be altered concurrently, you should make
144 /// sure that current statement has already been stepped once before
145 /// calling this method.
146 #[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 =
154 slice.map(|s| str::from_utf8(s.to_bytes()).expect("Invalid UTF-8 sequence in column declaration"));
155 cols.push(Column { name, decl_type });
156 }
157 cols
158 }
159}
160
161#[cfg(test)]
162mod test {
163 use crate::{Connection, Result};
164
165 #[test]
166 #[cfg(feature = "column_decltype")]
167 fn test_columns() -> Result<()> {
168 use super::Column;
169
170 let db = Connection::open_in_memory()?;
171 let query = db.prepare("SELECT * FROM sqlite_master")?;
172 let columns = query.columns();
173 let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
174 assert_eq!(
175 column_names.as_slice(),
176 &["type", "name", "tbl_name", "rootpage", "sql"]
177 );
178 let column_types: Vec<Option<&str>> = columns.iter().map(Column::decl_type).collect();
179 assert_eq!(
180 &column_types[..3],
181 &[Some("VARCHAR"), Some("VARCHAR"), Some("VARCHAR"),]
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::Int);
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}