WebSite | 简体中文
A highly Performant,Safe,Dynamic SQL(Compile time) ORM framework written in Rust, inspired by Mybatis and MybatisPlus.
Why not diesel or not sqlx ?
Framework |
Async/.await |
Learning curve |
Dynamic SQL/py/Wrapper/built-in CRUD |
Logical delete plugin |
Pagination plugin |
rbatis |
√ |
easy |
√ |
√ |
√ |
sqlx |
√ |
hard (depends on macros and env. variables) |
x |
x |
x |
diesel |
x |
hard (use FFI, unsafe) |
x |
x |
x |
Performance comparison with Golang (in a docker environment)
Framework |
Mysql(docker) |
SQL statement(10k) |
ns/operation(lower is better) |
Qps(higher is better) |
Memory usage(lower is better) |
Rust-rbatis/tokio |
1 CPU, 1G memory |
select count(1) from table; |
965649 ns/op |
1035 Qps/s |
2.1MB |
Go-GoMybatis/http |
1 CPU, 1G memory |
select count(1) from table; |
1184503 ns/op |
844 Qps/s |
28.4MB |
- No Runtimes,No Garbage Collection
- Zero cost Dynamic SQL, implemented using (proc-macro,compile-time,Cow(Reduce unnecessary cloning)) techniques。 don't need ONGL engine(mybatis)
- Free deserialization, Auto Deserialize to any struct(Option,Map,Vec...)
- High performance, Based on Future, with async_std/tokio, single threaded benchmark can easily achieve 200,000 QPS
- logical deletes, pagination, py-like SQL and basic Mybatis functionalities.
- Supports logging, customizable logging based on
log
crate
- 100% Safe Rust with
#![forbid(unsafe_code)]
enabled
- rbatis/example (import into Clion!)
- abs_admin project an complete background user management system(Vue.js+rbatis+actix-web)
Supported data structures
data structure |
is supported |
Option |
√ |
Vec |
√ |
HashMap |
√ |
i32,i64,f32,f64,bool,String...more rust type |
√ |
rbatis::Bytes |
√ |
rbatis::DateNative |
√ |
rbatis::DateUtc |
√ |
rbatis::DateTimeNative |
√ |
rbatis::DateTimeUtc |
√ |
rbatis::Decimal |
√ |
rbatis::Json |
√ |
rbatis::TimeNative |
√ |
rbatis::TimeUtc |
√ |
rbatis::Timestamp |
√ |
rbatis::TimestampZ |
√ |
rbatis::Uuid |
√ |
rbatis::plugin::page::{Page, PageRequest} |
√ |
rbson::Bson* |
√ |
serde_json::* |
√ |
any serde type |
√ |
Supported database √supported .WIP
database |
is supported |
Mysql |
√ |
Postgres |
√ |
Sqlite |
√ |
Mssql/Sqlserver |
√(50%) |
MariaDB(Mysql) |
√ |
TiDB(Mysql) |
√ |
CockroachDB(Postgres) |
√ |
Supported OS/Platforms
platform |
is supported |
Linux |
√ |
Apple/MacOS |
√ |
Windows |
√ |
Web Frameworks
Example Cargo.toml
# add this library,and cargo install
# bson (required)
serde = { version = "1", features = ["derive"] }
rbson = "2.0"
# logging lib(required)
log = "0.4"
fast_log="1.3"
# rbatis (required) default is all-database+runtime-async-std-rustls
rbatis = { version = "3.0" }
# also if you use actix-web+mysql
# rbatis = { version = "3.0", default-features = false, features = ["mysql","runtime-async-std-rustls"] }
Quick example: QueryWrapper and common usages (see example/crud_test.rs for details)
#[macro_use]
extern crate rbatis;
use rbatis::crud::CRUD;
#[crud_table]
#[derive(Clone, Debug)]
pub struct BizActivity {
pub id: Option<String>,
pub name: Option<String>,
pub pc_link: Option<String>,
pub h5_link: Option<String>,
pub pc_banner_img: Option<String>,
pub h5_banner_img: Option<String>,
pub sort: Option<String>,
pub status: Option<i32>,
pub remark: Option<String>,
pub create_time: Option<rbatis::DateTimeNative>,
pub version: Option<i32>,
pub delete_flag: Option<i32>,
}
impl_field_name_method!(BizActivity{id,name});
#[tokio::main]
async fn main() {
fast_log::init_log("requests.log", log::Level::Info, None, true);
let rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let wrapper = rb.new_wrapper()
.eq("id", 1) .and() .ne(BizActivity::id(), 1) .in_array("id", &[1, 2, 3]) .not_in("id", &[1, 2, 3]) .like("name", 1) .or() .not_like(BizActivity::name(), "asdf") .between("create_time", "2020-01-01 00:00:00", "2020-12-12 00:00:00") .group_by(&["id"]) .order_by(true, &["id", "name"]) ;
let activity = BizActivity {
id: Some("12312".to_string()),
name: None,
pc_link: None,
h5_link: None,
pc_banner_img: None,
h5_banner_img: None,
sort: None,
status: None,
remark: None,
create_time: Some(rbatis::DateTimeNative::now()),
version: Some(1),
delete_flag: Some(1),
};
rb.save(&activity, &[]).await;
rb.save_batch(&vec![activity], &[]).await;
let result: Option<BizActivity> = rb.fetch_by_column(BizActivity::id(), "1").await.unwrap();
let result: Vec<BizActivity> = rb.list().await.unwrap();
let result: Vec<BizActivity> = rb.list_by_column("id", &["1"]).await.unwrap();
let r: Result<Option<BizActivity>, Error> = rb.fetch_by_wrapper(rb.new_wrapper().eq("id", "1")).await;
rb.remove_by_column::<BizActivity, _>("id", &"1").await;
rb.remove_batch_by_column::<BizActivity, _>("id", &["1", "2"]).await;
let mut activity = activity.clone();
let r = rb.update_by_column("id", &activity).await;
rb.update_by_wrapper(&activity, rb.new_wrapper().eq("id", "12312"), &[Skip::Value(&serde_json::Value::Null), Skip::Column("id")]).await;
}
macros (new addition)
- Important update (pysql removes runtime, directly compiles to static rust code) This means that the performance of SQL generated using py_sql,html_sql is roughly similar to that of handwritten code.
Because of the compile time, the annotations need to declare the database type to be used
#[py_sql(
rb,
"select * from biz_activity where delete_flag = 0
if name != '':
and name=#{name}")]
async fn py_sql_tx(rb: &Rbatis, tx_id: &String, name: &str) -> Vec<BizActivity> { todo!() }
- Added html_sql support, a form of organization similar to MyBatis, to facilitate migration of Java systems to Rust(Note that it is also compiled as Rust code at build time and performs close to handwritten code) this is very faster
Because of the compile time, the annotations need to declare the database type to be used
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://github.com/rbatis/rbatis_sql/raw/main/mybatis-3-mapper.dtd">
<mapper>
<select id="select_by_condition">
select * from biz_activity where
<if test="name != ''">
name like #{name}
</if>
</select>
</mapper>
#[html_sql(rb, "example/example.html")]
async fn select_by_condition(rb: &mut RbatisExecutor<'_,'_>, page_req: &PageRequest, name: &str) -> Page<BizActivity> { todo!() }
#[macro_use]
extern crate lazy_static;
lazy_static! {
static ref RB:Rbatis=Rbatis::new();
}
#[sql(RB, "select * from biz_activity where id = ?")]
pub async fn select(name: &str) -> BizActivity {}
#[tokio::test]
pub async fn test_macro() {
fast_log::init_log("requests.log", log::Level::Info, None, true);
RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let a = select("1").await.unwrap();
println!("{:?}", a);
}
How to use logical deletes plugin (works for fetching or removing functions provided by rbatis,e.g. list**(),remove**(),fetch**())
let mut rb:Rbatis=Rbatis::new();
rb.set_logic_plugin(RbatisLogicDeletePlugin::<BizActivity>::new("delete_flag"));;
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let r = rb.remove_batch_by_id::<BizActivity>( & ["1", "2"]).await;
if r.is_err() {
println ! ("{}", r.err().unwrap().to_string());
}
How to use pagination plugin
let mut rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let req = PageRequest::new(1, 20);
let wraper= rb.new_wrapper()
.eq("delete_flag", 1);
let data: Page<BizActivity> = rb.fetch_page_by_wrapper( & wraper, & req).await.unwrap();
println!("{}", serde_json::to_string(&data).unwrap());
{
"records": [
{
"id": "12312",
"name": "null",
"pc_link": "null",
"h5_link": "null",
"pc_banner_img": "null",
"h5_banner_img": "null",
"sort": "null",
"status": 1,
"remark": "null",
"create_time": "2020-02-09T00:00:00+00:00",
"version": 1,
"delete_flag": 1
}
],
"total": 5,
"size": 20,
"current": 1,
"serch_count": true
}
logging system with fast_log here as an example
use log::{error, info, warn};
fn main(){
fast_log::init_log("requests.log", log::Level::Info, None, true);
info!("print data");
}
Customize connection pool's size, timeout, active number of connections, and etc.
use rbatis::core::db::PoolOptions;
pub async fn init_rbatis() -> Rbatis {
let rb = Rbatis::new();
let mut opt = PoolOptions::new();
opt.max_size = 20;
rb.link_opt("mysql://root:123456@localhost:3306/test", &opt).await.unwrap();
}
Transaction
let rb = Rbatis::new();
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();
let mut tx = rb.acquire_begin().await.unwrap();
let v: serde_json::Value = tx
.fetch("select count(1) from biz_activity;",&vec![])
.await
.unwrap();
println!("{}", v.clone());
tx.commit().await.unwrap();
Transaction defer
pub async fn forget_commit(rb: &Rbatis) -> rbatis::core::Result<()> {
let mut tx = rb.acquire_begin().await?.defer_async(|mut tx1| async move {
if !tx1.is_done() {
tx1.rollback().await;
println!("tx rollback success!");
} else {
println!("don't need rollback!");
}
});
let v = tx
.exec("update biz_activity set name = '6' where id = 1;", &vec![])
.await;
return Ok(());
}
How to use rbatis with Rust web frameworks (actix-web is used here as an example, but all web frameworks based on tokio or async_std are supported)
#[macro_use]
extern crate lazy_static;
lazy_static! {
static ref RB:Rbatis=Rbatis::new();
}
async fn index() -> impl Responder {
let v:Result<i32,rbatis::core::Error> = RB.fetch( "SELECT count(1) FROM biz_activity;",&vec![]).await;
HttpResponse::Ok().body(format!("count(1)={}",v.unwrap_or(0)))
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
fast_log::init_log("requests.log", log::Level::Info, None, true);
RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8000")?
.run()
.await
}
Progress - in sequential order
function |
is supported |
CRUD, with built-in CRUD template (built-in CRUD supports logical deletes) |
√ |
LogSystem (logging component) |
√ |
Tx(task/Nested transactions) |
√ |
Py(using py-like statement in SQL) |
√ |
async/await support |
√ |
PagePlugin(Pagincation) |
√ |
LogicDelPlugin |
√ |
Html(xml) Compile time dynamic SQL) |
√ |
DataBase Table ConvertPage(Web UI,Coming soon) |
x |
- Conlusion: Assuming zero time consumed on IO, single threaded benchmark achieves 200K QPS or QPS, which is a few times
more performant than GC languages like Go or Java.
FAQ
- Postgres Types Define Please see Doc
中文
English Doc
- Support for DateTime and BigDecimal?
Currently supports chrono::rbatis::DateTimeNative和bigdecimal::BigDecimal
- Supports for
async/.await
Currently supports both async_std
and tokio
- Stmt in postgres uses $1, $2 instead of ? in Mysql, does this require some special treatment? No, because rbatis uses
#{} to describe parametric variabls, you only need to write the correct parameter names and do not need to match it
with the symbols used by the database.
- Supports for Oracle database driver?
No, moving away from IOE is recommended.
- Which crate should be depended on if only the driver is needed?
rbatis-core, Cargo.toml add rbatis-core = "*"
- How to select
async/.await
runtime?
see https://rbatis.github.io/rbatis.io/#/en/
- column "id" is of type uuid but expression is of type text'?
see https://rbatis.github.io/rbatis.io/#/en/?id=database-column-formatting-macro
- How to use '::uuid','::timestamp' on PostgreSQL?
see https://rbatis.github.io/rbatis.io/#/en/?id=database-column-formatting-macro
changelog
changelog
roadmap
roadmap
Related Projects
Contact/donation, or click on star rbatis
联系方式/捐赠,或 rbatis 点star
捐赠
联系方式(添加好友请备注'rbatis') 微信群:先加微信,然后拉进群