rust_queries_core/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(),
26/// Order::user_id(),
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(),
72 /// Order::user_id(),
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(),
124 /// Order::user_id(),
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(),
182 /// Product::id(),
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(),
241 /// Order::user_id(),
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}
312