WebSite | 简体中文

Build Status doc.rs dependency status GitHub release

A highly Performant,Safe,Dynamic SQL(Compile time) ORM framework written in Rust, inspired by Mybatis and MybatisPlus.

Image text

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 |

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", 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 = "2.0" } ```

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, }

// (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 tablename()->String{ // "bizactivity".tostring() // } // fn tablecolumns()->String{ // "id,name,deleteflag".tostring() // } //}

[tokio::main]

async fn main() { /// enable log crate to show sql logs fastlog::initlog("requests.log", 1000, log::Level::Info, None, true); /// 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("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 ;

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.fetchbycolumn("id",&"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.listbycolumn("id",&["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"); 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.removebycolumn::("id", &"1".tostring()).await; //Exec ==> UPDATE bizactivity SET delete_flag = 0 WHERE id = 1

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

///update ///if versionlock plugin actived,update method will modify field 'version'= version + 1 let mut activity = activity.clone(); let w = rb.newwrapper().eq("id", "12312"); rb.updatebywrapper( &mut activity, &w, &[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!() } * 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

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

[macro_use]

extern crate lazy_static;

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 {}' ///

[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_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); } ```

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.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(); //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_log("requests.log", 1000, log::Level::Info, None, true); 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<serde_json::Value> { // tx will be commit.when func end let tx = rb .acquire_begin().await? .defer_async(|tx| async { tx.rollback().await; }); /// or use defer ///.defer(|tx|{ /// println!("tx is drop!"); /// async_std::task::block_on(async{ tx.rollback().await; }); ///}); let v: serde_json::Value = tx .fetch( "select count(1) from biz_activity;",&vec![]) .await?; return Ok(v); }

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]

extern crate lazystatic; lazystatic! { static ref RB:Rbatis=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::initlog("requests.log", 1000, log::Level::Info, None, true); //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 } ```

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 | √ |
| Mssql/Sqlserver | √ |
| MariaDB(Mysql) | √ | | TiDB(Mysql) | √ | | CockroachDB(Postgres) | √ |

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 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 * 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 = "*" * 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

Related Projects

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

微信捐赠支持我们 ~或者~右上角点下star

Image text