WebSite | 简体中文

Build Status doc.rs unsafe forbidden
dependency status GitHub release Gitter

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 |

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

``` rust

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)

```rust //#[macro_use] define in 'root crate' or 'mod.rs' or 'main.rs'

[macro_use]

extern crate rbatis;

use rbatis::crud::CRUD;

/// may also write CRUDTable as impl CRUDTable for BizActivity{} /// #[crudtable] /// #[crudtable(tablename:bizactivity)] /// #[crudtable(tablename:"bizactivity"|tablecolumns:"id,name,version,deleteflag")] /// #[crudtable(tablename:"bizactivity"|tablecolumns:"id,name,version,deleteflag"|formats_pg:"id:{}::uuid")]

[crud_table]

[derive(Clone, Debug)]

pub struct BizActivity { pub id: Option, pub name: Option, pub pclink: Option, pub h5link: Option, pub pcbannerimg: Option, pub h5bannerimg: Option, pub sort: Option, pub status: Option, pub remark: Option, pub createtime: Option, pub version: Option, pub deleteflag: Option, }

// this macro will create impl BizActivity{ pub fn id()->&str ..... } implfieldname_method!(BizActivity{id,name});

/// (optional) manually implement instead of using derive(CRUDTable). This allows manually rewriting table_name() function and supports code completion in IDE. /// (option) but this struct require #[derive(Serialize,Deserialize)] // use rbatis::crud::CRUDTable; //impl CRUDTable for BizActivity { // fn tablename()->String{ // "bizactivity".tostring() // } // fn tablecolumns()->String{ // "id,name,deleteflag".tostring() // } //}

[tokio::main]

async fn main() { /// enable log crate to show sql logs fastlog::init(fastlog::config::Config::new().console()); /// initialize rbatis. May use lazy_static crate to define rbatis as a global variable because rbatis is thread safe let 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.maxsize=100; // rb.linkopt("mysql://root:123456@localhost:3306/test",&opt).await.unwrap(); /// newly constructed wrapper sql logic let wrapper = rb.newwrapper() .eq("id", 1) //sql: id = 1 .and() //sql: and .ne(BizActivity::id(), 1) //sql: id <> 1 .inarray("id", &[1, 2, 3]) //sql: id in (1,2,3) .notin("id", &[1, 2, 3]) //sql: id not in (1,2,3) .like("name", 1) //sql: name like 1 .or() //sql: or .notlike(BizActivity::name(), "asdf") //sql: name not like 'asdf' .between("createtime", "2020-01-01 00:00:00", "2020-12-12 00:00:00")//sql: createtime between '2020-01-01 00:00:00' and '2020-01-01 00:00:00' .groupby(&["id"]) //sql: group by id .orderby(true, &["id", "name"])//sql: group by id,name ;

let activity = BizActivity { id: Some("12312".tostring()), name: None, pclink: None, h5link: None, pcbannerimg: None, h5bannerimg: None, sort: None, status: None, remark: None, createtime: Some(rbatis::DateTimeNative::now()), version: Some(1), deleteflag: Some(1), }; /// saving rb.save(&activity, &[]).await; //Exec ==> INSERT INTO bizactivity (createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )

/// batch saving rb.savebatch(&vec![activity], &[]).await; //Exec ==> INSERT INTO bizactivity (createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ),( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )

/// fetch allow None or one result. column you can use BizActivity::id() or "id" let result: Option = rb.fetchbycolumn(BizActivity::id(), "1").await.unwrap(); //Query ==> SELECT createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version FROM bizactivity WHERE deleteflag = 1 AND id = ?

/// query all let result: Vec = rb.list().await.unwrap(); //Query ==> SELECT createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version FROM bizactivity WHERE deleteflag = 1

///query by id vec let result: Vec = rb.listbycolumn("id", &["1"]).await.unwrap(); //Query ==> SELECT createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version FROM bizactivity WHERE deleteflag = 1 AND id IN (?)

///query by wrapper let r: Result, Error> = rb.fetchbywrapper(rb.newwrapper().eq("id", "1")).await; //Query ==> SELECT createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version FROM bizactivity WHERE delete_flag = 1 AND id = ?

///delete rb.removebycolumn::("id", &"1").await; //Exec ==> UPDATE bizactivity SET deleteflag = 0 WHERE id = 1

///delete batch rb.removebatchbycolumn::("id", &["1", "2"]).await; //Exec ==> UPDATE bizactivity SET delete_flag = 0 WHERE id IN ( ? , ? )

///update let mut activity = activity.clone(); let r = rb.updatebycolumn("id", &activity).await; //Exec ==> update bizactivity set status = ?, createtime = ?, version = ?, deleteflag = ? where id = ? rb.updatebywrapper(&activity, rb.newwrapper().eq("id", "12312"), &[Skip::Value(&serdejson::Value::Null), Skip::Column("id")]).await; //Exec ==> UPDATE bizactivity SET createtime = ? , deleteflag = ? , status = ? , version = ? WHERE id = ? }

///...more usage,see crud.rs ```

macros (new addition)

Because of the compile time, the annotations need to declare the database type to be used

rust #[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!() }

Because of the compile time, the annotations need to declare the database type to be used

html <!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>

rust ///select page must have '?:&PageRequest' arg and return 'Page<?>' #[html_sql(rb, "example/example.html")] async fn select_by_condition(rb: &mut RbatisExecutor<'_,'_>, page_req: &PageRequest, name: &str) -> Page<BizActivity> { todo!() }

```rust use once_cell::sync::Lazy; pub static RB:Lazy = Lazy::new(||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 {}' ///

[sql(RB, "select * from biz_activity where id = ?")]

pub async fn select(name: &str) -> BizActivity {} //or: pub async fn select(name: &str) -> rbatis::core::Result {}

[tokio::test]

pub async fn testmacro() { fastlog::init(fast_log::config::Config::new().console()); 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**())

rust let mut rb:Rbatis=Rbatis::new(); //rb.logic_plugin = Some(Box::new(RbatisLogicDeletePlugin::new_opt("delete_flag",1,0)));//Customize deleted/undeleted writing 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

```rust let mut rb = Rbatis::new(); rb.link("mysql://root:123456@localhost:3306/test").await.unwrap(); //replace page plugin //rb.page_plugin = Box::new(RbatisPagePlugin::new());

let req = PageRequest::new(1, 20); let wraper= rb.newwrapper() .eq("deleteflag", 1); let data: Page = rb.fetchpagebywrapper( & wraper, & req).await.unwrap(); println!("{}", serdejson::to_string(&data).unwrap());

//2020-07-10T21:28:40.036506700+08:00 INFO rbatis::rbatis - [rbatis] Query ==> SELECT count(1) FROM bizactivity WHERE deleteflag = ? LIMIT 0,20 //2020-07-10T21:28:40.040505200+08:00 INFO rbatis::rbatis - [rbatis] Args ==> [1] //2020-07-10T21:28:40.073506+08:00 INFO rbatis::rbatis - [rbatis] Total <== 1 //2020-07-10T21:28:40.073506+08:00 INFO rbatis::rbatis - [rbatis] Query ==> SELECT createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version FROM bizactivity WHERE deleteflag = ? LIMIT 0,20 //2020-07-10T21:28:40.073506+08:00 INFO rbatis::rbatis - [rbatis] Args ==> [1] //2020-07-10T21:28:40.076506500+08:00 INFO rbatis::rbatis - [rbatis] Total <== 5 ```

json { "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

rust use log::{error, info, warn}; fn main(){ fast_log::init(fast_log::config::Config::new().console()); info!("print data"); }

Customize connection pool's size, timeout, active number of connections, and etc.

```rust use rbatis::core::db::PoolOptions;

pub async fn initrbatis() -> Rbatis { let rb = Rbatis::new(); let mut opt = PoolOptions::new(); opt.maxsize = 20; rb.link_opt("mysql://root:123456@localhost:3306/test", &opt).await.unwrap(); } ```

Transaction

rust 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()); //rb.fetch**** and more method tx.commit().await.unwrap(); //tx.begin().await

Transaction defer

rust pub async fn forget_commit(rb: &Rbatis) -> rbatis::core::Result<()> { // tx will be commit.when func end 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; //tx.commit().await; //if commit, print 'don't need rollback!' ,if not,print 'tx rollback success!' 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)

``` rust

[macro_use]

use once_cell::sync::Lazy; pub static RB:Lazy = Lazy::new(||Rbatis::new());

async fn index() -> impl Responder { let v:Result = RB.fetch( "SELECT count(1) FROM bizactivity;",&vec![]).await; HttpResponse::Ok().body(format!("count(1)={}",v.unwrapor(0))) }

[actix_rt::main]

async fn main() -> std::io::Result<()> { //log fastlog::init(fastlog::config::Config::new().console()); //link database RB.link("mysql://root:123456@localhost:3306/test").await.unwrap(); //http server 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 |

FAQ

中文

English Doc

changelog

changelog

roadmap

roadmap

Related Projects

Contact/donation, or click on star rbatis

联系方式/捐赠,或 rbatis 点star

捐赠

zxj347284221

联系方式(添加好友请备注'rbatis') 微信群:先加微信,然后拉进群

zxj347284221