1use std::collections::{HashMap, HashSet};
8use std::hash::Hash;
9
10pub type JoinPair<T> = (Option<T>, Option<T>);
12
13pub type JoinPairs<T> = Vec<JoinPair<T>>;
15
16pub trait RowLike {
18 type Value: Clone;
20
21 fn values(&self) -> &[Self::Value];
23
24 fn arity(&self) -> usize {
26 self.values().len()
27 }
28}
29
30pub fn combine_rows<R, O>(
32 left: &R,
33 right: &R,
34 row_id: u64,
35 build_row: impl FnOnce(u64, Vec<R::Value>) -> O,
36) -> O
37where
38 R: RowLike,
39{
40 let mut values = Vec::with_capacity(left.arity() + right.arity());
41 values.extend(left.values().iter().cloned());
42 values.extend(right.values().iter().cloned());
43 build_row(row_id, values)
44}
45
46pub fn pad_right<R, O, N>(
48 left: &R,
49 right_width: usize,
50 row_id: u64,
51 mut null_value: N,
52 build_row: impl FnOnce(u64, Vec<R::Value>) -> O,
53) -> O
54where
55 R: RowLike,
56 N: FnMut() -> R::Value,
57{
58 let mut values = Vec::with_capacity(left.arity() + right_width);
59 values.extend(left.values().iter().cloned());
60 values.extend((0..right_width).map(|_| null_value()));
61 build_row(row_id, values)
62}
63
64pub fn pad_left<R, O, N>(
66 right: &R,
67 left_width: usize,
68 row_id: u64,
69 mut null_value: N,
70 build_row: impl FnOnce(u64, Vec<R::Value>) -> O,
71) -> O
72where
73 R: RowLike,
74 N: FnMut() -> R::Value,
75{
76 let mut values = Vec::with_capacity(left_width + right.arity());
77 values.extend((0..left_width).map(|_| null_value()));
78 values.extend(right.values().iter().cloned());
79 build_row(row_id, values)
80}
81
82pub fn nested_loop_join<T, E, P>(
87 left: &[T],
88 right: &[T],
89 include_unmatched_left: bool,
90 include_unmatched_right: bool,
91 mut matches: P,
92) -> Result<JoinPairs<T>, E>
93where
94 T: Clone,
95 P: FnMut(&T, &T) -> Result<bool, E>,
96{
97 let mut output = Vec::new();
98 let mut matched_right = HashSet::new();
99
100 for left_row in left {
101 let mut matched_left = false;
102 for (right_idx, right_row) in right.iter().enumerate() {
103 if matches(left_row, right_row)? {
104 matched_left = true;
105 matched_right.insert(right_idx);
106 output.push((Some(left_row.clone()), Some(right_row.clone())));
107 }
108 }
109 if !matched_left && include_unmatched_left {
110 output.push((Some(left_row.clone()), None));
111 }
112 }
113
114 if include_unmatched_right {
115 for (right_idx, right_row) in right.iter().enumerate() {
116 if !matched_right.contains(&right_idx) {
117 output.push((None, Some(right_row.clone())));
118 }
119 }
120 }
121
122 Ok(output)
123}
124
125pub fn hash_join<T, K, E, L, R>(
130 left: &[T],
131 right: &[T],
132 include_unmatched_left: bool,
133 include_unmatched_right: bool,
134 mut left_key: L,
135 mut right_key: R,
136) -> Result<JoinPairs<T>, E>
137where
138 T: Clone,
139 K: Eq + Hash,
140 L: FnMut(&T) -> Result<K, E>,
141 R: FnMut(&T) -> Result<K, E>,
142{
143 let mut buckets: HashMap<K, Vec<(usize, &T)>> = HashMap::new();
144 for (idx, right_row) in right.iter().enumerate() {
145 let key = right_key(right_row)?;
146 buckets.entry(key).or_default().push((idx, right_row));
147 }
148
149 let mut output = Vec::new();
150 let mut matched_right = HashSet::new();
151 for left_row in left {
152 let key = left_key(left_row)?;
153 let mut matched_left = false;
154 if let Some(matches) = buckets.get(&key) {
155 for (right_idx, right_row) in matches {
156 matched_left = true;
157 matched_right.insert(*right_idx);
158 output.push((Some(left_row.clone()), Some((*right_row).clone())));
159 }
160 }
161 if !matched_left && include_unmatched_left {
162 output.push((Some(left_row.clone()), None));
163 }
164 }
165
166 if include_unmatched_right {
167 for (right_idx, right_row) in right.iter().enumerate() {
168 if !matched_right.contains(&right_idx) {
169 output.push((None, Some(right_row.clone())));
170 }
171 }
172 }
173
174 Ok(output)
175}
176
177#[cfg(test)]
178mod tests {
179 use super::{combine_rows, hash_join, nested_loop_join, pad_left, pad_right, RowLike};
180
181 #[derive(Debug, Clone, PartialEq, Eq)]
182 struct TestRow {
183 row_id: u64,
184 values: Vec<Option<i64>>,
185 }
186
187 impl TestRow {
188 fn new(row_id: u64, values: Vec<Option<i64>>) -> Self {
189 Self { row_id, values }
190 }
191 }
192
193 impl RowLike for TestRow {
194 type Value = Option<i64>;
195
196 fn values(&self) -> &[Self::Value] {
197 &self.values
198 }
199 }
200
201 fn rows(values: &[&[i64]]) -> Vec<TestRow> {
202 values
203 .iter()
204 .enumerate()
205 .map(|(idx, row)| TestRow::new(idx as u64, row.iter().copied().map(Some).collect()))
206 .collect()
207 }
208
209 #[test]
210 fn combine_and_padding_build_join_rows() {
211 let left = TestRow::new(10, vec![Some(1), Some(2)]);
212 let right = TestRow::new(20, vec![Some(3)]);
213
214 assert_eq!(
215 combine_rows(&left, &right, 0, TestRow::new),
216 TestRow::new(0, vec![Some(1), Some(2), Some(3)])
217 );
218 assert_eq!(
219 pad_right(&left, 2, 1, || None, TestRow::new),
220 TestRow::new(1, vec![Some(1), Some(2), None, None])
221 );
222 assert_eq!(
223 pad_left(&right, 2, 2, || None, TestRow::new),
224 TestRow::new(2, vec![None, None, Some(3)])
225 );
226 }
227
228 #[test]
229 fn nested_loop_join_emits_inner_matches() {
230 let left = rows(&[&[1, 10], &[2, 20]]);
231 let right = rows(&[&[1, 100], &[3, 300]]);
232
233 let output = nested_loop_join(&left, &right, false, false, |left, right| {
234 Ok::<_, ()>(left.values[0] == right.values[0])
235 })
236 .unwrap();
237
238 assert_eq!(
239 output,
240 vec![(Some(left[0].clone()), Some(right[0].clone()))]
241 );
242 }
243
244 #[test]
245 fn nested_loop_join_emits_left_right_and_full_rows() {
246 let left = rows(&[&[1], &[2]]);
247 let right = rows(&[&[1], &[3]]);
248
249 let left_output = nested_loop_join(&left, &right, true, false, |left, right| {
250 Ok::<_, ()>(left.values[0] == right.values[0])
251 })
252 .unwrap();
253 assert_eq!(
254 left_output,
255 vec![
256 (Some(left[0].clone()), Some(right[0].clone())),
257 (Some(left[1].clone()), None),
258 ]
259 );
260
261 let right_output = nested_loop_join(&left, &right, false, true, |left, right| {
262 Ok::<_, ()>(left.values[0] == right.values[0])
263 })
264 .unwrap();
265 assert_eq!(
266 right_output,
267 vec![
268 (Some(left[0].clone()), Some(right[0].clone())),
269 (None, Some(right[1].clone())),
270 ]
271 );
272
273 let full_output = nested_loop_join(&left, &right, true, true, |left, right| {
274 Ok::<_, ()>(left.values[0] == right.values[0])
275 })
276 .unwrap();
277 assert_eq!(
278 full_output,
279 vec![
280 (Some(left[0].clone()), Some(right[0].clone())),
281 (Some(left[1].clone()), None),
282 (None, Some(right[1].clone())),
283 ]
284 );
285 }
286
287 #[test]
288 fn hash_join_emits_matches_and_outer_rows() {
289 let left = rows(&[&[1, 10], &[2, 20]]);
290 let right = rows(&[&[1, 100], &[3, 300]]);
291
292 let output = hash_join(
293 &left,
294 &right,
295 true,
296 true,
297 |row| Ok::<_, ()>(row.values[0]),
298 |row| Ok::<_, ()>(row.values[0]),
299 )
300 .unwrap();
301
302 assert_eq!(
303 output,
304 vec![
305 (Some(left[0].clone()), Some(right[0].clone())),
306 (Some(left[1].clone()), None),
307 (None, Some(right[1].clone())),
308 ]
309 );
310 }
311}