A Developer-Friendly, Production-Ready ORM for Rust
Complete documentation for TideORM - a developer-friendly ORM for Rust.
TideORM is a modern, type-safe ORM for Rust inspired by Laravel Eloquent, Rails ActiveRecord, and built on SeaORM. It provides a clean, intuitive API for database operations while maintaining full Rust type safety.
Compile-time guarantees for your database operations. No runtime surprises.
Built on async/await from the ground up. Perfect for high-concurrency apps.
Single API works with PostgreSQL, MySQL, and SQLite.
Fluent query builder with chainable methods. Readable and maintainable.
Full support for SeaORM 2.0 features: CTEs, window functions, and more.
Query logging, validation, file attachments, and i18n built-in.
#[tideorm::model] automatically implements Debug, Clone, Serialize, Deserialize[dependencies]
tideorm = "0.4"
tokio = { version = "1.37", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
Note: TideORM requires tokio for async runtime. Add serde and chrono for serialization and timestamps.
| Feature | Description | Default |
|---|---|---|
postgres | PostgreSQL support | ✓ |
mysql | MySQL/MariaDB support | |
sqlite | SQLite support | |
runtime-tokio | Tokio async runtime | ✓ |
runtime-async-std | async-std runtime |
[dependencies]
tideorm = { version = "0.4", features = ["postgres", "mysql", "sqlite"] }
use tideorm::prelude::*;
#[tokio::main]
async fn main() -> tideorm::Result<()> {
// Simple connection
Database::init("postgres://localhost/mydb").await?;
// Or with configuration
TideConfig::init()
.database("postgres://user:password@localhost/myapp")
.max_connections(20)
.min_connections(5)
.connect()
.await?;
Ok(())
}
use tideorm::prelude::*;
// Auto-derives Debug, Clone, Serialize, Deserialize
#[tideorm::model]
#[tide(table = "users")]
pub struct User {
#[tide(primary_key, auto_increment)]
pub id: i64,
pub name: String,
pub email: String,
pub active: bool,
}
// Create
let user = User {
id: 0,
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
active: true,
};
let user = user.save().await?;
println!("Created user with id: {}", user.id);
// Read
let user = User::find(1).await?;
let users = User::all().await?;
let active_users = User::query()
.where_eq("active", true)
.get()
.await?;
// Update
let mut user = User::find(1).await?.unwrap();
user.name = "Jane Doe".to_string();
let user = user.update().await?;
// Delete
user.delete().await?;
User::destroy(1).await?;
The #[tideorm::model] macro automatically implements Debug, Clone, Default, Serialize, and Deserialize:
#[tideorm::model]
#[tide(table = "products")]
#[index("category")]
#[unique_index("sku")]
pub struct Product {
#[tide(primary_key, auto_increment)]
pub id: i64,
pub name: String,
pub sku: String,
pub category: String,
pub price: i64,
#[tide(nullable)]
pub description: Option<String>,
pub active: bool,
}
Use skip_derives to provide your own implementations:
#[tideorm::model]
#[tide(table = "products", skip_derives)]
pub struct Product {
// ... fields ...
}
impl Debug for Product { /* custom impl */ }
impl Clone for Product { /* custom impl */ }
| Struct-Level | Description |
|---|---|
#[tide(table = "name")] | Custom table name |
#[tide(skip_derives)] | Skip all auto-generated traits |
#[tide(skip_debug)] | Skip Debug impl only |
#[tide(soft_delete)] | Enable soft deletes |
#[index("col")] | Create index |
#[unique_index("col")] | Create unique index |
| Field-Level | Description |
|---|---|
#[tide(primary_key)] | Mark as primary key |
#[tide(auto_increment)] | Auto-increment field |
#[tide(nullable)] | Optional/nullable field |
#[tide(column = "name")] | Custom column name |
#[tide(default = "value")] | Default value |
#[tide(skip)] | Skip field in queries |
use chrono::{DateTime, Utc};
#[tideorm::model]
#[tide(table = "posts")]
pub struct Post {
#[tide(primary_key, auto_increment)]
pub id: i64,
pub title: String,
pub created_at: DateTime<Utc>, // Auto-set on save()
pub updated_at: DateTime<Utc>, // Auto-set on save() and update()
}
// No need to set timestamps manually
let post = Post { title: "Hello".into(), ..Default::default() };
let post = post.save().await?; // Timestamps automatically set
#[tideorm::model]
#[tide(table = "posts", soft_delete)]
pub struct Post {
#[tide(primary_key, auto_increment)]
pub id: i64,
pub title: String,
pub deleted_at: Option<DateTime<Utc>>,
}
// Soft delete (sets deleted_at)
let post = post.soft_delete().await?;
// Restore
let post = post.restore().await?;
// Query active records (default)
let posts = Post::query().get().await?;
// Include soft-deleted
let all = Post::query().with_trashed().get().await?;
// Only soft-deleted
let trashed = Post::query().only_trashed().get().await?;
TideORM supports SeaORM-style relations defined as struct fields. Relations are lazy-loaded on demand.
use tideorm::prelude::*;
#[tideorm::model]
#[tide(table = "users")]
pub struct User {
#[tide(primary_key, auto_increment)]
pub id: i64,
pub name: String,
// One-to-one: User has one Profile
#[tide(has_one = "Profile", foreign_key = "user_id")]
pub profile: HasOne<Profile>,
// One-to-many: User has many Posts
#[tide(has_many = "Post", foreign_key = "user_id")]
pub posts: HasMany<Post>,
}
#[tideorm::model]
#[tide(table = "profiles")]
pub struct Profile {
#[tide(primary_key, auto_increment)]
pub id: i64,
pub user_id: i64,
pub bio: String,
// Inverse: Profile belongs to User
#[tide(belongs_to = "User", foreign_key = "user_id")]
pub user: BelongsTo<User>,
}
// Load HasOne relation
let user = User::find(1).await?.unwrap();
let profile: Option<Profile> = user.profile.load().await?;
// Load HasMany relation
let posts: Vec<Post> = user.posts.load().await?;
// Load BelongsTo relation
let post = Post::find(1).await?.unwrap();
let author: Option<User> = post.author.load().await?;
// Check existence
let has_profile = user.profile.exists().await?; // bool
// Count related records
let post_count = user.posts.count().await?; // u64
// Load posts with custom conditions
let recent_posts = user.posts.load_with(|query| {
query
.where_eq("published", true)
.where_gt("views", 100)
.order_desc("created_at")
.limit(10)
}).await?;
#[tideorm::model]
#[tide(table = "users")]
pub struct User {
#[tide(primary_key, auto_increment)]
pub id: i64,
// Many-to-many through pivot table
#[tide(has_many_through = "Role", pivot = "user_roles",
foreign_key = "user_id", related_key = "role_id")]
pub roles: HasManyThrough<Role, UserRole>,
}
// Usage
let user = User::find(1).await?.unwrap();
let roles = user.roles.load().await?;
// Attach a role
user.roles.attach(role_id).await?;
// Detach a role
user.roles.detach(role_id).await?;
// Sync roles (replace all)
user.roles.sync(vec![
serde_json::json!(1),
serde_json::json!(2),
]).await?;
NEW in v0.4: Support for hierarchical data like org charts or category trees.
#[tideorm::model]
#[tide(table = "employees")]
pub struct Employee {
#[tide(primary_key)]
pub id: i64,
pub name: String,
pub manager_id: Option<i64>,
// Parent reference (manager)
#[tide(self_ref = "id", foreign_key = "manager_id")]
pub manager: SelfRef<Employee>,
// Children reference (direct reports)
#[tide(self_ref_many = "id", foreign_key = "manager_id")]
pub reports: SelfRefMany<Employee>,
}
// Load entire subtree recursively
let tree = emp.reports.load_tree(3).await?; // 3 levels deep
// Equality
User::query().where_eq("status", "active")
User::query().where_not("role", "admin")
// Comparison
User::query().where_gt("age", 18)
User::query().where_gte("age", 18)
User::query().where_lt("age", 65)
User::query().where_lte("age", 65)
// Pattern matching
User::query().where_like("name", "%John%")
User::query().where_not_like("email", "%spam%")
// IN / NOT IN
User::query().where_in("role", vec!["admin", "moderator"])
User::query().where_not_in("status", vec!["banned"])
// NULL checks
User::query().where_null("deleted_at")
User::query().where_not_null("email")
// Range
User::query().where_between("age", 18, 65)
NEW in v0.4: Compile-time type safety for column operations. The compiler catches type mismatches!
use tideorm::columns::Column;
mod user_columns {
use tideorm::columns::Column;
pub const ID: Column<i64> = Column::new("id");
pub const NAME: Column<String> = Column::new("name");
pub const AGE: Column<Option<i32>> = Column::new("age");
pub const ACTIVE: Column<bool> = Column::new("active");
}
use user_columns::*;
// Type-safe queries - compiler catches errors!
let users = User::query()
.where_col(NAME.eq("Alice")) // ✓ String == &str
.where_col(NAME.contains("test")) // ✓ LIKE '%test%'
.where_col(AGE.gt(18)) // ✓ Option<i32> > i32
.where_col(AGE.is_null()) // ✓ Nullable check
.where_col(ACTIVE.eq(true)) // ✓ bool == bool
// .where_col(NAME.eq(123)) // ✗ COMPILE ERROR!
.get()
.await?;
// Ordering
User::query()
.order_by("created_at", Order::Desc)
.order_asc("name")
.order_desc("age")
.latest() // ORDER BY created_at DESC
.oldest() // ORDER BY created_at ASC
.get()
.await?;
// Pagination
User::query()
.limit(10)
.offset(20)
.page(3, 25) // Page 3, 25 per page
.get()
.await?;
// Get all records
let users = User::query().where_eq("active", true).get().await?;
// Get first
let user = User::query()
.where_eq("email", "admin@example.com")
.first()
.await?;
// First or fail
let user = User::query()
.where_eq("id", 1)
.first_or_fail()
.await?;
// Count (efficient SQL COUNT)
let count = User::query()
.where_eq("active", true)
.count()
.await?;
// Check existence
let exists = User::query()
.where_eq("email", "admin@example.com")
.exists()
.await?;
// Bulk delete (single DELETE statement)
let deleted = User::query()
.where_eq("status", "inactive")
.delete()
.await?;
NEW in v0.4: Full support for window functions across all databases!
// ROW_NUMBER - assign sequential numbers
let products = Product::query()
.row_number("row_num", Some("category"), "price", Order::Desc)
.get_raw()
.await?;
// RANK - rank with gaps for ties
let employees = Employee::query()
.rank("salary_rank", Some("department_id"), "salary", Order::Desc)
.get_raw()
.await?;
// DENSE_RANK - rank without gaps
let students = Student::query()
.dense_rank("score_rank", None, "score", Order::Desc)
.get_raw()
.await?;
// Running totals with SUM
let sales = Sale::query()
.running_sum("running_total", "amount", "date", Order::Asc)
.get_raw()
.await?;
// LAG - access previous row value
let orders = Order::query()
.lag("prev_total", "total", 1, Some("0"), "user_id", "created_at", Order::Asc)
.get_raw()
.await?;
// NTILE - distribute into buckets
let products = Product::query()
.ntile("price_quartile", 4, "price", Order::Asc)
.get_raw()
.await?;
NEW in v0.4: Define temporary named result sets for complex queries!
// Simple CTE
let orders = Order::query()
.with_cte(CTE::new(
"high_value_orders",
"SELECT * FROM orders WHERE total > 1000".to_string()
))
.where_raw("id IN (SELECT id FROM high_value_orders)")
.get()
.await?;
// CTE from another query builder
let active_users = User::query()
.where_eq("active", true)
.select(vec!["id", "name", "email"]);
let posts = Post::query()
.with_query("active_users", active_users)
.inner_join("active_users", "posts.user_id", "active_users.id")
.get()
.await?;
// Recursive CTE for hierarchical data
let employees = Employee::query()
.with_recursive_cte(
"org_tree",
vec!["id", "name", "manager_id", "level"],
// Base case: top-level managers
"SELECT id, name, manager_id, 0 FROM employees WHERE manager_id IS NULL",
// Recursive: employees under managers
"SELECT e.id, e.name, e.manager_id, t.level + 1
FROM employees e
INNER JOIN org_tree t ON e.manager_id = t.id"
)
.where_raw("id IN (SELECT id FROM org_tree)")
.get()
.await?;
NEW in v0.4: Combine results from multiple queries!
// UNION - removes duplicates
let users = User::query()
.where_eq("active", true)
.union(User::query().where_eq("role", "admin"))
.get()
.await?;
// UNION ALL - keeps duplicates (faster)
let orders = Order::query()
.where_eq("status", "pending")
.union_all(Order::query().where_eq("status", "processing"))
.union_all(Order::query().where_eq("status", "shipped"))
.order_by("created_at", Order::Desc)
.get()
.await?;
// Raw UNION for complex queries
let results = User::query()
.union_raw("SELECT * FROM archived_users WHERE year = 2023")
.get()
.await?;
NEW in v0.4: Built-in file attachment system for managing file relationships with metadata!
#[tideorm::model]
#[tide(table = "products")]
#[tide(has_one_file = "thumbnail")]
#[tide(has_many_files = "images,documents")]
pub struct Product {
#[tide(primary_key, auto_increment)]
pub id: i64,
pub name: String,
pub files: Option<Json>, // JSONB column storing attachments
}
// Attach files
product.attach("thumbnail", "uploads/thumb.jpg")?;
product.attach_many("images", vec!["img1.jpg", "img2.jpg"])?;
// Attach with metadata
let attachment = FileAttachment::with_metadata(
"uploads/document.pdf",
Some("My Document.pdf"), // Original filename
Some(1024 * 1024), // File size (1MB)
Some("application/pdf"), // MIME type
);
product.attach_with_metadata("documents", attachment)?;
// Add custom metadata
let attachment = FileAttachment::new("uploads/photo.jpg")
.add_metadata("width", 1920)
.add_metadata("height", 1080)
.add_metadata("photographer", "John Doe");
product.attach_with_metadata("images", attachment)?;
// Get files
if let Some(thumb) = product.get_file("thumbnail")? {
println!("Thumbnail: {}", thumb.key);
println!("Filename: {}", thumb.filename);
println!("Size: {:?}", thumb.size);
}
let images = product.get_files("images")?;
for img in images {
println!("Image: {} ({})", img.filename, img.key);
}
// Check if has files
if product.has_files("images")? {
let count = product.count_files("images")?;
println!("Product has {} images", count);
}
// Detach files
product.detach("images", Some("img1.jpg"))?; // Remove specific
product.detach("images", None)?; // Remove all
// Sync files (replace all)
product.sync("images", vec!["new1.jpg", "new2.jpg"])?;
product.update().await?;
NEW in v0.4: Multilingual content support with JSONB storage!
#[tideorm::model]
#[tide(table = "products")]
#[tide(translatable = "name,description")]
pub struct Product {
#[tide(primary_key, auto_increment)]
pub id: i64,
// Default/fallback values
pub name: String,
pub description: String,
pub price: f64,
// JSONB column for translations
pub translations: Option<Json>,
}
// Set individual translation
product.set_translation("name", "ar", "اسم المنتج")?;
product.set_translation("name", "fr", "Nom du produit")?;
product.set_translation("description", "ar", "وصف المنتج")?;
// Set multiple translations at once
let mut names = HashMap::new();
names.insert("en", "Product Name");
names.insert("ar", "اسم المنتج");
names.insert("fr", "Nom du produit");
product.set_translations("name", names)?;
// Get specific translation
if let Some(name) = product.get_translation("name", "ar")? {
println!("Arabic name: {}", name);
}
// Get with fallback chain: requested -> fallback language -> default field value
let name = product.get_translated("name", "ar")?;
// Get all translations for a field
let all_names = product.get_all_translations("name")?;
for (lang, value) in all_names {
println!("{}: {}", lang, value);
}
// Get all translations for a language
let arabic = product.get_translations_for_language("ar")?;
// Returns: {"name": "اسم المنتج", "description": "وصف المنتج"}
// Check translations
if product.has_translation("name", "ar")? {
println!("Arabic name available");
}
let languages = product.available_languages("name")?;
println!("Name available in: {:?}", languages);
// JSON output with translated fields
let mut opts = HashMap::new();
opts.insert("language".to_string(), "ar".to_string());
let json = product.to_translated_json(Some(opts));
// Remove translations
product.remove_translation("name", "fr")?;
product.remove_field_translations("name")?;
product.clear_translations()?;
product.update().await?;
NEW in v0.4: Comprehensive validation system with built-in rules!
use tideorm::validation::{Validator, ValidationRule};
// Available validation rules
ValidationRule::Required // Field must not be empty
ValidationRule::Email // Valid email format
ValidationRule::Url // Valid URL format
ValidationRule::MinLength(n) // Minimum string length
ValidationRule::MaxLength(n) // Maximum string length
ValidationRule::Min(n) // Minimum numeric value
ValidationRule::Max(n) // Maximum numeric value
ValidationRule::Range(min, max) // Numeric range
ValidationRule::Regex(pattern) // Custom regex pattern
ValidationRule::Alpha // Only alphabetic characters
ValidationRule::Alphanumeric // Only alphanumeric characters
ValidationRule::Numeric // Only numeric characters
ValidationRule::Uuid // Valid UUID format
ValidationRule::In(values) // Value must be in list
ValidationRule::NotIn(values) // Value must not be in list
// Create validator
let validator = Validator::new()
.field("email", vec![ValidationRule::Required, ValidationRule::Email])
.field("username", vec![
ValidationRule::Required,
ValidationRule::MinLength(3),
ValidationRule::MaxLength(20),
ValidationRule::Alphanumeric,
])
.field("age", vec![ValidationRule::Range(18.0, 120.0)]);
// Validate data
let mut data = HashMap::new();
data.insert("email".to_string(), "user@example.com".to_string());
data.insert("username".to_string(), "johndoe123".to_string());
data.insert("age".to_string(), "25".to_string());
match validator.validate_map(&data) {
Ok(_) => println!("Validation passed!"),
Err(errors) => {
for (field, message) in errors.errors() {
println!("{}: {}", field, message);
}
}
}
// Custom validation logic
let validator = ValidationBuilder::new()
.add("username", ValidationRule::Required)
.add("username", ValidationRule::MinLength(3))
.custom("username", |value| {
let reserved = ["admin", "root", "system"];
if reserved.contains(&value.to_lowercase().as_str()) {
Err(format!("Username '{}' is reserved", value))
} else {
Ok(())
}
})
.build();
NEW in v0.4: Cross-database full-text search support (PostgreSQL tsvector, MySQL FULLTEXT, SQLite FTS5)!
// Simple full-text search
let results = Article::search(&["title", "content"], "rust programming")
.await?;
// Search with ranking (ordered by relevance)
let ranked = Article::search_ranked(&["title", "content"], "rust async")
.limit(10)
.get_ranked()
.await?;
for result in ranked {
println!("{}: {} (rank: {:.2})",
result.record.id,
result.record.title,
result.rank
);
}
// Count matching results
let count = Article::search(&["title", "content"], "rust")
.count()
.await?;
// Get first matching result
let first = Article::search(&["title"], "rust")
.first()
.await?;
// Search modes
use tideorm::fulltext::SearchMode;
// Natural language search (default)
Article::search(&["content"], "learn rust programming").await?;
// Boolean search with operators
Article::search(&["content"], "+rust +async -javascript")
.mode(SearchMode::Boolean)
.get()
.await?;
// Phrase search (exact phrase matching)
Article::search(&["content"], "async await")
.mode(SearchMode::Phrase)
.get()
.await?;
// Prefix search (for autocomplete)
Article::search(&["title"], "prog")
.mode(SearchMode::Prefix)
.get()
.await?;
// Text highlighting
use tideorm::fulltext::{highlight_text, generate_snippet};
let text = "The quick brown fox jumps over the lazy dog.";
// Highlight search terms
let highlighted = highlight_text(text, "fox lazy", "", "");
// Result: "The quick brown fox jumps over the lazy dog."
// Generate snippet with context
let long_text = "Lorem ipsum... The fox jumped... More text here...";
let snippet = generate_snippet(long_text, "fox", 5, "", "");
// Result: "...dolor sit amet. The fox jumped over the..."
NEW in v0.4: Save parent and related models together with automatic foreign key handling!
// Save parent with single related model
let (user, profile) = user.save_with_one(profile, "user_id").await?;
// profile.user_id is automatically set to user.id
// Save parent with multiple related models
let posts = vec![post1, post2, post3];
let (user, posts) = user.save_with_many(posts, "user_id").await?;
// All posts have user_id set to user.id
// Cascade updates
let (user, profile) = user.update_with_one(profile).await?;
let (user, posts) = user.update_with_many(posts).await?;
// Cascade delete (children first for referential integrity)
let deleted_count = user.delete_with_many(posts).await?;
// Builder API for complex nested saves
let (user, related_json) = NestedSaveBuilder::new(user)
.with_one(profile, "user_id")
.with_many(posts, "user_id")
.with_many(comments, "author_id")
.save()
.await?;
NEW in v0.4: Transform flat JOIN results into nested structures!
use tideorm::prelude::JoinResultConsolidator;
// Flat JOIN results: Vec<(Order, LineItem)>
let flat = Order::query()
.find_also_related::<LineItem>()
.get()
.await?;
// [(order1, item1), (order1, item2), (order2, item3)]
// Consolidate into nested: Vec<(Order, Vec<LineItem>)>
let nested = JoinResultConsolidator::consolidate_two(flat, |o| o.id);
// [(order1, [item1, item2]), (order2, [item3])]
// For LEFT JOINs with Option<B>
let nested = JoinResultConsolidator::consolidate_two_optional(flat, |o| o.id);
// Three-level nesting
let flat3: Vec<(Order, LineItem, Product)> = /* ... */;
let nested3 = JoinResultConsolidator::consolidate_three(flat3, |o| o.id, |i| i.id);
// Vec<(Order, Vec<(LineItem, Vec<Product>)>)>
// Model-centric transactions
User::transaction(|tx| async move {
let user = User::create(User { ... }).await?;
let profile = Profile::create(Profile { user_id: user.id, ... }).await?;
Ok((user, profile))
}).await?;
// Database-level transactions
db.transaction(|tx| async move {
// ... operations ...
Ok(result)
}).await?;
// Insert multiple records at once
let users = vec![
User { id: 0, name: "John".into(), email: "john@example.com".into() },
User { id: 0, name: "Jane".into(), email: "jane@example.com".into() },
];
let inserted = User::insert_all(users).await?;
// Bulk update
let affected = User::update_all()
.set("active", false)
.where_eq("last_login_before", "2024-01-01")
.execute()
.await?;
// Bulk delete
let deleted = User::query()
.where_eq("status", "inactive")
.delete()
.await?;
// Define scope functions
fn active<M: Model>(q: QueryBuilder<M>) -> QueryBuilder<M> {
q.where_eq("active", true)
}
fn recent<M: Model>(q: QueryBuilder<M>) -> QueryBuilder<M> {
q.order_desc("created_at").limit(10)
}
// Apply scopes
let users = User::query()
.scope(active)
.scope(recent)
.get()
.await?;
// Conditional scopes
let include_inactive = false;
let users = User::query()
.when(include_inactive, |q| q.with_trashed())
.get()
.await?;
// Apply scope based on Option value
let status_filter: Option<&str> = Some("active");
let users = User::query()
.when_some(status_filter, |q, status| q.where_eq("status", status))
.get()
.await?;
use tideorm::callbacks::Callbacks;
impl Callbacks for User {
fn before_save(&mut self) -> tideorm::Result<()> {
// Normalize email before saving
self.email = self.email.to_lowercase().trim().to_string();
Ok(())
}
fn after_create(&self) -> tideorm::Result<()> {
println!("User {} created with id {}", self.email, self.id);
// Could send welcome email, create audit log, etc.
Ok(())
}
fn before_delete(&self) -> tideorm::Result<()> {
// Prevent deletion of important accounts
if self.email == "admin@example.com" {
return Err(tideorm::Error::validation("Cannot delete admin account"));
}
Ok(())
}
}
The same code works seamlessly across PostgreSQL, MySQL, and SQLite!
// PostgreSQL
TideConfig::init()
.database("postgres://user:pass@localhost/mydb")
.connect()
.await?;
// MySQL / MariaDB
TideConfig::init()
.database("mysql://user:pass@localhost/mydb")
.connect()
.await?;
// SQLite
TideConfig::init()
.database("sqlite://./data.db?mode=rwc")
.connect()
.await?;
// Feature detection
let db_type = Database::global().backend();
if db_type.supports_json() {
// JSON/JSONB operations available
}
if db_type.supports_window_functions() {
// OVER(), ROW_NUMBER(), etc.
}
if db_type.supports_cte() {
// WITH ... AS (Common Table Expressions)
}
if db_type.supports_fulltext_search() {
// Full-text search capabilities
}
// Execute raw SQL and return model instances
let users: Vec<User> = Database::raw::<User>(
"SELECT * FROM users WHERE age > 18"
).await?;
// With parameters (use $1, $2 for PostgreSQL, ? for MySQL/SQLite)
let users: Vec<User> = Database::raw_with_params::<User>(
"SELECT * FROM users WHERE age > $1 AND status = $2",
vec![18.into(), "active".into()]
).await?;
// Execute statements (INSERT, UPDATE, DELETE)
let affected = Database::execute(
"UPDATE users SET active = false WHERE last_login < NOW() - INTERVAL '1 year'"
).await?;
// Execute with parameters
let affected = Database::execute_with_params(
"DELETE FROM users WHERE status = $1",
vec!["banned".into()]
).await?;
// Enable in development
TIDE_LOG_QUERIES=true cargo run
// Or programmatically
TideConfig::init()
.database(url)
.enable_logging(true)
.connect()
.await?;
⚠️ Warning: Do NOT use sync(true) in production! Use proper migrations instead.
// Development only - auto-sync schema with models
TideConfig::init()
.database("postgres://localhost/mydb")
.sync(true) // Enable auto-sync (development only!)
.connect()
.await?;
// Or export schema to a file
TideConfig::init()
.database("postgres://localhost/mydb")
.schema_file("schema.sql") // Generate SQL file
.connect()
.await?;