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