sql_comparison/
sql_comparison.rs

1// Compares SQL queries (HSQLDB-style) with Rust Query Builder
2// This example demonstrates that the Rust query builder produces
3// the same results as equivalent SQL queries would in a database.
4// cargo run --example sql_comparison
5
6use rust_queries_builder::{Query, JoinQuery};
7use key_paths_derive::Keypaths;
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Keypaths)]
11struct Employee {
12    id: u32,
13    name: String,
14    department: String,
15    salary: f64,
16    age: u32,
17    city: String,
18}
19
20#[derive(Debug, Clone, Keypaths)]
21struct Department {
22    id: u32,
23    name: String,
24    budget: f64,
25    location: String,
26}
27
28#[derive(Debug, Clone, Keypaths)]
29struct Project {
30    id: u32,
31    name: String,
32    department_id: u32,
33    budget: f64,
34}
35
36// Sample data
37fn create_employees() -> Vec<Employee> {
38    vec![
39        Employee { id: 1, name: "Alice Johnson".to_string(), department: "Engineering".to_string(), salary: 95000.0, age: 32, city: "San Francisco".to_string() },
40        Employee { id: 2, name: "Bob Smith".to_string(), department: "Engineering".to_string(), salary: 87000.0, age: 28, city: "San Francisco".to_string() },
41        Employee { id: 3, name: "Carol White".to_string(), department: "Sales".to_string(), salary: 75000.0, age: 35, city: "New York".to_string() },
42        Employee { id: 4, name: "David Brown".to_string(), department: "Sales".to_string(), salary: 82000.0, age: 41, city: "New York".to_string() },
43        Employee { id: 5, name: "Eve Davis".to_string(), department: "Marketing".to_string(), salary: 71000.0, age: 29, city: "Chicago".to_string() },
44        Employee { id: 6, name: "Frank Wilson".to_string(), department: "Engineering".to_string(), salary: 105000.0, age: 38, city: "Seattle".to_string() },
45        Employee { id: 7, name: "Grace Lee".to_string(), department: "Marketing".to_string(), salary: 68000.0, age: 26, city: "Chicago".to_string() },
46        Employee { id: 8, name: "Henry Taylor".to_string(), department: "HR".to_string(), salary: 62000.0, age: 33, city: "Boston".to_string() },
47        Employee { id: 9, name: "Iris Moore".to_string(), department: "Engineering".to_string(), salary: 92000.0, age: 30, city: "San Francisco".to_string() },
48        Employee { id: 10, name: "Jack Anderson".to_string(), department: "Sales".to_string(), salary: 79000.0, age: 45, city: "New York".to_string() },
49    ]
50}
51
52fn create_departments() -> Vec<Department> {
53    vec![
54        Department { id: 1, name: "Engineering".to_string(), budget: 1500000.0, location: "San Francisco".to_string() },
55        Department { id: 2, name: "Sales".to_string(), budget: 800000.0, location: "New York".to_string() },
56        Department { id: 3, name: "Marketing".to_string(), budget: 600000.0, location: "Chicago".to_string() },
57        Department { id: 4, name: "HR".to_string(), budget: 400000.0, location: "Boston".to_string() },
58    ]
59}
60
61fn create_projects() -> Vec<Project> {
62    vec![
63        Project { id: 1, name: "Cloud Migration".to_string(), department_id: 1, budget: 250000.0 },
64        Project { id: 2, name: "Mobile App".to_string(), department_id: 1, budget: 180000.0 },
65        Project { id: 3, name: "Q4 Campaign".to_string(), department_id: 2, budget: 120000.0 },
66        Project { id: 4, name: "Brand Refresh".to_string(), department_id: 3, budget: 95000.0 },
67        Project { id: 5, name: "Employee Portal".to_string(), department_id: 4, budget: 65000.0 },
68    ]
69}
70
71fn print_separator(title: &str) {
72    println!("\n{}", "=".repeat(80));
73    println!("  {}", title);
74    println!("{}", "=".repeat(80));
75}
76
77fn print_query(sql: &str, description: &str) {
78    println!("\n--- {} ---", description);
79    println!("SQL Query:");
80    println!("{}", sql);
81    println!("\nResults:");
82}
83
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}
547