A highly performant ORM framework written in Rust, inspired by Mybatis and MybatisPlus.

Dynamic SQL, no runtime, no Garbage Collector, and low memory use.

Supports async_std, tokio

This crate uses #![forbid(unsafe_code)] to ensure everything is implemented in 100% safe Rust.

Build Status

Image text

Why not diesel or not sqlx ?

| Framework | Async/.await | Learning curve | Supports for xml/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 |

Example Rust backend service https://github.com/rbatis/abs_admin
Example Cargo.toml

``` rust

add this library,and cargo install

json (required)

serde = { version = "1.0", features = ["derive"] } serde_json = "1.0"

Date time (required)

chrono = { version = "0.4", features = ["serde"] }

logging (required)

log = "0.4" fast_log="1.2.2"

BigDecimal (optional)

bigdecimal = "0.2"

rbatis, must maintain the same versions (required)

rbatis-core = { version = "1.7.2", features = ["all"]} rbatis = { version = "1.7.2" } rbatis-macro-driver = { version = "1.7.2" }

```

Quick example: QueryWrapper and common usages (see example/crud_test.rs for details)

```rust

[macro_use]

extern crate rbatismacrodriver; /// may also write CRUDEnable as impl CRUDEnable for BizActivity{}

[derive(CRUDEnable,Serialize, Deserialize, 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, }

// (optional) manually implement instead of using derive(CRUDEnable). This allows manually rewriting table_name() function and supports code completion in IDE. //impl CRUDEnable for BizActivity { // type IdType = String;
// fn tablename()->String{ // "bizactivity".tostring() // } // fn tablecolumns()->String{ // "id,name,deleteflag".tostring() // } //}

[actix_rt::main]

async fn main() { /// initialize rbatis. May use lazy_static 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("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("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 .check().unwrap();

let activity = BizActivity { id: Some("12312".tostring()), name: None, remark: None, createtime: Some(NaiveDateTime::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 ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ),( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )

/// The query, Option wrapper, is None if the data is not found let result: Option = rb.fetchbyid("", &"1".tostring()).await.unwrap(); //Query ==> SELECT createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version FROM bizactivity WHERE delete_flag = 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.listbyids("",&["1".tostring()]).await.unwrap(); //Query ==> SELECT createtime,deleteflag,h5bannerimg,h5link,id,name,pcbannerimg,pclink,remark,sort,status,version FROM bizactivity WHERE delete_flag = 1 AND id IN (?)

///query by wrapper let w = rb.newwrapper().eq("id", "1").check().unwrap(); let r: Result, Error> = rb.fetchbywrapper("", &w).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.removebyid::("", &"1".tostring()).await; //Exec ==> UPDATE bizactivity SET delete_flag = 0 WHERE id = 1

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

///update let w = rb.newwrapper().eq("id", "12312").check().unwrap(); rb.updatebywrapper("", &activity, &w).await; //Exec ==> UPDATE bizactivity SET createtime = ? , deleteflag = ? , status = ? , version = ? WHERE id = ? }

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

macros (new addition)

```rust 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 = ?")]
fn select(name: &str) -> BizActivity {}
//or: pub async fn select(name: &str) -> rbatis_core::Result<BizActivity> {}

#[async_std::test]
pub async fn test_macro() {
    fast_log::log::init_log("requests.log", &RuntimeType::Std);
    RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();
    let a = select("1").await.unwrap();
    println!("{:?}", a);
}

rust lazy_static! { static ref RB:Rbatis=Rbatis::new(); }

#[py_sql(RB, "select * from biz_activity where id = #{name}
              if name != '':
                and name=#{name}")]
fn py_select(name: &str) -> Option<BizActivity> {}
//or: pub async fn select(name: &str) -> rbatis_core::Result<BizActivity> {}

#[async_std::test]
pub async fn test_macro_py_select() {
    fast_log::log::init_log("requests.log", &RuntimeType::Std);
    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**())

rust let mut rb = init_rbatis().await; //rb.logic_plugin = Some(Box::new(RbatisLogicDeletePlugin::new_opt("delete_flag",1,0)));//Customize deleted/undeleted writing rb.logic_plugin = Some(Box::new(RbatisLogicDeletePlugin::new("delete_flag"))); rb.link("mysql://root:123456@localhost:3306/test").await.unwrap(); let r = rb.remove_batch_by_id::<BizActivity>("", &["1".to_string(), "2".to_string()]).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(); //框架默认RbatisPagePlugin,如果需要自定义的话需要结构体 必须实现impl PagePlugin for Plugin*{},例如: //rb.page_plugin = Box::new(RbatisPagePlugin {});

    let req = PageRequest::new(1, 20);
    let wraper= rb.new_wrapper()
                .eq("delete_flag",1)
                .check()
                .unwrap();
    let data: Page<BizActivity> = rb.fetch_page_by_wrapper("", &wraper,  &req).await.unwrap();
    println!("{}", serde_json::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", "pclink": "null", "h5link": "null", "pcbannerimg": "null", "h5bannerimg": "null", "sort": "null", "status": 1, "remark": "null", "createtime": "2020-02-09T00:00:00+00:00", "version": 1, "deleteflag": 1 }], "total": 5, "size": 20, "current": 1, "serch_count": true } ```

py-like sql example

python //Execute to remote mysql and get the result. Supports any serializable type of SERde_JSON let rb = Rbatis::new(); rb.link("mysql://root:123456@localhost:3306/test").await.unwrap(); let py = r#" SELECT * FROM biz_activity WHERE delete_flag = #{delete_flag} if name != null: AND name like #{name+'%'} if ids != null: AND id in ( trim ',': for item in ids: #{item}, )"#; let data: serde_json::Value = rb.py_fetch("", py, &json!({ "delete_flag": 1 })).await.unwrap(); println!("{}", data);

logging system with fast_log here as an example

rust use log::{error, info, warn}; fn main(){ fast_log::log::init_log("requests.log", &RuntimeType::Std).unwrap(); 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(); } ```

How to use XML

``` rust /** * table */

[derive(Serialize, Deserialize, Debug, Clone)]

pub struct Activity { 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, } fn main() { asyncstd::task::blockon( async move { fastlog::log::initlog("requests.log", &RuntimeType::Std).unwrap(); let mut rb = Rbatis::new(); rb.link("mysql://root:123456@localhost:3306/test").await.unwrap(); //xml数据建议以 XXMapper.xml 的格式存储管理 rb.loadxml("test", r#" "#).unwrap();

           let arg = &json!({
           "delete_flag": 1,
           "name": "test",
           "startTime": null,
           "endTime": null,
           "page": 0,
           "size": 20
           });
           let data: Vec<BizActivity> = rb.xml_fetch("", "test", "select_by_condition", arg).await.unwrap();
           println!("{}", serde_json::to_string(&data).unwrap_or("".to_string()));
       }
   )

} //输出结果 //2020-06-27T03:13:40.422307200+08:00 INFO rbatis::rbatis - [rbatis] >> fetch sql: select * from bizactivity where name like ? order by createtime desc limit ? , ? (src\rbatis.rs:198) //2020-06-27T03:13:40.424307300+08:00 INFO rbatis::rbatis - [rbatis] >> fetch arg:["%test%",0,20] (src\rbatis.rs:199) //2020-06-27T03:13:40.446308900+08:00 INFO rbatis::rbatis - [rbatis] << 4 (src\rbatis.rs:234) //[{"id":"221","name":"test","pclink":"","h5link":"","pcbannerimg":null,"h5bannerimg":null,"sort":"0","status":0,"remark":"","createtime":"2020-06-17T20:10:23Z","version":0,"deleteflag":1},{"id":"222","name":"test","pclink":"","h5link":"","pcbannerimg":null,"h5bannerimg":null,"sort":"0","status":0,"remark":"","createtime":"2020-06-17T20:10:23Z","version":0,"deleteflag":1},{"id":"223","name":"test","pclink":"","h5link":"","pcbannerimg":null,"h5bannerimg":null,"sort":"0","status":0,"remark":"","createtime":"2020-06-17T20:10:23Z","version":0,"deleteflag":1},{"id":"178","name":"testinsret","pclink":"","h5link":"","pcbannerimg":null,"h5bannerimg":null,"sort":"1","status":1,"remark":"","createtime":"2020-06-17T20:08:13Z","version":0,"delete_flag":1}] ```

Async/.await task support

rust async_std::task::block_on(async { let rb = Rbatis::new(); rb.link("mysql://root:123456@localhost:3306/test").await.unwrap(); let tx_id = "1";//事务id号 rb.begin(tx_id).await.unwrap(); let v: serde_json::Value = rb.fetch(tx_id, "SELECT count(1) FROM biz_activity;").await.unwrap(); println!("{}", v.clone()); rb.commit(tx_id).await.unwrap(); });

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 lazy_static! { static ref RB:Rbatis=Rbatis::new(); }

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

[actix_rt::main]

async fn main() -> std::io::Result<()> { //日志 fastlog::log::initlog("requests.log", &RuntimeType::Std).unwrap(); //链接数据库 RB.link("mysql://root:123456@localhost:3306/test").await.unwrap(); //http路由 HttpServer::new(|| { App::new() .route("/", web::get().to(index)) }) .bind("127.0.0.1:8000")? .run() .await } ```

Supported data structures

| data structure | is supported | | ------ | ------ | | Option | √ | | Vec | √ |
| HashMap | √ |
| Slice | √ |
| i32,i64,f32,f64,bool,String...more rust type | √ |
| NativeDateTime | √ |
| BigDecimal | √ | | serde_json::Value...more serde type | √ |

Supported database √supported .WIP

| database | is supported | | ------ | ------ | | Mysql | √ |
| Postgres | √ |
| Sqlite | √ |
| TiDB | √ | | CockroachDB | √ |

Supported OS/Platforms

| platform | is supported | | ------ | ------ | | Linux | √ | | Apple/MacOS | √ |
| Windows | √ |

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 xml-equivalent statement in SQL) | √ | | async/await support | √ | | PagePlugin(Pagincation) | √ | | LogicDelPlugin | √ | | DataBaseConvertPlugin(The database table structure is converted to the configuration plug-in) | x | | web(Web UI) | x |

FAQ

rbatis-core = { features = ["runtime-async-std","all-type"]}

add the line below in Cargo.toml to used async_std

rbatis-core = { features = ["runtime-tokio","all-type"]}

```

Related Projects

TODO and upcoming features

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

为了称心如意的ORM框架,您的支持永远是我们的动力,迫切欢迎微信捐赠支持我们 ~或者~右上角点下star

Image text