1use std::collections::HashMap;
15use std::ops::Index;
16
17#[derive(Debug, Clone, Default, PartialEq, Eq)]
19pub struct QueryColumns {
20 columns: Vec<(String, Vec<String>)>,
21}
22
23impl QueryColumns {
24 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn with_names<I, S>(names: I, row_capacity: usize) -> Self
35 where
36 I: IntoIterator<Item = S>,
37 S: Into<String>,
38 {
39 Self {
40 columns: names
41 .into_iter()
42 .map(|name| (name.into(), Vec::with_capacity(row_capacity)))
43 .collect(),
44 }
45 }
46
47 pub fn push_value(&mut self, index: usize, value: String) {
53 if let Some((_, data)) = self.columns.get_mut(index) {
54 data.push(value);
55 }
56 }
57
58 pub fn len(&self) -> usize {
60 self.columns.len()
61 }
62
63 pub fn is_empty(&self) -> bool {
65 self.columns.is_empty()
66 }
67
68 pub fn row_count(&self) -> usize {
70 self.columns.first().map_or(0, |(_, data)| data.len())
71 }
72
73 pub fn names(&self) -> impl Iterator<Item = &str> {
75 self.columns.iter().map(|(name, _)| name.as_str())
76 }
77
78 pub fn values(&self) -> impl Iterator<Item = &Vec<String>> {
80 self.columns.iter().map(|(_, data)| data)
81 }
82
83 pub fn iter(&self) -> impl Iterator<Item = (&str, &Vec<String>)> {
85 self.columns
86 .iter()
87 .map(|(name, data)| (name.as_str(), data))
88 }
89
90 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Vec<String>> {
92 self.columns.iter_mut().map(|(_, data)| data)
93 }
94
95 pub fn get(&self, name: &str) -> Option<&Vec<String>> {
97 self.columns
98 .iter()
99 .find(|(column, _)| column == name)
100 .map(|(_, data)| data)
101 }
102
103 pub fn into_map(self) -> HashMap<String, Vec<String>> {
108 self.columns.into_iter().collect()
109 }
110
111 fn extend_from(&mut self, batch: QueryColumns) {
118 for (name, data) in batch.columns {
119 match self.columns.iter_mut().find(|(column, _)| *column == name) {
120 Some((_, existing)) => existing.extend(data),
121 None => self.columns.push((name, data)),
122 }
123 }
124 }
125}
126
127impl Index<&str> for QueryColumns {
128 type Output = Vec<String>;
129
130 fn index(&self, name: &str) -> &Self::Output {
131 self.get(name)
132 .unwrap_or_else(|| panic!("no column named {name} in query result"))
133 }
134}
135
136impl FromIterator<(String, Vec<String>)> for QueryColumns {
137 fn from_iter<I: IntoIterator<Item = (String, Vec<String>)>>(iter: I) -> Self {
138 Self {
139 columns: iter.into_iter().collect(),
140 }
141 }
142}
143
144impl IntoIterator for QueryColumns {
145 type Item = (String, Vec<String>);
146 type IntoIter = std::vec::IntoIter<(String, Vec<String>)>;
147
148 fn into_iter(self) -> Self::IntoIter {
149 self.columns.into_iter()
150 }
151}
152
153pub fn merge_column_batches(batches: Vec<QueryColumns>) -> QueryColumns {
157 let mut merged = QueryColumns::new();
158 for batch in batches {
159 merged.extend_from(batch);
160 }
161 merged
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 fn columns(pairs: &[(&str, &[&str])]) -> QueryColumns {
169 pairs
170 .iter()
171 .map(|(name, data)| {
172 (
173 (*name).to_string(),
174 data.iter().map(|v| (*v).to_string()).collect(),
175 )
176 })
177 .collect()
178 }
179
180 #[test]
181 fn names_come_back_in_the_order_they_went_in() {
182 let result = QueryColumns::with_names(["id", "amount", "active"], 0);
185 assert_eq!(
186 result.names().collect::<Vec<_>>(),
187 ["id", "amount", "active"]
188 );
189 }
190
191 #[test]
192 fn values_land_in_the_column_they_were_pushed_to() {
193 let mut result = QueryColumns::with_names(["b", "a"], 2);
194 result.push_value(0, "b1".to_string());
195 result.push_value(1, "a1".to_string());
196 result.push_value(0, "b2".to_string());
197 result.push_value(1, "a2".to_string());
198
199 assert_eq!(result["b"], vec!["b1", "b2"]);
200 assert_eq!(result["a"], vec!["a1", "a2"]);
201 assert_eq!(result.row_count(), 2);
202 }
203
204 #[test]
205 fn merging_keeps_the_first_batch_order() {
206 let merged = merge_column_batches(vec![
207 columns(&[("id", &["1"]), ("cap", &["20121"])]),
208 columns(&[("id", &["2"]), ("cap", &["00184"])]),
209 ]);
210
211 assert_eq!(merged.names().collect::<Vec<_>>(), ["id", "cap"]);
212 assert_eq!(merged["id"], vec!["1", "2"]);
213 assert_eq!(merged["cap"], vec!["20121", "00184"]);
214 }
215
216 #[test]
217 fn merging_matches_columns_by_name_not_position() {
218 let merged = merge_column_batches(vec![
221 columns(&[("id", &["1"]), ("cap", &["20121"])]),
222 columns(&[("cap", &["00184"]), ("id", &["2"])]),
223 ]);
224
225 assert_eq!(merged.names().collect::<Vec<_>>(), ["id", "cap"]);
226 assert_eq!(merged["id"], vec!["1", "2"]);
227 assert_eq!(merged["cap"], vec!["20121", "00184"]);
228 }
229
230 #[test]
231 fn merging_nothing_yields_nothing() {
232 let merged = merge_column_batches(Vec::new());
233 assert!(merged.is_empty());
234 assert_eq!(merged.row_count(), 0);
235 }
236}