better_duck_core/raw/
row.rs1use crate::{
2 error::{Error, Result},
3 ffi::{self, DUCKDB_TYPE},
4 raw::data_chunk::DataChunk,
5 types::value::DuckValue,
6};
7use std::ptr;
8use std::sync::Arc;
9
10#[derive(Debug, Clone)]
20pub struct DuckRow(Vec<DuckValue>, Arc<[Box<str>]>);
21
22impl DuckRow {
23 pub fn new(
31 result: Vec<DuckValue>,
32 col_names: Arc<[Box<str>]>,
33 ) -> DuckRow {
34 DuckRow(result, col_names)
35 }
36
37 #[allow(unused)]
46 pub fn get(
47 &self,
48 name: &str,
49 ) -> Option<&DuckValue> {
50 self.1
51 .iter()
52 .zip(self.0.iter())
53 .find(|(col_name, _)| col_name.as_ref() == name)
54 .map(|(_, value)| value)
55 }
56
57 #[allow(unused)]
64 pub fn get_idx(
65 &self,
66 idx: usize,
67 ) -> Option<&DuckValue> {
68 if idx < self.0.len() {
69 Some(&self.0[idx])
70 } else {
71 None
72 }
73 }
74
75 pub fn column_count(&self) -> u64 {
77 self.1.len() as u64
78 }
79
80 pub fn from_chunk(
86 chunk: &mut DataChunk,
87 col_names: Arc<[Box<str>]>,
88 col_types: &[DUCKDB_TYPE],
89 ) -> Result<Self> {
90 let row_idx = chunk.current_row() - 1; let column_count = col_names.len() as u64;
92 if column_count == 0 {
93 return Err(Error::DuckDBFailure(
94 ffi::Error::new(ffi::DuckDBError),
95 Some("No columns in result".to_owned()),
96 ));
97 }
98 let mut values: Vec<DuckValue> = Vec::with_capacity(column_count as usize);
99 let values_ptr: *mut DuckValue = values.as_mut_ptr();
100
101 for col_idx in 0..column_count {
102 let col_vec = unsafe { ffi::duckdb_data_chunk_get_vector(**chunk, col_idx) };
106
107 if col_vec.is_null() {
108 return Err(Error::DuckDBFailure(
109 ffi::Error::new(ffi::DuckDBError),
110 Some("Column returned invalid null ptr".to_owned()),
111 ));
112 }
113 let val = DuckValue::from_duckdb_vec(col_vec, col_types[col_idx as usize], row_idx)
114 .map_err(Error::ConversionError)?;
115
116 unsafe { ptr::write(values_ptr.add(col_idx as usize), val) };
120 }
121 unsafe { values.set_len(column_count as usize) };
124
125 Ok(DuckRow(values, col_names))
126 }
127}
128
129#[cfg(test)]
130#[allow(clippy::undocumented_unsafe_blocks)]
131mod tests {
132 use super::*;
133 use crate::{config::Config, helpers::path::path_to_cstring, raw::connection::RawConnection};
134
135 fn get_test_connection() -> RawConnection {
136 let c_path = path_to_cstring(":memory:".as_ref()).unwrap();
137 let config = Config::default().with("duckdb_api", "rust").unwrap();
138 RawConnection::open_with_flags(&c_path, config).unwrap()
139 }
140
141 #[test]
142 fn test_get_by_column_name() {
143 let mut con = get_test_connection();
144 con.query("CREATE TABLE t (id INTEGER, name TEXT)").unwrap();
145 con.query("INSERT INTO t VALUES (42, 'hello')").unwrap();
146
147 let mut stmt = con.prepare("SELECT id, name FROM t").unwrap();
148 let mut result = stmt.execute().unwrap();
149
150 let row = result.next().expect("expected a row").unwrap();
151 assert_eq!(row.get("id"), Some(&DuckValue::Int(42)));
152 assert_eq!(row.get("name"), Some(&DuckValue::Text("hello".to_string())));
153 assert_eq!(row.get("nonexistent"), None);
154 }
155}