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: Clone, R: Clone> JoinQuery<'a, L, R>
impl<'a, L: Clone, R: Clone> 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.
§Arguments
left- The left collection to joinright- The right collection to join
§Example
let join = JoinQuery::new(&users, &orders);Examples found in repository?
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}More examples
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}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?
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}More examples
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}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(),
});