pub struct JoinQuery<'a, L: 'static, R: 'static> { /* private fields */ }Expand description
A query builder for joining two collections.
Supports inner joins, left joins, and filtered joins using key-paths for type-safe join conditions.
§Type Parameters
'a- The lifetime of the data being joinedL- The type of items in the left collectionR- The type of items in the right collection
§Example
let user_orders = JoinQuery::new(&users, &orders)
.inner_join(
User::id_r(),
Order::user_id_r(),
|user, order| (user.name.clone(), order.total)
);Implementations§
Source§impl<'a, L: 'static, R: 'static> JoinQuery<'a, L, R>
impl<'a, L: 'static, R: 'static> JoinQuery<'a, L, R>
Sourcepub fn new(left: &'a [L], right: &'a [R]) -> Self
pub fn new(left: &'a [L], right: &'a [R]) -> Self
Creates a new join query from two collections.
Note: No Clone required on L or R. The mapper function
handles any cloning needed for the result type.
§Arguments
left- The left collection to joinright- The right collection to join
§Example
let join = JoinQuery::new(&users, &orders);Examples found in repository?
28fn main() {
29 println!("╔════════════════════════════════════════════════════════════╗");
30 println!("║ Query Builder Without Clone - Performance Optimization ║");
31 println!("╚════════════════════════════════════════════════════════════╝\n");
32
33 let employees = vec![
34 Employee {
35 id: 1,
36 name: "Alice".to_string(),
37 email: "alice@example.com".to_string(),
38 department: "Engineering".to_string(),
39 salary: 95000.0,
40 large_data: vec![0; 1000], // Large data - expensive to clone!
41 },
42 Employee {
43 id: 2,
44 name: "Bob".to_string(),
45 email: "bob@example.com".to_string(),
46 department: "Engineering".to_string(),
47 salary: 87000.0,
48 large_data: vec![0; 1000],
49 },
50 Employee {
51 id: 3,
52 name: "Carol".to_string(),
53 email: "carol@example.com".to_string(),
54 department: "Sales".to_string(),
55 salary: 75000.0,
56 large_data: vec![0; 1000],
57 },
58 ];
59
60 let departments = vec![
61 Department {
62 id: 1,
63 name: "Engineering".to_string(),
64 budget: 1000000.0,
65 },
66 Department {
67 id: 2,
68 name: "Sales".to_string(),
69 budget: 500000.0,
70 },
71 ];
72
73 println!("✅ Operations that DON'T require Clone:\n");
74
75 // 1. WHERE filtering - returns Vec<&T>
76 println!("1. WHERE filtering (returns references)");
77 let query = Query::new(&employees)
78 .where_(Employee::department_r(), |dept| dept == "Engineering");
79 let engineering = query.all();
80 println!(" Found {} engineering employees", engineering.len());
81 for emp in &engineering {
82 println!(" - {}: ${:.0}", emp.name, emp.salary);
83 }
84
85 // 2. COUNT - no cloning needed
86 println!("\n2. COUNT aggregation");
87 let count = Query::new(&employees)
88 .where_(Employee::salary_r(), |&sal| sal > 80000.0)
89 .count();
90 println!(" {} employees earn over $80k", count);
91
92 // 3. SELECT - only clones the selected field
93 println!("\n3. SELECT (only selected fields are cloned)");
94 let names: Vec<String> = Query::new(&employees)
95 .select(Employee::name_r());
96 println!(" Employee names: {:?}", names);
97
98 // 4. FIRST - returns Option<&T>
99 println!("\n4. FIRST (returns reference)");
100 let query = Query::new(&employees)
101 .where_(Employee::salary_r(), |&sal| sal > 90000.0);
102 if let Some(emp) = query.first() {
103 println!(" First high earner: {} (${:.0})", emp.name, emp.salary);
104 }
105
106 // 5. SUM/AVG aggregations - no cloning
107 println!("\n5. Aggregations (SUM/AVG)");
108 let eng_query = Query::new(&employees)
109 .where_(Employee::department_r(), |dept| dept == "Engineering");
110 let total = eng_query.sum(Employee::salary_r());
111 let avg = eng_query.avg(Employee::salary_r()).unwrap_or(0.0);
112 println!(" Engineering total: ${:.0}", total);
113 println!(" Engineering average: ${:.0}", avg);
114
115 // 6. MIN/MAX - no cloning
116 println!("\n6. MIN/MAX");
117 let min = Query::new(&employees).min_float(Employee::salary_r());
118 let max = Query::new(&employees).max_float(Employee::salary_r());
119 println!(" Salary range: ${:.0} - ${:.0}", min.unwrap(), max.unwrap());
120
121 // 7. LIMIT - returns Vec<&T>
122 println!("\n7. LIMIT (returns references)");
123 let query = Query::new(&employees);
124 let first_two = query.limit(2);
125 println!(" First 2 employees:");
126 for emp in &first_two {
127 println!(" - {}", emp.name);
128 }
129
130 // 8. SKIP/Pagination - returns Vec<&T>
131 println!("\n8. SKIP/Pagination (returns references)");
132 let query = Query::new(&employees);
133 let page_2 = query.skip(2).limit(1);
134 println!(" Page 2:");
135 for emp in &page_2 {
136 println!(" - {}", emp.name);
137 }
138
139 // 9. EXISTS - just checks
140 println!("\n9. EXISTS check");
141 let has_sales = Query::new(&employees)
142 .where_(Employee::department_r(), |dept| dept == "Sales")
143 .exists();
144 println!(" Has Sales employees: {}", has_sales);
145
146 // 10. JOIN - no Clone required on L or R!
147 println!("\n10. JOIN operations (no Clone required!)");
148 let results = JoinQuery::new(&employees, &departments)
149 .inner_join(
150 Employee::department_r(),
151 Department::name_r(),
152 |emp, dept| {
153 // Mapper only clones what it needs for the result
154 (emp.name.clone(), dept.budget)
155 },
156 );
157 println!(" Employee-Department pairs:");
158 for (name, budget) in &results {
159 println!(" - {} works in dept with ${:.0} budget", name, budget);
160 }
161
162 println!("\n╔════════════════════════════════════════════════════════════╗");
163 println!("║ Operations that REQUIRE Clone (only when needed) ║");
164 println!("╚════════════════════════════════════════════════════════════╝\n");
165
166 println!("⚠️ The following operations require Clone because they return owned Vec<T>:");
167 println!(" - order_by() / order_by_desc()");
168 println!(" - order_by_float() / order_by_float_desc()");
169 println!(" - group_by()");
170 println!("\n To use these, add #[derive(Clone)] to your struct:");
171 println!(" ```rust");
172 println!(" #[derive(Clone, Keypaths)] // Add Clone here");
173 println!(" struct Employee {{ ... }}");
174 println!(" ```");
175
176 println!("\n╔════════════════════════════════════════════════════════════╗");
177 println!("║ Performance Benefits ║");
178 println!("╚════════════════════════════════════════════════════════════╝\n");
179
180 println!("✅ Zero cloning for most operations");
181 println!("✅ Work with large structs efficiently");
182 println!("✅ No unnecessary memory allocations");
183 println!("✅ Only clone when you actually need owned data");
184 println!("✅ Pay for what you use");
185
186 println!("\n✓ Example complete! Most operations work without Clone.\n");
187}More examples
30fn main() {
31 println!("Testing documentation examples...\n");
32
33 // Example from README - Quick Start
34 println!("Test 1: README Quick Start Example");
35 {
36 let products = vec![
37 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
38 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
39 Product { id: 3, name: "Desk".to_string(), price: 299.99, category: "Furniture".to_string(), stock: 10, rating: 4.8 },
40 ];
41
42 let affordable_query = Query::new(&products)
43 .where_(Product::category_r(), |cat| cat == "Electronics")
44 .where_(Product::price_r(), |&price| price < 100.0);
45 let affordable_electronics = affordable_query.all();
46
47 println!(" Found {} affordable electronics ✅", affordable_electronics.len());
48 }
49
50 // Example from README - Filtering
51 println!("\nTest 2: Filtering Example");
52 {
53 let products = vec![
54 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
55 ];
56
57 let electronics_query = Query::new(&products)
58 .where_(Product::category_r(), |cat| cat == "Electronics");
59 let electronics = electronics_query.all();
60
61 println!(" Found {} electronics ✅", electronics.len());
62 }
63
64 // Example from README - Selecting
65 println!("\nTest 3: Selecting Fields Example");
66 {
67 let products = vec![
68 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
69 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
70 ];
71
72 let names: Vec<String> = Query::new(&products)
73 .select(Product::name_r());
74
75 println!(" Selected {} names ✅", names.len());
76 }
77
78 // Example from README - Ordering
79 println!("\nTest 4: Ordering Example");
80 {
81 let products = vec![
82 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
83 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
84 ];
85
86 let by_price = Query::new(&products).order_by_float(Product::price_r());
87 println!(" Ordered {} products ✅", by_price.len());
88 }
89
90 // Example from README - Aggregations
91 println!("\nTest 5: Aggregations Example");
92 {
93 let products = vec![
94 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
95 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
96 ];
97
98 let electronics_query = Query::new(&products)
99 .where_(Product::category_r(), |cat| cat == "Electronics");
100
101 let count = electronics_query.count();
102 let total_value: f64 = electronics_query.sum(Product::price_r());
103 let avg_price = electronics_query.avg(Product::price_r()).unwrap_or(0.0);
104
105 println!(" Count: {}, Total: ${:.2}, Avg: ${:.2} ✅", count, total_value, avg_price);
106 }
107
108 // Example from README - Grouping
109 println!("\nTest 6: Grouping Example");
110 {
111 let products = vec![
112 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
113 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
114 Product { id: 3, name: "Desk".to_string(), price: 299.99, category: "Furniture".to_string(), stock: 10, rating: 4.8 },
115 ];
116
117 let by_category = Query::new(&products).group_by(Product::category_r());
118 println!(" Grouped into {} categories ✅", by_category.len());
119 }
120
121 // Example from README - Pagination
122 println!("\nTest 7: Pagination Example");
123 {
124 let products = vec![
125 Product { id: 1, name: "P1".to_string(), price: 10.0, category: "A".to_string(), stock: 1, rating: 4.0 },
126 Product { id: 2, name: "P2".to_string(), price: 20.0, category: "A".to_string(), stock: 1, rating: 4.0 },
127 Product { id: 3, name: "P3".to_string(), price: 30.0, category: "A".to_string(), stock: 1, rating: 4.0 },
128 ];
129
130 let query = Query::new(&products);
131 let first_10 = query.limit(10);
132 let page_1 = query.skip(0).limit(10);
133
134 println!(" Limited to {} products ✅", first_10.len());
135 println!(" Page 1 has {} products ✅", page_1.len());
136 }
137
138 // Example from README - Join
139 println!("\nTest 8: Join Example");
140 {
141 let users = vec![
142 User { id: 1, name: "Alice".to_string() },
143 User { id: 2, name: "Bob".to_string() },
144 ];
145
146 let orders = vec![
147 Order { id: 101, user_id: 1, total: 99.99 },
148 Order { id: 102, user_id: 1, total: 149.99 },
149 ];
150
151 let user_orders = JoinQuery::new(&users, &orders).inner_join(
152 User::id_r(),
153 Order::user_id_r(),
154 |user, order| (user.name.clone(), order.total),
155 );
156
157 println!(" Joined {} user-order pairs ✅", user_orders.len());
158 }
159
160 // Example from SQL_COMPARISON - SELECT with WHERE
161 println!("\nTest 9: SQL Comparison - SELECT with WHERE");
162 {
163 #[derive(Clone, Keypaths)]
164 struct Employee {
165 department: String,
166 }
167
168 let employees = vec![
169 Employee { department: "Engineering".to_string() },
170 Employee { department: "Sales".to_string() },
171 ];
172
173 let engineering_query = Query::new(&employees)
174 .where_(Employee::department_r(), |dept| dept == "Engineering");
175 let engineering = engineering_query.all();
176
177 println!(" Found {} engineering employees ✅", engineering.len());
178 }
179
180 // Example from USAGE.md - Complex Filtering
181 println!("\nTest 10: USAGE - Complex Filtering");
182 {
183 let products = vec![
184 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
185 ];
186
187 let results_query = Query::new(&products)
188 .where_(Product::category_r(), |cat| cat == "Electronics")
189 .where_(Product::price_r(), |&price| price >= 100.0 && price <= 500.0)
190 .where_(Product::stock_r(), |&stock| stock > 10);
191 let results = results_query.order_by_float(Product::price_r());
192
193 println!(" Filtered {} products ✅", results.len());
194 }
195
196 println!("\n✅ All documentation examples compile and run successfully!");
197}193fn main() {
194 println!("=== Join Query Builder Demo ===\n");
195
196 let (users, orders, products) = create_sample_data();
197
198 println!("Database:");
199 println!(" Users: {}", users.len());
200 println!(" Orders: {}", orders.len());
201 println!(" Products: {}\n", products.len());
202
203 // Join 1: Inner join Users and Orders
204 println!("--- Join 1: Users with Their Orders ---");
205 let user_orders = JoinQuery::new(&users, &orders).inner_join(
206 User::id_r(),
207 Order::user_id_r(),
208 |user, order| UserOrder {
209 user_name: user.name.clone(),
210 user_email: user.email.clone(),
211 order_id: order.id,
212 quantity: order.quantity,
213 total: order.total,
214 },
215 );
216
217 for uo in &user_orders {
218 println!(
219 " • {} - Order #{} - {} items - ${:.2}",
220 uo.user_name, uo.order_id, uo.quantity, uo.total
221 );
222 }
223 println!("Total: {} user-order pairs", user_orders.len());
224
225 // Join 2: Three-way join (Orders -> Users, Orders -> Products)
226 println!("\n--- Join 2: Complete Order Details (3-Way Join) ---");
227
228 // First join: Orders with Users
229 let orders_with_users = JoinQuery::new(&orders, &users).inner_join(
230 Order::user_id_r(),
231 User::id_r(),
232 |order, user| (order.clone(), user.clone()),
233 );
234
235 // Second join: (Orders+Users) with Products
236 let mut order_details = Vec::new();
237 for (order, user) in &orders_with_users {
238 for product in &products {
239 if order.product_id == product.id {
240 order_details.push(OrderDetail {
241 order_id: order.id,
242 user_name: user.name.clone(),
243 product_name: product.name.clone(),
244 quantity: order.quantity,
245 price: product.price,
246 total: order.total,
247 });
248 }
249 }
250 }
251
252 for od in &order_details {
253 println!(
254 " • Order #{}: {} bought {} x {} @ ${:.2} = ${:.2}",
255 od.order_id, od.user_name, od.quantity, od.product_name, od.price, od.total
256 );
257 }
258
259 // Join 3: Left join to show all users (including those without orders)
260 println!("\n--- Join 3: All Users with Order Count (Left Join) ---");
261
262 // Use left_join to get all users with their orders (or None)
263 let user_order_pairs = JoinQuery::new(&users, &orders).left_join(
264 User::id_r(),
265 Order::user_id_r(),
266 |user, order| (user.clone(), order.map(|o| o.clone())),
267 );
268
269 // Group by user to count orders
270 let mut user_stats: HashMap<u32, (String, String, usize, f64)> = HashMap::new();
271 for (user, order) in &user_order_pairs {
272 let entry = user_stats
273 .entry(user.id)
274 .or_insert_with(|| (user.name.clone(), user.city.clone(), 0, 0.0));
275 if let Some(order) = order {
276 entry.2 += 1; // order count
277 entry.3 += order.total; // total spent
278 }
279 }
280
281 let mut user_order_stats: Vec<_> = user_stats
282 .into_iter()
283 .map(|(_, (name, city, count, total))| UserOrderCount {
284 user_name: name,
285 user_city: city,
286 order_count: count,
287 total_spent: total,
288 })
289 .collect();
290
291 user_order_stats.sort_by(|a, b| a.user_name.cmp(&b.user_name));
292
293 for stat in &user_order_stats {
294 if stat.order_count > 0 {
295 println!(
296 " • {} ({}) - {} orders - ${:.2} total",
297 stat.user_name, stat.user_city, stat.order_count, stat.total_spent
298 );
299 } else {
300 println!(" • {} ({}) - No orders yet", stat.user_name, stat.user_city);
301 }
302 }
303
304 // Join 4: Aggregated join - Category sales analysis
305 println!("\n--- Join 4: Sales by Product Category ---");
306
307 // Join orders with products to get category information
308 let order_products = JoinQuery::new(&orders, &products).inner_join(
309 Order::product_id_r(),
310 Product::id_r(),
311 |order, product| (order.clone(), product.clone()),
312 );
313
314 // Aggregate by category
315 let mut category_stats: HashMap<String, (Vec<u32>, f64, std::collections::HashSet<u32>)> =
316 HashMap::new();
317
318 for (order, product) in &order_products {
319 let entry = category_stats
320 .entry(product.category.clone())
321 .or_insert_with(|| (Vec::new(), 0.0, std::collections::HashSet::new()));
322 entry.0.push(order.id);
323 entry.1 += order.total;
324 entry.2.insert(order.user_id);
325 }
326
327 let mut category_sales: Vec<CategorySales> = category_stats
328 .into_iter()
329 .map(|(category, (orders, revenue, customers))| CategorySales {
330 category,
331 total_orders: orders.len(),
332 total_revenue: revenue,
333 unique_customers: customers.len(),
334 })
335 .collect();
336
337 category_sales.sort_by(|a, b| {
338 b.total_revenue
339 .partial_cmp(&a.total_revenue)
340 .unwrap_or(std::cmp::Ordering::Equal)
341 });
342
343 for cs in &category_sales {
344 println!(
345 " • {}: {} orders - ${:.2} revenue - {} customers",
346 cs.category, cs.total_orders, cs.total_revenue, cs.unique_customers
347 );
348 }
349
350 // Join 5: Filtered join - High value orders
351 println!("\n--- Join 5: High Value Orders (>$100) with User Details ---");
352 let high_value_orders = JoinQuery::new(&orders, &users).inner_join_where(
353 Order::user_id_r(),
354 User::id_r(),
355 |order, _user| order.total > 100.0,
356 |order, user| (user.name.clone(), order.id, order.total),
357 );
358
359 for (name, order_id, total) in &high_value_orders {
360 println!(" • {} - Order #{} - ${:.2}", name, order_id, total);
361 }
362
363 // Join 6: Users in same city analysis
364 println!("\n--- Join 6: Users from Same City ---");
365 let user_pairs = JoinQuery::new(&users, &users).inner_join_where(
366 User::city_r(),
367 User::city_r(),
368 |u1, u2| u1.id < u2.id, // Avoid duplicates and self-pairs
369 |u1, u2| (u1.name.clone(), u2.name.clone(), u1.city.clone()),
370 );
371
372 for (name1, name2, city) in &user_pairs {
373 println!(" • {} and {} both live in {}", name1, name2, city);
374 }
375
376 // Join 7: Product popularity
377 println!("\n--- Join 7: Product Popularity Ranking ---");
378
379 // Join orders with products
380 let product_order_pairs = JoinQuery::new(&products, &orders).inner_join(
381 Product::id_r(),
382 Order::product_id_r(),
383 |product, order| (product.clone(), order.clone()),
384 );
385
386 // Aggregate by product
387 let mut product_sales: HashMap<u32, (String, usize, u32, f64)> = HashMap::new();
388 for (product, order) in &product_order_pairs {
389 let entry = product_sales
390 .entry(product.id)
391 .or_insert_with(|| (product.name.clone(), 0, 0, 0.0));
392 entry.1 += 1; // order count
393 entry.2 += order.quantity; // total quantity
394 entry.3 += order.total; // total revenue
395 }
396
397 let mut popularity: Vec<_> = product_sales.into_iter().collect();
398 popularity.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); // sort by order count
399
400 for (_, (name, order_count, total_qty, revenue)) in &popularity {
401 println!(
402 " • {} - {} orders - {} units - ${:.2}",
403 name, order_count, total_qty, revenue
404 );
405 }
406
407 // Join 8: User spending by city
408 println!("\n--- Join 8: Total Spending by City ---");
409
410 // Join users with orders to get city and spending info
411 let user_city_orders = JoinQuery::new(&users, &orders).inner_join(
412 User::id_r(),
413 Order::user_id_r(),
414 |user, order| (user.city.clone(), order.total, user.id),
415 );
416
417 // Aggregate by city
418 let mut city_spending: HashMap<String, (f64, std::collections::HashSet<u32>)> = HashMap::new();
419 for (city, total, user_id) in &user_city_orders {
420 let entry = city_spending
421 .entry(city.clone())
422 .or_insert_with(|| (0.0, std::collections::HashSet::new()));
423 entry.0 += total;
424 entry.1.insert(*user_id);
425 }
426
427 let mut city_stats: Vec<_> = city_spending
428 .into_iter()
429 .map(|(city, (total, customers))| (city, total, customers.len()))
430 .collect();
431
432 city_stats.sort_by(|a, b| {
433 b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
434 });
435
436 for (city, total, customer_count) in &city_stats {
437 println!(
438 " • {} - ${:.2} total - {} customers - ${:.2} avg",
439 city,
440 total,
441 customer_count,
442 total / *customer_count as f64
443 );
444 }
445
446 // Statistics summary
447 println!("\n=== Summary Statistics ===");
448 println!("Total orders: {}", orders.len());
449
450 let total_revenue: f64 = orders.iter().map(|o| o.total).sum();
451 println!("Total revenue: ${:.2}", total_revenue);
452 println!("Average order value: ${:.2}", total_revenue / orders.len() as f64);
453
454 // Count unique customers using a join
455 let unique_customers: std::collections::HashSet<u32> =
456 orders.iter().map(|o| o.user_id).collect();
457 println!("Active customers: {}", unique_customers.len());
458 println!(
459 "Average orders per customer: {:.1}",
460 orders.len() as f64 / unique_customers.len() as f64
461 );
462
463 println!("\n✓ Join query builder demo complete!");
464}84fn main() {
85 let employees = create_employees();
86 let departments = create_departments();
87 let projects = create_projects();
88
89 println!("\n╔════════════════════════════════════════════════════════════════════════╗");
90 println!("║ SQL vs Rust Query Builder Comparison ║");
91 println!("║ Demonstrating equivalent operations ║");
92 println!("╚════════════════════════════════════════════════════════════════════════╝");
93
94 // ============================================================================
95 // EXAMPLE 1: Simple SELECT with WHERE
96 // ============================================================================
97 print_separator("Example 1: SELECT with WHERE clause");
98 print_query(
99 "SELECT * FROM employees WHERE department = 'Engineering';",
100 "Filter employees by department"
101 );
102
103 let eng_query = Query::new(&employees)
104 .where_(Employee::department_r(), |dept| dept == "Engineering");
105 let engineering_employees = eng_query.all();
106
107 for emp in &engineering_employees {
108 println!(" ID: {}, Name: {}, Salary: ${:.2}", emp.id, emp.name, emp.salary);
109 }
110 println!("\nRust Query Builder:");
111 println!("Query::new(&employees)");
112 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\")");
113 println!(" .all()");
114 println!("✓ Found {} employees", engineering_employees.len());
115
116 // ============================================================================
117 // EXAMPLE 2: SELECT specific columns
118 // ============================================================================
119 print_separator("Example 2: SELECT specific columns (Projection)");
120 print_query(
121 "SELECT name FROM employees WHERE salary > 80000;",
122 "Get names of high-earning employees"
123 );
124
125 let high_earners = Query::new(&employees)
126 .where_(Employee::salary_r(), |&salary| salary > 80000.0)
127 .select(Employee::name_r());
128
129 for name in &high_earners {
130 println!(" {}", name);
131 }
132 println!("\nRust Query Builder:");
133 println!("Query::new(&employees)");
134 println!(" .where_(Employee::salary_r(), |&salary| salary > 80000.0)");
135 println!(" .select(Employee::name_r())");
136 println!("✓ Found {} employees", high_earners.len());
137
138 // ============================================================================
139 // EXAMPLE 3: COUNT
140 // ============================================================================
141 print_separator("Example 3: COUNT aggregation");
142 print_query(
143 "SELECT COUNT(*) FROM employees WHERE age < 30;",
144 "Count young employees"
145 );
146
147 let young_count = Query::new(&employees)
148 .where_(Employee::age_r(), |&age| age < 30)
149 .count();
150
151 println!(" Count: {}", young_count);
152 println!("\nRust Query Builder:");
153 println!("Query::new(&employees)");
154 println!(" .where_(Employee::age_r(), |&age| age < 30)");
155 println!(" .count()");
156 println!("✓ Result: {}", young_count);
157
158 // ============================================================================
159 // EXAMPLE 4: SUM, AVG, MIN, MAX
160 // ============================================================================
161 print_separator("Example 4: Aggregate functions (SUM, AVG, MIN, MAX)");
162 print_query(
163 "SELECT \n\
164 SUM(salary) as total_salary,\n\
165 AVG(salary) as avg_salary,\n\
166 MIN(salary) as min_salary,\n\
167 MAX(salary) as max_salary\n\
168 FROM employees WHERE department = 'Engineering';",
169 "Engineering department salary statistics"
170 );
171
172 let eng_query = Query::new(&employees)
173 .where_(Employee::department_r(), |dept| dept == "Engineering");
174
175 let total = eng_query.sum(Employee::salary_r());
176 let avg = eng_query.avg(Employee::salary_r()).unwrap_or(0.0);
177 let min = eng_query.min_float(Employee::salary_r()).unwrap_or(0.0);
178 let max = eng_query.max_float(Employee::salary_r()).unwrap_or(0.0);
179
180 println!(" Total Salary: ${:.2}", total);
181 println!(" Avg Salary: ${:.2}", avg);
182 println!(" Min Salary: ${:.2}", min);
183 println!(" Max Salary: ${:.2}", max);
184
185 println!("\nRust Query Builder:");
186 println!("let query = Query::new(&employees)");
187 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\");");
188 println!("query.sum(Employee::salary_r()) // ${:.2}", total);
189 println!("query.avg(Employee::salary_r()) // ${:.2}", avg);
190 println!("query.min_float(Employee::salary_r()) // ${:.2}", min);
191 println!("query.max_float(Employee::salary_r()) // ${:.2}", max);
192
193 // ============================================================================
194 // EXAMPLE 5: GROUP BY with aggregation
195 // ============================================================================
196 print_separator("Example 5: GROUP BY with aggregation");
197 print_query(
198 "SELECT \n\
199 department,\n\
200 COUNT(*) as emp_count,\n\
201 AVG(salary) as avg_salary\n\
202 FROM employees\n\
203 GROUP BY department;",
204 "Statistics by department"
205 );
206
207 let by_dept = Query::new(&employees).group_by(Employee::department_r());
208
209 for (dept, emps) in &by_dept {
210 let dept_query = Query::new(emps);
211 let count = emps.len();
212 let avg_sal = dept_query.avg(Employee::salary_r()).unwrap_or(0.0);
213 println!(" {}: {} employees, avg salary ${:.2}", dept, count, avg_sal);
214 }
215
216 println!("\nRust Query Builder:");
217 println!("let by_dept = Query::new(&employees).group_by(Employee::department_r());");
218 println!("for (dept, emps) in &by_dept {{");
219 println!(" let dept_query = Query::new(emps);");
220 println!(" dept_query.avg(Employee::salary_r())");
221 println!("}}");
222
223 // ============================================================================
224 // EXAMPLE 6: ORDER BY
225 // ============================================================================
226 print_separator("Example 6: ORDER BY");
227 print_query(
228 "SELECT name, salary FROM employees\n\
229 WHERE department = 'Sales'\n\
230 ORDER BY salary DESC;",
231 "Sales employees ordered by salary (descending)"
232 );
233
234 let sales_sorted = Query::new(&employees)
235 .where_(Employee::department_r(), |dept| dept == "Sales")
236 .order_by_float_desc(Employee::salary_r());
237
238 for emp in &sales_sorted {
239 println!(" {}: ${:.2}", emp.name, emp.salary);
240 }
241
242 println!("\nRust Query Builder:");
243 println!("Query::new(&employees)");
244 println!(" .where_(Employee::department_r(), |dept| dept == \"Sales\")");
245 println!(" .order_by_float_desc(Employee::salary_r())");
246
247 // ============================================================================
248 // EXAMPLE 7: Multiple WHERE conditions (AND)
249 // ============================================================================
250 print_separator("Example 7: Multiple WHERE conditions (AND)");
251 print_query(
252 "SELECT * FROM employees\n\
253 WHERE salary > 70000 AND age < 35;",
254 "High-earning young employees"
255 );
256
257 let filter_query = Query::new(&employees)
258 .where_(Employee::salary_r(), |&salary| salary > 70000.0)
259 .where_(Employee::age_r(), |&age| age < 35);
260 let filtered = filter_query.all();
261
262 for emp in &filtered {
263 println!(" {}: Age {}, Salary ${:.2}", emp.name, emp.age, emp.salary);
264 }
265
266 println!("\nRust Query Builder:");
267 println!("Query::new(&employees)");
268 println!(" .where_(Employee::salary_r(), |&salary| salary > 70000.0)");
269 println!(" .where_(Employee::age_r(), |&age| age < 35)");
270 println!(" .all()");
271 println!("✓ Found {} employees", filtered.len());
272
273 // ============================================================================
274 // EXAMPLE 8: LIMIT (TOP N)
275 // ============================================================================
276 print_separator("Example 8: LIMIT / TOP N");
277 print_query(
278 "SELECT TOP 3 name, salary FROM employees\n\
279 ORDER BY salary DESC;",
280 "Top 3 highest paid employees"
281 );
282
283 let top_earners = Query::new(&employees)
284 .order_by_float_desc(Employee::salary_r());
285
286 for (i, emp) in top_earners.iter().take(3).enumerate() {
287 println!(" {}. {}: ${:.2}", i + 1, emp.name, emp.salary);
288 }
289
290 println!("\nRust Query Builder:");
291 println!("Query::new(&employees)");
292 println!(" .order_by_float_desc(Employee::salary_r())");
293 println!(" .into_iter().take(3)");
294
295 // ============================================================================
296 // EXAMPLE 9: OFFSET and LIMIT (Pagination)
297 // ============================================================================
298 print_separator("Example 9: OFFSET and LIMIT (Pagination)");
299 print_query(
300 "SELECT name FROM employees\n\
301 ORDER BY name\n\
302 LIMIT 3 OFFSET 3;",
303 "Page 2 of employee names (3 per page)"
304 );
305
306 let page_2 = Query::new(&employees)
307 .order_by(Employee::name_r())
308 .into_iter()
309 .skip(3)
310 .take(3)
311 .collect::<Vec<_>>();
312
313 for emp in &page_2 {
314 println!(" {}", emp.name);
315 }
316
317 println!("\nRust Query Builder:");
318 println!("Query::new(&employees)");
319 println!(" .order_by(Employee::name_r())");
320 println!(" .into_iter().skip(3).take(3)");
321
322 // ============================================================================
323 // EXAMPLE 10: INNER JOIN
324 // ============================================================================
325 print_separator("Example 10: INNER JOIN");
326 print_query(
327 "SELECT e.name, d.name as dept_name, d.budget\n\
328 FROM employees e\n\
329 INNER JOIN departments d ON e.department = d.name;",
330 "Employees with their department info"
331 );
332
333 let emp_dept = JoinQuery::new(&employees, &departments).inner_join(
334 Employee::department_r(),
335 Department::name_r(),
336 |emp, dept| (emp.name.clone(), dept.name.clone(), dept.budget),
337 );
338
339 for (emp_name, dept_name, budget) in emp_dept.iter().take(5) {
340 println!(" {} works in {} (Budget: ${:.0})", emp_name, dept_name, budget);
341 }
342 println!(" ... (showing first 5)");
343
344 println!("\nRust Query Builder:");
345 println!("JoinQuery::new(&employees, &departments).inner_join(");
346 println!(" Employee::department_r(),");
347 println!(" Department::name_r(),");
348 println!(" |emp, dept| (emp.name.clone(), dept.name.clone(), dept.budget)");
349 println!(")");
350 println!("✓ Found {} matches", emp_dept.len());
351
352 // ============================================================================
353 // EXAMPLE 11: GROUP BY with HAVING equivalent
354 // ============================================================================
355 print_separator("Example 11: GROUP BY with filtering (HAVING equivalent)");
356 print_query(
357 "SELECT city, COUNT(*) as emp_count, AVG(salary) as avg_salary\n\
358 FROM employees\n\
359 GROUP BY city\n\
360 HAVING COUNT(*) > 1;",
361 "Cities with multiple employees"
362 );
363
364 let by_city = Query::new(&employees).group_by(Employee::city_r());
365 let mut city_stats: Vec<_> = by_city
366 .iter()
367 .filter(|(_, emps)| emps.len() > 1) // HAVING equivalent
368 .map(|(city, emps)| {
369 let avg_sal = Query::new(emps).avg(Employee::salary_r()).unwrap_or(0.0);
370 (city.clone(), emps.len(), avg_sal)
371 })
372 .collect();
373
374 city_stats.sort_by(|a, b| b.1.cmp(&a.1)); // Sort by count
375
376 for (city, count, avg_sal) in &city_stats {
377 println!(" {}: {} employees, avg salary ${:.2}", city, count, avg_sal);
378 }
379
380 println!("\nRust Query Builder:");
381 println!("let by_city = Query::new(&employees).group_by(Employee::city_r());");
382 println!("by_city.iter()");
383 println!(" .filter(|(_, emps)| emps.len() > 1) // HAVING equivalent");
384 println!(" .map(|(city, emps)| {{");
385 println!(" let avg = Query::new(emps).avg(Employee::salary_r());");
386 println!(" (city, emps.len(), avg)");
387 println!(" }})");
388
389 // ============================================================================
390 // EXAMPLE 12: Complex query with multiple operations
391 // ============================================================================
392 print_separator("Example 12: Complex multi-operation query");
393 print_query(
394 "SELECT name, salary, age\n\
395 FROM employees\n\
396 WHERE department IN ('Engineering', 'Sales')\n\
397 AND salary BETWEEN 80000 AND 100000\n\
398 AND age >= 30\n\
399 ORDER BY salary DESC;",
400 "Experienced mid-to-senior level employees in core departments"
401 );
402
403 let complex_query = Query::new(&employees)
404 .where_(Employee::department_r(), |dept| dept == "Engineering" || dept == "Sales")
405 .where_(Employee::salary_r(), |&sal| sal >= 80000.0 && sal <= 100000.0)
406 .where_(Employee::age_r(), |&age| age >= 30)
407 .order_by_float_desc(Employee::salary_r());
408
409 for emp in &complex_query {
410 println!(" {}: Age {}, {} dept, ${:.2}", emp.name, emp.age, emp.department, emp.salary);
411 }
412
413 println!("\nRust Query Builder:");
414 println!("Query::new(&employees)");
415 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\" || dept == \"Sales\")");
416 println!(" .where_(Employee::salary_r(), |&sal| sal >= 80000.0 && sal <= 100000.0)");
417 println!(" .where_(Employee::age_r(), |&age| age >= 30)");
418 println!(" .order_by_float_desc(Employee::salary_r())");
419 println!("✓ Found {} employees", complex_query.len());
420
421 // ============================================================================
422 // EXAMPLE 13: Three-table JOIN
423 // ============================================================================
424 print_separator("Example 13: Three-table JOIN");
425 print_query(
426 "SELECT p.name as project, d.name as department, d.location\n\
427 FROM projects p\n\
428 INNER JOIN departments d ON p.department_id = d.id;",
429 "Projects with their department details"
430 );
431
432 let proj_dept = JoinQuery::new(&projects, &departments).inner_join(
433 Project::department_id_r(),
434 Department::id_r(),
435 |proj, dept| {
436 (proj.name.clone(), dept.name.clone(), dept.location.clone(), proj.budget)
437 },
438 );
439
440 for (proj_name, dept_name, location, budget) in &proj_dept {
441 println!(" {}: {} dept in {} (${:.0})", proj_name, dept_name, location, budget);
442 }
443
444 println!("\nRust Query Builder:");
445 println!("JoinQuery::new(&projects, &departments).inner_join(");
446 println!(" Project::department_id_r(),");
447 println!(" Department::id_r(),");
448 println!(" |proj, dept| (proj.name, dept.name, dept.location, proj.budget)");
449 println!(")");
450
451 // ============================================================================
452 // EXAMPLE 14: Subquery equivalent (using intermediate results)
453 // ============================================================================
454 print_separator("Example 14: Subquery equivalent");
455 print_query(
456 "SELECT * FROM employees\n\
457 WHERE salary > (SELECT AVG(salary) FROM employees);",
458 "Employees earning above average"
459 );
460
461 let avg_salary = Query::new(&employees)
462 .avg(Employee::salary_r())
463 .unwrap_or(0.0);
464
465 let above_avg_query = Query::new(&employees)
466 .where_(Employee::salary_r(), move |&sal| sal > avg_salary);
467 let above_avg = above_avg_query.all();
468
469 println!(" Average salary: ${:.2}", avg_salary);
470 for emp in &above_avg {
471 println!(" {}: ${:.2} ({:.1}% above average)",
472 emp.name, emp.salary, ((emp.salary - avg_salary) / avg_salary * 100.0));
473 }
474
475 println!("\nRust Query Builder:");
476 println!("let avg = Query::new(&employees).avg(Employee::salary_r()).unwrap_or(0.0);");
477 println!("Query::new(&employees)");
478 println!(" .where_(Employee::salary_r(), |&sal| sal > avg)");
479 println!(" .all()");
480 println!("✓ Found {} employees above average", above_avg.len());
481
482 // ============================================================================
483 // EXAMPLE 15: Advanced aggregation (revenue calculation)
484 // ============================================================================
485 print_separator("Example 15: Advanced aggregation");
486 print_query(
487 "SELECT \n\
488 d.name,\n\
489 d.budget as dept_budget,\n\
490 SUM(p.budget) as total_project_budget,\n\
491 (d.budget - SUM(p.budget)) as remaining_budget\n\
492 FROM departments d\n\
493 LEFT JOIN projects p ON d.id = p.department_id\n\
494 GROUP BY d.name, d.budget;",
495 "Department budget utilization"
496 );
497
498 // Join departments with projects
499 let dept_projects = JoinQuery::new(&departments, &projects).left_join(
500 Department::id_r(),
501 Project::department_id_r(),
502 |dept, proj| (dept.clone(), proj.map(|p| p.clone())),
503 );
504
505 // Aggregate by department
506 let mut dept_budget_map: HashMap<String, (f64, Vec<f64>)> = HashMap::new();
507 for (dept, proj) in dept_projects {
508 let entry = dept_budget_map
509 .entry(dept.name.clone())
510 .or_insert((dept.budget, Vec::new()));
511 if let Some(p) = proj {
512 entry.1.push(p.budget);
513 }
514 }
515
516 for (dept_name, (total_budget, project_budgets)) in dept_budget_map.iter() {
517 let used: f64 = project_budgets.iter().sum();
518 let remaining = total_budget - used;
519 println!(" {}: Budget ${:.0}, Used ${:.0}, Remaining ${:.0} ({:.1}% utilized)",
520 dept_name, total_budget, used, remaining, (used / total_budget * 100.0));
521 }
522
523 println!("\nRust Query Builder:");
524 println!("let dept_projects = JoinQuery::new(&departments, &projects)");
525 println!(" .left_join(Department::id_r(), Project::department_id_r(), ...)");
526 println!("// Then aggregate using HashMap");
527
528 // ============================================================================
529 // Summary
530 // ============================================================================
531 print_separator("Summary");
532 println!("\n✓ All 15 SQL queries successfully replicated with Rust Query Builder!");
533 println!("\nDemonstrated equivalents for:");
534 println!(" • SELECT with WHERE");
535 println!(" • Projection (SELECT specific columns)");
536 println!(" • Aggregations (COUNT, SUM, AVG, MIN, MAX)");
537 println!(" • GROUP BY");
538 println!(" • ORDER BY");
539 println!(" • LIMIT and OFFSET");
540 println!(" • INNER JOIN and LEFT JOIN");
541 println!(" • Complex conditions (AND, OR, BETWEEN)");
542 println!(" • Subqueries");
543 println!(" • Multi-table operations");
544 println!("\n🎯 Type-safe, compile-time checked, and zero runtime overhead!");
545 println!();
546}98fn main() {
99 println!("\n╔═══════════════════════════════════════════════════════════════╗");
100 println!("║ Memory Safety Verification ║");
101 println!("║ Proving 'static doesn't cause memory leaks ║");
102 println!("╚═══════════════════════════════════════════════════════════════╝\n");
103
104 println!("🔍 Understanding 'static:\n");
105 println!(" • T: 'static means: Type T doesn't contain non-'static references");
106 println!(" • It does NOT mean: Data lives for entire program");
107 println!(" • It's needed for: Storing types in trait objects");
108 println!(" • Safety: Compiler ensures no dangling references\n");
109
110 // ============================================================================
111 // TEST 1: Basic Query - Verify Cleanup
112 // ============================================================================
113 println!("═══════════════════════════════════════════════════════════════");
114 println!("Test 1: Basic WHERE query - verify all data is dropped");
115 println!("═══════════════════════════════════════════════════════════════\n");
116
117 reset_stats();
118 {
119 let employees = vec![
120 create_employee(1, "Alice", "Engineering", 95000.0),
121 create_employee(2, "Bob", "Engineering", 87000.0),
122 create_employee(3, "Carol", "Sales", 75000.0),
123 ];
124 print_memory_status("After creating employees");
125
126 {
127 let query = Query::new(&employees)
128 .where_(Employee::department_r(), |dept| dept == "Engineering");
129 let results = query.all();
130
131 println!(" Found {} engineering employees", results.len());
132 print_memory_status("During query execution");
133 }
134
135 print_memory_status("After query scope ends");
136 }
137
138 print_memory_status("After employees scope ends");
139 println!();
140
141 // ============================================================================
142 // TEST 2: Multiple Queries - No Accumulation
143 // ============================================================================
144 println!("═══════════════════════════════════════════════════════════════");
145 println!("Test 2: Multiple queries - verify no memory accumulation");
146 println!("═══════════════════════════════════════════════════════════════\n");
147
148 reset_stats();
149 {
150 let employees = vec![
151 create_employee(1, "Alice", "Engineering", 95000.0),
152 create_employee(2, "Bob", "Sales", 75000.0),
153 ];
154
155 let initial_stats = get_stats();
156 println!(" Initial allocations: {}", initial_stats.0);
157
158 // Run 10 queries
159 for i in 1..=10 {
160 let query = Query::new(&employees)
161 .where_(Employee::salary_r(), |&s| s > 70000.0);
162 let _results = query.all();
163
164 if i % 3 == 0 {
165 let (allocs, drops) = get_stats();
166 println!(" After {} queries - Allocated: {}, Dropped: {}", i, allocs, drops);
167 }
168 }
169
170 let final_stats = get_stats();
171 println!("\n After 10 queries:");
172 println!(" Allocations: {} (should be same as initial)", final_stats.0);
173 println!(" ✅ No memory accumulation from queries!");
174 }
175
176 print_memory_status("After all queries and employees dropped");
177 println!();
178
179 // ============================================================================
180 // TEST 3: ORDER BY (requires Clone) - Track Cloning
181 // ============================================================================
182 println!("═══════════════════════════════════════════════════════════════");
183 println!("Test 3: ORDER BY (with Clone) - verify controlled cloning");
184 println!("═══════════════════════════════════════════════════════════════\n");
185
186 reset_stats();
187 {
188 let employees = vec![
189 create_employee(1, "Alice", "Engineering", 95000.0),
190 create_employee(2, "Bob", "Sales", 87000.0),
191 create_employee(3, "Carol", "Marketing", 75000.0),
192 ];
193
194 let (before_allocs, _) = get_stats();
195 println!(" Before sorting: {} allocations", before_allocs);
196
197 {
198 let sorted = Query::new(&employees)
199 .order_by_float_desc(Employee::salary_r());
200
201 let (after_allocs, _) = get_stats();
202 println!(" After sorting: {} allocations", after_allocs);
203 println!(" Cloned items: {} (expected: {})", after_allocs - before_allocs, employees.len());
204 println!(" Sorted {} employees by salary", sorted.len());
205
206 // sorted goes out of scope here
207 }
208
209 print_memory_status("After sorted results dropped");
210 }
211
212 print_memory_status("After employees dropped");
213 println!();
214
215 // ============================================================================
216 // TEST 4: JOIN Operations - No Leaks
217 // ============================================================================
218 println!("═══════════════════════════════════════════════════════════════");
219 println!("Test 4: JOIN operations - verify no memory leaks");
220 println!("═══════════════════════════════════════════════════════════════\n");
221
222 #[derive(Keypaths)]
223 struct Department {
224 id: u32,
225 name: String,
226 drop_tracker: DropTracker,
227 }
228
229 reset_stats();
230 {
231 let employees = vec![
232 create_employee(1, "Alice", "Engineering", 95000.0),
233 create_employee(2, "Bob", "Sales", 87000.0),
234 ];
235
236 let departments = vec![
237 Department {
238 id: 1,
239 name: "Engineering".to_string(),
240 drop_tracker: DropTracker::new(),
241 },
242 Department {
243 id: 2,
244 name: "Sales".to_string(),
245 drop_tracker: DropTracker::new(),
246 },
247 ];
248
249 print_memory_status("After creating data");
250
251 {
252 let results = JoinQuery::new(&employees, &departments)
253 .inner_join(
254 Employee::department_r(),
255 Department::name_r(),
256 |emp, dept| (emp.name.clone(), dept.name.clone()),
257 );
258
259 println!(" Joined {} pairs", results.len());
260 print_memory_status("During join results");
261 }
262
263 print_memory_status("After join results dropped");
264 }
265
266 print_memory_status("After all data dropped");
267 println!();
268
269 // ============================================================================
270 // TEST 5: Large Scale - Memory Behavior
271 // ============================================================================
272 println!("═══════════════════════════════════════════════════════════════");
273 println!("Test 5: Large scale (1000 items) - verify cleanup");
274 println!("═══════════════════════════════════════════════════════════════\n");
275
276 reset_stats();
277 {
278 let mut large_dataset = Vec::new();
279 for i in 0..1000 {
280 large_dataset.push(create_employee(
281 i,
282 &format!("Employee {}", i),
283 if i % 3 == 0 { "Engineering" } else if i % 3 == 1 { "Sales" } else { "Marketing" },
284 50000.0 + (i as f64 * 100.0),
285 ));
286 }
287
288 let (initial_allocs, _) = get_stats();
289 println!(" Created 1000 employees: {} allocations (~10MB)", initial_allocs);
290
291 // Run complex query
292 {
293 let query = Query::new(&large_dataset)
294 .where_(Employee::salary_r(), |&s| s > 80000.0)
295 .where_(Employee::department_r(), |d| d == "Engineering");
296 let results = query.all();
297
298 println!(" Filtered to {} employees", results.len());
299
300 let (during_allocs, _) = get_stats();
301 let extra = during_allocs - initial_allocs;
302 println!(" Extra allocations during query: {} (should be 0)", extra);
303
304 if extra == 0 {
305 println!(" ✅ Zero-copy filtering confirmed!");
306 }
307 }
308
309 print_memory_status("After query results dropped");
310 }
311
312 print_memory_status("After 1000 employees dropped");
313 println!();
314
315 // ============================================================================
316 // TEST 6: Explanation of 'static
317 // ============================================================================
318 println!("═══════════════════════════════════════════════════════════════");
319 println!("Explanation: Why 'static is safe and doesn't leak");
320 println!("═══════════════════════════════════════════════════════════════\n");
321
322 println!("❓ What does T: 'static mean?\n");
323 println!(" WRONG ❌: \"T lives for the entire program\"");
324 println!(" RIGHT ✅: \"T doesn't contain non-'static references\"\n");
325
326 println!("Examples:\n");
327 println!(" struct OwnedData {{ // T: 'static ✅");
328 println!(" id: u32, // Owned data");
329 println!(" name: String, // Owned data");
330 println!(" }}");
331 println!();
332 println!(" struct WithReference<'a> {{ // NOT 'static ❌");
333 println!(" data: &'a String, // Contains reference");
334 println!(" }}");
335 println!();
336
337 println!("Why we use T: 'static:\n");
338 println!(" 1. Store type in trait objects: Box<dyn Fn(&T) -> bool>");
339 println!(" 2. Prevent dangling references in closures");
340 println!(" 3. Ensure type safety at compile time");
341 println!();
342
343 println!("Lifetime of data:\n");
344 println!(" • Data is owned by your Vec<T>");
345 println!(" • Query just borrows &'a [T]");
346 println!(" • When Vec<T> is dropped, all T are dropped");
347 println!(" • No memory leaks possible!\n");
348
349 // ============================================================================
350 // TEST 7: Drop Order Verification
351 // ============================================================================
352 println!("═══════════════════════════════════════════════════════════════");
353 println!("Test 7: Drop order - verify proper RAII");
354 println!("═══════════════════════════════════════════════════════════════\n");
355
356 reset_stats();
357
358 println!("Creating scoped data...");
359 {
360 let employees = vec![
361 create_employee(1, "Alice", "Engineering", 95000.0),
362 create_employee(2, "Bob", "Sales", 87000.0),
363 ];
364 println!(" Created 2 employees");
365
366 {
367 println!(" Creating query...");
368 let query = Query::new(&employees)
369 .where_(Employee::department_r(), |dept| dept == "Engineering");
370
371 {
372 println!(" Executing query...");
373 let results = query.all();
374 println!(" Found {} results", results.len());
375 println!(" Query results going out of scope...");
376 }
377 println!(" Results dropped (just Vec<&Employee>, no Employee drops)");
378
379 println!(" Query going out of scope...");
380 }
381 println!(" Query dropped (just filters, no Employee drops)");
382
383 println!(" Employees vector going out of scope...");
384 }
385 println!(" Employees dropped - NOW Employees are freed!\n");
386
387 let (allocs, drops) = get_stats();
388 println!("Final stats:");
389 println!(" Allocated: {}", allocs);
390 println!(" Dropped: {}", drops);
391 println!(" Leaked: {}", allocs - drops);
392
393 if allocs == drops {
394 println!("\n✅ Perfect! All allocated memory was freed!");
395 } else {
396 println!("\n❌ Memory leak detected!");
397 }
398
399 // ============================================================================
400 // TEST 8: Arc/Rc Compatibility
401 // ============================================================================
402 println!("\n═══════════════════════════════════════════════════════════════");
403 println!("Test 8: Arc/Rc compatibility - shared ownership works");
404 println!("═══════════════════════════════════════════════════════════════\n");
405
406 {
407 use std::sync::Arc;
408
409 #[derive(Keypaths)]
410 struct SharedData {
411 id: u32,
412 value: Arc<String>, // Shared ownership
413 }
414
415 let shared_string = Arc::new("Shared Value".to_string());
416 println!(" Arc strong count: {}", Arc::strong_count(&shared_string));
417
418 let data = vec![
419 SharedData { id: 1, value: Arc::clone(&shared_string) },
420 SharedData { id: 2, value: Arc::clone(&shared_string) },
421 ];
422
423 println!(" Arc strong count after creating data: {}", Arc::strong_count(&shared_string));
424
425 {
426 let query = Query::new(&data)
427 .where_(SharedData::id_r(), |&id| id > 0);
428 let results = query.all();
429 println!(" Found {} items", results.len());
430 println!(" Arc strong count during query: {}", Arc::strong_count(&shared_string));
431 }
432
433 println!(" Arc strong count after query: {}", Arc::strong_count(&shared_string));
434 }
435
436 println!(" ✅ Arc reference counting works correctly!\n");
437
438 // ============================================================================
439 // TEST 9: Large Data Without Clone - Zero Copy
440 // ============================================================================
441 println!("═══════════════════════════════════════════════════════════════");
442 println!("Test 9: Large data without Clone - verify zero-copy");
443 println!("═══════════════════════════════════════════════════════════════\n");
444
445 #[derive(Keypaths)] // NO Clone!
446 struct LargeRecord {
447 id: u32,
448 // Simulate 1MB of data that we DON'T want to clone
449 huge_data: Vec<u8>,
450 }
451
452 {
453 println!(" Creating 10 records (1MB each = 10MB total)...");
454 let large_records: Vec<LargeRecord> = (0..10)
455 .map(|i| LargeRecord {
456 id: i,
457 huge_data: vec![i as u8; 1_000_000], // 1MB each
458 })
459 .collect();
460
461 println!(" Total memory: ~10MB");
462
463 {
464 println!("\n Running query without Clone...");
465 let query = Query::new(&large_records)
466 .where_(LargeRecord::id_r(), |&id| id < 5);
467 let results = query.all(); // Vec<&LargeRecord> - NO CLONING!
468
469 println!(" Found {} records", results.len());
470 println!(" Memory copied: 0 bytes (just references)");
471 println!(" ✅ Zero-copy achieved!");
472 }
473
474 println!("\n Query dropped - no memory freed (no cloning happened)");
475 }
476
477 println!(" Records dropped - 10MB freed\n");
478
479 // ============================================================================
480 // TEST 10: Lifetime Safety
481 // ============================================================================
482 println!("═══════════════════════════════════════════════════════════════");
483 println!("Test 10: Lifetime safety - compiler prevents dangling refs");
484 println!("═══════════════════════════════════════════════════════════════\n");
485
486 println!(" The following code WILL NOT COMPILE (by design):\n");
487 println!(" ```rust");
488 println!(" let query;");
489 println!(" {{");
490 println!(" let data = vec![...];");
491 println!(" query = Query::new(&data); // data borrowed here");
492 println!(" }} // data dropped");
493 println!(" let results = query.all(); // ❌ ERROR: data doesn't live long enough");
494 println!(" ```\n");
495 println!(" ✅ Rust's borrow checker prevents use-after-free!");
496 println!(" ✅ 'static bound + lifetimes = memory safety guaranteed!\n");
497
498 // ============================================================================
499 // Summary
500 // ============================================================================
501 println!("═══════════════════════════════════════════════════════════════");
502 println!("Summary: Memory Safety Guarantees");
503 println!("═══════════════════════════════════════════════════════════════\n");
504
505 let (total_allocs, total_drops) = get_stats();
506 let leaked = total_allocs.saturating_sub(total_drops);
507
508 println!("Overall Statistics:");
509 println!(" Total allocations: {}", total_allocs);
510 println!(" Total drops: {}", total_drops);
511 println!(" Memory leaks: {}", leaked);
512
513 if leaked == 0 {
514 println!("\n🎉 VERIFIED: Zero memory leaks!\n");
515 } else {
516 println!("\n⚠️ WARNING: Potential memory leak detected!\n");
517 }
518
519 println!("Guarantees Verified:");
520 println!(" ✅ 'static doesn't cause data to live forever");
521 println!(" ✅ All allocated memory is properly freed");
522 println!(" ✅ No memory leaks from queries");
523 println!(" ✅ Query only holds references, not ownership");
524 println!(" ✅ Rust's borrow checker prevents dangling references");
525 println!(" ✅ RAII ensures proper cleanup");
526 println!(" ✅ Zero-copy operations don't allocate");
527 println!(" ✅ Clone operations are explicit and controlled\n");
528
529 println!("Performance Benefits:");
530 println!(" ✅ Filtering: 0 bytes copied (v0.2.0) vs 10MB (v0.1.0)");
531 println!(" ✅ Counting: 0 bytes copied");
532 println!(" ✅ Aggregations: 0 bytes copied");
533 println!(" ✅ Only ordering/grouping clone when needed\n");
534
535 println!("Safety Guarantees:");
536 println!(" ✅ Compile-time prevention of dangling references");
537 println!(" ✅ No use-after-free possible");
538 println!(" ✅ No double-free possible");
539 println!(" ✅ Automatic cleanup via RAII\n");
540
541 println!("✓ All memory safety tests PASSED!\n");
542}Sourcepub fn inner_join<K, O, F>(
&self,
left_key: KeyPaths<L, K>,
right_key: KeyPaths<R, K>,
mapper: F,
) -> Vec<O>
pub fn inner_join<K, O, F>( &self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F, ) -> Vec<O>
Performs an inner join between two collections.
Returns only the pairs where the join keys match. Uses a hash-based algorithm for O(n + m) performance.
§Arguments
left_key- Key-path to the join field in the left collectionright_key- Key-path to the join field in the right collectionmapper- Function to transform matching pairs into the result type
§Example
let results = JoinQuery::new(&users, &orders)
.inner_join(
User::id_r(),
Order::user_id_r(),
|user, order| UserOrder {
user_name: user.name.clone(),
order_total: order.total,
}
);Examples found in repository?
28fn main() {
29 println!("╔════════════════════════════════════════════════════════════╗");
30 println!("║ Query Builder Without Clone - Performance Optimization ║");
31 println!("╚════════════════════════════════════════════════════════════╝\n");
32
33 let employees = vec![
34 Employee {
35 id: 1,
36 name: "Alice".to_string(),
37 email: "alice@example.com".to_string(),
38 department: "Engineering".to_string(),
39 salary: 95000.0,
40 large_data: vec![0; 1000], // Large data - expensive to clone!
41 },
42 Employee {
43 id: 2,
44 name: "Bob".to_string(),
45 email: "bob@example.com".to_string(),
46 department: "Engineering".to_string(),
47 salary: 87000.0,
48 large_data: vec![0; 1000],
49 },
50 Employee {
51 id: 3,
52 name: "Carol".to_string(),
53 email: "carol@example.com".to_string(),
54 department: "Sales".to_string(),
55 salary: 75000.0,
56 large_data: vec![0; 1000],
57 },
58 ];
59
60 let departments = vec![
61 Department {
62 id: 1,
63 name: "Engineering".to_string(),
64 budget: 1000000.0,
65 },
66 Department {
67 id: 2,
68 name: "Sales".to_string(),
69 budget: 500000.0,
70 },
71 ];
72
73 println!("✅ Operations that DON'T require Clone:\n");
74
75 // 1. WHERE filtering - returns Vec<&T>
76 println!("1. WHERE filtering (returns references)");
77 let query = Query::new(&employees)
78 .where_(Employee::department_r(), |dept| dept == "Engineering");
79 let engineering = query.all();
80 println!(" Found {} engineering employees", engineering.len());
81 for emp in &engineering {
82 println!(" - {}: ${:.0}", emp.name, emp.salary);
83 }
84
85 // 2. COUNT - no cloning needed
86 println!("\n2. COUNT aggregation");
87 let count = Query::new(&employees)
88 .where_(Employee::salary_r(), |&sal| sal > 80000.0)
89 .count();
90 println!(" {} employees earn over $80k", count);
91
92 // 3. SELECT - only clones the selected field
93 println!("\n3. SELECT (only selected fields are cloned)");
94 let names: Vec<String> = Query::new(&employees)
95 .select(Employee::name_r());
96 println!(" Employee names: {:?}", names);
97
98 // 4. FIRST - returns Option<&T>
99 println!("\n4. FIRST (returns reference)");
100 let query = Query::new(&employees)
101 .where_(Employee::salary_r(), |&sal| sal > 90000.0);
102 if let Some(emp) = query.first() {
103 println!(" First high earner: {} (${:.0})", emp.name, emp.salary);
104 }
105
106 // 5. SUM/AVG aggregations - no cloning
107 println!("\n5. Aggregations (SUM/AVG)");
108 let eng_query = Query::new(&employees)
109 .where_(Employee::department_r(), |dept| dept == "Engineering");
110 let total = eng_query.sum(Employee::salary_r());
111 let avg = eng_query.avg(Employee::salary_r()).unwrap_or(0.0);
112 println!(" Engineering total: ${:.0}", total);
113 println!(" Engineering average: ${:.0}", avg);
114
115 // 6. MIN/MAX - no cloning
116 println!("\n6. MIN/MAX");
117 let min = Query::new(&employees).min_float(Employee::salary_r());
118 let max = Query::new(&employees).max_float(Employee::salary_r());
119 println!(" Salary range: ${:.0} - ${:.0}", min.unwrap(), max.unwrap());
120
121 // 7. LIMIT - returns Vec<&T>
122 println!("\n7. LIMIT (returns references)");
123 let query = Query::new(&employees);
124 let first_two = query.limit(2);
125 println!(" First 2 employees:");
126 for emp in &first_two {
127 println!(" - {}", emp.name);
128 }
129
130 // 8. SKIP/Pagination - returns Vec<&T>
131 println!("\n8. SKIP/Pagination (returns references)");
132 let query = Query::new(&employees);
133 let page_2 = query.skip(2).limit(1);
134 println!(" Page 2:");
135 for emp in &page_2 {
136 println!(" - {}", emp.name);
137 }
138
139 // 9. EXISTS - just checks
140 println!("\n9. EXISTS check");
141 let has_sales = Query::new(&employees)
142 .where_(Employee::department_r(), |dept| dept == "Sales")
143 .exists();
144 println!(" Has Sales employees: {}", has_sales);
145
146 // 10. JOIN - no Clone required on L or R!
147 println!("\n10. JOIN operations (no Clone required!)");
148 let results = JoinQuery::new(&employees, &departments)
149 .inner_join(
150 Employee::department_r(),
151 Department::name_r(),
152 |emp, dept| {
153 // Mapper only clones what it needs for the result
154 (emp.name.clone(), dept.budget)
155 },
156 );
157 println!(" Employee-Department pairs:");
158 for (name, budget) in &results {
159 println!(" - {} works in dept with ${:.0} budget", name, budget);
160 }
161
162 println!("\n╔════════════════════════════════════════════════════════════╗");
163 println!("║ Operations that REQUIRE Clone (only when needed) ║");
164 println!("╚════════════════════════════════════════════════════════════╝\n");
165
166 println!("⚠️ The following operations require Clone because they return owned Vec<T>:");
167 println!(" - order_by() / order_by_desc()");
168 println!(" - order_by_float() / order_by_float_desc()");
169 println!(" - group_by()");
170 println!("\n To use these, add #[derive(Clone)] to your struct:");
171 println!(" ```rust");
172 println!(" #[derive(Clone, Keypaths)] // Add Clone here");
173 println!(" struct Employee {{ ... }}");
174 println!(" ```");
175
176 println!("\n╔════════════════════════════════════════════════════════════╗");
177 println!("║ Performance Benefits ║");
178 println!("╚════════════════════════════════════════════════════════════╝\n");
179
180 println!("✅ Zero cloning for most operations");
181 println!("✅ Work with large structs efficiently");
182 println!("✅ No unnecessary memory allocations");
183 println!("✅ Only clone when you actually need owned data");
184 println!("✅ Pay for what you use");
185
186 println!("\n✓ Example complete! Most operations work without Clone.\n");
187}More examples
30fn main() {
31 println!("Testing documentation examples...\n");
32
33 // Example from README - Quick Start
34 println!("Test 1: README Quick Start Example");
35 {
36 let products = vec![
37 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
38 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
39 Product { id: 3, name: "Desk".to_string(), price: 299.99, category: "Furniture".to_string(), stock: 10, rating: 4.8 },
40 ];
41
42 let affordable_query = Query::new(&products)
43 .where_(Product::category_r(), |cat| cat == "Electronics")
44 .where_(Product::price_r(), |&price| price < 100.0);
45 let affordable_electronics = affordable_query.all();
46
47 println!(" Found {} affordable electronics ✅", affordable_electronics.len());
48 }
49
50 // Example from README - Filtering
51 println!("\nTest 2: Filtering Example");
52 {
53 let products = vec![
54 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
55 ];
56
57 let electronics_query = Query::new(&products)
58 .where_(Product::category_r(), |cat| cat == "Electronics");
59 let electronics = electronics_query.all();
60
61 println!(" Found {} electronics ✅", electronics.len());
62 }
63
64 // Example from README - Selecting
65 println!("\nTest 3: Selecting Fields Example");
66 {
67 let products = vec![
68 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
69 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
70 ];
71
72 let names: Vec<String> = Query::new(&products)
73 .select(Product::name_r());
74
75 println!(" Selected {} names ✅", names.len());
76 }
77
78 // Example from README - Ordering
79 println!("\nTest 4: Ordering Example");
80 {
81 let products = vec![
82 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
83 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
84 ];
85
86 let by_price = Query::new(&products).order_by_float(Product::price_r());
87 println!(" Ordered {} products ✅", by_price.len());
88 }
89
90 // Example from README - Aggregations
91 println!("\nTest 5: Aggregations Example");
92 {
93 let products = vec![
94 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
95 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
96 ];
97
98 let electronics_query = Query::new(&products)
99 .where_(Product::category_r(), |cat| cat == "Electronics");
100
101 let count = electronics_query.count();
102 let total_value: f64 = electronics_query.sum(Product::price_r());
103 let avg_price = electronics_query.avg(Product::price_r()).unwrap_or(0.0);
104
105 println!(" Count: {}, Total: ${:.2}, Avg: ${:.2} ✅", count, total_value, avg_price);
106 }
107
108 // Example from README - Grouping
109 println!("\nTest 6: Grouping Example");
110 {
111 let products = vec![
112 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
113 Product { id: 2, name: "Mouse".to_string(), price: 29.99, category: "Electronics".to_string(), stock: 50, rating: 4.0 },
114 Product { id: 3, name: "Desk".to_string(), price: 299.99, category: "Furniture".to_string(), stock: 10, rating: 4.8 },
115 ];
116
117 let by_category = Query::new(&products).group_by(Product::category_r());
118 println!(" Grouped into {} categories ✅", by_category.len());
119 }
120
121 // Example from README - Pagination
122 println!("\nTest 7: Pagination Example");
123 {
124 let products = vec![
125 Product { id: 1, name: "P1".to_string(), price: 10.0, category: "A".to_string(), stock: 1, rating: 4.0 },
126 Product { id: 2, name: "P2".to_string(), price: 20.0, category: "A".to_string(), stock: 1, rating: 4.0 },
127 Product { id: 3, name: "P3".to_string(), price: 30.0, category: "A".to_string(), stock: 1, rating: 4.0 },
128 ];
129
130 let query = Query::new(&products);
131 let first_10 = query.limit(10);
132 let page_1 = query.skip(0).limit(10);
133
134 println!(" Limited to {} products ✅", first_10.len());
135 println!(" Page 1 has {} products ✅", page_1.len());
136 }
137
138 // Example from README - Join
139 println!("\nTest 8: Join Example");
140 {
141 let users = vec![
142 User { id: 1, name: "Alice".to_string() },
143 User { id: 2, name: "Bob".to_string() },
144 ];
145
146 let orders = vec![
147 Order { id: 101, user_id: 1, total: 99.99 },
148 Order { id: 102, user_id: 1, total: 149.99 },
149 ];
150
151 let user_orders = JoinQuery::new(&users, &orders).inner_join(
152 User::id_r(),
153 Order::user_id_r(),
154 |user, order| (user.name.clone(), order.total),
155 );
156
157 println!(" Joined {} user-order pairs ✅", user_orders.len());
158 }
159
160 // Example from SQL_COMPARISON - SELECT with WHERE
161 println!("\nTest 9: SQL Comparison - SELECT with WHERE");
162 {
163 #[derive(Clone, Keypaths)]
164 struct Employee {
165 department: String,
166 }
167
168 let employees = vec![
169 Employee { department: "Engineering".to_string() },
170 Employee { department: "Sales".to_string() },
171 ];
172
173 let engineering_query = Query::new(&employees)
174 .where_(Employee::department_r(), |dept| dept == "Engineering");
175 let engineering = engineering_query.all();
176
177 println!(" Found {} engineering employees ✅", engineering.len());
178 }
179
180 // Example from USAGE.md - Complex Filtering
181 println!("\nTest 10: USAGE - Complex Filtering");
182 {
183 let products = vec![
184 Product { id: 1, name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string(), stock: 15, rating: 4.5 },
185 ];
186
187 let results_query = Query::new(&products)
188 .where_(Product::category_r(), |cat| cat == "Electronics")
189 .where_(Product::price_r(), |&price| price >= 100.0 && price <= 500.0)
190 .where_(Product::stock_r(), |&stock| stock > 10);
191 let results = results_query.order_by_float(Product::price_r());
192
193 println!(" Filtered {} products ✅", results.len());
194 }
195
196 println!("\n✅ All documentation examples compile and run successfully!");
197}193fn main() {
194 println!("=== Join Query Builder Demo ===\n");
195
196 let (users, orders, products) = create_sample_data();
197
198 println!("Database:");
199 println!(" Users: {}", users.len());
200 println!(" Orders: {}", orders.len());
201 println!(" Products: {}\n", products.len());
202
203 // Join 1: Inner join Users and Orders
204 println!("--- Join 1: Users with Their Orders ---");
205 let user_orders = JoinQuery::new(&users, &orders).inner_join(
206 User::id_r(),
207 Order::user_id_r(),
208 |user, order| UserOrder {
209 user_name: user.name.clone(),
210 user_email: user.email.clone(),
211 order_id: order.id,
212 quantity: order.quantity,
213 total: order.total,
214 },
215 );
216
217 for uo in &user_orders {
218 println!(
219 " • {} - Order #{} - {} items - ${:.2}",
220 uo.user_name, uo.order_id, uo.quantity, uo.total
221 );
222 }
223 println!("Total: {} user-order pairs", user_orders.len());
224
225 // Join 2: Three-way join (Orders -> Users, Orders -> Products)
226 println!("\n--- Join 2: Complete Order Details (3-Way Join) ---");
227
228 // First join: Orders with Users
229 let orders_with_users = JoinQuery::new(&orders, &users).inner_join(
230 Order::user_id_r(),
231 User::id_r(),
232 |order, user| (order.clone(), user.clone()),
233 );
234
235 // Second join: (Orders+Users) with Products
236 let mut order_details = Vec::new();
237 for (order, user) in &orders_with_users {
238 for product in &products {
239 if order.product_id == product.id {
240 order_details.push(OrderDetail {
241 order_id: order.id,
242 user_name: user.name.clone(),
243 product_name: product.name.clone(),
244 quantity: order.quantity,
245 price: product.price,
246 total: order.total,
247 });
248 }
249 }
250 }
251
252 for od in &order_details {
253 println!(
254 " • Order #{}: {} bought {} x {} @ ${:.2} = ${:.2}",
255 od.order_id, od.user_name, od.quantity, od.product_name, od.price, od.total
256 );
257 }
258
259 // Join 3: Left join to show all users (including those without orders)
260 println!("\n--- Join 3: All Users with Order Count (Left Join) ---");
261
262 // Use left_join to get all users with their orders (or None)
263 let user_order_pairs = JoinQuery::new(&users, &orders).left_join(
264 User::id_r(),
265 Order::user_id_r(),
266 |user, order| (user.clone(), order.map(|o| o.clone())),
267 );
268
269 // Group by user to count orders
270 let mut user_stats: HashMap<u32, (String, String, usize, f64)> = HashMap::new();
271 for (user, order) in &user_order_pairs {
272 let entry = user_stats
273 .entry(user.id)
274 .or_insert_with(|| (user.name.clone(), user.city.clone(), 0, 0.0));
275 if let Some(order) = order {
276 entry.2 += 1; // order count
277 entry.3 += order.total; // total spent
278 }
279 }
280
281 let mut user_order_stats: Vec<_> = user_stats
282 .into_iter()
283 .map(|(_, (name, city, count, total))| UserOrderCount {
284 user_name: name,
285 user_city: city,
286 order_count: count,
287 total_spent: total,
288 })
289 .collect();
290
291 user_order_stats.sort_by(|a, b| a.user_name.cmp(&b.user_name));
292
293 for stat in &user_order_stats {
294 if stat.order_count > 0 {
295 println!(
296 " • {} ({}) - {} orders - ${:.2} total",
297 stat.user_name, stat.user_city, stat.order_count, stat.total_spent
298 );
299 } else {
300 println!(" • {} ({}) - No orders yet", stat.user_name, stat.user_city);
301 }
302 }
303
304 // Join 4: Aggregated join - Category sales analysis
305 println!("\n--- Join 4: Sales by Product Category ---");
306
307 // Join orders with products to get category information
308 let order_products = JoinQuery::new(&orders, &products).inner_join(
309 Order::product_id_r(),
310 Product::id_r(),
311 |order, product| (order.clone(), product.clone()),
312 );
313
314 // Aggregate by category
315 let mut category_stats: HashMap<String, (Vec<u32>, f64, std::collections::HashSet<u32>)> =
316 HashMap::new();
317
318 for (order, product) in &order_products {
319 let entry = category_stats
320 .entry(product.category.clone())
321 .or_insert_with(|| (Vec::new(), 0.0, std::collections::HashSet::new()));
322 entry.0.push(order.id);
323 entry.1 += order.total;
324 entry.2.insert(order.user_id);
325 }
326
327 let mut category_sales: Vec<CategorySales> = category_stats
328 .into_iter()
329 .map(|(category, (orders, revenue, customers))| CategorySales {
330 category,
331 total_orders: orders.len(),
332 total_revenue: revenue,
333 unique_customers: customers.len(),
334 })
335 .collect();
336
337 category_sales.sort_by(|a, b| {
338 b.total_revenue
339 .partial_cmp(&a.total_revenue)
340 .unwrap_or(std::cmp::Ordering::Equal)
341 });
342
343 for cs in &category_sales {
344 println!(
345 " • {}: {} orders - ${:.2} revenue - {} customers",
346 cs.category, cs.total_orders, cs.total_revenue, cs.unique_customers
347 );
348 }
349
350 // Join 5: Filtered join - High value orders
351 println!("\n--- Join 5: High Value Orders (>$100) with User Details ---");
352 let high_value_orders = JoinQuery::new(&orders, &users).inner_join_where(
353 Order::user_id_r(),
354 User::id_r(),
355 |order, _user| order.total > 100.0,
356 |order, user| (user.name.clone(), order.id, order.total),
357 );
358
359 for (name, order_id, total) in &high_value_orders {
360 println!(" • {} - Order #{} - ${:.2}", name, order_id, total);
361 }
362
363 // Join 6: Users in same city analysis
364 println!("\n--- Join 6: Users from Same City ---");
365 let user_pairs = JoinQuery::new(&users, &users).inner_join_where(
366 User::city_r(),
367 User::city_r(),
368 |u1, u2| u1.id < u2.id, // Avoid duplicates and self-pairs
369 |u1, u2| (u1.name.clone(), u2.name.clone(), u1.city.clone()),
370 );
371
372 for (name1, name2, city) in &user_pairs {
373 println!(" • {} and {} both live in {}", name1, name2, city);
374 }
375
376 // Join 7: Product popularity
377 println!("\n--- Join 7: Product Popularity Ranking ---");
378
379 // Join orders with products
380 let product_order_pairs = JoinQuery::new(&products, &orders).inner_join(
381 Product::id_r(),
382 Order::product_id_r(),
383 |product, order| (product.clone(), order.clone()),
384 );
385
386 // Aggregate by product
387 let mut product_sales: HashMap<u32, (String, usize, u32, f64)> = HashMap::new();
388 for (product, order) in &product_order_pairs {
389 let entry = product_sales
390 .entry(product.id)
391 .or_insert_with(|| (product.name.clone(), 0, 0, 0.0));
392 entry.1 += 1; // order count
393 entry.2 += order.quantity; // total quantity
394 entry.3 += order.total; // total revenue
395 }
396
397 let mut popularity: Vec<_> = product_sales.into_iter().collect();
398 popularity.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); // sort by order count
399
400 for (_, (name, order_count, total_qty, revenue)) in &popularity {
401 println!(
402 " • {} - {} orders - {} units - ${:.2}",
403 name, order_count, total_qty, revenue
404 );
405 }
406
407 // Join 8: User spending by city
408 println!("\n--- Join 8: Total Spending by City ---");
409
410 // Join users with orders to get city and spending info
411 let user_city_orders = JoinQuery::new(&users, &orders).inner_join(
412 User::id_r(),
413 Order::user_id_r(),
414 |user, order| (user.city.clone(), order.total, user.id),
415 );
416
417 // Aggregate by city
418 let mut city_spending: HashMap<String, (f64, std::collections::HashSet<u32>)> = HashMap::new();
419 for (city, total, user_id) in &user_city_orders {
420 let entry = city_spending
421 .entry(city.clone())
422 .or_insert_with(|| (0.0, std::collections::HashSet::new()));
423 entry.0 += total;
424 entry.1.insert(*user_id);
425 }
426
427 let mut city_stats: Vec<_> = city_spending
428 .into_iter()
429 .map(|(city, (total, customers))| (city, total, customers.len()))
430 .collect();
431
432 city_stats.sort_by(|a, b| {
433 b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
434 });
435
436 for (city, total, customer_count) in &city_stats {
437 println!(
438 " • {} - ${:.2} total - {} customers - ${:.2} avg",
439 city,
440 total,
441 customer_count,
442 total / *customer_count as f64
443 );
444 }
445
446 // Statistics summary
447 println!("\n=== Summary Statistics ===");
448 println!("Total orders: {}", orders.len());
449
450 let total_revenue: f64 = orders.iter().map(|o| o.total).sum();
451 println!("Total revenue: ${:.2}", total_revenue);
452 println!("Average order value: ${:.2}", total_revenue / orders.len() as f64);
453
454 // Count unique customers using a join
455 let unique_customers: std::collections::HashSet<u32> =
456 orders.iter().map(|o| o.user_id).collect();
457 println!("Active customers: {}", unique_customers.len());
458 println!(
459 "Average orders per customer: {:.1}",
460 orders.len() as f64 / unique_customers.len() as f64
461 );
462
463 println!("\n✓ Join query builder demo complete!");
464}84fn main() {
85 let employees = create_employees();
86 let departments = create_departments();
87 let projects = create_projects();
88
89 println!("\n╔════════════════════════════════════════════════════════════════════════╗");
90 println!("║ SQL vs Rust Query Builder Comparison ║");
91 println!("║ Demonstrating equivalent operations ║");
92 println!("╚════════════════════════════════════════════════════════════════════════╝");
93
94 // ============================================================================
95 // EXAMPLE 1: Simple SELECT with WHERE
96 // ============================================================================
97 print_separator("Example 1: SELECT with WHERE clause");
98 print_query(
99 "SELECT * FROM employees WHERE department = 'Engineering';",
100 "Filter employees by department"
101 );
102
103 let eng_query = Query::new(&employees)
104 .where_(Employee::department_r(), |dept| dept == "Engineering");
105 let engineering_employees = eng_query.all();
106
107 for emp in &engineering_employees {
108 println!(" ID: {}, Name: {}, Salary: ${:.2}", emp.id, emp.name, emp.salary);
109 }
110 println!("\nRust Query Builder:");
111 println!("Query::new(&employees)");
112 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\")");
113 println!(" .all()");
114 println!("✓ Found {} employees", engineering_employees.len());
115
116 // ============================================================================
117 // EXAMPLE 2: SELECT specific columns
118 // ============================================================================
119 print_separator("Example 2: SELECT specific columns (Projection)");
120 print_query(
121 "SELECT name FROM employees WHERE salary > 80000;",
122 "Get names of high-earning employees"
123 );
124
125 let high_earners = Query::new(&employees)
126 .where_(Employee::salary_r(), |&salary| salary > 80000.0)
127 .select(Employee::name_r());
128
129 for name in &high_earners {
130 println!(" {}", name);
131 }
132 println!("\nRust Query Builder:");
133 println!("Query::new(&employees)");
134 println!(" .where_(Employee::salary_r(), |&salary| salary > 80000.0)");
135 println!(" .select(Employee::name_r())");
136 println!("✓ Found {} employees", high_earners.len());
137
138 // ============================================================================
139 // EXAMPLE 3: COUNT
140 // ============================================================================
141 print_separator("Example 3: COUNT aggregation");
142 print_query(
143 "SELECT COUNT(*) FROM employees WHERE age < 30;",
144 "Count young employees"
145 );
146
147 let young_count = Query::new(&employees)
148 .where_(Employee::age_r(), |&age| age < 30)
149 .count();
150
151 println!(" Count: {}", young_count);
152 println!("\nRust Query Builder:");
153 println!("Query::new(&employees)");
154 println!(" .where_(Employee::age_r(), |&age| age < 30)");
155 println!(" .count()");
156 println!("✓ Result: {}", young_count);
157
158 // ============================================================================
159 // EXAMPLE 4: SUM, AVG, MIN, MAX
160 // ============================================================================
161 print_separator("Example 4: Aggregate functions (SUM, AVG, MIN, MAX)");
162 print_query(
163 "SELECT \n\
164 SUM(salary) as total_salary,\n\
165 AVG(salary) as avg_salary,\n\
166 MIN(salary) as min_salary,\n\
167 MAX(salary) as max_salary\n\
168 FROM employees WHERE department = 'Engineering';",
169 "Engineering department salary statistics"
170 );
171
172 let eng_query = Query::new(&employees)
173 .where_(Employee::department_r(), |dept| dept == "Engineering");
174
175 let total = eng_query.sum(Employee::salary_r());
176 let avg = eng_query.avg(Employee::salary_r()).unwrap_or(0.0);
177 let min = eng_query.min_float(Employee::salary_r()).unwrap_or(0.0);
178 let max = eng_query.max_float(Employee::salary_r()).unwrap_or(0.0);
179
180 println!(" Total Salary: ${:.2}", total);
181 println!(" Avg Salary: ${:.2}", avg);
182 println!(" Min Salary: ${:.2}", min);
183 println!(" Max Salary: ${:.2}", max);
184
185 println!("\nRust Query Builder:");
186 println!("let query = Query::new(&employees)");
187 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\");");
188 println!("query.sum(Employee::salary_r()) // ${:.2}", total);
189 println!("query.avg(Employee::salary_r()) // ${:.2}", avg);
190 println!("query.min_float(Employee::salary_r()) // ${:.2}", min);
191 println!("query.max_float(Employee::salary_r()) // ${:.2}", max);
192
193 // ============================================================================
194 // EXAMPLE 5: GROUP BY with aggregation
195 // ============================================================================
196 print_separator("Example 5: GROUP BY with aggregation");
197 print_query(
198 "SELECT \n\
199 department,\n\
200 COUNT(*) as emp_count,\n\
201 AVG(salary) as avg_salary\n\
202 FROM employees\n\
203 GROUP BY department;",
204 "Statistics by department"
205 );
206
207 let by_dept = Query::new(&employees).group_by(Employee::department_r());
208
209 for (dept, emps) in &by_dept {
210 let dept_query = Query::new(emps);
211 let count = emps.len();
212 let avg_sal = dept_query.avg(Employee::salary_r()).unwrap_or(0.0);
213 println!(" {}: {} employees, avg salary ${:.2}", dept, count, avg_sal);
214 }
215
216 println!("\nRust Query Builder:");
217 println!("let by_dept = Query::new(&employees).group_by(Employee::department_r());");
218 println!("for (dept, emps) in &by_dept {{");
219 println!(" let dept_query = Query::new(emps);");
220 println!(" dept_query.avg(Employee::salary_r())");
221 println!("}}");
222
223 // ============================================================================
224 // EXAMPLE 6: ORDER BY
225 // ============================================================================
226 print_separator("Example 6: ORDER BY");
227 print_query(
228 "SELECT name, salary FROM employees\n\
229 WHERE department = 'Sales'\n\
230 ORDER BY salary DESC;",
231 "Sales employees ordered by salary (descending)"
232 );
233
234 let sales_sorted = Query::new(&employees)
235 .where_(Employee::department_r(), |dept| dept == "Sales")
236 .order_by_float_desc(Employee::salary_r());
237
238 for emp in &sales_sorted {
239 println!(" {}: ${:.2}", emp.name, emp.salary);
240 }
241
242 println!("\nRust Query Builder:");
243 println!("Query::new(&employees)");
244 println!(" .where_(Employee::department_r(), |dept| dept == \"Sales\")");
245 println!(" .order_by_float_desc(Employee::salary_r())");
246
247 // ============================================================================
248 // EXAMPLE 7: Multiple WHERE conditions (AND)
249 // ============================================================================
250 print_separator("Example 7: Multiple WHERE conditions (AND)");
251 print_query(
252 "SELECT * FROM employees\n\
253 WHERE salary > 70000 AND age < 35;",
254 "High-earning young employees"
255 );
256
257 let filter_query = Query::new(&employees)
258 .where_(Employee::salary_r(), |&salary| salary > 70000.0)
259 .where_(Employee::age_r(), |&age| age < 35);
260 let filtered = filter_query.all();
261
262 for emp in &filtered {
263 println!(" {}: Age {}, Salary ${:.2}", emp.name, emp.age, emp.salary);
264 }
265
266 println!("\nRust Query Builder:");
267 println!("Query::new(&employees)");
268 println!(" .where_(Employee::salary_r(), |&salary| salary > 70000.0)");
269 println!(" .where_(Employee::age_r(), |&age| age < 35)");
270 println!(" .all()");
271 println!("✓ Found {} employees", filtered.len());
272
273 // ============================================================================
274 // EXAMPLE 8: LIMIT (TOP N)
275 // ============================================================================
276 print_separator("Example 8: LIMIT / TOP N");
277 print_query(
278 "SELECT TOP 3 name, salary FROM employees\n\
279 ORDER BY salary DESC;",
280 "Top 3 highest paid employees"
281 );
282
283 let top_earners = Query::new(&employees)
284 .order_by_float_desc(Employee::salary_r());
285
286 for (i, emp) in top_earners.iter().take(3).enumerate() {
287 println!(" {}. {}: ${:.2}", i + 1, emp.name, emp.salary);
288 }
289
290 println!("\nRust Query Builder:");
291 println!("Query::new(&employees)");
292 println!(" .order_by_float_desc(Employee::salary_r())");
293 println!(" .into_iter().take(3)");
294
295 // ============================================================================
296 // EXAMPLE 9: OFFSET and LIMIT (Pagination)
297 // ============================================================================
298 print_separator("Example 9: OFFSET and LIMIT (Pagination)");
299 print_query(
300 "SELECT name FROM employees\n\
301 ORDER BY name\n\
302 LIMIT 3 OFFSET 3;",
303 "Page 2 of employee names (3 per page)"
304 );
305
306 let page_2 = Query::new(&employees)
307 .order_by(Employee::name_r())
308 .into_iter()
309 .skip(3)
310 .take(3)
311 .collect::<Vec<_>>();
312
313 for emp in &page_2 {
314 println!(" {}", emp.name);
315 }
316
317 println!("\nRust Query Builder:");
318 println!("Query::new(&employees)");
319 println!(" .order_by(Employee::name_r())");
320 println!(" .into_iter().skip(3).take(3)");
321
322 // ============================================================================
323 // EXAMPLE 10: INNER JOIN
324 // ============================================================================
325 print_separator("Example 10: INNER JOIN");
326 print_query(
327 "SELECT e.name, d.name as dept_name, d.budget\n\
328 FROM employees e\n\
329 INNER JOIN departments d ON e.department = d.name;",
330 "Employees with their department info"
331 );
332
333 let emp_dept = JoinQuery::new(&employees, &departments).inner_join(
334 Employee::department_r(),
335 Department::name_r(),
336 |emp, dept| (emp.name.clone(), dept.name.clone(), dept.budget),
337 );
338
339 for (emp_name, dept_name, budget) in emp_dept.iter().take(5) {
340 println!(" {} works in {} (Budget: ${:.0})", emp_name, dept_name, budget);
341 }
342 println!(" ... (showing first 5)");
343
344 println!("\nRust Query Builder:");
345 println!("JoinQuery::new(&employees, &departments).inner_join(");
346 println!(" Employee::department_r(),");
347 println!(" Department::name_r(),");
348 println!(" |emp, dept| (emp.name.clone(), dept.name.clone(), dept.budget)");
349 println!(")");
350 println!("✓ Found {} matches", emp_dept.len());
351
352 // ============================================================================
353 // EXAMPLE 11: GROUP BY with HAVING equivalent
354 // ============================================================================
355 print_separator("Example 11: GROUP BY with filtering (HAVING equivalent)");
356 print_query(
357 "SELECT city, COUNT(*) as emp_count, AVG(salary) as avg_salary\n\
358 FROM employees\n\
359 GROUP BY city\n\
360 HAVING COUNT(*) > 1;",
361 "Cities with multiple employees"
362 );
363
364 let by_city = Query::new(&employees).group_by(Employee::city_r());
365 let mut city_stats: Vec<_> = by_city
366 .iter()
367 .filter(|(_, emps)| emps.len() > 1) // HAVING equivalent
368 .map(|(city, emps)| {
369 let avg_sal = Query::new(emps).avg(Employee::salary_r()).unwrap_or(0.0);
370 (city.clone(), emps.len(), avg_sal)
371 })
372 .collect();
373
374 city_stats.sort_by(|a, b| b.1.cmp(&a.1)); // Sort by count
375
376 for (city, count, avg_sal) in &city_stats {
377 println!(" {}: {} employees, avg salary ${:.2}", city, count, avg_sal);
378 }
379
380 println!("\nRust Query Builder:");
381 println!("let by_city = Query::new(&employees).group_by(Employee::city_r());");
382 println!("by_city.iter()");
383 println!(" .filter(|(_, emps)| emps.len() > 1) // HAVING equivalent");
384 println!(" .map(|(city, emps)| {{");
385 println!(" let avg = Query::new(emps).avg(Employee::salary_r());");
386 println!(" (city, emps.len(), avg)");
387 println!(" }})");
388
389 // ============================================================================
390 // EXAMPLE 12: Complex query with multiple operations
391 // ============================================================================
392 print_separator("Example 12: Complex multi-operation query");
393 print_query(
394 "SELECT name, salary, age\n\
395 FROM employees\n\
396 WHERE department IN ('Engineering', 'Sales')\n\
397 AND salary BETWEEN 80000 AND 100000\n\
398 AND age >= 30\n\
399 ORDER BY salary DESC;",
400 "Experienced mid-to-senior level employees in core departments"
401 );
402
403 let complex_query = Query::new(&employees)
404 .where_(Employee::department_r(), |dept| dept == "Engineering" || dept == "Sales")
405 .where_(Employee::salary_r(), |&sal| sal >= 80000.0 && sal <= 100000.0)
406 .where_(Employee::age_r(), |&age| age >= 30)
407 .order_by_float_desc(Employee::salary_r());
408
409 for emp in &complex_query {
410 println!(" {}: Age {}, {} dept, ${:.2}", emp.name, emp.age, emp.department, emp.salary);
411 }
412
413 println!("\nRust Query Builder:");
414 println!("Query::new(&employees)");
415 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\" || dept == \"Sales\")");
416 println!(" .where_(Employee::salary_r(), |&sal| sal >= 80000.0 && sal <= 100000.0)");
417 println!(" .where_(Employee::age_r(), |&age| age >= 30)");
418 println!(" .order_by_float_desc(Employee::salary_r())");
419 println!("✓ Found {} employees", complex_query.len());
420
421 // ============================================================================
422 // EXAMPLE 13: Three-table JOIN
423 // ============================================================================
424 print_separator("Example 13: Three-table JOIN");
425 print_query(
426 "SELECT p.name as project, d.name as department, d.location\n\
427 FROM projects p\n\
428 INNER JOIN departments d ON p.department_id = d.id;",
429 "Projects with their department details"
430 );
431
432 let proj_dept = JoinQuery::new(&projects, &departments).inner_join(
433 Project::department_id_r(),
434 Department::id_r(),
435 |proj, dept| {
436 (proj.name.clone(), dept.name.clone(), dept.location.clone(), proj.budget)
437 },
438 );
439
440 for (proj_name, dept_name, location, budget) in &proj_dept {
441 println!(" {}: {} dept in {} (${:.0})", proj_name, dept_name, location, budget);
442 }
443
444 println!("\nRust Query Builder:");
445 println!("JoinQuery::new(&projects, &departments).inner_join(");
446 println!(" Project::department_id_r(),");
447 println!(" Department::id_r(),");
448 println!(" |proj, dept| (proj.name, dept.name, dept.location, proj.budget)");
449 println!(")");
450
451 // ============================================================================
452 // EXAMPLE 14: Subquery equivalent (using intermediate results)
453 // ============================================================================
454 print_separator("Example 14: Subquery equivalent");
455 print_query(
456 "SELECT * FROM employees\n\
457 WHERE salary > (SELECT AVG(salary) FROM employees);",
458 "Employees earning above average"
459 );
460
461 let avg_salary = Query::new(&employees)
462 .avg(Employee::salary_r())
463 .unwrap_or(0.0);
464
465 let above_avg_query = Query::new(&employees)
466 .where_(Employee::salary_r(), move |&sal| sal > avg_salary);
467 let above_avg = above_avg_query.all();
468
469 println!(" Average salary: ${:.2}", avg_salary);
470 for emp in &above_avg {
471 println!(" {}: ${:.2} ({:.1}% above average)",
472 emp.name, emp.salary, ((emp.salary - avg_salary) / avg_salary * 100.0));
473 }
474
475 println!("\nRust Query Builder:");
476 println!("let avg = Query::new(&employees).avg(Employee::salary_r()).unwrap_or(0.0);");
477 println!("Query::new(&employees)");
478 println!(" .where_(Employee::salary_r(), |&sal| sal > avg)");
479 println!(" .all()");
480 println!("✓ Found {} employees above average", above_avg.len());
481
482 // ============================================================================
483 // EXAMPLE 15: Advanced aggregation (revenue calculation)
484 // ============================================================================
485 print_separator("Example 15: Advanced aggregation");
486 print_query(
487 "SELECT \n\
488 d.name,\n\
489 d.budget as dept_budget,\n\
490 SUM(p.budget) as total_project_budget,\n\
491 (d.budget - SUM(p.budget)) as remaining_budget\n\
492 FROM departments d\n\
493 LEFT JOIN projects p ON d.id = p.department_id\n\
494 GROUP BY d.name, d.budget;",
495 "Department budget utilization"
496 );
497
498 // Join departments with projects
499 let dept_projects = JoinQuery::new(&departments, &projects).left_join(
500 Department::id_r(),
501 Project::department_id_r(),
502 |dept, proj| (dept.clone(), proj.map(|p| p.clone())),
503 );
504
505 // Aggregate by department
506 let mut dept_budget_map: HashMap<String, (f64, Vec<f64>)> = HashMap::new();
507 for (dept, proj) in dept_projects {
508 let entry = dept_budget_map
509 .entry(dept.name.clone())
510 .or_insert((dept.budget, Vec::new()));
511 if let Some(p) = proj {
512 entry.1.push(p.budget);
513 }
514 }
515
516 for (dept_name, (total_budget, project_budgets)) in dept_budget_map.iter() {
517 let used: f64 = project_budgets.iter().sum();
518 let remaining = total_budget - used;
519 println!(" {}: Budget ${:.0}, Used ${:.0}, Remaining ${:.0} ({:.1}% utilized)",
520 dept_name, total_budget, used, remaining, (used / total_budget * 100.0));
521 }
522
523 println!("\nRust Query Builder:");
524 println!("let dept_projects = JoinQuery::new(&departments, &projects)");
525 println!(" .left_join(Department::id_r(), Project::department_id_r(), ...)");
526 println!("// Then aggregate using HashMap");
527
528 // ============================================================================
529 // Summary
530 // ============================================================================
531 print_separator("Summary");
532 println!("\n✓ All 15 SQL queries successfully replicated with Rust Query Builder!");
533 println!("\nDemonstrated equivalents for:");
534 println!(" • SELECT with WHERE");
535 println!(" • Projection (SELECT specific columns)");
536 println!(" • Aggregations (COUNT, SUM, AVG, MIN, MAX)");
537 println!(" • GROUP BY");
538 println!(" • ORDER BY");
539 println!(" • LIMIT and OFFSET");
540 println!(" • INNER JOIN and LEFT JOIN");
541 println!(" • Complex conditions (AND, OR, BETWEEN)");
542 println!(" • Subqueries");
543 println!(" • Multi-table operations");
544 println!("\n🎯 Type-safe, compile-time checked, and zero runtime overhead!");
545 println!();
546}98fn main() {
99 println!("\n╔═══════════════════════════════════════════════════════════════╗");
100 println!("║ Memory Safety Verification ║");
101 println!("║ Proving 'static doesn't cause memory leaks ║");
102 println!("╚═══════════════════════════════════════════════════════════════╝\n");
103
104 println!("🔍 Understanding 'static:\n");
105 println!(" • T: 'static means: Type T doesn't contain non-'static references");
106 println!(" • It does NOT mean: Data lives for entire program");
107 println!(" • It's needed for: Storing types in trait objects");
108 println!(" • Safety: Compiler ensures no dangling references\n");
109
110 // ============================================================================
111 // TEST 1: Basic Query - Verify Cleanup
112 // ============================================================================
113 println!("═══════════════════════════════════════════════════════════════");
114 println!("Test 1: Basic WHERE query - verify all data is dropped");
115 println!("═══════════════════════════════════════════════════════════════\n");
116
117 reset_stats();
118 {
119 let employees = vec![
120 create_employee(1, "Alice", "Engineering", 95000.0),
121 create_employee(2, "Bob", "Engineering", 87000.0),
122 create_employee(3, "Carol", "Sales", 75000.0),
123 ];
124 print_memory_status("After creating employees");
125
126 {
127 let query = Query::new(&employees)
128 .where_(Employee::department_r(), |dept| dept == "Engineering");
129 let results = query.all();
130
131 println!(" Found {} engineering employees", results.len());
132 print_memory_status("During query execution");
133 }
134
135 print_memory_status("After query scope ends");
136 }
137
138 print_memory_status("After employees scope ends");
139 println!();
140
141 // ============================================================================
142 // TEST 2: Multiple Queries - No Accumulation
143 // ============================================================================
144 println!("═══════════════════════════════════════════════════════════════");
145 println!("Test 2: Multiple queries - verify no memory accumulation");
146 println!("═══════════════════════════════════════════════════════════════\n");
147
148 reset_stats();
149 {
150 let employees = vec![
151 create_employee(1, "Alice", "Engineering", 95000.0),
152 create_employee(2, "Bob", "Sales", 75000.0),
153 ];
154
155 let initial_stats = get_stats();
156 println!(" Initial allocations: {}", initial_stats.0);
157
158 // Run 10 queries
159 for i in 1..=10 {
160 let query = Query::new(&employees)
161 .where_(Employee::salary_r(), |&s| s > 70000.0);
162 let _results = query.all();
163
164 if i % 3 == 0 {
165 let (allocs, drops) = get_stats();
166 println!(" After {} queries - Allocated: {}, Dropped: {}", i, allocs, drops);
167 }
168 }
169
170 let final_stats = get_stats();
171 println!("\n After 10 queries:");
172 println!(" Allocations: {} (should be same as initial)", final_stats.0);
173 println!(" ✅ No memory accumulation from queries!");
174 }
175
176 print_memory_status("After all queries and employees dropped");
177 println!();
178
179 // ============================================================================
180 // TEST 3: ORDER BY (requires Clone) - Track Cloning
181 // ============================================================================
182 println!("═══════════════════════════════════════════════════════════════");
183 println!("Test 3: ORDER BY (with Clone) - verify controlled cloning");
184 println!("═══════════════════════════════════════════════════════════════\n");
185
186 reset_stats();
187 {
188 let employees = vec![
189 create_employee(1, "Alice", "Engineering", 95000.0),
190 create_employee(2, "Bob", "Sales", 87000.0),
191 create_employee(3, "Carol", "Marketing", 75000.0),
192 ];
193
194 let (before_allocs, _) = get_stats();
195 println!(" Before sorting: {} allocations", before_allocs);
196
197 {
198 let sorted = Query::new(&employees)
199 .order_by_float_desc(Employee::salary_r());
200
201 let (after_allocs, _) = get_stats();
202 println!(" After sorting: {} allocations", after_allocs);
203 println!(" Cloned items: {} (expected: {})", after_allocs - before_allocs, employees.len());
204 println!(" Sorted {} employees by salary", sorted.len());
205
206 // sorted goes out of scope here
207 }
208
209 print_memory_status("After sorted results dropped");
210 }
211
212 print_memory_status("After employees dropped");
213 println!();
214
215 // ============================================================================
216 // TEST 4: JOIN Operations - No Leaks
217 // ============================================================================
218 println!("═══════════════════════════════════════════════════════════════");
219 println!("Test 4: JOIN operations - verify no memory leaks");
220 println!("═══════════════════════════════════════════════════════════════\n");
221
222 #[derive(Keypaths)]
223 struct Department {
224 id: u32,
225 name: String,
226 drop_tracker: DropTracker,
227 }
228
229 reset_stats();
230 {
231 let employees = vec![
232 create_employee(1, "Alice", "Engineering", 95000.0),
233 create_employee(2, "Bob", "Sales", 87000.0),
234 ];
235
236 let departments = vec![
237 Department {
238 id: 1,
239 name: "Engineering".to_string(),
240 drop_tracker: DropTracker::new(),
241 },
242 Department {
243 id: 2,
244 name: "Sales".to_string(),
245 drop_tracker: DropTracker::new(),
246 },
247 ];
248
249 print_memory_status("After creating data");
250
251 {
252 let results = JoinQuery::new(&employees, &departments)
253 .inner_join(
254 Employee::department_r(),
255 Department::name_r(),
256 |emp, dept| (emp.name.clone(), dept.name.clone()),
257 );
258
259 println!(" Joined {} pairs", results.len());
260 print_memory_status("During join results");
261 }
262
263 print_memory_status("After join results dropped");
264 }
265
266 print_memory_status("After all data dropped");
267 println!();
268
269 // ============================================================================
270 // TEST 5: Large Scale - Memory Behavior
271 // ============================================================================
272 println!("═══════════════════════════════════════════════════════════════");
273 println!("Test 5: Large scale (1000 items) - verify cleanup");
274 println!("═══════════════════════════════════════════════════════════════\n");
275
276 reset_stats();
277 {
278 let mut large_dataset = Vec::new();
279 for i in 0..1000 {
280 large_dataset.push(create_employee(
281 i,
282 &format!("Employee {}", i),
283 if i % 3 == 0 { "Engineering" } else if i % 3 == 1 { "Sales" } else { "Marketing" },
284 50000.0 + (i as f64 * 100.0),
285 ));
286 }
287
288 let (initial_allocs, _) = get_stats();
289 println!(" Created 1000 employees: {} allocations (~10MB)", initial_allocs);
290
291 // Run complex query
292 {
293 let query = Query::new(&large_dataset)
294 .where_(Employee::salary_r(), |&s| s > 80000.0)
295 .where_(Employee::department_r(), |d| d == "Engineering");
296 let results = query.all();
297
298 println!(" Filtered to {} employees", results.len());
299
300 let (during_allocs, _) = get_stats();
301 let extra = during_allocs - initial_allocs;
302 println!(" Extra allocations during query: {} (should be 0)", extra);
303
304 if extra == 0 {
305 println!(" ✅ Zero-copy filtering confirmed!");
306 }
307 }
308
309 print_memory_status("After query results dropped");
310 }
311
312 print_memory_status("After 1000 employees dropped");
313 println!();
314
315 // ============================================================================
316 // TEST 6: Explanation of 'static
317 // ============================================================================
318 println!("═══════════════════════════════════════════════════════════════");
319 println!("Explanation: Why 'static is safe and doesn't leak");
320 println!("═══════════════════════════════════════════════════════════════\n");
321
322 println!("❓ What does T: 'static mean?\n");
323 println!(" WRONG ❌: \"T lives for the entire program\"");
324 println!(" RIGHT ✅: \"T doesn't contain non-'static references\"\n");
325
326 println!("Examples:\n");
327 println!(" struct OwnedData {{ // T: 'static ✅");
328 println!(" id: u32, // Owned data");
329 println!(" name: String, // Owned data");
330 println!(" }}");
331 println!();
332 println!(" struct WithReference<'a> {{ // NOT 'static ❌");
333 println!(" data: &'a String, // Contains reference");
334 println!(" }}");
335 println!();
336
337 println!("Why we use T: 'static:\n");
338 println!(" 1. Store type in trait objects: Box<dyn Fn(&T) -> bool>");
339 println!(" 2. Prevent dangling references in closures");
340 println!(" 3. Ensure type safety at compile time");
341 println!();
342
343 println!("Lifetime of data:\n");
344 println!(" • Data is owned by your Vec<T>");
345 println!(" • Query just borrows &'a [T]");
346 println!(" • When Vec<T> is dropped, all T are dropped");
347 println!(" • No memory leaks possible!\n");
348
349 // ============================================================================
350 // TEST 7: Drop Order Verification
351 // ============================================================================
352 println!("═══════════════════════════════════════════════════════════════");
353 println!("Test 7: Drop order - verify proper RAII");
354 println!("═══════════════════════════════════════════════════════════════\n");
355
356 reset_stats();
357
358 println!("Creating scoped data...");
359 {
360 let employees = vec![
361 create_employee(1, "Alice", "Engineering", 95000.0),
362 create_employee(2, "Bob", "Sales", 87000.0),
363 ];
364 println!(" Created 2 employees");
365
366 {
367 println!(" Creating query...");
368 let query = Query::new(&employees)
369 .where_(Employee::department_r(), |dept| dept == "Engineering");
370
371 {
372 println!(" Executing query...");
373 let results = query.all();
374 println!(" Found {} results", results.len());
375 println!(" Query results going out of scope...");
376 }
377 println!(" Results dropped (just Vec<&Employee>, no Employee drops)");
378
379 println!(" Query going out of scope...");
380 }
381 println!(" Query dropped (just filters, no Employee drops)");
382
383 println!(" Employees vector going out of scope...");
384 }
385 println!(" Employees dropped - NOW Employees are freed!\n");
386
387 let (allocs, drops) = get_stats();
388 println!("Final stats:");
389 println!(" Allocated: {}", allocs);
390 println!(" Dropped: {}", drops);
391 println!(" Leaked: {}", allocs - drops);
392
393 if allocs == drops {
394 println!("\n✅ Perfect! All allocated memory was freed!");
395 } else {
396 println!("\n❌ Memory leak detected!");
397 }
398
399 // ============================================================================
400 // TEST 8: Arc/Rc Compatibility
401 // ============================================================================
402 println!("\n═══════════════════════════════════════════════════════════════");
403 println!("Test 8: Arc/Rc compatibility - shared ownership works");
404 println!("═══════════════════════════════════════════════════════════════\n");
405
406 {
407 use std::sync::Arc;
408
409 #[derive(Keypaths)]
410 struct SharedData {
411 id: u32,
412 value: Arc<String>, // Shared ownership
413 }
414
415 let shared_string = Arc::new("Shared Value".to_string());
416 println!(" Arc strong count: {}", Arc::strong_count(&shared_string));
417
418 let data = vec![
419 SharedData { id: 1, value: Arc::clone(&shared_string) },
420 SharedData { id: 2, value: Arc::clone(&shared_string) },
421 ];
422
423 println!(" Arc strong count after creating data: {}", Arc::strong_count(&shared_string));
424
425 {
426 let query = Query::new(&data)
427 .where_(SharedData::id_r(), |&id| id > 0);
428 let results = query.all();
429 println!(" Found {} items", results.len());
430 println!(" Arc strong count during query: {}", Arc::strong_count(&shared_string));
431 }
432
433 println!(" Arc strong count after query: {}", Arc::strong_count(&shared_string));
434 }
435
436 println!(" ✅ Arc reference counting works correctly!\n");
437
438 // ============================================================================
439 // TEST 9: Large Data Without Clone - Zero Copy
440 // ============================================================================
441 println!("═══════════════════════════════════════════════════════════════");
442 println!("Test 9: Large data without Clone - verify zero-copy");
443 println!("═══════════════════════════════════════════════════════════════\n");
444
445 #[derive(Keypaths)] // NO Clone!
446 struct LargeRecord {
447 id: u32,
448 // Simulate 1MB of data that we DON'T want to clone
449 huge_data: Vec<u8>,
450 }
451
452 {
453 println!(" Creating 10 records (1MB each = 10MB total)...");
454 let large_records: Vec<LargeRecord> = (0..10)
455 .map(|i| LargeRecord {
456 id: i,
457 huge_data: vec![i as u8; 1_000_000], // 1MB each
458 })
459 .collect();
460
461 println!(" Total memory: ~10MB");
462
463 {
464 println!("\n Running query without Clone...");
465 let query = Query::new(&large_records)
466 .where_(LargeRecord::id_r(), |&id| id < 5);
467 let results = query.all(); // Vec<&LargeRecord> - NO CLONING!
468
469 println!(" Found {} records", results.len());
470 println!(" Memory copied: 0 bytes (just references)");
471 println!(" ✅ Zero-copy achieved!");
472 }
473
474 println!("\n Query dropped - no memory freed (no cloning happened)");
475 }
476
477 println!(" Records dropped - 10MB freed\n");
478
479 // ============================================================================
480 // TEST 10: Lifetime Safety
481 // ============================================================================
482 println!("═══════════════════════════════════════════════════════════════");
483 println!("Test 10: Lifetime safety - compiler prevents dangling refs");
484 println!("═══════════════════════════════════════════════════════════════\n");
485
486 println!(" The following code WILL NOT COMPILE (by design):\n");
487 println!(" ```rust");
488 println!(" let query;");
489 println!(" {{");
490 println!(" let data = vec![...];");
491 println!(" query = Query::new(&data); // data borrowed here");
492 println!(" }} // data dropped");
493 println!(" let results = query.all(); // ❌ ERROR: data doesn't live long enough");
494 println!(" ```\n");
495 println!(" ✅ Rust's borrow checker prevents use-after-free!");
496 println!(" ✅ 'static bound + lifetimes = memory safety guaranteed!\n");
497
498 // ============================================================================
499 // Summary
500 // ============================================================================
501 println!("═══════════════════════════════════════════════════════════════");
502 println!("Summary: Memory Safety Guarantees");
503 println!("═══════════════════════════════════════════════════════════════\n");
504
505 let (total_allocs, total_drops) = get_stats();
506 let leaked = total_allocs.saturating_sub(total_drops);
507
508 println!("Overall Statistics:");
509 println!(" Total allocations: {}", total_allocs);
510 println!(" Total drops: {}", total_drops);
511 println!(" Memory leaks: {}", leaked);
512
513 if leaked == 0 {
514 println!("\n🎉 VERIFIED: Zero memory leaks!\n");
515 } else {
516 println!("\n⚠️ WARNING: Potential memory leak detected!\n");
517 }
518
519 println!("Guarantees Verified:");
520 println!(" ✅ 'static doesn't cause data to live forever");
521 println!(" ✅ All allocated memory is properly freed");
522 println!(" ✅ No memory leaks from queries");
523 println!(" ✅ Query only holds references, not ownership");
524 println!(" ✅ Rust's borrow checker prevents dangling references");
525 println!(" ✅ RAII ensures proper cleanup");
526 println!(" ✅ Zero-copy operations don't allocate");
527 println!(" ✅ Clone operations are explicit and controlled\n");
528
529 println!("Performance Benefits:");
530 println!(" ✅ Filtering: 0 bytes copied (v0.2.0) vs 10MB (v0.1.0)");
531 println!(" ✅ Counting: 0 bytes copied");
532 println!(" ✅ Aggregations: 0 bytes copied");
533 println!(" ✅ Only ordering/grouping clone when needed\n");
534
535 println!("Safety Guarantees:");
536 println!(" ✅ Compile-time prevention of dangling references");
537 println!(" ✅ No use-after-free possible");
538 println!(" ✅ No double-free possible");
539 println!(" ✅ Automatic cleanup via RAII\n");
540
541 println!("✓ All memory safety tests PASSED!\n");
542}Sourcepub fn left_join<K, O, F>(
&self,
left_key: KeyPaths<L, K>,
right_key: KeyPaths<R, K>,
mapper: F,
) -> Vec<O>
pub fn left_join<K, O, F>( &self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F, ) -> Vec<O>
Performs a left join between two collections.
Returns all items from the left collection with optional matching items
from the right collection. If no match is found, the right item is None.
§Arguments
left_key- Key-path to the join field in the left collectionright_key- Key-path to the join field in the right collectionmapper- Function to transform pairs into the result type (right item may be None)
§Example
let results = JoinQuery::new(&users, &orders)
.left_join(
User::id_r(),
Order::user_id_r(),
|user, order| match order {
Some(o) => format!("{} has order {}", user.name, o.id),
None => format!("{} has no orders", user.name),
}
);Examples found in repository?
193fn main() {
194 println!("=== Join Query Builder Demo ===\n");
195
196 let (users, orders, products) = create_sample_data();
197
198 println!("Database:");
199 println!(" Users: {}", users.len());
200 println!(" Orders: {}", orders.len());
201 println!(" Products: {}\n", products.len());
202
203 // Join 1: Inner join Users and Orders
204 println!("--- Join 1: Users with Their Orders ---");
205 let user_orders = JoinQuery::new(&users, &orders).inner_join(
206 User::id_r(),
207 Order::user_id_r(),
208 |user, order| UserOrder {
209 user_name: user.name.clone(),
210 user_email: user.email.clone(),
211 order_id: order.id,
212 quantity: order.quantity,
213 total: order.total,
214 },
215 );
216
217 for uo in &user_orders {
218 println!(
219 " • {} - Order #{} - {} items - ${:.2}",
220 uo.user_name, uo.order_id, uo.quantity, uo.total
221 );
222 }
223 println!("Total: {} user-order pairs", user_orders.len());
224
225 // Join 2: Three-way join (Orders -> Users, Orders -> Products)
226 println!("\n--- Join 2: Complete Order Details (3-Way Join) ---");
227
228 // First join: Orders with Users
229 let orders_with_users = JoinQuery::new(&orders, &users).inner_join(
230 Order::user_id_r(),
231 User::id_r(),
232 |order, user| (order.clone(), user.clone()),
233 );
234
235 // Second join: (Orders+Users) with Products
236 let mut order_details = Vec::new();
237 for (order, user) in &orders_with_users {
238 for product in &products {
239 if order.product_id == product.id {
240 order_details.push(OrderDetail {
241 order_id: order.id,
242 user_name: user.name.clone(),
243 product_name: product.name.clone(),
244 quantity: order.quantity,
245 price: product.price,
246 total: order.total,
247 });
248 }
249 }
250 }
251
252 for od in &order_details {
253 println!(
254 " • Order #{}: {} bought {} x {} @ ${:.2} = ${:.2}",
255 od.order_id, od.user_name, od.quantity, od.product_name, od.price, od.total
256 );
257 }
258
259 // Join 3: Left join to show all users (including those without orders)
260 println!("\n--- Join 3: All Users with Order Count (Left Join) ---");
261
262 // Use left_join to get all users with their orders (or None)
263 let user_order_pairs = JoinQuery::new(&users, &orders).left_join(
264 User::id_r(),
265 Order::user_id_r(),
266 |user, order| (user.clone(), order.map(|o| o.clone())),
267 );
268
269 // Group by user to count orders
270 let mut user_stats: HashMap<u32, (String, String, usize, f64)> = HashMap::new();
271 for (user, order) in &user_order_pairs {
272 let entry = user_stats
273 .entry(user.id)
274 .or_insert_with(|| (user.name.clone(), user.city.clone(), 0, 0.0));
275 if let Some(order) = order {
276 entry.2 += 1; // order count
277 entry.3 += order.total; // total spent
278 }
279 }
280
281 let mut user_order_stats: Vec<_> = user_stats
282 .into_iter()
283 .map(|(_, (name, city, count, total))| UserOrderCount {
284 user_name: name,
285 user_city: city,
286 order_count: count,
287 total_spent: total,
288 })
289 .collect();
290
291 user_order_stats.sort_by(|a, b| a.user_name.cmp(&b.user_name));
292
293 for stat in &user_order_stats {
294 if stat.order_count > 0 {
295 println!(
296 " • {} ({}) - {} orders - ${:.2} total",
297 stat.user_name, stat.user_city, stat.order_count, stat.total_spent
298 );
299 } else {
300 println!(" • {} ({}) - No orders yet", stat.user_name, stat.user_city);
301 }
302 }
303
304 // Join 4: Aggregated join - Category sales analysis
305 println!("\n--- Join 4: Sales by Product Category ---");
306
307 // Join orders with products to get category information
308 let order_products = JoinQuery::new(&orders, &products).inner_join(
309 Order::product_id_r(),
310 Product::id_r(),
311 |order, product| (order.clone(), product.clone()),
312 );
313
314 // Aggregate by category
315 let mut category_stats: HashMap<String, (Vec<u32>, f64, std::collections::HashSet<u32>)> =
316 HashMap::new();
317
318 for (order, product) in &order_products {
319 let entry = category_stats
320 .entry(product.category.clone())
321 .or_insert_with(|| (Vec::new(), 0.0, std::collections::HashSet::new()));
322 entry.0.push(order.id);
323 entry.1 += order.total;
324 entry.2.insert(order.user_id);
325 }
326
327 let mut category_sales: Vec<CategorySales> = category_stats
328 .into_iter()
329 .map(|(category, (orders, revenue, customers))| CategorySales {
330 category,
331 total_orders: orders.len(),
332 total_revenue: revenue,
333 unique_customers: customers.len(),
334 })
335 .collect();
336
337 category_sales.sort_by(|a, b| {
338 b.total_revenue
339 .partial_cmp(&a.total_revenue)
340 .unwrap_or(std::cmp::Ordering::Equal)
341 });
342
343 for cs in &category_sales {
344 println!(
345 " • {}: {} orders - ${:.2} revenue - {} customers",
346 cs.category, cs.total_orders, cs.total_revenue, cs.unique_customers
347 );
348 }
349
350 // Join 5: Filtered join - High value orders
351 println!("\n--- Join 5: High Value Orders (>$100) with User Details ---");
352 let high_value_orders = JoinQuery::new(&orders, &users).inner_join_where(
353 Order::user_id_r(),
354 User::id_r(),
355 |order, _user| order.total > 100.0,
356 |order, user| (user.name.clone(), order.id, order.total),
357 );
358
359 for (name, order_id, total) in &high_value_orders {
360 println!(" • {} - Order #{} - ${:.2}", name, order_id, total);
361 }
362
363 // Join 6: Users in same city analysis
364 println!("\n--- Join 6: Users from Same City ---");
365 let user_pairs = JoinQuery::new(&users, &users).inner_join_where(
366 User::city_r(),
367 User::city_r(),
368 |u1, u2| u1.id < u2.id, // Avoid duplicates and self-pairs
369 |u1, u2| (u1.name.clone(), u2.name.clone(), u1.city.clone()),
370 );
371
372 for (name1, name2, city) in &user_pairs {
373 println!(" • {} and {} both live in {}", name1, name2, city);
374 }
375
376 // Join 7: Product popularity
377 println!("\n--- Join 7: Product Popularity Ranking ---");
378
379 // Join orders with products
380 let product_order_pairs = JoinQuery::new(&products, &orders).inner_join(
381 Product::id_r(),
382 Order::product_id_r(),
383 |product, order| (product.clone(), order.clone()),
384 );
385
386 // Aggregate by product
387 let mut product_sales: HashMap<u32, (String, usize, u32, f64)> = HashMap::new();
388 for (product, order) in &product_order_pairs {
389 let entry = product_sales
390 .entry(product.id)
391 .or_insert_with(|| (product.name.clone(), 0, 0, 0.0));
392 entry.1 += 1; // order count
393 entry.2 += order.quantity; // total quantity
394 entry.3 += order.total; // total revenue
395 }
396
397 let mut popularity: Vec<_> = product_sales.into_iter().collect();
398 popularity.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); // sort by order count
399
400 for (_, (name, order_count, total_qty, revenue)) in &popularity {
401 println!(
402 " • {} - {} orders - {} units - ${:.2}",
403 name, order_count, total_qty, revenue
404 );
405 }
406
407 // Join 8: User spending by city
408 println!("\n--- Join 8: Total Spending by City ---");
409
410 // Join users with orders to get city and spending info
411 let user_city_orders = JoinQuery::new(&users, &orders).inner_join(
412 User::id_r(),
413 Order::user_id_r(),
414 |user, order| (user.city.clone(), order.total, user.id),
415 );
416
417 // Aggregate by city
418 let mut city_spending: HashMap<String, (f64, std::collections::HashSet<u32>)> = HashMap::new();
419 for (city, total, user_id) in &user_city_orders {
420 let entry = city_spending
421 .entry(city.clone())
422 .or_insert_with(|| (0.0, std::collections::HashSet::new()));
423 entry.0 += total;
424 entry.1.insert(*user_id);
425 }
426
427 let mut city_stats: Vec<_> = city_spending
428 .into_iter()
429 .map(|(city, (total, customers))| (city, total, customers.len()))
430 .collect();
431
432 city_stats.sort_by(|a, b| {
433 b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
434 });
435
436 for (city, total, customer_count) in &city_stats {
437 println!(
438 " • {} - ${:.2} total - {} customers - ${:.2} avg",
439 city,
440 total,
441 customer_count,
442 total / *customer_count as f64
443 );
444 }
445
446 // Statistics summary
447 println!("\n=== Summary Statistics ===");
448 println!("Total orders: {}", orders.len());
449
450 let total_revenue: f64 = orders.iter().map(|o| o.total).sum();
451 println!("Total revenue: ${:.2}", total_revenue);
452 println!("Average order value: ${:.2}", total_revenue / orders.len() as f64);
453
454 // Count unique customers using a join
455 let unique_customers: std::collections::HashSet<u32> =
456 orders.iter().map(|o| o.user_id).collect();
457 println!("Active customers: {}", unique_customers.len());
458 println!(
459 "Average orders per customer: {:.1}",
460 orders.len() as f64 / unique_customers.len() as f64
461 );
462
463 println!("\n✓ Join query builder demo complete!");
464}More examples
84fn main() {
85 let employees = create_employees();
86 let departments = create_departments();
87 let projects = create_projects();
88
89 println!("\n╔════════════════════════════════════════════════════════════════════════╗");
90 println!("║ SQL vs Rust Query Builder Comparison ║");
91 println!("║ Demonstrating equivalent operations ║");
92 println!("╚════════════════════════════════════════════════════════════════════════╝");
93
94 // ============================================================================
95 // EXAMPLE 1: Simple SELECT with WHERE
96 // ============================================================================
97 print_separator("Example 1: SELECT with WHERE clause");
98 print_query(
99 "SELECT * FROM employees WHERE department = 'Engineering';",
100 "Filter employees by department"
101 );
102
103 let eng_query = Query::new(&employees)
104 .where_(Employee::department_r(), |dept| dept == "Engineering");
105 let engineering_employees = eng_query.all();
106
107 for emp in &engineering_employees {
108 println!(" ID: {}, Name: {}, Salary: ${:.2}", emp.id, emp.name, emp.salary);
109 }
110 println!("\nRust Query Builder:");
111 println!("Query::new(&employees)");
112 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\")");
113 println!(" .all()");
114 println!("✓ Found {} employees", engineering_employees.len());
115
116 // ============================================================================
117 // EXAMPLE 2: SELECT specific columns
118 // ============================================================================
119 print_separator("Example 2: SELECT specific columns (Projection)");
120 print_query(
121 "SELECT name FROM employees WHERE salary > 80000;",
122 "Get names of high-earning employees"
123 );
124
125 let high_earners = Query::new(&employees)
126 .where_(Employee::salary_r(), |&salary| salary > 80000.0)
127 .select(Employee::name_r());
128
129 for name in &high_earners {
130 println!(" {}", name);
131 }
132 println!("\nRust Query Builder:");
133 println!("Query::new(&employees)");
134 println!(" .where_(Employee::salary_r(), |&salary| salary > 80000.0)");
135 println!(" .select(Employee::name_r())");
136 println!("✓ Found {} employees", high_earners.len());
137
138 // ============================================================================
139 // EXAMPLE 3: COUNT
140 // ============================================================================
141 print_separator("Example 3: COUNT aggregation");
142 print_query(
143 "SELECT COUNT(*) FROM employees WHERE age < 30;",
144 "Count young employees"
145 );
146
147 let young_count = Query::new(&employees)
148 .where_(Employee::age_r(), |&age| age < 30)
149 .count();
150
151 println!(" Count: {}", young_count);
152 println!("\nRust Query Builder:");
153 println!("Query::new(&employees)");
154 println!(" .where_(Employee::age_r(), |&age| age < 30)");
155 println!(" .count()");
156 println!("✓ Result: {}", young_count);
157
158 // ============================================================================
159 // EXAMPLE 4: SUM, AVG, MIN, MAX
160 // ============================================================================
161 print_separator("Example 4: Aggregate functions (SUM, AVG, MIN, MAX)");
162 print_query(
163 "SELECT \n\
164 SUM(salary) as total_salary,\n\
165 AVG(salary) as avg_salary,\n\
166 MIN(salary) as min_salary,\n\
167 MAX(salary) as max_salary\n\
168 FROM employees WHERE department = 'Engineering';",
169 "Engineering department salary statistics"
170 );
171
172 let eng_query = Query::new(&employees)
173 .where_(Employee::department_r(), |dept| dept == "Engineering");
174
175 let total = eng_query.sum(Employee::salary_r());
176 let avg = eng_query.avg(Employee::salary_r()).unwrap_or(0.0);
177 let min = eng_query.min_float(Employee::salary_r()).unwrap_or(0.0);
178 let max = eng_query.max_float(Employee::salary_r()).unwrap_or(0.0);
179
180 println!(" Total Salary: ${:.2}", total);
181 println!(" Avg Salary: ${:.2}", avg);
182 println!(" Min Salary: ${:.2}", min);
183 println!(" Max Salary: ${:.2}", max);
184
185 println!("\nRust Query Builder:");
186 println!("let query = Query::new(&employees)");
187 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\");");
188 println!("query.sum(Employee::salary_r()) // ${:.2}", total);
189 println!("query.avg(Employee::salary_r()) // ${:.2}", avg);
190 println!("query.min_float(Employee::salary_r()) // ${:.2}", min);
191 println!("query.max_float(Employee::salary_r()) // ${:.2}", max);
192
193 // ============================================================================
194 // EXAMPLE 5: GROUP BY with aggregation
195 // ============================================================================
196 print_separator("Example 5: GROUP BY with aggregation");
197 print_query(
198 "SELECT \n\
199 department,\n\
200 COUNT(*) as emp_count,\n\
201 AVG(salary) as avg_salary\n\
202 FROM employees\n\
203 GROUP BY department;",
204 "Statistics by department"
205 );
206
207 let by_dept = Query::new(&employees).group_by(Employee::department_r());
208
209 for (dept, emps) in &by_dept {
210 let dept_query = Query::new(emps);
211 let count = emps.len();
212 let avg_sal = dept_query.avg(Employee::salary_r()).unwrap_or(0.0);
213 println!(" {}: {} employees, avg salary ${:.2}", dept, count, avg_sal);
214 }
215
216 println!("\nRust Query Builder:");
217 println!("let by_dept = Query::new(&employees).group_by(Employee::department_r());");
218 println!("for (dept, emps) in &by_dept {{");
219 println!(" let dept_query = Query::new(emps);");
220 println!(" dept_query.avg(Employee::salary_r())");
221 println!("}}");
222
223 // ============================================================================
224 // EXAMPLE 6: ORDER BY
225 // ============================================================================
226 print_separator("Example 6: ORDER BY");
227 print_query(
228 "SELECT name, salary FROM employees\n\
229 WHERE department = 'Sales'\n\
230 ORDER BY salary DESC;",
231 "Sales employees ordered by salary (descending)"
232 );
233
234 let sales_sorted = Query::new(&employees)
235 .where_(Employee::department_r(), |dept| dept == "Sales")
236 .order_by_float_desc(Employee::salary_r());
237
238 for emp in &sales_sorted {
239 println!(" {}: ${:.2}", emp.name, emp.salary);
240 }
241
242 println!("\nRust Query Builder:");
243 println!("Query::new(&employees)");
244 println!(" .where_(Employee::department_r(), |dept| dept == \"Sales\")");
245 println!(" .order_by_float_desc(Employee::salary_r())");
246
247 // ============================================================================
248 // EXAMPLE 7: Multiple WHERE conditions (AND)
249 // ============================================================================
250 print_separator("Example 7: Multiple WHERE conditions (AND)");
251 print_query(
252 "SELECT * FROM employees\n\
253 WHERE salary > 70000 AND age < 35;",
254 "High-earning young employees"
255 );
256
257 let filter_query = Query::new(&employees)
258 .where_(Employee::salary_r(), |&salary| salary > 70000.0)
259 .where_(Employee::age_r(), |&age| age < 35);
260 let filtered = filter_query.all();
261
262 for emp in &filtered {
263 println!(" {}: Age {}, Salary ${:.2}", emp.name, emp.age, emp.salary);
264 }
265
266 println!("\nRust Query Builder:");
267 println!("Query::new(&employees)");
268 println!(" .where_(Employee::salary_r(), |&salary| salary > 70000.0)");
269 println!(" .where_(Employee::age_r(), |&age| age < 35)");
270 println!(" .all()");
271 println!("✓ Found {} employees", filtered.len());
272
273 // ============================================================================
274 // EXAMPLE 8: LIMIT (TOP N)
275 // ============================================================================
276 print_separator("Example 8: LIMIT / TOP N");
277 print_query(
278 "SELECT TOP 3 name, salary FROM employees\n\
279 ORDER BY salary DESC;",
280 "Top 3 highest paid employees"
281 );
282
283 let top_earners = Query::new(&employees)
284 .order_by_float_desc(Employee::salary_r());
285
286 for (i, emp) in top_earners.iter().take(3).enumerate() {
287 println!(" {}. {}: ${:.2}", i + 1, emp.name, emp.salary);
288 }
289
290 println!("\nRust Query Builder:");
291 println!("Query::new(&employees)");
292 println!(" .order_by_float_desc(Employee::salary_r())");
293 println!(" .into_iter().take(3)");
294
295 // ============================================================================
296 // EXAMPLE 9: OFFSET and LIMIT (Pagination)
297 // ============================================================================
298 print_separator("Example 9: OFFSET and LIMIT (Pagination)");
299 print_query(
300 "SELECT name FROM employees\n\
301 ORDER BY name\n\
302 LIMIT 3 OFFSET 3;",
303 "Page 2 of employee names (3 per page)"
304 );
305
306 let page_2 = Query::new(&employees)
307 .order_by(Employee::name_r())
308 .into_iter()
309 .skip(3)
310 .take(3)
311 .collect::<Vec<_>>();
312
313 for emp in &page_2 {
314 println!(" {}", emp.name);
315 }
316
317 println!("\nRust Query Builder:");
318 println!("Query::new(&employees)");
319 println!(" .order_by(Employee::name_r())");
320 println!(" .into_iter().skip(3).take(3)");
321
322 // ============================================================================
323 // EXAMPLE 10: INNER JOIN
324 // ============================================================================
325 print_separator("Example 10: INNER JOIN");
326 print_query(
327 "SELECT e.name, d.name as dept_name, d.budget\n\
328 FROM employees e\n\
329 INNER JOIN departments d ON e.department = d.name;",
330 "Employees with their department info"
331 );
332
333 let emp_dept = JoinQuery::new(&employees, &departments).inner_join(
334 Employee::department_r(),
335 Department::name_r(),
336 |emp, dept| (emp.name.clone(), dept.name.clone(), dept.budget),
337 );
338
339 for (emp_name, dept_name, budget) in emp_dept.iter().take(5) {
340 println!(" {} works in {} (Budget: ${:.0})", emp_name, dept_name, budget);
341 }
342 println!(" ... (showing first 5)");
343
344 println!("\nRust Query Builder:");
345 println!("JoinQuery::new(&employees, &departments).inner_join(");
346 println!(" Employee::department_r(),");
347 println!(" Department::name_r(),");
348 println!(" |emp, dept| (emp.name.clone(), dept.name.clone(), dept.budget)");
349 println!(")");
350 println!("✓ Found {} matches", emp_dept.len());
351
352 // ============================================================================
353 // EXAMPLE 11: GROUP BY with HAVING equivalent
354 // ============================================================================
355 print_separator("Example 11: GROUP BY with filtering (HAVING equivalent)");
356 print_query(
357 "SELECT city, COUNT(*) as emp_count, AVG(salary) as avg_salary\n\
358 FROM employees\n\
359 GROUP BY city\n\
360 HAVING COUNT(*) > 1;",
361 "Cities with multiple employees"
362 );
363
364 let by_city = Query::new(&employees).group_by(Employee::city_r());
365 let mut city_stats: Vec<_> = by_city
366 .iter()
367 .filter(|(_, emps)| emps.len() > 1) // HAVING equivalent
368 .map(|(city, emps)| {
369 let avg_sal = Query::new(emps).avg(Employee::salary_r()).unwrap_or(0.0);
370 (city.clone(), emps.len(), avg_sal)
371 })
372 .collect();
373
374 city_stats.sort_by(|a, b| b.1.cmp(&a.1)); // Sort by count
375
376 for (city, count, avg_sal) in &city_stats {
377 println!(" {}: {} employees, avg salary ${:.2}", city, count, avg_sal);
378 }
379
380 println!("\nRust Query Builder:");
381 println!("let by_city = Query::new(&employees).group_by(Employee::city_r());");
382 println!("by_city.iter()");
383 println!(" .filter(|(_, emps)| emps.len() > 1) // HAVING equivalent");
384 println!(" .map(|(city, emps)| {{");
385 println!(" let avg = Query::new(emps).avg(Employee::salary_r());");
386 println!(" (city, emps.len(), avg)");
387 println!(" }})");
388
389 // ============================================================================
390 // EXAMPLE 12: Complex query with multiple operations
391 // ============================================================================
392 print_separator("Example 12: Complex multi-operation query");
393 print_query(
394 "SELECT name, salary, age\n\
395 FROM employees\n\
396 WHERE department IN ('Engineering', 'Sales')\n\
397 AND salary BETWEEN 80000 AND 100000\n\
398 AND age >= 30\n\
399 ORDER BY salary DESC;",
400 "Experienced mid-to-senior level employees in core departments"
401 );
402
403 let complex_query = Query::new(&employees)
404 .where_(Employee::department_r(), |dept| dept == "Engineering" || dept == "Sales")
405 .where_(Employee::salary_r(), |&sal| sal >= 80000.0 && sal <= 100000.0)
406 .where_(Employee::age_r(), |&age| age >= 30)
407 .order_by_float_desc(Employee::salary_r());
408
409 for emp in &complex_query {
410 println!(" {}: Age {}, {} dept, ${:.2}", emp.name, emp.age, emp.department, emp.salary);
411 }
412
413 println!("\nRust Query Builder:");
414 println!("Query::new(&employees)");
415 println!(" .where_(Employee::department_r(), |dept| dept == \"Engineering\" || dept == \"Sales\")");
416 println!(" .where_(Employee::salary_r(), |&sal| sal >= 80000.0 && sal <= 100000.0)");
417 println!(" .where_(Employee::age_r(), |&age| age >= 30)");
418 println!(" .order_by_float_desc(Employee::salary_r())");
419 println!("✓ Found {} employees", complex_query.len());
420
421 // ============================================================================
422 // EXAMPLE 13: Three-table JOIN
423 // ============================================================================
424 print_separator("Example 13: Three-table JOIN");
425 print_query(
426 "SELECT p.name as project, d.name as department, d.location\n\
427 FROM projects p\n\
428 INNER JOIN departments d ON p.department_id = d.id;",
429 "Projects with their department details"
430 );
431
432 let proj_dept = JoinQuery::new(&projects, &departments).inner_join(
433 Project::department_id_r(),
434 Department::id_r(),
435 |proj, dept| {
436 (proj.name.clone(), dept.name.clone(), dept.location.clone(), proj.budget)
437 },
438 );
439
440 for (proj_name, dept_name, location, budget) in &proj_dept {
441 println!(" {}: {} dept in {} (${:.0})", proj_name, dept_name, location, budget);
442 }
443
444 println!("\nRust Query Builder:");
445 println!("JoinQuery::new(&projects, &departments).inner_join(");
446 println!(" Project::department_id_r(),");
447 println!(" Department::id_r(),");
448 println!(" |proj, dept| (proj.name, dept.name, dept.location, proj.budget)");
449 println!(")");
450
451 // ============================================================================
452 // EXAMPLE 14: Subquery equivalent (using intermediate results)
453 // ============================================================================
454 print_separator("Example 14: Subquery equivalent");
455 print_query(
456 "SELECT * FROM employees\n\
457 WHERE salary > (SELECT AVG(salary) FROM employees);",
458 "Employees earning above average"
459 );
460
461 let avg_salary = Query::new(&employees)
462 .avg(Employee::salary_r())
463 .unwrap_or(0.0);
464
465 let above_avg_query = Query::new(&employees)
466 .where_(Employee::salary_r(), move |&sal| sal > avg_salary);
467 let above_avg = above_avg_query.all();
468
469 println!(" Average salary: ${:.2}", avg_salary);
470 for emp in &above_avg {
471 println!(" {}: ${:.2} ({:.1}% above average)",
472 emp.name, emp.salary, ((emp.salary - avg_salary) / avg_salary * 100.0));
473 }
474
475 println!("\nRust Query Builder:");
476 println!("let avg = Query::new(&employees).avg(Employee::salary_r()).unwrap_or(0.0);");
477 println!("Query::new(&employees)");
478 println!(" .where_(Employee::salary_r(), |&sal| sal > avg)");
479 println!(" .all()");
480 println!("✓ Found {} employees above average", above_avg.len());
481
482 // ============================================================================
483 // EXAMPLE 15: Advanced aggregation (revenue calculation)
484 // ============================================================================
485 print_separator("Example 15: Advanced aggregation");
486 print_query(
487 "SELECT \n\
488 d.name,\n\
489 d.budget as dept_budget,\n\
490 SUM(p.budget) as total_project_budget,\n\
491 (d.budget - SUM(p.budget)) as remaining_budget\n\
492 FROM departments d\n\
493 LEFT JOIN projects p ON d.id = p.department_id\n\
494 GROUP BY d.name, d.budget;",
495 "Department budget utilization"
496 );
497
498 // Join departments with projects
499 let dept_projects = JoinQuery::new(&departments, &projects).left_join(
500 Department::id_r(),
501 Project::department_id_r(),
502 |dept, proj| (dept.clone(), proj.map(|p| p.clone())),
503 );
504
505 // Aggregate by department
506 let mut dept_budget_map: HashMap<String, (f64, Vec<f64>)> = HashMap::new();
507 for (dept, proj) in dept_projects {
508 let entry = dept_budget_map
509 .entry(dept.name.clone())
510 .or_insert((dept.budget, Vec::new()));
511 if let Some(p) = proj {
512 entry.1.push(p.budget);
513 }
514 }
515
516 for (dept_name, (total_budget, project_budgets)) in dept_budget_map.iter() {
517 let used: f64 = project_budgets.iter().sum();
518 let remaining = total_budget - used;
519 println!(" {}: Budget ${:.0}, Used ${:.0}, Remaining ${:.0} ({:.1}% utilized)",
520 dept_name, total_budget, used, remaining, (used / total_budget * 100.0));
521 }
522
523 println!("\nRust Query Builder:");
524 println!("let dept_projects = JoinQuery::new(&departments, &projects)");
525 println!(" .left_join(Department::id_r(), Project::department_id_r(), ...)");
526 println!("// Then aggregate using HashMap");
527
528 // ============================================================================
529 // Summary
530 // ============================================================================
531 print_separator("Summary");
532 println!("\n✓ All 15 SQL queries successfully replicated with Rust Query Builder!");
533 println!("\nDemonstrated equivalents for:");
534 println!(" • SELECT with WHERE");
535 println!(" • Projection (SELECT specific columns)");
536 println!(" • Aggregations (COUNT, SUM, AVG, MIN, MAX)");
537 println!(" • GROUP BY");
538 println!(" • ORDER BY");
539 println!(" • LIMIT and OFFSET");
540 println!(" • INNER JOIN and LEFT JOIN");
541 println!(" • Complex conditions (AND, OR, BETWEEN)");
542 println!(" • Subqueries");
543 println!(" • Multi-table operations");
544 println!("\n🎯 Type-safe, compile-time checked, and zero runtime overhead!");
545 println!();
546}Sourcepub fn inner_join_where<K, O, F, P>(
&self,
left_key: KeyPaths<L, K>,
right_key: KeyPaths<R, K>,
predicate: P,
mapper: F,
) -> Vec<O>
pub fn inner_join_where<K, O, F, P>( &self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, predicate: P, mapper: F, ) -> Vec<O>
Performs an inner join with an additional filter predicate.
Like inner_join, but only includes pairs that satisfy both the join
condition and the additional predicate.
§Arguments
left_key- Key-path to the join field in the left collectionright_key- Key-path to the join field in the right collectionpredicate- Additional condition that must be true for pairs to be includedmapper- Function to transform matching pairs into the result type
§Example
// Join orders with products, but only high-value orders
let results = JoinQuery::new(&orders, &products)
.inner_join_where(
Order::product_id_r(),
Product::id_r(),
|order, _product| order.total > 100.0,
|order, product| (product.name.clone(), order.total)
);Examples found in repository?
193fn main() {
194 println!("=== Join Query Builder Demo ===\n");
195
196 let (users, orders, products) = create_sample_data();
197
198 println!("Database:");
199 println!(" Users: {}", users.len());
200 println!(" Orders: {}", orders.len());
201 println!(" Products: {}\n", products.len());
202
203 // Join 1: Inner join Users and Orders
204 println!("--- Join 1: Users with Their Orders ---");
205 let user_orders = JoinQuery::new(&users, &orders).inner_join(
206 User::id_r(),
207 Order::user_id_r(),
208 |user, order| UserOrder {
209 user_name: user.name.clone(),
210 user_email: user.email.clone(),
211 order_id: order.id,
212 quantity: order.quantity,
213 total: order.total,
214 },
215 );
216
217 for uo in &user_orders {
218 println!(
219 " • {} - Order #{} - {} items - ${:.2}",
220 uo.user_name, uo.order_id, uo.quantity, uo.total
221 );
222 }
223 println!("Total: {} user-order pairs", user_orders.len());
224
225 // Join 2: Three-way join (Orders -> Users, Orders -> Products)
226 println!("\n--- Join 2: Complete Order Details (3-Way Join) ---");
227
228 // First join: Orders with Users
229 let orders_with_users = JoinQuery::new(&orders, &users).inner_join(
230 Order::user_id_r(),
231 User::id_r(),
232 |order, user| (order.clone(), user.clone()),
233 );
234
235 // Second join: (Orders+Users) with Products
236 let mut order_details = Vec::new();
237 for (order, user) in &orders_with_users {
238 for product in &products {
239 if order.product_id == product.id {
240 order_details.push(OrderDetail {
241 order_id: order.id,
242 user_name: user.name.clone(),
243 product_name: product.name.clone(),
244 quantity: order.quantity,
245 price: product.price,
246 total: order.total,
247 });
248 }
249 }
250 }
251
252 for od in &order_details {
253 println!(
254 " • Order #{}: {} bought {} x {} @ ${:.2} = ${:.2}",
255 od.order_id, od.user_name, od.quantity, od.product_name, od.price, od.total
256 );
257 }
258
259 // Join 3: Left join to show all users (including those without orders)
260 println!("\n--- Join 3: All Users with Order Count (Left Join) ---");
261
262 // Use left_join to get all users with their orders (or None)
263 let user_order_pairs = JoinQuery::new(&users, &orders).left_join(
264 User::id_r(),
265 Order::user_id_r(),
266 |user, order| (user.clone(), order.map(|o| o.clone())),
267 );
268
269 // Group by user to count orders
270 let mut user_stats: HashMap<u32, (String, String, usize, f64)> = HashMap::new();
271 for (user, order) in &user_order_pairs {
272 let entry = user_stats
273 .entry(user.id)
274 .or_insert_with(|| (user.name.clone(), user.city.clone(), 0, 0.0));
275 if let Some(order) = order {
276 entry.2 += 1; // order count
277 entry.3 += order.total; // total spent
278 }
279 }
280
281 let mut user_order_stats: Vec<_> = user_stats
282 .into_iter()
283 .map(|(_, (name, city, count, total))| UserOrderCount {
284 user_name: name,
285 user_city: city,
286 order_count: count,
287 total_spent: total,
288 })
289 .collect();
290
291 user_order_stats.sort_by(|a, b| a.user_name.cmp(&b.user_name));
292
293 for stat in &user_order_stats {
294 if stat.order_count > 0 {
295 println!(
296 " • {} ({}) - {} orders - ${:.2} total",
297 stat.user_name, stat.user_city, stat.order_count, stat.total_spent
298 );
299 } else {
300 println!(" • {} ({}) - No orders yet", stat.user_name, stat.user_city);
301 }
302 }
303
304 // Join 4: Aggregated join - Category sales analysis
305 println!("\n--- Join 4: Sales by Product Category ---");
306
307 // Join orders with products to get category information
308 let order_products = JoinQuery::new(&orders, &products).inner_join(
309 Order::product_id_r(),
310 Product::id_r(),
311 |order, product| (order.clone(), product.clone()),
312 );
313
314 // Aggregate by category
315 let mut category_stats: HashMap<String, (Vec<u32>, f64, std::collections::HashSet<u32>)> =
316 HashMap::new();
317
318 for (order, product) in &order_products {
319 let entry = category_stats
320 .entry(product.category.clone())
321 .or_insert_with(|| (Vec::new(), 0.0, std::collections::HashSet::new()));
322 entry.0.push(order.id);
323 entry.1 += order.total;
324 entry.2.insert(order.user_id);
325 }
326
327 let mut category_sales: Vec<CategorySales> = category_stats
328 .into_iter()
329 .map(|(category, (orders, revenue, customers))| CategorySales {
330 category,
331 total_orders: orders.len(),
332 total_revenue: revenue,
333 unique_customers: customers.len(),
334 })
335 .collect();
336
337 category_sales.sort_by(|a, b| {
338 b.total_revenue
339 .partial_cmp(&a.total_revenue)
340 .unwrap_or(std::cmp::Ordering::Equal)
341 });
342
343 for cs in &category_sales {
344 println!(
345 " • {}: {} orders - ${:.2} revenue - {} customers",
346 cs.category, cs.total_orders, cs.total_revenue, cs.unique_customers
347 );
348 }
349
350 // Join 5: Filtered join - High value orders
351 println!("\n--- Join 5: High Value Orders (>$100) with User Details ---");
352 let high_value_orders = JoinQuery::new(&orders, &users).inner_join_where(
353 Order::user_id_r(),
354 User::id_r(),
355 |order, _user| order.total > 100.0,
356 |order, user| (user.name.clone(), order.id, order.total),
357 );
358
359 for (name, order_id, total) in &high_value_orders {
360 println!(" • {} - Order #{} - ${:.2}", name, order_id, total);
361 }
362
363 // Join 6: Users in same city analysis
364 println!("\n--- Join 6: Users from Same City ---");
365 let user_pairs = JoinQuery::new(&users, &users).inner_join_where(
366 User::city_r(),
367 User::city_r(),
368 |u1, u2| u1.id < u2.id, // Avoid duplicates and self-pairs
369 |u1, u2| (u1.name.clone(), u2.name.clone(), u1.city.clone()),
370 );
371
372 for (name1, name2, city) in &user_pairs {
373 println!(" • {} and {} both live in {}", name1, name2, city);
374 }
375
376 // Join 7: Product popularity
377 println!("\n--- Join 7: Product Popularity Ranking ---");
378
379 // Join orders with products
380 let product_order_pairs = JoinQuery::new(&products, &orders).inner_join(
381 Product::id_r(),
382 Order::product_id_r(),
383 |product, order| (product.clone(), order.clone()),
384 );
385
386 // Aggregate by product
387 let mut product_sales: HashMap<u32, (String, usize, u32, f64)> = HashMap::new();
388 for (product, order) in &product_order_pairs {
389 let entry = product_sales
390 .entry(product.id)
391 .or_insert_with(|| (product.name.clone(), 0, 0, 0.0));
392 entry.1 += 1; // order count
393 entry.2 += order.quantity; // total quantity
394 entry.3 += order.total; // total revenue
395 }
396
397 let mut popularity: Vec<_> = product_sales.into_iter().collect();
398 popularity.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); // sort by order count
399
400 for (_, (name, order_count, total_qty, revenue)) in &popularity {
401 println!(
402 " • {} - {} orders - {} units - ${:.2}",
403 name, order_count, total_qty, revenue
404 );
405 }
406
407 // Join 8: User spending by city
408 println!("\n--- Join 8: Total Spending by City ---");
409
410 // Join users with orders to get city and spending info
411 let user_city_orders = JoinQuery::new(&users, &orders).inner_join(
412 User::id_r(),
413 Order::user_id_r(),
414 |user, order| (user.city.clone(), order.total, user.id),
415 );
416
417 // Aggregate by city
418 let mut city_spending: HashMap<String, (f64, std::collections::HashSet<u32>)> = HashMap::new();
419 for (city, total, user_id) in &user_city_orders {
420 let entry = city_spending
421 .entry(city.clone())
422 .or_insert_with(|| (0.0, std::collections::HashSet::new()));
423 entry.0 += total;
424 entry.1.insert(*user_id);
425 }
426
427 let mut city_stats: Vec<_> = city_spending
428 .into_iter()
429 .map(|(city, (total, customers))| (city, total, customers.len()))
430 .collect();
431
432 city_stats.sort_by(|a, b| {
433 b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
434 });
435
436 for (city, total, customer_count) in &city_stats {
437 println!(
438 " • {} - ${:.2} total - {} customers - ${:.2} avg",
439 city,
440 total,
441 customer_count,
442 total / *customer_count as f64
443 );
444 }
445
446 // Statistics summary
447 println!("\n=== Summary Statistics ===");
448 println!("Total orders: {}", orders.len());
449
450 let total_revenue: f64 = orders.iter().map(|o| o.total).sum();
451 println!("Total revenue: ${:.2}", total_revenue);
452 println!("Average order value: ${:.2}", total_revenue / orders.len() as f64);
453
454 // Count unique customers using a join
455 let unique_customers: std::collections::HashSet<u32> =
456 orders.iter().map(|o| o.user_id).collect();
457 println!("Active customers: {}", unique_customers.len());
458 println!(
459 "Average orders per customer: {:.1}",
460 orders.len() as f64 / unique_customers.len() as f64
461 );
462
463 println!("\n✓ Join query builder demo complete!");
464}Sourcepub fn right_join<K, O, F>(
&self,
left_key: KeyPaths<L, K>,
right_key: KeyPaths<R, K>,
mapper: F,
) -> Vec<O>
pub fn right_join<K, O, F>( &self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F, ) -> Vec<O>
Performs a right join between two collections.
Returns all items from the right collection with optional matching items
from the left collection. If no match is found, the left item is None.
§Arguments
left_key- Key-path to the join field in the left collectionright_key- Key-path to the join field in the right collectionmapper- Function to transform pairs into the result type (left item may be None)
§Example
let results = JoinQuery::new(&users, &orders)
.right_join(
User::id_r(),
Order::user_id_r(),
|user, order| match user {
Some(u) => format!("Order {} by {}", order.id, u.name),
None => format!("Order {} by unknown user", order.id),
}
);Sourcepub fn cross_join<O, F>(&self, mapper: F) -> Vec<O>
pub fn cross_join<O, F>(&self, mapper: F) -> Vec<O>
Performs a cross join (Cartesian product) between two collections.
Returns all possible pairs of items from both collections. Warning: This can produce very large result sets (size = left.len() * right.len()).
§Arguments
mapper- Function to transform pairs into the result type
§Example
let all_combinations = JoinQuery::new(&colors, &sizes)
.cross_join(|color, size| ProductVariant {
color: color.clone(),
size: size.clone(),
});