rust_queries_builder/
join.rs

1//! Join query implementation for combining multiple collections.
2//!
3//! This module provides the `JoinQuery` struct which enables SQL-like JOIN operations
4//! between collections using type-safe key-paths.
5
6use key_paths_core::KeyPaths;
7use std::collections::HashMap;
8
9/// A query builder for joining two collections.
10///
11/// Supports inner joins, left joins, and filtered joins using key-paths for type-safe
12/// join conditions.
13///
14/// # Type Parameters
15///
16/// * `'a` - The lifetime of the data being joined
17/// * `L` - The type of items in the left collection
18/// * `R` - The type of items in the right collection
19///
20/// # Example
21///
22/// ```ignore
23/// let user_orders = JoinQuery::new(&users, &orders)
24///     .inner_join(
25///         User::id_r(),
26///         Order::user_id_r(),
27///         |user, order| (user.name.clone(), order.total)
28///     );
29/// ```
30pub struct JoinQuery<'a, L: 'static, R: 'static> {
31    left: &'a [L],
32    right: &'a [R],
33}
34
35impl<'a, L: Clone, R: Clone> JoinQuery<'a, L, R> {
36    /// Creates a new join query from two collections.
37    ///
38    /// # Arguments
39    ///
40    /// * `left` - The left collection to join
41    /// * `right` - The right collection to join
42    ///
43    /// # Example
44    ///
45    /// ```ignore
46    /// let join = JoinQuery::new(&users, &orders);
47    /// ```
48    pub fn new(left: &'a [L], right: &'a [R]) -> Self {
49        Self { left, right }
50    }
51
52    /// Performs an inner join between two collections.
53    ///
54    /// Returns only the pairs where the join keys match. Uses a hash-based
55    /// algorithm for O(n + m) performance.
56    ///
57    /// # Arguments
58    ///
59    /// * `left_key` - Key-path to the join field in the left collection
60    /// * `right_key` - Key-path to the join field in the right collection
61    /// * `mapper` - Function to transform matching pairs into the result type
62    ///
63    /// # Example
64    ///
65    /// ```ignore
66    /// let results = JoinQuery::new(&users, &orders)
67    ///     .inner_join(
68    ///         User::id_r(),
69    ///         Order::user_id_r(),
70    ///         |user, order| UserOrder {
71    ///             user_name: user.name.clone(),
72    ///             order_total: order.total,
73    ///         }
74    ///     );
75    /// ```
76    pub fn inner_join<K, O, F>(&self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F) -> Vec<O>
77    where
78        K: Eq + std::hash::Hash + Clone + 'static,
79        F: Fn(&L, &R) -> O,
80    {
81        // Build index for right side for O(n) lookup
82        let mut right_index: HashMap<K, Vec<&R>> = HashMap::new();
83        for item in self.right.iter() {
84            if let Some(key) = right_key.get(item).cloned() {
85                right_index.entry(key).or_insert_with(Vec::new).push(item);
86            }
87        }
88
89        // Join left with indexed right
90        let mut results = Vec::new();
91        for left_item in self.left.iter() {
92            if let Some(key) = left_key.get(left_item).cloned() {
93                if let Some(right_items) = right_index.get(&key) {
94                    for right_item in right_items {
95                        results.push(mapper(left_item, right_item));
96                    }
97                }
98            }
99        }
100
101        results
102    }
103
104    /// Performs a left join between two collections.
105    ///
106    /// Returns all items from the left collection with optional matching items
107    /// from the right collection. If no match is found, the right item is `None`.
108    ///
109    /// # Arguments
110    ///
111    /// * `left_key` - Key-path to the join field in the left collection
112    /// * `right_key` - Key-path to the join field in the right collection
113    /// * `mapper` - Function to transform pairs into the result type (right item may be None)
114    ///
115    /// # Example
116    ///
117    /// ```ignore
118    /// let results = JoinQuery::new(&users, &orders)
119    ///     .left_join(
120    ///         User::id_r(),
121    ///         Order::user_id_r(),
122    ///         |user, order| match order {
123    ///             Some(o) => format!("{} has order {}", user.name, o.id),
124    ///             None => format!("{} has no orders", user.name),
125    ///         }
126    ///     );
127    /// ```
128    pub fn left_join<K, O, F>(&self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F) -> Vec<O>
129    where
130        K: Eq + std::hash::Hash + Clone + 'static,
131        F: Fn(&L, Option<&R>) -> O,
132    {
133        // Build index for right side
134        let mut right_index: HashMap<K, Vec<&R>> = HashMap::new();
135        for item in self.right.iter() {
136            if let Some(key) = right_key.get(item).cloned() {
137                right_index.entry(key).or_insert_with(Vec::new).push(item);
138            }
139        }
140
141        // Join left with indexed right
142        let mut results = Vec::new();
143        for left_item in self.left.iter() {
144            if let Some(key) = left_key.get(left_item).cloned() {
145                if let Some(right_items) = right_index.get(&key) {
146                    for right_item in right_items {
147                        results.push(mapper(left_item, Some(right_item)));
148                    }
149                } else {
150                    results.push(mapper(left_item, None));
151                }
152            } else {
153                results.push(mapper(left_item, None));
154            }
155        }
156
157        results
158    }
159
160    /// Performs an inner join with an additional filter predicate.
161    ///
162    /// Like `inner_join`, but only includes pairs that satisfy both the join
163    /// condition and the additional predicate.
164    ///
165    /// # Arguments
166    ///
167    /// * `left_key` - Key-path to the join field in the left collection
168    /// * `right_key` - Key-path to the join field in the right collection
169    /// * `predicate` - Additional condition that must be true for pairs to be included
170    /// * `mapper` - Function to transform matching pairs into the result type
171    ///
172    /// # Example
173    ///
174    /// ```ignore
175    /// // Join orders with products, but only high-value orders
176    /// let results = JoinQuery::new(&orders, &products)
177    ///     .inner_join_where(
178    ///         Order::product_id_r(),
179    ///         Product::id_r(),
180    ///         |order, _product| order.total > 100.0,
181    ///         |order, product| (product.name.clone(), order.total)
182    ///     );
183    /// ```
184    pub fn inner_join_where<K, O, F, P>(
185        &self,
186        left_key: KeyPaths<L, K>,
187        right_key: KeyPaths<R, K>,
188        predicate: P,
189        mapper: F,
190    ) -> Vec<O>
191    where
192        K: Eq + std::hash::Hash + Clone + 'static,
193        F: Fn(&L, &R) -> O,
194        P: Fn(&L, &R) -> bool,
195    {
196        // Build index for right side
197        let mut right_index: HashMap<K, Vec<&R>> = HashMap::new();
198        for item in self.right.iter() {
199            if let Some(key) = right_key.get(item).cloned() {
200                right_index.entry(key).or_insert_with(Vec::new).push(item);
201            }
202        }
203
204        // Join left with indexed right, applying predicate
205        let mut results = Vec::new();
206        for left_item in self.left.iter() {
207            if let Some(key) = left_key.get(left_item).cloned() {
208                if let Some(right_items) = right_index.get(&key) {
209                    for right_item in right_items {
210                        if predicate(left_item, right_item) {
211                            results.push(mapper(left_item, right_item));
212                        }
213                    }
214                }
215            }
216        }
217
218        results
219    }
220
221    /// Performs a right join between two collections.
222    ///
223    /// Returns all items from the right collection with optional matching items
224    /// from the left collection. If no match is found, the left item is `None`.
225    ///
226    /// # Arguments
227    ///
228    /// * `left_key` - Key-path to the join field in the left collection
229    /// * `right_key` - Key-path to the join field in the right collection
230    /// * `mapper` - Function to transform pairs into the result type (left item may be None)
231    ///
232    /// # Example
233    ///
234    /// ```ignore
235    /// let results = JoinQuery::new(&users, &orders)
236    ///     .right_join(
237    ///         User::id_r(),
238    ///         Order::user_id_r(),
239    ///         |user, order| match user {
240    ///             Some(u) => format!("Order {} by {}", order.id, u.name),
241    ///             None => format!("Order {} by unknown user", order.id),
242    ///         }
243    ///     );
244    /// ```
245    pub fn right_join<K, O, F>(&self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F) -> Vec<O>
246    where
247        K: Eq + std::hash::Hash + Clone + 'static,
248        F: Fn(Option<&L>, &R) -> O,
249    {
250        // Build index for left side
251        let mut left_index: HashMap<K, Vec<&L>> = HashMap::new();
252        for item in self.left.iter() {
253            if let Some(key) = left_key.get(item).cloned() {
254                left_index.entry(key).or_insert_with(Vec::new).push(item);
255            }
256        }
257
258        // Join right with indexed left
259        let mut results = Vec::new();
260        for right_item in self.right.iter() {
261            if let Some(key) = right_key.get(right_item).cloned() {
262                if let Some(left_items) = left_index.get(&key) {
263                    for left_item in left_items {
264                        results.push(mapper(Some(left_item), right_item));
265                    }
266                } else {
267                    results.push(mapper(None, right_item));
268                }
269            } else {
270                results.push(mapper(None, right_item));
271            }
272        }
273
274        results
275    }
276
277    /// Performs a cross join (Cartesian product) between two collections.
278    ///
279    /// Returns all possible pairs of items from both collections.
280    /// **Warning**: This can produce very large result sets (size = left.len() * right.len()).
281    ///
282    /// # Arguments
283    ///
284    /// * `mapper` - Function to transform pairs into the result type
285    ///
286    /// # Example
287    ///
288    /// ```ignore
289    /// let all_combinations = JoinQuery::new(&colors, &sizes)
290    ///     .cross_join(|color, size| ProductVariant {
291    ///         color: color.clone(),
292    ///         size: size.clone(),
293    ///     });
294    /// ```
295    pub fn cross_join<O, F>(&self, mapper: F) -> Vec<O>
296    where
297        F: Fn(&L, &R) -> O,
298    {
299        let mut results = Vec::new();
300        for left_item in self.left.iter() {
301            for right_item in self.right.iter() {
302                results.push(mapper(left_item, right_item));
303            }
304        }
305        results
306    }
307}
308