duckdb/
column.rs

1use std::str;
2
3use arrow::datatypes::DataType;
4
5use crate::{core::LogicalTypeHandle, 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    ///
53    /// # Example
54    ///
55    /// ```rust,no_run
56    /// # use duckdb::{Connection, Result};
57    /// fn get_column_count(conn: &Connection) -> Result<usize> {
58    ///     let mut stmt = conn.prepare("SELECT id, name FROM people")?;
59    ///
60    ///     // Option 1: Execute first, then get column count
61    ///     stmt.execute([])?;
62    ///     let count = stmt.column_count();
63    ///
64    ///     // Option 2: Get column count from rows (avoids borrowing issues)
65    ///     let mut stmt2 = conn.prepare("SELECT id, name FROM people")?;
66    ///     let rows = stmt2.query([])?;
67    ///     let count2 = rows.as_ref().unwrap().column_count();
68    ///
69    ///     Ok(count)
70    /// }
71    /// ```
72    ///
73    /// # Caveats
74    /// Panics if the query has not been [`execute`](Statement::execute)d yet.
75    #[inline]
76    pub fn column_count(&self) -> usize {
77        self.stmt.column_count()
78    }
79
80    /// Check that column name reference lifetime is limited:
81    /// https://www.sqlite.org/c3ref/column_name.html
82    /// > The returned string pointer is valid...
83    ///
84    /// `column_name` reference can become invalid if `stmt` is reprepared
85    /// (because of schema change) when `query_row` is called. So we assert
86    /// that a compilation error happens if this reference is kept alive:
87    /// ```compile_fail
88    /// use duckdb::{Connection, Result};
89    /// fn main() -> Result<()> {
90    ///     let db = Connection::open_in_memory()?;
91    ///     let mut stmt = db.prepare("SELECT 1 as x")?;
92    ///     let column_name = stmt.column_name(0)?;
93    ///     let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502
94    ///     assert_eq!(1, x);
95    ///     assert_eq!("x", column_name);
96    ///     Ok(())
97    /// }
98    /// ```
99    #[inline]
100    pub(super) fn column_name_unwrap(&self, col: usize) -> &String {
101        // Just panic if the bounds are wrong for now, we never call this
102        // without checking first.
103        self.column_name(col).expect("Column out of bounds")
104    }
105
106    /// Returns the name assigned to a particular column in the result set
107    /// returned by the prepared statement.
108    ///
109    /// If associated DB schema can be altered concurrently, you should make
110    /// sure that current statement has already been stepped once before
111    /// calling this method.
112    ///
113    /// ## Failure
114    ///
115    /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
116    /// column range for this row.
117    ///
118    /// # Caveats
119    /// Panics if the query has not been [`execute`](Statement::execute)d yet
120    /// or when column name is not valid UTF-8.
121    #[inline]
122    pub fn column_name(&self, col: usize) -> Result<&String> {
123        self.stmt.column_name(col).ok_or(Error::InvalidColumnIndex(col))
124    }
125
126    /// Returns the column index in the result set for a given column name.
127    ///
128    /// If there is no AS clause then the name of the column is unspecified and
129    /// may change from one release of DuckDB to the next.
130    ///
131    /// If associated DB schema can be altered concurrently, you should make
132    /// sure that current statement has already been stepped once before
133    /// calling this method.
134    ///
135    /// # Failure
136    ///
137    /// Will return an `Error::InvalidColumnName` when there is no column with
138    /// the specified `name`.
139    ///
140    /// # Caveats
141    /// Panics if the query has not been [`execute`](Statement::execute)d yet.
142    #[inline]
143    pub fn column_index(&self, name: &str) -> Result<usize> {
144        let n = self.column_count();
145        for i in 0..n {
146            // Note: `column_name` is only fallible if `i` is out of bounds,
147            // which we've already checked.
148            if name.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap()) {
149                return Ok(i);
150            }
151        }
152        Err(Error::InvalidColumnName(String::from(name)))
153    }
154
155    /// Returns the declared data type of the column.
156    ///
157    /// # Caveats
158    /// Panics if the query has not been [`execute`](Statement::execute)d yet.
159    #[inline]
160    pub fn column_type(&self, idx: usize) -> DataType {
161        self.stmt.column_type(idx)
162    }
163
164    /// Returns the declared logical data type of the column.
165    pub fn column_logical_type(&self, idx: usize) -> LogicalTypeHandle {
166        self.stmt.column_logical_type(idx)
167    }
168}
169
170#[cfg(test)]
171mod test {
172    use crate::{Connection, Result};
173
174    #[test]
175    fn test_column_name_in_error() -> Result<()> {
176        use crate::{types::Type, Error};
177        let db = Connection::open_in_memory()?;
178        db.execute_batch(
179            "BEGIN;
180             CREATE TABLE foo(x INTEGER, y TEXT);
181             INSERT INTO foo VALUES(4, NULL);
182             END;",
183        )?;
184        let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
185        let mut rows = stmt.query([])?;
186        let row = rows.next()?.unwrap();
187        match row.get::<_, String>(0).unwrap_err() {
188            Error::InvalidColumnType(idx, name, ty) => {
189                assert_eq!(idx, 0);
190                assert_eq!(name, "renamed");
191                assert_eq!(ty, Type::Int);
192            }
193            e => {
194                panic!("Unexpected error type: {e:?}");
195            }
196        }
197        match row.get::<_, String>("y").unwrap_err() {
198            Error::InvalidColumnType(idx, name, ty) => {
199                assert_eq!(idx, 1);
200                assert_eq!(name, "y");
201                assert_eq!(ty, Type::Null);
202            }
203            e => {
204                panic!("Unexpected error type: {e:?}");
205            }
206        }
207        Ok(())
208    }
209}