better_duck_core/
result_set.rs1use crate::raw::row::DuckRow;
2
3#[derive(Debug, Clone)]
10pub struct ResultSet {
11 rows: Vec<DuckRow>,
12 changes: u64,
13 column_names: Box<[Box<str>]>,
14 column_schema: std::sync::Arc<[crate::types::TypeInfo]>,
17 statement_type: crate::raw::statement::StatementType,
19 result_type: crate::raw::result::ResultType,
21}
22
23impl ResultSet {
24 pub(crate) fn new(
26 rows: Vec<DuckRow>,
27 changes: u64,
28 column_names: Box<[Box<str>]>,
29 column_schema: std::sync::Arc<[crate::types::TypeInfo]>,
30 statement_type: crate::raw::statement::StatementType,
31 result_type: crate::raw::result::ResultType,
32 ) -> ResultSet {
33 ResultSet { rows, changes, column_names, column_schema, statement_type, result_type }
34 }
35
36 pub fn rows(&self) -> &[DuckRow] {
38 &self.rows
39 }
40
41 pub fn into_rows(self) -> Vec<DuckRow> {
43 self.rows
44 }
45
46 pub fn changes(&self) -> u64 {
50 self.changes
51 }
52
53 pub fn column_names(&self) -> &[Box<str>] {
55 &self.column_names
56 }
57
58 pub fn column_schema(&self) -> &[crate::types::TypeInfo] {
62 &self.column_schema
63 }
64
65 pub fn statement_type(&self) -> crate::raw::statement::StatementType {
67 self.statement_type
68 }
69
70 pub fn result_type(&self) -> crate::raw::result::ResultType {
72 self.result_type
73 }
74
75 pub fn len(&self) -> usize {
77 self.rows.len()
78 }
79
80 pub fn is_empty(&self) -> bool {
82 self.rows.is_empty()
83 }
84
85 pub fn first(&self) -> Option<&DuckRow> {
87 self.rows.first()
88 }
89}
90
91impl IntoIterator for ResultSet {
92 type Item = DuckRow;
93 type IntoIter = std::vec::IntoIter<DuckRow>;
94
95 fn into_iter(self) -> Self::IntoIter {
96 self.rows.into_iter()
97 }
98}
99
100impl<'a> IntoIterator for &'a ResultSet {
101 type Item = &'a DuckRow;
102 type IntoIter = std::slice::Iter<'a, DuckRow>;
103
104 fn into_iter(self) -> Self::IntoIter {
105 self.rows.iter()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use std::sync::Arc;
112
113 use super::*;
114 use crate::types::value::DuckValue;
115
116 fn column_names() -> Box<[Box<str>]> {
117 vec![Box::from("id"), Box::from("name")].into_boxed_slice()
118 }
119
120 fn result_set() -> ResultSet {
121 let column_names = column_names();
122 let row_column_names = Arc::from(column_names.clone());
123 let rows = vec![
124 DuckRow::new(
125 vec![DuckValue::Int(20), DuckValue::Text("second".into())],
126 Arc::clone(&row_column_names),
127 ),
128 DuckRow::new(
129 vec![DuckValue::Int(10), DuckValue::Text("first".into())],
130 row_column_names,
131 ),
132 ];
133
134 let schema = Arc::from([
135 crate::types::TypeInfo::Scalar(crate::ffi::DUCKDB_TYPE_DUCKDB_TYPE_INTEGER),
136 crate::types::TypeInfo::Scalar(crate::ffi::DUCKDB_TYPE_DUCKDB_TYPE_VARCHAR),
137 ]);
138 ResultSet::new(
139 rows,
140 2,
141 column_names,
142 schema,
143 crate::raw::statement::StatementType::Select,
144 crate::raw::result::ResultType::QueryResult,
145 )
146 }
147
148 fn ids<'a>(rows: impl IntoIterator<Item = &'a DuckRow>) -> Vec<i32> {
149 rows.into_iter()
150 .map(|row| match row.get_idx(0) {
151 Some(DuckValue::Int(id)) => *id,
152 value => panic!("expected integer id, got {value:?}"),
153 })
154 .collect()
155 }
156
157 #[test]
158 fn empty_result_exposes_metadata_and_no_rows() {
159 let result = ResultSet::new(
160 Vec::new(),
161 3,
162 column_names(),
163 Arc::from([]),
164 crate::raw::statement::StatementType::Select,
165 crate::raw::result::ResultType::QueryResult,
166 );
167
168 assert!(result.is_empty());
169 assert_eq!(result.len(), 0);
170 assert!(result.rows().is_empty());
171 assert!(result.first().is_none());
172 assert_eq!(result.changes(), 3);
173 assert_eq!(result.column_names(), &[Box::from("id"), Box::from("name")]);
174 assert!(result.into_rows().is_empty());
175 }
176
177 #[test]
178 fn non_empty_accessors_clone_and_debug_preserve_data() {
179 let result = result_set();
180 let cloned = result.clone();
181
182 assert!(!result.is_empty());
183 assert_eq!(result.len(), 2);
184 assert_eq!(result.changes(), 2);
185 assert_eq!(result.column_names(), &[Box::from("id"), Box::from("name")]);
186 assert_eq!(
187 result.first().and_then(|row| row.get("name")),
188 Some(&DuckValue::Text("second".into()))
189 );
190 assert_eq!(ids(result.rows()), vec![20, 10]);
191 assert_eq!(ids(cloned.rows()), vec![20, 10]);
192
193 let debug = format!("{result:?}");
194 assert!(debug.contains("ResultSet"));
195 assert!(debug.contains("changes: 2"));
196 assert!(debug.contains("second"));
197 }
198
199 #[test]
200 fn borrowed_and_consuming_iteration_retain_order() {
201 let result = result_set();
202
203 assert_eq!(ids(&result), vec![20, 10]);
204 assert_eq!(result.len(), 2, "borrowing must not consume the result");
205
206 let names: Vec<_> = result.into_iter().map(|row| row.get("name").cloned()).collect();
207 assert_eq!(
208 names,
209 vec![Some(DuckValue::Text("second".into())), Some(DuckValue::Text("first".into())),]
210 );
211 }
212}