A highly Performant,Safe,Dynamic SQL 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
used json with serde_json for passing parameters and communication
high performance, single threaded benchmark can easily achieve 200,000 QPS - data returned from database directly (
zero lookup time) on a Windows 10 6 core i7 with 16 GB memory machine. Performace will be better using multiple
threads, and it outperforms Go's GoMyBatis.
supports logical deletes, pagination, py-like SQL and basic Mybatis functionalities.
supports future,(in theory, if all io operations are replaced with async_std/tokio, it could achieve higher
concurrency than Go-lang)
supports logging, customizable logging based on log crate
used 100% safe Rust with #![forbid(unsafe_code)] enabled
# add this library,and cargo install
# json(required)
serde = { version = "1", features = ["derive"]}
serde_json = "1"
# Date time (required)
chrono = { version = "0.4", features = ["serde"]}
# logging lib(required)
log = "0.4"
fast_log="1.3"
# BigDecimal lib(optional)
bigdecimal = "0.2"
# rbatis lib(required)
rbatis = { version = "1.8"}
Quick example: QueryWrapper and common usages (see example/crud_test.rs for details)
#[macro_use]externcrate rbatis;use rbatis::crud::CRUD;/// may also write `CRUDTable` as `impl CRUDTable for BizActivity{}`/// #[crud_enable( table_name:biz_activity)]/// #[crud_enable(id_name:"id"|id_type:"String"|table_name:"biz_activity"|table_columns:"id,name,version,delete_flag"|formats_pg:"id:{}::uuid")]#[crud_enable]#[derive(Clone,Debug)]pubstructBizActivity{pubid:Option<String>,pubname:Option<String>,pubpc_link:Option<String>,pubh5_link:Option<String>,pubpc_banner_img:Option<String>,pubh5_banner_img:Option<String>,pubsort:Option<String>,pubstatus:Option<i32>,pubremark:Option<String>,pubcreate_time:Option<NaiveDateTime>,pubversion:Option<i32>,pubdelete_flag:Option<i32>,}// (optional) manually implement instead of using `derive(CRUDTable)`. This allows manually rewriting `table_name()` function and supports code completion in IDE.// use rbatis::crud::CRUDTable;//impl CRUDTable for BizActivity {// type IdType = String; // fn table_name()->String{// "biz_activity".to_string()// }// fn table_columns()->String{// "id,name,delete_flag".to_string()// }//}#[async_std::main]asyncfnmain(){/// initialize rbatis. May use `lazy_static` crate to define rbatis as a global variable because rbatis is thread safelet rb = Rbatis::new();/// connect to database
rb.link("mysql://root:123456@localhost:3306/test").await.unwrap();/// customize connection pool parameters (optional)// let mut opt =PoolOptions::new();// opt.max_size=100;// rb.link_opt("mysql://root:123456@localhost:3306/test",&opt).await.unwrap();/// newly constructed wrapper sql logiclet wrapper = rb.new_wrapper().eq("id",1)//sql: id = 1.and()//sql: and .ne("id",1)//sql: id <> 1.in_array("id",&[1,2,3])//sql: id in (1,2,3).not_in("id",&[1,2,3])//sql: id not in (1,2,3).like("name",1)//sql: name like 1.or()//sql: or.not_like("name","asdf")//sql: name not like 'asdf'.between("create_time","2020-01-01 00:00:00","2020-12-12 00:00:00")//sql: create_time between '2020-01-01 00:00:00' and '2020-01-01 00:00:00'.group_by(&["id"])//sql: group by id.order_by(true,&["id","name"])//sql: group by id,name;let activity = BizActivity{id:Some("12312".to_string()),name:None,remark:None,create_time:Some(NaiveDateTime::now()),version:Some(1),delete_flag:Some(1),};/// saving
rb.save("",&activity).await;//Exec ==> INSERT INTO biz_activity (create_time,delete_flag,h5_banner_img,h5_link,id,name,pc_banner_img,pc_link,remark,sort,status,version) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )/// batch saving
rb.save_batch("",&vec![activity]).await;//Exec ==> INSERT INTO biz_activity (create_time,delete_flag,h5_banner_img,h5_link,id,name,pc_banner_img,pc_link,remark,sort,status,version) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ),( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )/// The query, Option wrapper, is None if the data is not foundlet result:Option<BizActivity> = rb.fetch_by_id("",&"1".to_string()).await.unwrap();//Query ==> SELECT create_time,delete_flag,h5_banner_img,h5_link,id,name,pc_banner_img,pc_link,remark,sort,status,version FROM biz_activity WHERE delete_flag = 1 AND id = ? /// query alllet result:Vec<BizActivity> = rb.list("").await.unwrap();//Query ==> SELECT create_time,delete_flag,h5_banner_img,h5_link,id,name,pc_banner_img,pc_link,remark,sort,status,version FROM biz_activity WHERE delete_flag = 1///query by id veclet result:Vec<BizActivity> = rb.list_by_ids("",&["1".to_string()]).await.unwrap();//Query ==> SELECT create_time,delete_flag,h5_banner_img,h5_link,id,name,pc_banner_img,pc_link,remark,sort,status,version FROM biz_activity WHERE delete_flag = 1 AND id IN (?) ///query by wrapperlet w = rb.new_wrapper().eq("id","1");let r:Result<Option<BizActivity>,Error> = rb.fetch_by_wrapper("",&w).await;//Query ==> SELECT create_time,delete_flag,h5_banner_img,h5_link,id,name,pc_banner_img,pc_link,remark,sort,status,version FROM biz_activity WHERE delete_flag = 1 AND id = ? ///delete
rb.remove_by_id::<BizActivity>("",&"1".to_string()).await;//Exec ==> UPDATE biz_activity SET delete_flag = 0 WHERE id = 1///delete batch
rb.remove_batch_by_id::<BizActivity>("",&["1".to_string(),"2".to_string()]).await;//Exec ==> UPDATE biz_activity SET delete_flag = 0 WHERE id IN ( ? , ? ) ///updatelet w = rb.new_wrapper().eq("id","12312");
rb.update_by_wrapper("",&activity,&w).await;//Exec ==> UPDATE biz_activity SET create_time = ? , delete_flag = ? , status = ? , version = ? WHERE id = ? }///...more usage,see crud.rs
macros (new addition)
lazy_static!{static ref RB:Rbatis=Rbatis::new();
}/// Macro generates execution logic based on method definition, similar to @select dynamic SQL of Java/Mybatis/// RB is the name referenced locally by Rbatis, for example DAO ::RB, com:: XXX ::RB... Can be/// The second parameter is the standard driver SQL. Note that the corresponding database parameter mysql is? , pg is $1.../// macro auto edit method to 'pub async fn select(name: &str) -> rbatis::core::Result<BizActivity> {}'///#[sql(RB, "select * from biz_activity where id = ?")]pubasyncfnselect(name:&str) -> BizActivity{}//or: pub async fn select(name: &str) -> rbatis::core::Result<BizActivity> {}#[async_std::test]pubasyncfntest_macro(){
fast_log::init_log("requests.log",1000, log::Level::Info,None,true);RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();let a = select("1").await.unwrap();println!("{:?}", a);}
lazy_static!{static ref RB:Rbatis=Rbatis::new();
}#[py_sql(RB, "select * from biz_activity where id = #{name} if name != '': and name=#{name}")]pubasyncfnpy_select(name:&str) -> Option<BizActivity>{}//or: pub async fn select(name: &str) -> rbatis::core::Result<BizActivity> {}#[async_std::test]pubasyncfntest_macro_py_select(){
fast_log::init_log("requests.log",1000, log::Level::Info,None,true);RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();let a = py_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**())
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
√
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.
Support for DateTime and BigDecimal?
Currently supports chrono::NaiveDateTime和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 = "*"
In order to achieve the satisfaction of this ORM framework, your support is always our motivation, we are eager to welcome WeChat to donate to support us ~ or ~ star at the top right corner