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