🦀 Query Builder in TuskLang for Rust

Rust Documentation

Query Builder in TuskLang for Rust

TuskLang's Rust query builder provides a type-safe, fluent interface for building complex database queries with compile-time guarantees, zero-cost abstractions, and full async/await support.

🚀 Why Rust Query Builder?

Rust's type system and ownership model make it the perfect language for building safe, efficient database queries:

- Compile-Time Safety: Catch query errors before runtime - Type Inference: Automatic type deduction for query results - Memory Safety: No null pointer dereferences or data races - Performance: Zero-cost abstractions with native speed - Async/Await: Non-blocking query execution

Basic Query Building

use tusk_db::{QueryBuilder, Database, Result};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)] struct User { id: i32, name: String, email: String, active: bool, created_at: chrono::DateTime<Utc>, }

// Simple select query async fn get_all_users() -> Result<Vec<User>> { let users = @db.table::<User>("users") .get() .await?; Ok(users) }

// Select specific columns async fn get_user_names() -> Result<Vec<String>> { let names = @db.table("users") .select(&["name"]) .get::<String>() .await?; Ok(names) }

// Where clauses with type safety async fn get_active_users() -> Result<Vec<User>> { let users = @db.table::<User>("users") .where_eq("active", true) .get() .await?; Ok(users) }

// Multiple where conditions async fn get_recent_active_users() -> Result<Vec<User>> { let users = @db.table::<User>("users") .where_eq("active", true) .where_gt("created_at", chrono::Utc::now() - chrono::Duration::days(30)) .get() .await?; Ok(users) }

Advanced Where Clauses

// Complex where conditions with type safety
async fn complex_user_query() -> Result<Vec<User>> {
    let users = @db.table::<User>("users")
        .where_eq("active", true)
        .where_in("id", &[1, 2, 3, 4, 5])
        .where_not_in("email", &["admin@example.com", "test@example.com"])
        .where_between("created_at", &[
            chrono::Utc::now() - chrono::Duration::days(90),
            chrono::Utc::now()
        ])
        .where_like("name", "%John%")
        .where_not_null("email_verified_at")
        .get()
        .await?;
    
    Ok(users)
}

// Raw where clauses for complex conditions async fn raw_where_query() -> Result<Vec<User>> { let users = @db.table::<User>("users") .where_raw("LENGTH(name) > ? AND email LIKE ?", &[&10, &"%@gmail.com"]) .where_raw("created_at > DATE_SUB(NOW(), INTERVAL ? DAY)", &[&30]) .get() .await?; Ok(users) }

// Null handling with Rust's Option type async fn handle_null_values() -> Result<Vec<User>> { let users = @db.table::<User>("users") .where_null("deleted_at") .where_not_null("email") .or_where_null("phone") .get() .await?; Ok(users) }

Joins and Relationships

use tusk_db::{JoinType, JoinClause};

#[derive(Debug, Serialize, Deserialize)] struct Post { id: i32, title: String, content: String, user_id: i32, published: bool, }

#[derive(Debug, Serialize, Deserialize)] struct UserWithPosts { id: i32, name: String, email: String, post_count: i64, latest_post: Option<String>, }

// Inner join with type safety async fn get_users_with_posts() -> Result<Vec<UserWithPosts>> { let users = @db.table::<UserWithPosts>("users") .select(&[ "users.*", "COUNT(posts.id) as post_count", "MAX(posts.title) as latest_post" ]) .join("posts", "users.id", "=", "posts.user_id") .where_eq("posts.published", true) .group_by("users.id") .having("COUNT(posts.id)", ">", 0) .get() .await?; Ok(users) }

// Multiple joins with complex conditions async fn complex_join_query() -> Result<Vec<UserWithPosts>> { let users = @db.table::<UserWithPosts>("users") .select(&[ "users.*", "COUNT(DISTINCT posts.id) as post_count", "COUNT(DISTINCT comments.id) as comment_count" ]) .left_join("posts", "users.id", "=", "posts.user_id") .left_join("comments", "posts.id", "=", "comments.post_id") .where_eq("users.active", true) .where_eq("posts.published", true) .group_by("users.id") .order_by("post_count", "DESC") .limit(10) .get() .await?; Ok(users) }

// Cross join for complex relationships async fn cross_join_example() -> Result<Vec<UserWithPosts>> { let users = @db.table::<UserWithPosts>("users") .cross_join("user_roles") .cross_join("roles") .where_raw("users.id = user_roles.user_id AND user_roles.role_id = roles.id", &[]) .where_eq("roles.name", "admin") .get() .await?; Ok(users) }

Aggregations and Grouping

#[derive(Debug, Serialize, Deserialize)]
struct UserStats {
    total_users: i64,
    active_users: i64,
    avg_age: Option<f64>,
    max_created_at: Option<chrono::DateTime<Utc>>,
}

// Basic aggregations async fn get_user_statistics() -> Result<UserStats> { let stats = @db.table("users") .select(&[ "COUNT(*) as total_users", "SUM(CASE WHEN active = true THEN 1 ELSE 0 END) as active_users", "AVG(age) as avg_age", "MAX(created_at) as max_created_at" ]) .get_one::<UserStats>() .await?; Ok(stats) }

// Grouped aggregations async fn get_users_by_month() -> Result<Vec<UserStats>> { let stats = @db.table("users") .select(&[ "DATE_FORMAT(created_at, '%Y-%m') as month", "COUNT(*) as total_users", "SUM(CASE WHEN active = true THEN 1 ELSE 0 END) as active_users" ]) .group_by("DATE_FORMAT(created_at, '%Y-%m')") .order_by("month", "DESC") .get() .await?; Ok(stats) }

// Having clauses for grouped results async fn get_popular_users() -> Result<Vec<UserWithPosts>> { let users = @db.table::<UserWithPosts>("users") .select(&[ "users.*", "COUNT(posts.id) as post_count" ]) .left_join("posts", "users.id", "=", "posts.user_id") .group_by("users.id") .having("COUNT(posts.id)", ">", 5) .having("COUNT(posts.id)", "<", 100) .order_by("post_count", "DESC") .get() .await?; Ok(users) }

Ordering and Limiting

// Basic ordering
async fn get_users_ordered() -> Result<Vec<User>> {
    let users = @db.table::<User>("users")
        .order_by("name", "ASC")
        .order_by("created_at", "DESC")
        .get()
        .await?;
    
    Ok(users)
}

// Raw ordering for complex expressions async fn complex_ordering() -> Result<Vec<User>> { let users = @db.table::<User>("users") .order_by_raw("CASE WHEN active = true THEN 0 ELSE 1 END") .order_by("name", "ASC") .get() .await?; Ok(users) }

// Limiting and offset for pagination async fn paginated_users(page: i32, per_page: i32) -> Result<Vec<User>> { let offset = (page - 1) * per_page; let users = @db.table::<User>("users") .where_eq("active", true) .order_by("created_at", "DESC") .limit(per_page as usize) .offset(offset as usize) .get() .await?; Ok(users) }

// Cursor-based pagination for large datasets async fn cursor_paginated_users(last_id: Option<i32>, limit: usize) -> Result<Vec<User>> { let mut query = @db.table::<User>("users") .where_eq("active", true) .order_by("id", "ASC") .limit(limit); if let Some(id) = last_id { query = query.where_gt("id", id); } let users = query.get().await?; Ok(users) }

Subqueries and Complex Queries

// Subquery in where clause
async fn users_with_recent_posts() -> Result<Vec<User>> {
    let users = @db.table::<User>("users")
        .where_in("id", |query| {
            query.table("posts")
                .select(&["user_id"])
                .where_gt("created_at", chrono::Utc::now() - chrono::Duration::days(7))
                .group_by("user_id")
        })
        .get()
        .await?;
    
    Ok(users)
}

// EXISTS subquery async fn users_with_posts() -> Result<Vec<User>> { let users = @db.table::<User>("users") .where_exists(|query| { query.table("posts") .where_raw("posts.user_id = users.id", &[]) .where_eq("published", true) }) .get() .await?; Ok(users) }

// UNION queries async fn combined_user_data() -> Result<Vec<User>> { let users = @db.table::<User>("users") .select(&["id", "name", "email", "created_at"]) .where_eq("active", true) .union(|query| { query.table("archived_users") .select(&["id", "name", "email", "created_at"]) .where_eq("restore_eligible", true) }) .order_by("created_at", "DESC") .get() .await?; Ok(users) }

Insert, Update, and Delete Operations

// Insert with query builder
async fn create_user(name: &str, email: &str) -> Result<i32> {
    let user_id = @db.table("users")
        .insert(&[
            ("name", name),
            ("email", email),
            ("active", &true),
            ("created_at", &chrono::Utc::now()),
        ])
        .await?;
    
    Ok(user_id)
}

// Batch insert for performance async fn create_multiple_users(users: Vec<(String, String)>) -> Result<Vec<i32>> { let user_data: Vec<_> = users.into_iter() .map(|(name, email)| { vec![ ("name", name.as_str()), ("email", email.as_str()), ("active", "true"), ("created_at", &chrono::Utc::now().to_rfc3339()), ] }) .collect(); let user_ids = @db.table("users") .insert_batch(&user_data) .await?; Ok(user_ids) }

// Update with conditions async fn update_user_status(user_id: i32, active: bool) -> Result<u64> { let affected = @db.table("users") .where_eq("id", user_id) .update(&[ ("active", &active), ("updated_at", &chrono::Utc::now()), ]) .await?; Ok(affected) }

// Increment/decrement operations async fn increment_user_post_count(user_id: i32) -> Result<u64> { let affected = @db.table("users") .where_eq("id", user_id) .increment("post_count", 1) .await?; Ok(affected) }

// Delete with conditions async fn delete_inactive_users() -> Result<u64> { let deleted = @db.table("users") .where_eq("active", false) .where_lt("last_login", chrono::Utc::now() - chrono::Duration::days(365)) .delete() .await?; Ok(deleted) }

Raw SQL and Custom Queries

// Raw SQL with type safety
async fn complex_raw_query() -> Result<Vec<User>> {
    let users = @db.raw::<User>(
        "SELECT u.*, COUNT(p.id) as post_count 
         FROM users u 
         LEFT JOIN posts p ON u.id = p.user_id 
         WHERE u.active = ? 
         GROUP BY u.id 
         HAVING COUNT(p.id) > ? 
         ORDER BY post_count DESC",
        &[&true, &5]
    ).await?;
    
    Ok(users)
}

// Stored procedures async fn call_stored_procedure(user_id: i32) -> Result<Vec<User>> { let users = @db.raw::<User>( "CALL GetUserWithPosts(?)", &[&user_id] ).await?; Ok(users) }

// Dynamic query building async fn dynamic_query(filters: HashMap<String, String>) -> Result<Vec<User>> { let mut query = @db.table::<User>("users"); for (field, value) in filters { match field.as_str() { "name" => query = query.where_like("name", &format!("%{}%", value)), "email" => query = query.where_like("email", &format!("%{}%", value)), "active" => { if let Ok(active) = value.parse::<bool>() { query = query.where_eq("active", active); } } _ => {} // Ignore unknown filters } } let users = query.get().await?; Ok(users) }

Query Optimization and Performance

// Eager loading to avoid N+1 queries
async fn users_with_posts_eager() -> Result<Vec<User>> {
    let users = @db.table::<User>("users")
        .with(&["posts", "posts.comments"])
        .where_eq("active", true)
        .get()
        .await?;
    
    Ok(users)
}

// Query caching async fn cached_user_query() -> Result<Vec<User>> { let cache_key = "active_users"; let users = @cache::remember(cache_key, 3600, || async { @db.table::<User>("users") .where_eq("active", true) .order_by("name") .get() .await }).await?; Ok(users) }

// Chunk processing for large datasets async fn process_large_user_dataset() -> Result<()> { @db.table::<User>("users") .where_eq("active", true) .chunk(1000, |users| async { for user in users { // Process each user @process_user(user).await?; } Ok::<(), Box<dyn std::error::Error>>(()) }) .await?; Ok(()) }

// Query monitoring and logging async fn monitored_query() -> Result<Vec<User>> { let start = std::time::Instant::now(); let users = @db.table::<User>("users") .where_eq("active", true) .get() .await?; let duration = start.elapsed(); // Log slow queries if duration > std::time::Duration::from_millis(100) { @log::warn!("Slow query detected", { query: "get_active_users", duration: duration.as_millis(), count: users.len() }); } Ok(users) }

Error Handling and Recovery

use tusk_db::{QueryError, DatabaseError};
use thiserror::Error;

#[derive(Error, Debug)] pub enum QueryBuilderError { #[error("Query execution failed: {0}")] Query(#[from] QueryError), #[error("No results found")] NoResults, #[error("Multiple results found when expecting one")] MultipleResults, }

// Robust query execution with error handling async fn safe_user_query(user_id: i32) -> Result<User, QueryBuilderError> { let user = @db.table::<User>("users") .where_eq("id", user_id) .first() .await .map_err(QueryBuilderError::Query)?; user.ok_or(QueryBuilderError::NoResults) }

// Retry logic for transient failures async fn retry_query<F, T>(mut f: F, max_attempts: u32) -> Result<T> where F: FnMut() -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T>> + Send>>, { let mut attempts = 0; loop { match f().await { Ok(result) => return Ok(result), Err(e) if attempts < max_attempts => { attempts += 1; let delay = std::time::Duration::from_secs(2_u64.pow(attempts)); tokio::time::sleep(delay).await; continue; } Err(e) => return Err(e), } } }

Testing Query Builder

use tusk_db::test_utils::{TestDatabase, TestTransaction};

#[tokio::test] async fn test_query_builder() -> Result<()> { let test_db = @TestDatabase::new().await?; let mut tx = test_db.begin_transaction().await?; // Test basic query let users = @db.table::<User>("users") .where_eq("active", true) .get() .await?; assert!(users.is_empty()); // Should be empty in test database // Test insert and query let user_id = @db.table("users") .insert(&[ ("name", "Test User"), ("email", "test@example.com"), ("active", &true), ]) .await?; let user = @db.table::<User>("users") .where_eq("id", user_id) .first() .await? .expect("User should exist"); assert_eq!(user.name, "Test User"); assert_eq!(user.email, "test@example.com"); tx.rollback().await?; Ok(()) }

Best Practices for Rust Query Builder

1. Use Type Safety: Leverage Rust's type system for compile-time validation 2. Handle Errors: Use proper error types and Result handling 3. Async/Await: Use non-blocking operations for better performance 4. Connection Pooling: Let the query builder manage connections 5. Prepared Statements: Use parameterized queries (automatic) 6. Indexing: Ensure proper database indexes for your queries 7. Monitoring: Track query performance and errors 8. Testing: Use test databases and transactions 9. Caching: Cache frequently accessed data 10. Pagination: Use proper pagination for large datasets

Related Topics

- database-overview-rust - Database integration overview - orm-models-rust - Model definition and usage - migrations-rust - Database schema versioning - relationships-rust - Model relationships - database-transactions-rust - Transaction handling

---

Ready to build type-safe, performant database queries with Rust and TuskLang?