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}