- Ergonomics : Ergonomics API design and Error design.
- Transactional : Beautiful Transaction Abstraction, As not a Transaction.
- Template : Write Your Own Sql Like Mybatis.
- Asynchronous : Fully Async Based on Sqlx.
- Compile-Time : Maximizing Compile-Time Processing, For API and Performance.
🎆The next two weeks are for the Chinese Spring Festival, and I will be taking two weeks off. Following the Spring Festival, I plan to release a significant update. This new version will encompass a wide range of enhancements and API updates.
taitan-orm = { version = "0.1.9" }
use std::borrow::Cow;
use sqlx::types::time::PrimitiveDateTime;
use time::macros::datetime;
use taitan_orm::database::sqlite::SqliteDatabase;
use taitan_orm::database::sqlite::SqliteLocalConfig;
use taitan_orm::prelude::*;
#[derive(Schema, Clone, Debug)]
#[table_name = "user"]
#[unique_key = "name"]
#[index(name = "idx_hello", fields("age", "birthday"))]
pub struct User {
#[primary_key]
id: i32,
name: String,
age: Optional<i32>,
birthday: Optional<PrimitiveDateTime>
}
#[tokio::main]
async fn main() -> taitan_orm::result::Result<()> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
.init();
// 0. setup database
// refer to docs/setup.md for database setup
let db = ...;
// 1. insert entity
let entity = User {
id: 1,
name: "Allen".to_string(),
age: Optional::Some(23),
birthday: Optional::Some(datetime!(2019-01-01 0:00))
};
let result = db.insert(&entity).await?;
assert_eq!(result, true);
// 2. update
let mutation = UserMutation {
name: Optional::None,
age: Optional::Some(24),
birthday: Optional::None
};
let primary = UserPrimary { id: 1 };
let result = db.update(&mutation, &primary).await?;
assert_eq!(result, true);
// 3. select
let selection = UserSelectedEntity::full_fields();
let entity: Option<UserSelectedEntity>
= db.select(&selection, &primary).await?;
assert!(entity.is_some());
// 4. select by unique
let uk = UserNameUnique { name: "Allen".to_string() };
let unique_entity : Option<UserSelectedEntity>
= db.select(&selection, &uk).await?;
assert!(unique_entity.is_some());
// 5. search by index
let index = UserIndexIdxHello::AgeBirthday {
age: LocationExpr::from("=", 24)?,
birthday: LocationExpr::from("=", datetime!(2019-01-01 0:00))?
};
let index_entities: Vec<UserSelectedEntity> = db.search(&selection, &index, &None, &None).await?;
assert_eq!(index_entities.len(), 1);
// 6. search
let selection = UserSelectedEntity::full_fields();
let location = UserLocationExpr::id(">=", 1)?;
let entities: Vec<UserSelectedEntity> = db.search(&selection, &location, &None, &None).await?;
assert_eq!(entities.len(), 1);
// 5. delete
let result = db.delete(&primary).await?;
assert_eq!(result, true);
let entity: Option<UserSelectedEntity> = db.select(&selection, &primary).await?;
assert!(entity.is_none());
println!("crud success!");
Ok(())
}
- you can run the crud example in examples/crud directory.
When derive(Schema), TaiTan-ORM will generate helper struct for you.
#[derive(Schema, Clone, Debug)]
#[table_name = "user"]
#[unique_key = "name"]
#[index(name = "idx_hello", fields("age", "birthday"))]
pub struct User {
#[primary_key]
id: i32,
name: String,
age: Optional<i32>,
birthday: Optional<PrimitiveDateTime>
}
// struct for primary key
pub struct UserPrimary {
id: i32
}
// struct for update
pub struct UserMutation {
name: Optional<String>,
age: Optional<i32>,
birthday: Optional<PrimitiveDateTime>
}
// struct for unique key
pub struct UserNameUnique {
name: String
}
// struct for index_hello, designed for index prefix matching
// age is allowed,
// age, birthday is allowed
// birthday is not allowed
pub enum UserIndexIdxHello {
Age {
age: LocationExpr<i32>
},
AgeBirthday{
age: LocationExpr<i32>,
birthday: LocationExpr<PrimitiveDateTime>
}
}
// struct to generate where condition
pub struct UserLocation {
mode: LocationMode,
id: Optional<LocationExpr<i32>>,
name: Optional<LocationExpr<String>>,
age: Optional<LocationExpr<i32>>,
}
// struct to select field and recv result from database
pub struct UserSelectedEntity {
id: Optional<i32>,
name: Optional<String>,
age: Optional<i32>,
}
TaiTan-ORM: The Most Powerful ORM Template Engine You'll Ever Meet
Titan-ORM offers a full-featured template engine that supports the popular Jinja syntax, providing powerful templating capabilities.
In the vast majority of cases, compile-time processing is utilized, resulting in essentially zero overhead during runtime. This ensures optimal performance and efficiency.
Introducing a new Optional syntax that helps you avoid cumbersome and unsightly template code, making your templates cleaner and more maintainable.
syntax | description |
---|---|
#{} | compile time render syntax, may change |
${} | dynamic render syntax |
%{} | now used for optional, but it may confused with {% %}, so maybe change |
{% %} | Jinja template syntax, this will not change |
@{} | candidate for compile time render |
/// This is the #{} syntax, this will be parsed at compile-time,
/// In run time, engine will get the sql:
/// UPDATE `user` SET name = ? WHERE `id` = ?
/// There will be zero-overhead at run-time
#[derive(TemplateRecord, Debug)]
#[sql = "UPDATE `user` SET name = #{name} WHERE `id` = #{id}"]
pub struct UserUpdateTemplate {
id: i32,
name: String,
}
/// This is the ${} syntax, and will render the sql at run-time,
/// After render, engine get the sql:
/// UPDATE `user` SET user_name = 2 WHERE `id` = 100
/// This is usually used for dynamic sql generation
#[derive(TemplateRecord, Debug)]
#[sql = "UPDATE `user` SET ${name} = 2 WHERE `id` = ${id}"]
pub struct UserUpdateTemplate {
id: i32, // suppose to be 100
name: String, // suppose to be "user_name"
}
/// This is the %{} syntax, the most beautiful one, will be parsed at run time,
/// But Special for Optional variable
/// 1. When template is { name: "Allen", age: None },
/// After render, engine will get the sql:
/// select `id`, `name`, `age` FROM `user` where `name` = ?
/// 2. When template is { name: "Allen", age: Some(33) },
/// After render, engine will get the sql:
/// select `id`, `name`, `age` FROM `user` where age >= ? AND `name` = ?
///
/// when parser encounters "%{age}",
/// it will treat the entire expression with connective
/// "age >= %{age} AND" as an integrated unit
/// when Optional variable is None, the entire expression will be ignored
#[derive(TemplateRecord, Debug)]
#[sql = "select `id`, `name`, `age` FROM `user` where age >= %{age} AND `name` = #{name}"]
pub struct UserCustomTemplate {
name: String,
age: Optional<i32>,
}
- you can run the template example in examples/template directory.
Seamless transaction handling
async fn trx_insert_user(
db: &mut SqliteDatabase,
user1: &User,
user2: &User,
) -> taitan_orm::result::Result<()> {
let mut trx = db.transaction().await?; // create a transaction, trx
trx.insert(user1).await?; // same api as database
trx.insert(user2).await?; // rollback if there is any error
trx.commit().await?; // commit it
Ok(()) // when trx drop, if not commit, rollback
}
At present, the documentation for this newly-born project is limited. You can refer to the examples project for more details.
example | description |
---|---|
crud | basic crud example |
template | template with paged example |
transaction | basic transaction example |
search | multi search features |
axum_crud | integrate with axum |
MySql
Postgres
Sqlite
Please refer to 🔗Setup for details
-
0.1 API 🔧
⚠️ API may change, so just have a taste!
What is being polished? -
- write api: to support postgres insert returning syntax and batch insert/batch upsert
-
- search api: support index and more
-
- error
-
0.2 Correctness: specification and code coverage and fuzz 📎
🙏Help is wanted, maybe a long-running mysql instance and a postgres instance
now there is a rough coverage report: 🪧report -
0.3 Documentation: doc the usage and implementation 📎
🖊️Starting from version 0.3, I will focus my efforts on documentation. -
0.4 Performance: benchmark and optimize 📎
🚀The ultimate speed originates from maximizing compile-time processing. But we need to exhibit it. -
1.0 Stable: stabilize the api, macro and error 📎
Apache License