Akita
🎯 Features
- 🚀 High Performance: Pure Rust implementation, zero runtime overhead
- 🎯 Easy to Use: Intuitive API, quick to learn
- 🔧 Flexible Query: Powerful query builder with type safety
- 📦 Multi-Database: Native support for MySQL, PostgreSQL, SQLite, and any MySQL-compatible databases (TiDB, MariaDB, etc.)
- 🔌 Dual Runtime: Both synchronous and asynchronous operation modes
- 🛡️ Type Safe: Full Rust type system support with compile-time checking
- 🔄 Transaction: Complete ACID transaction management with savepoint support
- ⚡ Connection Pool: Built-in high-performance connection pooling
- 🎨 Annotation Driven: Simplify entity definition with derive macros
- 🔌 Interceptors: Extensible interceptor system for AOP (Aspect-Oriented Programming)
- 📊 Pagination: Built-in smart pagination with total count
- 🔍 Complex Query: Support for joins, subqueries, and complex SQL operations
- 🛠️ Raw SQL: Direct SQL execution when needed
📦 Installation
Add this to your Cargo.toml:
For MySQL (Synchronous)
[dependencies]
akita = { version = "0.6", features = ["mysql-sync"] }
For MySQL (Asynchronous)
[dependencies]
akita = { version = "0.6", features = ["mysql-async"] }
For PostgreSQL:
[dependencies]
akita = { version = "0.6", features = ["postgres-sync"] }
For Oracle:
[dependencies]
akita = { version = "0.6", features = ["oracle-sync"] }
SqlServer:
[dependencies]
akita = { version = "0.6", features = ["mssql-sync"] }
For SQLite:
[dependencies]
akita = { version = "0.6", features = ["sqlite-sync"] }
For TiDB and MySQL-compatible Databases:
TiDB, MariaDB, and other MySQL-compatible databases can use the MySQL features:
[dependencies]
akita = { version = "0.6", features = ["mysql-sync"] }
chrono = "0.4"
🚀 Quick Start
1. Define Your Entity
use akita::*;
use chrono::{NaiveDate, NaiveDateTime};
use serde_json::Value;
#[derive(Entity, Clone, Default, Debug)]
#[table(name = "users")]
pub struct User {
#[id(name = "id")]
pub id: i64,
#[field(name = "user_name")]
pub username: String,
pub email: String,
pub age: Option<u8>,
#[field(name = "is_active")]
pub active: bool,
pub level: u8,
pub metadata: Option<Value>,
pub birthday: Option<NaiveDate>,
pub created_at: Option<NaiveDateTime>,
#[field(exist = "false")]
pub full_name: String,
}
2. Initialize Akita
Synchronous Mode
use akita::prelude::*;
use std::time::Duration;
fn main() -> Result<()> {
let cfg = AkitaConfig::new()
.url("mysql://root:password@localhost:3306/mydb")
.max_size(10) .connection_timeout(Duration::from_secs(5));
let akita = Akita::new(cfg)?;
let tidb_cfg = AkitaConfig::new()
.url("mysql://root:@tidb-host:4000/mydb")
.max_size(20);
Ok(())
}
Asynchronous Mode
use akita::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<()> {
let cfg = AkitaConfig::new()
.url("mysql://root:password@localhost:3306/mydb")
.max_size(10)
.connection_timeout(Duration::from_secs(5));
let akita = Akita::new(cfg).await?;
Ok(())
}
3. Basic Operations
Synchronous Operations
fn main() {
let user = User {
username: "john_doe".to_string(),
email: "john@example.com".to_string(),
active: true,
level: 1,
..Default::default()
};
let user_id: Option<i64> = akita.save(&user)?;
let user: Option<User> = akita.select_by_id(user_id.unwrap())?;
let mut user = user.unwrap();
user.level = 2;
akita.update_by_id(&user)?;
akita.remove_by_id::<User, _>(user_id.unwrap())?;
}
Asynchronous Operations
fn main() {
let user = User {
username: "jane_doe".to_string(),
email: "jane@example.com".to_string(),
active: true,
level: 1,
..Default::default()
};
let user_id: Option<i64> = akita.save(&user).await?;
let user: Option<User> = akita.select_by_id(user_id.unwrap()).await?;
let mut user = user.unwrap();
user.level = 2;
akita.update_by_id(&user).await?;
akita.remove_by_id::<User, _>(user_id.unwrap()).await?;
}
⬆️Database Compatibility Matrix
| Database |
Sync Feature |
Async Feature |
Protocol |
Sync Implementation |
Async Implementation |
Status |
Notes |
| MySQL |
mysql-sync |
mysql-async |
MySQL |
mysql crate |
mysql_async crate |
✅ Production Ready |
Native Rust implementations |
| PostgreSQL |
postgres-sync |
postgres-async |
PostgreSQL |
tokio-postgres (blocking) |
tokio-postgres (async) |
✅ Production Ready |
Both use tokio-postgres under the hood |
| SQLite |
sqlite-sync |
sqlite-async |
SQLite |
rusqlite crate |
sqlx with async runtime |
✅ Production Ready |
Different implementation strategies |
| Oracle |
oracle-sync |
oracle-async |
Oracle |
oracle crate (blocking) |
oracle crate + async runtime |
✅ Production Ready |
Oracle driver with async wrapper |
| SQL Server |
sqlserver-sync |
sqlserver-async |
TDS |
tiberius (blocking) |
tiberius (async) |
✅ Production Ready |
Tiberius driver support |
| TiDB |
mysql-sync |
mysql-async |
MySQL |
Same as MySQL |
Same as MySQL |
✅ Production Ready |
100% MySQL compatible |
| MariaDB |
mysql-sync |
mysql-async |
MySQL |
Same as MySQL |
Same as MySQL |
✅ Production Ready |
100% MySQL compatible |
| OceanBase |
mysql-sync |
mysql-async |
MySQL |
Same as MySQL |
Same as MySQL |
✅ Production Ready |
MySQL compatible mode |
Implementation Details Summary
PostgreSQL Implementation
- Sync: Uses
tokio-postgres with blocking wrapper
- Async: Direct
tokio-postgres async client
- Both share same underlying library
Oracle Implementation
- Sync: Native
oracle crate (synchronous driver)
- Async:
oracle crate wrapped with async runtime
- Same driver, different execution model
SQLite Implementation
- Sync:
rusqlite crate (synchronous SQLite)
- Async:
sqlx with async SQLite support
- Different libraries, same protocol
SQL Server Implementation
- Sync:
tiberius with blocking API
- Async:
tiberius native async API
- Same library, different APIs
Feature Comparison
| Feature |
MySQL/TiDB |
PostgreSQL |
SQLite |
Oracle |
SQL Server |
| ACID Transactions |
✅ |
✅ |
✅ |
✅ |
✅ |
| Connection Pool |
✅ |
✅ |
✅ |
✅ |
✅ |
| Native Async |
✅ |
✅ |
⚠️ (via sqlx) |
⚠️ (wrapped) |
✅ |
| Sync via Async Runtime |
❌ |
✅ (blocking) |
❌ |
❌ |
✅ (blocking) |
| JSON Support |
✅ (JSON) |
✅ (JSONB) |
✅ (JSON1) |
✅ |
⚠️ (limited) |
| Full-text Search |
✅ |
✅ |
✅ (FTS5) |
✅ |
✅ |
| Spatial Data |
✅ |
✅ |
✅ (R*Tree) |
✅ |
✅ |
| Stored Procedures |
✅ |
✅ |
❌ |
✅ |
✅ |
| Replication |
✅ |
✅ |
❌ |
✅ |
✅ |
| Distributed |
TiDB ✅ |
❌ |
❌ |
❌ |
❌ |
| Protocol |
MySQL |
PostgreSQL |
SQLite |
Oracle |
TDS |
Key Implementation Notes
- PostgreSQL: Both sync and async use
tokio-postgres, sync is just a blocking wrapper
- Oracle: Sync is native driver, async is wrapper around same driver
- SQLite: Different libraries for sync (
rusqlite) and async (sqlx)
- SQL Server:
tiberius provides both sync (blocking) and async APIs
- MySQL: Separate sync (
mysql) and async (mysql_async) crates
- TiDB/MariaDB/OceanBase: Use MySQL drivers with full compatibility
📚 Detailed Usage
Query Builder
Akita provides a powerful, type-safe query builder that works across all supported databases:
fn main() {
use akita::*;
let wrapper = Wrapper::new()
.select(vec!["id", "username", "email"])
.eq("status", 1)
.ne("deleted", true)
.gt("age", 18)
.ge("score", 60)
.lt("age", 65)
.le("level", 10)
.like("username", "%john%")
.not_like("email", "%test%")
.r#in("role", vec!["admin", "user"])
.not_in("status", vec![0, 9])
.is_null("deleted_at")
.is_not_null("created_at")
.between("age", 18, 65)
.not_between("score", 0, 60)
.and(|w| {
w.eq("status", 1).or_direct().eq("status", 2)
})
.or(|w| {
w.like("username", "%admin%").like("email", "%admin%")
})
.order_by_asc(vec!["created_at"])
.order_by_desc(vec!["id", "level"])
.group_by(vec!["department", "level"])
.having("COUNT(*)", SqlOperator::Gt, 1)
.limit(10)
.offset(20);
}
Complex Queries with Database Optimizations
fn main() {
let users: Vec<User> = akita.list(
Wrapper::new()
.eq("u.status", 1)
.inner_join("departments d", "u.department_id = d.id")
.select(vec!["u.*", "d.name as department_name"])
)?;
let active_users: Vec<User> = akita.list(
Wrapper::new()
.r#in("id", |w| {
w.select(vec!["user_id"])
.from("user_logs")
.eq("action", "login")
.gt("created_at", "2023-01-01")
})
)?;
let query = Wrapper::new()
.eq("status", 1)
.order_by_desc(vec!["created_at"]);
let result = akita.list::<User>(query.limit(100))?;
}
Database-Specific Features
MySQL/TiDB Specific Features
fn main() {
let users: Vec<User> = akita.exec_raw(
"SELECT * FROM users WHERE JSON_EXTRACT(metadata, '$.premium') = true",
()
)?;
let users: Vec<User> = akita.list(
Wrapper::new()
.raw("MATCH(username, email) AGAINST(:search IN BOOLEAN MODE)")
.set_param("search", "john*")
)?;
}
PostgreSQL Specific Features
fn main() {
let users: Vec<User> = akita.exec_raw(
"SELECT * FROM users WHERE metadata @> '{\"premium\": true}'",
()
)?;
let users: Vec<User> = akita.exec_raw(
"SELECT * FROM users WHERE 'admin' = ANY(roles)",
()
)?;
}
SQLite Specific Features
fn main() {
let users: Vec<User> = akita.exec_raw(
"SELECT * FROM users WHERE json_extract(metadata, '$.premium') = 1",
()
)?;
let users: Vec<User> = akita.exec_raw(
"SELECT * FROM users_fts WHERE users_fts MATCH 'john'",
()
)?;
}
Raw SQL Queries with Database Portability
fn main() {
let users: Vec<User> = akita.exec_raw(
"SELECT * FROM users WHERE status = ? AND level > ?",
(1, 0)
)?;
let user: Option<User> = akita.exec_first(
"SELECT * FROM users WHERE username = :name AND email = :email",
params! {
"name" => "john",
"email" => "john@example.com"
}
)?;
akita.exec_drop(
"CREATE TABLE IF NOT EXISTS users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL
)",
()
)?;
}
Transactions with Database-Specific Features
fn main() {
akita.start_transaction().and_then(|mut tx| {
tx.save(&user1)?;
tx.save(&user2)?;
tx.update(&user3, wrapper)?;
tx.commit()
})?;
akita.start_transaction().and_then(|mut tx| {
tx.save(&user1)?;
match tx.save(&user2) {
Ok(_) => {
tx.commit()
}
Err(e) => {
tx.rollback()
}
}
})?;
}
Interceptors with Database Awareness
Akita supports powerful interceptor system that can adapt to different databases:
use akita::*;
use std::sync::Arc;
use std::time::Duration;
fn main() {
let akita = Akita::new(config).unwrap()
.with_interceptor_builder(
InterceptorBuilder::new()
.register(tenant_interceptor)
.register(performance_interceptor)
.register(logging_interceptor)
.enable("trackable_tenant").unwrap()
.enable("trackable_performance").unwrap()
.enable("trackable_logging").unwrap()
)?;
}
#[derive(Debug)]
struct AuditInterceptor {
user_id: String,
}
impl AkitaInterceptor for AuditInterceptor {
fn name(&self) -> &'static str {
"audit"
}
fn interceptor_type(&self) -> InterceptorType {
InterceptorType::Audit
}
fn order(&self) -> i32 {
50
}
fn before_execute(&self, ctx: &mut ExecuteContext) -> Result<()> {
ctx.set_metadata("audit_user", self.user_id.clone());
ctx.set_metadata("audit_time", chrono::Utc::now().to_rfc3339());
Ok(())
}
}
Entity Methods with Database Portability
Entities can have their own methods:
impl User {
pub fn find_active(akita: &Akita) -> Result<Vec<User>> {
akita.list(Wrapper::new().eq("status", 1))
}
pub fn find_by_email(akita: &Akita, email: &str) -> Result<Option<User>> {
akita.exec_first(
"SELECT * FROM users WHERE email = ?",
(email,)
)
}
pub fn promote(&mut self) {
self.level += 1;
}
pub fn is_vip(&self) -> bool {
self.level >= 2
}
}
fn main() {
let active_users = User::find_active(&akita)?;
let user = User::find_by_email(&akita, "john@example.com")?;
let mut user = create_test_user();
let result = user.update_by_id::<_>(&akita);
assert!(result.is_ok(), "The entity update method should succeed");
let result = user.remove_by_id::<_,i32>(&akita, 1);
assert!(result.is_ok(), "The entity deletion method should succeed");
let result = User::list(&akita, Wrapper::new().eq("name", "Jack"));
assert!(result.is_ok(), "The entity list query should succeed");
let result = User::page::<_>(&akita, 1, 1, Wrapper::new().eq("name", "Jack"));
assert!(result.is_ok(), "The entity paging query should succeed");
}
Pagination with Database Optimization
fn main() {
let page: IPage<User> = akita.page(1, 10, Wrapper::new().eq("status", 1))?;
println!("Page {} of {}", page.current, page.size);
println!("Total records: {}", page.total);
println!("Records on this page: {}", page.records.len());
let _page = akita.page(
1,
10,
Wrapper::new()
.eq("department", "engineering")
.ge("level", 3)
)?;
let ipage = akita.page::<User>(
1,
10,
Wrapper::new().eq("active", true)
)?;
}
Batch Operations with Database Optimization
fn main() {
let users = vec![
User { username: "user1".to_string(), ..Default::default() },
User { username: "user2".to_string(), ..Default::default() },
User { username: "user3".to_string(), ..Default::default() },
];
let _ = akita.save_batch(&users)?;
let mut users_to_update = vec![];
for mut user in users {
user.level += 1;
users_to_update.push(user);
}
akita.update_batch_by_id(&users_to_update)?;
akita.remove_by_ids::<User, _>(vec![1, 2, 3, 4, 5])?;
}
🔧 Configuration
AkitaConfig Options
fn main() {
let config = AkitaConfig::new().url("mysql://root:password@localhost:3306/mydb")
.max_size(20) .min_size(Some(5)) .connection_timeout(Duration::from_secs(30))
.idle_timeout(Duration::from_secs(300))
.max_lifetime(Duration::from_secs(1800));
}
Environment-based Configuration
fn main() {
use std::env;
let database_url = env::var("DATABASE_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost:3306/mydb".to_string());
let max_connections: u32 = env::var("DATABASE_MAX_CONNECTIONS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(10);
let config = AkitaConfig::new().url(&database_url)
.max_size(max_connections);
}
🎨 Advanced Features
Custom Type Conversion
use akita::*;
#[derive(Debug, Clone)]
pub struct Email(String);
impl FromAkitaValue for Email {
fn from_value(value: &AkitaValue) -> Result<Self, AkitaDataError> {
match value {
AkitaValue::Text(s) => Ok(Email(s.clone())),
_ => Err(AkitaDataError::ConversionError(ConversionError::conversion_error(format!("Cannot convert {:?} to Email", value)))),
}
}
}
impl IntoAkitaValue for Email {
fn into_value(&self) -> AkitaValue {
AkitaValue::Text(self.0.clone())
}
}
#[derive(Entity)]
pub struct UserWithEmail {
pub id: i64,
pub email: Email, }
📊 Performance Tips
- Connection Pooling: Always configure appropriate pool sizes
-
MySQL/TiDB: 10-100 connections based on workload
-
PostgreSQL: 5-50 connections
-
SQLite: 1 connection (file-based)
- Batch Operations: Use database-specific batch methods
-
MySQL: Multi-value INSERT statements
-
PostgreSQL: COPY command for large datasets
-
SQLite: Transactions around batch operations
- Query Optimization:
- Statement Caching: Akita caches prepared statements automatically
- Connection Reuse: Keep connections alive for related operations
- Database-Specific Features:
-
MySQL/TiDB: Use connection compression for remote connections
-
PostgreSQL: Use prepared statements for complex queries
-
SQLite: Enable WAL mode for better concurrency
🤝 Contributing
We welcome contributions! Here's how you can help:
-
Report Bugs: Create an issue with database-specific details
-
Suggest Features: Start a discussion about new database support or features
-
Submit PRs: Follow our contributing guide
-
Improve Documentation: Help us make the docs better for all database backends
-
Add Database Support: Implement support for new databases
Development Setup
git clone https://github.com/wslongchen/akita.git
cd akita
cargo test --features mysql-sync
cargo test --features mysql-async
cargo test --features postgres-sync
cargo test --features sqlite-sync
cargo test --features oracle-sync
cargo test --all-features
cargo run --example basic --features mysql-sync
cargo run --example async-basic --features mysql-async
cargo doc --open --all-features
📄 License
Licensed under either of:
at your option.
🙏 Acknowledgments
-
Thanks to all contributors who have helped shape Akita
-
Inspired by great ORMs like Diesel, SQLx, and MyBatis
-
Built with ❤️ by the Cat&Dog Lab team
📞 Contact