🦀 Database Overview in TuskLang for Rust
Database Overview in TuskLang for Rust
TuskLang provides a powerful and type-safe database abstraction layer for Rust applications, leveraging Rust's memory safety, zero-cost abstractions, and async/await capabilities to create a revolutionary database experience.
🚀 Why Rust + TuskLang Database Integration?
Rust's ownership system, zero-cost abstractions, and compile-time guarantees make it the perfect language for database operations. TuskLang's Rust integration provides:
- Memory Safety: No null pointer dereferences or data races
- Type Safety: Compile-time validation of database schemas
- Performance: Zero-cost abstractions with native performance
- Async/Await: Non-blocking database operations
- Error Handling: Comprehensive error types with Result<T, E>
Database Connection Configuration
// Database configuration in config.tsk
config: {
database: {
default: "postgres"
connections: {
postgres: {
driver: "postgres"
host: @env("DB_HOST", "localhost")
port: @env("DB_PORT", 5432)
database: @env("DB_DATABASE", "myapp")
username: @env("DB_USERNAME", "postgres")
password: @env("DB_PASSWORD", "")
ssl_mode: "prefer"
max_connections: 10
min_connections: 2
connect_timeout: 30
idle_timeout: 300
}
mysql: {
driver: "mysql"
host: @env("MYSQL_HOST", "localhost")
port: @env("MYSQL_PORT", 3306)
database: @env("MYSQL_DATABASE", "myapp")
username: @env("MYSQL_USERNAME", "root")
password: @env("MYSQL_PASSWORD", "")
charset: "utf8mb4"
collation: "utf8mb4_unicode_ci"
max_connections: 10
}
sqlite: {
driver: "sqlite"
database: @env("SQLITE_DATABASE", "database/database.sqlite")
foreign_key_constraints: true
journal_mode: "WAL"
synchronous: "NORMAL"
}
}
}
}
Basic Database Operations with Rust
use tusk_db::{Database, QueryBuilder, Result};
use serde::{Deserialize, Serialize};// Direct database queries with type safety
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: i32,
name: String,
email: String,
active: bool,
}
// Async database operations
async fn get_users() -> Result<Vec<User>> {
let users = @db.query::<User>(
"SELECT * FROM users WHERE active = ?",
&[&true]
).await?;
Ok(users)
}
// Insert data with type validation
async fn create_user(name: &str, email: &str) -> Result<i32> {
let user_id = @db.insert(
"INSERT INTO users (name, email, active) VALUES (?, ?, ?)",
&[&name, &email, &true]
).await?;
Ok(user_id)
}
// Update data with error handling
async fn update_user_last_login(user_id: i32) -> Result<u64> {
let affected = @db.update(
"UPDATE users SET last_login = ? WHERE id = ?",
&[&chrono::Utc::now(), &user_id]
).await?;
Ok(affected)
}
// Delete data with transaction safety
async fn delete_expired_sessions() -> Result<u64> {
let deleted = @db.delete(
"DELETE FROM sessions WHERE expired_at < ?",
&[&chrono::Utc::now()]
).await?;
Ok(deleted)
}
Advanced Transaction Handling
use tusk_db::{Transaction, DatabaseError};// Complex transaction with rollback on error
async fn process_order(user_id: i32, cart_items: Vec<CartItem>) -> Result<Order> {
let mut tx = @db.begin_transaction().await?;
// Use try-catch pattern for automatic rollback
let result = async {
// Create order
let order_id = tx.insert(
"INSERT INTO orders (user_id, total, status) VALUES (?, ?, ?)",
&[&user_id, &calculate_total(&cart_items), &"pending"]
).await?;
// Add order items
for item in &cart_items {
tx.insert(
"INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)",
&[&order_id, &item.product_id, &item.quantity, &item.price]
).await?;
// Update inventory atomically
tx.update(
"UPDATE inventory SET stock = stock - ? WHERE product_id = ? AND stock >= ?",
&[&item.quantity, &item.product_id, &item.quantity]
).await?;
}
// Update order status
tx.update(
"UPDATE orders SET status = ? WHERE id = ?",
&[&"completed", &order_id]
).await?;
// Fetch the complete order
let order = tx.query_one::<Order>(
"SELECT * FROM orders WHERE id = ?",
&[&order_id]
).await?;
Ok(order)
}.await;
match result {
Ok(order) => {
tx.commit().await?;
Ok(order)
}
Err(e) => {
tx.rollback().await?;
Err(e)
}
}
}
Type-Safe Query Builder
use tusk_db::{QueryBuilder, WhereClause, JoinType};// Fluent query builder with compile-time type checking
async fn get_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))
.order_by("name", "ASC")
.limit(10)
.get()
.await?;
Ok(users)
}
// Complex queries with joins and aggregates
#[derive(Debug, Serialize, Deserialize)]
struct PostWithAuthor {
id: i32,
title: String,
content: String,
author_name: String,
comment_count: i64,
}
async fn get_featured_posts() -> Result<Vec<PostWithAuthor>> {
let posts = @db.table::<PostWithAuthor>("posts")
.select(&["posts.*", "users.name as author_name", "COUNT(comments.id) as comment_count"])
.join("users", "posts.user_id", "=", "users.id")
.left_join("comments", "posts.id", "=", "comments.post_id")
.where_eq("posts.published", true)
.where_in("posts.status", &["active", "featured"])
.where_between("posts.created_at", &[start_date, end_date])
.group_by("posts.id, users.name")
.having("COUNT(comments.id)", ">", 5)
.order_by("posts.created_at", "DESC")
.paginate(20)
.get()
.await?;
Ok(posts)
}
// Aggregates with type safety
async fn get_user_statistics() -> Result<UserStats> {
let stats = @db.table("users")
.select(&[
"COUNT(*) as total_users",
"AVG(age) as avg_age",
"MAX(last_login) as last_activity",
"SUM(CASE WHEN active = true THEN 1 ELSE 0 END) as active_users"
])
.get_one::<UserStats>()
.await?;
Ok(stats)
}
Rust-Specific Model System
use tusk_db::{Model, ModelBuilder, Relationship};
use serde::{Deserialize, Serialize};
use async_trait::async_trait;// Define a model with Rust traits
#[derive(Debug, Serialize, Deserialize, Clone)]
struct User {
pub id: Option<i32>,
pub name: String,
pub email: String,
pub password_hash: String,
pub email_verified_at: Option<chrono::DateTime<Utc>>,
pub is_active: bool,
pub created_at: Option<chrono::DateTime<Utc>>,
pub updated_at: Option<chrono::DateTime<Utc>>,
}
#[async_trait]
impl Model for User {
fn table_name() -> &'static str {
"users"
}
fn fillable_fields() -> &'static [&'static str] {
&["name", "email", "password_hash", "is_active"]
}
fn hidden_fields() -> &'static [&'static str] {
&["password_hash"]
}
// Relationships with type safety
async fn posts(&self) -> Result<Vec<Post>> {
@has_many::<Post>(self.id.unwrap(), "user_id").await
}
async fn profile(&self) -> Result<Option<UserProfile>> {
@has_one::<UserProfile>(self.id.unwrap(), "user_id").await
}
async fn roles(&self) -> Result<Vec<Role>> {
@belongs_to_many::<Role>(self.id.unwrap(), "user_roles", "user_id", "role_id").await
}
// Scopes for query building
fn scope_active(query: QueryBuilder) -> QueryBuilder {
query.where_eq("is_active", true)
}
fn scope_recent(query: QueryBuilder) -> QueryBuilder {
query.where_gt("created_at", chrono::Utc::now() - chrono::Duration::days(30))
}
}
// Using models with Rust patterns
async fn user_operations() -> Result<()> {
// Find user with error handling
let user = match @User::find(1).await {
Ok(user) => user,
Err(DatabaseError::NotFound) => {
eprintln!("User not found");
return Ok(());
}
Err(e) => return Err(e.into()),
};
// Query with scopes
let active_users = @User::query()
.scope_active()
.scope_recent()
.order_by("name")
.get()
.await?;
// Create with validation
let new_user = @User::create(User {
id: None,
name: "Jane Doe".to_string(),
email: "jane@example.com".to_string(),
password_hash: hash_password("secret")?,
email_verified_at: None,
is_active: true,
created_at: None,
updated_at: None,
}).await?;
// Update with optimistic locking
let updated_user = @User::update(new_user.id.unwrap(), |user| {
user.is_active = false;
user
}).await?;
Ok(())
}
Database Migrations with Rust
use tusk_db::{Migration, Schema, ColumnType, IndexType};// Type-safe migration system
#[derive(Debug)]
struct CreateUsersTable;
#[async_trait]
impl Migration for CreateUsersTable {
fn name() -> &'static str {
"create_users_table"
}
async fn up(schema: &Schema) -> Result<()> {
schema.create_table("users", |table| {
table.id("id");
table.string("name", 255).not_null();
table.string("email", 255).unique().not_null();
table.timestamp("email_verified_at").nullable();
table.string("password_hash", 255).not_null();
table.boolean("is_active").default(true);
table.timestamp("created_at").default_current();
table.timestamp("updated_at").default_current();
// Indexes for performance
table.index(&["email"], IndexType::BTree);
table.index(&["created_at"], IndexType::BTree);
table.index(&["is_active", "created_at"], IndexType::BTree);
}).await?;
Ok(())
}
async fn down(schema: &Schema) -> Result<()> {
schema.drop_table_if_exists("users").await?;
Ok(())
}
}
// Migration runner with Rust async patterns
async fn run_migrations() -> Result<()> {
let migrations = vec![
Box::new(CreateUsersTable),
Box::new(CreatePostsTable),
Box::new(CreateCommentsTable),
];
for migration in migrations {
@migrate::run(migration).await?;
}
Ok(())
}
Connection Pooling and Performance
use tusk_db::{ConnectionPool, PoolConfig};
use tokio::sync::Semaphore;// Connection pooling with Rust async patterns
async fn setup_database_pool() -> Result<ConnectionPool> {
let config = PoolConfig {
max_connections: 20,
min_connections: 5,
connect_timeout: std::time::Duration::from_secs(30),
idle_timeout: std::time::Duration::from_secs(300),
max_lifetime: std::time::Duration::from_secs(3600),
};
let pool = @db::create_pool(config).await?;
Ok(pool)
}
// Concurrent database operations
async fn process_users_concurrently(user_ids: Vec<i32>) -> Result<Vec<User>> {
let semaphore = Semaphore::new(10); // Limit concurrent connections
let futures: Vec<_> = user_ids.into_iter().map(|id| {
let sem = semaphore.clone();
async move {
let _permit = sem.acquire().await.unwrap();
@User::find(id).await
}
}).collect();
let results = futures::future::join_all(futures).await;
// Handle results with error aggregation
let mut users = Vec::new();
let mut errors = Vec::new();
for result in results {
match result {
Ok(user) => users.push(user),
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
return Err(DatabaseError::BatchError(errors).into());
}
Ok(users)
}
Error Handling and Recovery
use tusk_db::{DatabaseError, ConnectionError, QueryError};
use thiserror::Error;#[derive(Error, Debug)]
pub enum AppError {
#[error("Database connection failed: {0}")]
Connection(#[from] ConnectionError),
#[error("Query execution failed: {0}")]
Query(#[from] QueryError),
#[error("User not found: {id}")]
UserNotFound { id: i32 },
#[error("Validation failed: {field}")]
Validation { field: String },
}
// Comprehensive error handling
async fn robust_user_operation(user_id: i32) -> Result<User, AppError> {
// Retry logic with exponential backoff
let mut attempts = 0;
let max_attempts = 3;
loop {
match @User::find(user_id).await {
Ok(user) => return Ok(user),
Err(DatabaseError::NotFound) => {
return Err(AppError::UserNotFound { id: user_id });
}
Err(DatabaseError::Connection(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.into()),
}
}
}
Testing Database Operations
use tusk_db::test_utils::{TestDatabase, TestTransaction};
use tokio_test::assert_ok;// Database testing with Rust async testing
#[tokio::test]
async fn test_user_creation() -> Result<()> {
let test_db = @TestDatabase::new().await?;
let mut tx = test_db.begin_transaction().await?;
// Test user creation
let user = @User::create(User {
id: None,
name: "Test User".to_string(),
email: "test@example.com".to_string(),
password_hash: "hashed_password".to_string(),
email_verified_at: None,
is_active: true,
created_at: None,
updated_at: None,
}).await?;
assert!(user.id.is_some());
assert_eq!(user.name, "Test User");
assert_eq!(user.email, "test@example.com");
// Verify in database
let found_user = @User::find(user.id.unwrap()).await?;
assert_eq!(found_user.name, "Test User");
tx.rollback().await?;
Ok(())
}
// Integration tests with real database
#[tokio::test]
#[ignore] // Only run in integration test environment
async fn test_database_integration() -> Result<()> {
let pool = setup_database_pool().await?;
// Test complex operations
let result = process_order(1, vec![
CartItem { product_id: 1, quantity: 2, price: 29.99 },
CartItem { product_id: 2, quantity: 1, price: 49.99 },
]).await?;
assert!(result.id.is_some());
assert_eq!(result.status, "completed");
Ok(())
}
Performance Monitoring and Metrics
use tusk_db::metrics::{QueryMetrics, PerformanceMonitor};
use std::time::Instant;// Performance monitoring with Rust metrics
async fn monitored_query() -> Result<Vec<User>> {
let start = Instant::now();
let users = @db.table::<User>("users")
.where_eq("active", true)
.get()
.await?;
let duration = start.elapsed();
// Record metrics
@metrics::record_query_duration("get_active_users", duration).await;
@metrics::record_query_count("get_active_users", 1).await;
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)
}
Best Practices for Rust Database Integration
1. Use Strong Types: Leverage Rust's type system for compile-time safety
2. Handle Errors Properly: Use Result<T, E>
and proper error types
3. Async/Await: Use non-blocking database operations
4. Connection Pooling: Manage database connections efficiently
5. Transactions: Use transactions for related operations
6. Prepared Statements: Let the ORM handle query preparation
7. Indexing: Add proper database indexes for performance
8. Monitoring: Track query performance and errors
9. Testing: Use test databases and transactions for testing
10. Security: Use parameterized queries (automatic with ORM)
Related Topics
- query-builder-rust
- Fluent query interface for Rust
- orm-models-rust
- Model definition and usage in Rust
- migrations-rust
- Database schema versioning for Rust
- relationships-rust
- Model relationships in Rust
- database-transactions-rust
- Transaction handling in Rust
---
Ready to revolutionize your Rust database operations with TuskLang's type-safe, async-first approach?