JoinQuery

Struct JoinQuery 

Source
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 joined
  • L - The type of items in the left collection
  • R - 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>

Source

pub fn new(left: &'a [L], right: &'a [R]) -> Self

Creates a new join query from two collections.

§Arguments
  • left - The left collection to join
  • right - The right collection to join
§Example
let join = JoinQuery::new(&users, &orders);
Examples found in repository?
examples/doc_examples.rs (line 151)
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
Hide additional examples
examples/join_query_builder.rs (line 205)
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}
examples/sql_comparison.rs (line 333)
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}
Source

pub fn inner_join<K, O, F>( &self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F, ) -> Vec<O>
where K: Eq + Hash + Clone + 'static, F: Fn(&L, &R) -> 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 collection
  • right_key - Key-path to the join field in the right collection
  • mapper - 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?
examples/doc_examples.rs (lines 151-155)
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
Hide additional examples
examples/join_query_builder.rs (lines 205-215)
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}
examples/sql_comparison.rs (lines 333-337)
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}
Source

pub fn left_join<K, O, F>( &self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F, ) -> Vec<O>
where K: Eq + Hash + Clone + 'static, F: Fn(&L, Option<&R>) -> 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 collection
  • right_key - Key-path to the join field in the right collection
  • mapper - 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?
examples/join_query_builder.rs (lines 263-267)
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
Hide additional examples
examples/sql_comparison.rs (lines 499-503)
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}
Source

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>
where K: Eq + Hash + Clone + 'static, F: Fn(&L, &R) -> O, P: Fn(&L, &R) -> bool,

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 collection
  • right_key - Key-path to the join field in the right collection
  • predicate - Additional condition that must be true for pairs to be included
  • mapper - 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?
examples/join_query_builder.rs (lines 352-357)
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}
Source

pub fn right_join<K, O, F>( &self, left_key: KeyPaths<L, K>, right_key: KeyPaths<R, K>, mapper: F, ) -> Vec<O>
where K: Eq + Hash + Clone + 'static, F: Fn(Option<&L>, &R) -> 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 collection
  • right_key - Key-path to the join field in the right collection
  • mapper - 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),
        }
    );
Source

pub fn cross_join<O, F>(&self, mapper: F) -> Vec<O>
where F: Fn(&L, &R) -> 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(),
    });

Auto Trait Implementations§

§

impl<'a, L, R> Freeze for JoinQuery<'a, L, R>

§

impl<'a, L, R> RefUnwindSafe for JoinQuery<'a, L, R>

§

impl<'a, L, R> Send for JoinQuery<'a, L, R>
where L: Sync, R: Sync,

§

impl<'a, L, R> Sync for JoinQuery<'a, L, R>
where L: Sync, R: Sync,

§

impl<'a, L, R> Unpin for JoinQuery<'a, L, R>

§

impl<'a, L, R> UnwindSafe for JoinQuery<'a, L, R>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.