wasm_dbms_api/dbms/query/
join_result.rs1use serde::{Deserialize, Serialize};
4
5use crate::dbms::table::JoinColumnDef;
6use crate::dbms::value::Value;
7
8#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
15#[cfg_attr(feature = "candid", derive(candid::CandidType))]
16pub struct JoinResultSet {
17 pub columns: Vec<JoinColumnDef>,
20 pub rows: Vec<Vec<Value>>,
22}
23
24impl JoinResultSet {
25 pub fn len(&self) -> usize {
27 self.rows.len()
28 }
29
30 pub fn is_empty(&self) -> bool {
32 self.rows.is_empty()
33 }
34
35 pub fn column_index(&self, column: &str) -> Option<usize> {
40 column_index(&self.columns, column)
41 }
42
43 pub fn row(&self, index: usize) -> Option<JoinRow<'_>> {
45 self.rows.get(index).map(|values| JoinRow {
46 columns: &self.columns,
47 values,
48 })
49 }
50
51 pub fn iter(&self) -> impl Iterator<Item = JoinRow<'_>> {
53 self.rows.iter().map(|values| JoinRow {
54 columns: &self.columns,
55 values,
56 })
57 }
58}
59
60impl<'a> IntoIterator for &'a JoinResultSet {
61 type Item = JoinRow<'a>;
62 type IntoIter = JoinRows<'a>;
63
64 fn into_iter(self) -> Self::IntoIter {
65 JoinRows {
66 columns: &self.columns,
67 rows: self.rows.iter(),
68 }
69 }
70}
71
72#[derive(Debug)]
74pub struct JoinRows<'a> {
75 columns: &'a [JoinColumnDef],
76 rows: std::slice::Iter<'a, Vec<Value>>,
77}
78
79impl<'a> Iterator for JoinRows<'a> {
80 type Item = JoinRow<'a>;
81
82 fn next(&mut self) -> Option<Self::Item> {
83 self.rows.next().map(|values| JoinRow {
84 columns: self.columns,
85 values,
86 })
87 }
88}
89
90#[derive(Debug, Clone, Copy)]
92pub struct JoinRow<'a> {
93 columns: &'a [JoinColumnDef],
94 values: &'a [Value],
95}
96
97impl<'a> JoinRow<'a> {
98 pub fn columns(&self) -> &'a [JoinColumnDef] {
100 self.columns
101 }
102
103 pub fn values(&self) -> &'a [Value] {
105 self.values
106 }
107
108 pub fn len(&self) -> usize {
110 self.values.len()
111 }
112
113 pub fn is_empty(&self) -> bool {
115 self.values.is_empty()
116 }
117
118 pub fn get(&self, column: &str) -> Option<&'a Value> {
121 column_index(self.columns, column).and_then(|index| self.values.get(index))
122 }
123
124 pub fn iter(&self) -> impl Iterator<Item = (&'a JoinColumnDef, &'a Value)> + use<'a> {
126 self.columns.iter().zip(self.values)
127 }
128}
129
130fn column_index(columns: &[JoinColumnDef], column: &str) -> Option<usize> {
132 match column.split_once('.') {
133 Some((table, name)) => columns
134 .iter()
135 .position(|def| def.table.as_deref() == Some(table) && def.name == name),
136 None => columns.iter().position(|def| def.name == column),
137 }
138}
139
140#[cfg(test)]
141mod tests {
142
143 use super::*;
144 use crate::dbms::table::ColumnDef;
145 use crate::dbms::types::{DataTypeKind, Text};
146
147 fn column(table: &str, name: &'static str) -> JoinColumnDef {
148 let mut def = JoinColumnDef::from(ColumnDef {
149 name,
150 data_type: DataTypeKind::Text,
151 auto_increment: false,
152 nullable: false,
153 primary_key: false,
154 unique: false,
155 foreign_key: None,
156 default: None,
157 renamed_from: &[],
158 });
159 def.table = Some(table.to_string());
160 def
161 }
162
163 fn text(value: &str) -> Value {
164 Value::Text(Text(value.to_string()))
165 }
166
167 fn result_set() -> JoinResultSet {
168 JoinResultSet {
169 columns: vec![
170 column("users", "id"),
171 column("users", "name"),
172 column("posts", "id"),
173 column("posts", "title"),
174 ],
175 rows: vec![
176 vec![text("1"), text("alice"), text("10"), text("hello")],
177 vec![text("1"), text("alice"), text("11"), text("world")],
178 ],
179 }
180 }
181
182 #[test]
183 fn test_should_report_length_and_emptiness() {
184 let set = result_set();
185 assert_eq!(set.len(), 2);
186 assert!(!set.is_empty());
187 assert!(JoinResultSet::default().is_empty());
188 }
189
190 #[test]
191 fn test_should_resolve_qualified_and_unqualified_column_index() {
192 let set = result_set();
193 assert_eq!(set.column_index("posts.id"), Some(2));
194 assert_eq!(set.column_index("users.id"), Some(0));
195 assert_eq!(set.column_index("id"), Some(0));
196 assert_eq!(set.column_index("title"), Some(3));
197 assert_eq!(set.column_index("comments.id"), None);
198 assert_eq!(set.column_index("missing"), None);
199 }
200
201 #[test]
202 fn test_should_read_row_values_by_name() {
203 let set = result_set();
204 let row = set.row(1).expect("second row exists");
205 assert_eq!(row.len(), 4);
206 assert_eq!(row.get("posts.title"), Some(&text("world")));
207 assert_eq!(row.get("name"), Some(&text("alice")));
208 assert_eq!(row.get("posts.missing"), None);
209 assert!(set.row(2).is_none());
210 }
211
212 #[test]
213 fn test_should_iterate_rows_zipped_with_columns() {
214 let set = result_set();
215 let pairs: Vec<(Option<&str>, &str, &Value)> = set
216 .iter()
217 .flat_map(|row| row.iter())
218 .map(|(def, value)| (def.table.as_deref(), def.name.as_str(), value))
219 .collect();
220 assert_eq!(pairs.len(), 8);
221 assert_eq!(pairs[2], (Some("posts"), "id", &text("10")));
222 assert_eq!(pairs[7], (Some("posts"), "title", &text("world")));
223
224 let mut count = 0;
225 for row in &set {
226 assert_eq!(row.columns().len(), 4);
227 assert_eq!(row.values().len(), 4);
228 count += 1;
229 }
230 assert_eq!(count, 2);
231 }
232
233 #[test]
234 fn test_should_serialize_as_columns_and_rows() {
235 let set = result_set();
236 let json = serde_json::to_value(&set).expect("serialize");
237 assert_eq!(json["columns"].as_array().map(Vec::len), Some(4));
238 assert_eq!(json["rows"].as_array().map(Vec::len), Some(2));
239 let back: JoinResultSet = serde_json::from_value(json).expect("deserialize");
240 assert_eq!(back, set);
241 }
242}