# Rusticx ORM
**Blazingly fast, async-first, multi-database ORM for Rust.**
Rusticx lets you define models with a derive macro, auto-generate tables, and perform full CRUD — synchronously or asynchronously — across PostgreSQL, MySQL/MariaDB, and MongoDB from a single unified API.
---
## Features
- **Multi-database** — PostgreSQL, MySQL, MariaDB, MongoDB (more planned: CockroachDB, Cassandra, SQLite)
- **Async-first** — built on `tokio` + `sqlx` + `mongodb` async drivers
- **Sync support** — `SyncAdapter` wrapper for blocking contexts
- **Derive macro** — `#[derive(Model)]` generates all boilerplate from your struct
- **Type-safe query builder** — composable, no raw strings required for common queries
- **Connection pooling** — built-in via `sqlx` pool for SQL, native pool for MongoDB
- **Schema migration** — `repo.migrate()` creates the table if it doesn't exist
- **Feature-gated backends** — only compile what you use
---
## Installation
```toml
[dependencies]
# Pick your backends:
rusticx = { version = "0.1", features = ["postgres"] } # PostgreSQL only
rusticx = { version = "0.1", features = ["mysql"] } # MySQL / MariaDB only
rusticx = { version = "0.1", features = ["mongo"] } # MongoDB only
rusticx = { version = "0.1", features = ["full"] } # All backends
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
uuid = { version = "1", features = ["v4", "serde"] }
```
---
## Quick Start
### 1. Define a Model
```rust
use rusticx::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Serialize, Deserialize, Model)]
#[rusticx(table = "users")] // optional: defaults to "users" (snake_case plural)
pub struct User {
#[rusticx(primary_key)]
pub id: Uuid,
pub name: String,
#[rusticx(unique)]
pub email: String,
pub age: i32,
pub active: bool,
pub created_at: DateTime<Utc>,
}
```
#### Supported `#[rusticx(...)]` attributes
| `table = "name"` | struct | Override table / collection name |
| `primary_key = "field"` | struct | Override PK field name (default: `id`) |
| `primary_key` | field | Mark this field as primary key |
| `nullable` | field | Allow NULL (auto-detected for `Option<T>`) |
| `unique` | field | Add UNIQUE constraint |
| `default = "expr"` | field | SQL DEFAULT expression |
| `column = "name"` | field | Override column name |
| `skip` | field | Do not persist this field |
---
### 2. Async Usage (recommended)
```rust
use rusticx::prelude::*;
use std::sync::Arc;
use uuid::Uuid;
use chrono::Utc;
#[tokio::main]
async fn main() -> rusticx::Result<()> {
// ── Connect ───────────────────────────────────────────────────────
let adapter = PostgresAdapter::connect_url("postgres://user:pass@localhost/mydb").await?;
let repo: Repository<User, _> = Repository::new(Arc::new(adapter));
// ── Migrate (creates table if not exists) ─────────────────────────
repo.migrate().await?;
// ── Insert ────────────────────────────────────────────────────────
let user = User {
id: Uuid::new_v4(),
name: "Alice".into(),
email: "alice@example.com".into(),
age: 30,
active: true,
created_at: Utc::now(),
};
let inserted = repo.insert(&user).await?;
println!("Inserted: {:?}", inserted);
// ── Insert many ───────────────────────────────────────────────────
let users = vec![
User { id: Uuid::new_v4(), name: "Bob".into(), email: "bob@example.com".into(), age: 25, active: true, created_at: Utc::now() },
User { id: Uuid::new_v4(), name: "Carol".into(), email: "carol@example.com".into(), age: 22, active: false, created_at: Utc::now() },
];
let count = repo.insert_many(&users).await?;
println!("Inserted {count} users");
// ── Find by primary key ───────────────────────────────────────────
let found = repo.find_by_id(inserted.id).await?;
println!("Found: {:?}", found);
// ── Find all ──────────────────────────────────────────────────────
let all = repo.find_all().await?;
println!("Total users: {}", all.len());
// ── Query builder ─────────────────────────────────────────────────
let adults = repo.find(
repo.query()
.r#where("age", CondOp::Gte, 18)
.r#where("active", CondOp::Eq, true)
.order_by("name", Direction::Asc)
.limit(10)
).await?;
// ── Find one ──────────────────────────────────────────────────────
let alice = repo.find_one(
repo.query().r#where("email", CondOp::Eq, "alice@example.com")
).await?;
// ── Paginate ──────────────────────────────────────────────────────
let page_1 = repo.paginate(1, 20).await?; // page 1, 20 per page
// ── Count ─────────────────────────────────────────────────────────
let total = repo.count(None).await?;
let active_count = repo.count(Some(
repo.query().r#where("active", CondOp::Eq, true)
)).await?;
// ── Update ────────────────────────────────────────────────────────
let updated = repo.update(
repo.query()
.r#where("email", CondOp::Eq, "alice@example.com")
.set("age", 31)
.set("active", false)
).await?;
println!("Updated {updated} rows");
// ── Save (insert-or-update) ───────────────────────────────────────
let mut alice_model = alice.unwrap();
alice_model.age = 32;
repo.save(&alice_model).await?;
// ── Delete by ID ──────────────────────────────────────────────────
repo.delete_by_id(inserted.id).await?;
// ── Delete by query ───────────────────────────────────────────────
repo.delete(
repo.query().r#where("active", CondOp::Eq, false)
).await?;
// ── Raw SQL ───────────────────────────────────────────────────────
repo.adapter().execute_raw(
"UPDATE users SET active = $1 WHERE age < $2",
vec![Value::Bool(false), Value::Int(18)]
).await?;
let rows = repo.adapter().query_raw(
"SELECT id, name FROM users WHERE age > $1",
vec![Value::Int(21)]
).await?;
Ok(())
}
```
---
### 3. Sync Usage
Use `SyncAdapter` to wrap any async adapter for blocking contexts — no `tokio::main` required.
```rust
use rusticx::prelude::*;
use std::sync::Arc;
use uuid::Uuid;
use chrono::Utc;
fn main() -> rusticx::Result<()> {
// Build the async adapter first (SyncAdapter creates its own runtime)
let rt = tokio::runtime::Runtime::new().unwrap();
let async_adapter = rt.block_on(
PostgresAdapter::connect_url("postgres://user:pass@localhost/mydb")
)?;
// Wrap in SyncAdapter
let sync_adapter = SyncAdapter::new(async_adapter)?;
// ── Schema ────────────────────────────────────────────────────────
let schema = TableSchema::from_model::<User>();
sync_adapter.create_table(&schema)?;
// ── Insert ────────────────────────────────────────────────────────
let mut row = rusticx::Row::new();
row.insert("id".into(), Value::Uuid(Uuid::new_v4()));
row.insert("name".into(), Value::Text("Dave".into()));
row.insert("email".into(), Value::Text("dave@example.com".into()));
row.insert("age".into(), Value::Int(28));
row.insert("active".into(), Value::Bool(true));
row.insert("created_at".into(), Value::DateTime(Utc::now()));
let inserted = sync_adapter.insert("users", row)?;
println!("Inserted: {:?}", inserted);
// ── Find ──────────────────────────────────────────────────────────
let query = QueryBuilder::table("users")
.r#where("age", CondOp::Gte, 18)
.order_by("name", Direction::Asc)
.limit(5);
let rows = sync_adapter.find(&query)?;
println!("Found {} rows", rows.len());
// ── Count ─────────────────────────────────────────────────────────
let count = sync_adapter.count(&QueryBuilder::table("users"))?;
println!("Total: {count}");
// ── Update ────────────────────────────────────────────────────────
let update_query = QueryBuilder::table("users")
.r#where("email", CondOp::Eq, "dave@example.com")
.set("age", 29);
sync_adapter.update(&update_query)?;
// ── Delete ────────────────────────────────────────────────────────
let delete_query = QueryBuilder::table("users")
.r#where("active", CondOp::Eq, false);
sync_adapter.delete(&delete_query)?;
// ── Raw SQL ───────────────────────────────────────────────────────
sync_adapter.execute_raw(
"TRUNCATE TABLE users",
vec![]
)?;
Ok(())
}
```
---
### 4. MongoDB
Same API, different adapter. `migrate()` creates a collection + indexes.
```rust
use rusticx::prelude::*;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, Model)]
#[rusticx(table = "products")]
pub struct Product {
#[rusticx(primary_key, column = "_id")]
pub id: String,
pub name: String,
pub price: f64,
pub in_stock: bool,
}
#[tokio::main]
async fn main() -> rusticx::Result<()> {
let adapter = MongoAdapter::connect_url(
"mongodb://localhost:27017",
"mydb",
).await?;
let repo: Repository<Product, _> = Repository::new(Arc::new(adapter));
// Creates the collection if not exists
repo.migrate().await?;
// All the same CRUD methods work identically
let cheap = repo.find(
repo.query()
.r#where("price", CondOp::Lt, 100.0)
.r#where("in_stock", CondOp::Eq, true)
.order_by("price", Direction::Asc)
).await?;
Ok(())
}
```
---
### 5. MySQL / MariaDB
```rust
let adapter = MysqlAdapter::connect_url("mysql://user:pass@localhost/mydb").await?;
let repo: Repository<User, _> = Repository::new(Arc::new(adapter));
repo.migrate().await?;
// ... identical API
```
---
## Query Builder Reference
```rust
repo.query()
// Conditions (AND by default)
.r#where("column", CondOp::Eq, value)
.r#where("column", CondOp::Ne, value)
.r#where("column", CondOp::Gt, value)
.r#where("column", CondOp::Gte, value)
.r#where("column", CondOp::Lt, value)
.r#where("column", CondOp::Lte, value)
.r#where("column", CondOp::Like, "%pattern%")
.r#where("column", CondOp::ILike, "%pattern%") // case-insensitive (Postgres)
.r#where("column", CondOp::IsNull, Value::Null)
.r#where("column", CondOp::IsNotNull, Value::Null)
// OR condition
.or_where("column", CondOp::Eq, value)
// Ordering
.order_by("column", Direction::Asc)
.order_by("column", Direction::Desc)
// Pagination
.limit(20)
.offset(40)
// Mutation (for update)
.set("column", new_value)
```
---
## Connection Pool Configuration
```rust
// Postgres
let adapter = PostgresAdapter::connect(
PostgresConfig::new("postgres://localhost/mydb")
.max_connections(20)
.min_connections(2)
).await?;
// MySQL
let adapter = MysqlAdapter::connect(
MysqlConfig::new("mysql://localhost/mydb")
.max_connections(15)
).await?;
// MongoDB
let adapter = MongoAdapter::connect(
MongoConfig::new("mongodb://localhost:27017", "mydb")
.max_pool_size(10)
).await?;
```
---
## Architecture
```
rusticx/ ← public facade crate (feature-gated re-exports)
├── rusticx-core/ ← Model trait, Repository, QueryBuilder, Value, DatabaseAdapter
├── rusticx-macros/ ← #[derive(Model)] procedural macro
├── rusticx-sql/ ← SQL dialect compiler (DDL + DML for Postgres / MySQL)
├── rusticx-postgres/ ← PostgreSQL adapter (sqlx)
├── rusticx-mysql/ ← MySQL / MariaDB adapter (sqlx)
└── rusticx-mongo/ ← MongoDB adapter (mongodb)
```
---
## Supported Databases
| PostgreSQL | Stable | `postgres` |
| MySQL | Stable | `mysql` |
| MariaDB | Stable (MySQL driver) | `mysql` |
| MongoDB | Stable | `mongo` |
| CockroachDB | Planned (PostgreSQL wire) | — |
| Cassandra | Planned | — |
| SQLite | Planned | — |
---
## License
MIT — © Tarun Vishwakarma