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}