Skip to content
zxj edited this page Nov 24, 2025 · 4 revisions

rbatis-v4

A highly Performant SQL Toolkit and Compile time ORM Library. An async, pure Rust SQL crate featuring compile-time Dynamic SQL

It is an ORM, a small compiler, a dynamic SQL languages

  • Compatible with most mybatis3 syntax.You can start recoding Java projects into Rust!
  • No Runtimes,No Garbage Collection,High performance, Based on Future/Tokio
  • Zero cost Dynamic SQL, implemented using (proc-macro,compile-time,Cow(Reduce unnecessary cloning)) techniques。 don't need ONGL engine(mybatis)
  • JDBC-like driver design, driver use cargo.toml dependency and Box<dyn Driver> separation
  • All database drivers supported #{arg}, ${arg},? placeholder(pg/mssql auto processing '?' to '$1' and '@P1')
  • Dynamic SQL(Write code freely in SQL),pagination, py_sql query lang and html_sql(Inspired Mybatis).
  • Dynamic configuration connection pool(Based on the https://github.com/rbatis/fast_pool)
  • Supports Logging based on interceptor implementation
  • 100% Safe pure Rust with #![forbid(unsafe_code)] enabled
  • rbatis/example
  • abs_admin project an background user management system(Vue.js+rbatis+axum)
  • salvo_admin project an background permission management system(react+rbatis+salvo)

Supported database driver

the RBatis support any impl rdbc drivers. If you don't have the following driver you want, you can write one yourself, just as long as the impl rbdc::db::* traits

database(crates.io) github_link
Mysql rbatis/rbdc-mysql
Postgres rbatis/rbdc-pg
Sqlite rbatis/rbdc-sqlite
Mssql rbatis/rbdc-mssql
MariaDB rbatis/rbdc-mysql
TiDB rbatis/rbdc-mysql
CockroachDB rbatis/rbdc-pg
Oracle chenpengfan/rbdc-oracle
TDengine tdcare/rbdc-tdengine

CRUD-install/use

  • install step: Cargo.toml(run command cargo update)

  • toml(default)

#rbatis deps
rbs = { version = "4.6"}
rbatis = { version = "4.6"}
rbdc-sqlite = { version = "4.6" }
#rbdc-mysql={version="4.6"}
#rbdc-pg={version="4.6"}
#rbdc-mssql={version="4.6"}

serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
fast_log = "1.6"
  • toml native-tls (option)
rbs = { version = "4.6" }
rbdc-sqlite = { version = "4.6", default-features = false, features = ["tls-native-tls"] }
#rbdc-mysql={version="4.6", default-features = false, features = ["tls-native-tls"]}
#rbdc-pg={version="4.6", default-features = false, features = ["tls-native-tls"]}
#rbdc-mssql={version="4.6", default-features = false, features = ["tls-native-tls"]}
rbatis = { version = "4.6" }

serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
fast_log = "1.6"
TableDefine

RBatis follows a clean code style,so that A database table structure is just a normal structure that may use the database types provided by RBatis We use the crud!() macro impl_*!() macro Enables the table structure to have the ability to query and modify the database

crud! macros contains several impl_**() macros,so. crud! is a superset of macros impl_*!()

use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
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<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}
//crud = async fn insert(...)+async fn  select_by_map(...)+ async fn  update_by_map(...)+async fn  delete_by_map(...)...and more
rbatis::crud!(BizActivity {}); 
custom table_name

rbatis allow custom your table_name, crud macro and impl_*() macro is different just like sql select * from ${table_name}

rbatis::crud!(BizActivity {},"biz_activity"); // this way custom table name
rbatis::impl_select!(BizActivity{select_by_id(id:String) -> Option => "`where id = #{id} limit 1`"},"biz_activity");// this way custom table_name/table_column
rbatis::impl_select!(BizActivity{select_by_id2(table_name:&str,id:String) -> Option => "`where id = #{id} limit 1`"});// this way custom table_name/table_column
custom table_column

rbatis allow custom your table_column,it's support any rbatis::impl_*!() macros
just like sql select ${table_column} from ${table_name}

rbatis::impl_select!(BizActivity{select_by_id(table_name:&str,table_column:&str,id:String) -> Option => "`where id = #{id} limit 1`"});
macros-insert
//#[macro_use] define in 'root crate' or 'mod.rs' or 'main.rs'
#[macro_use]
extern crate rbatis;


use rbatis::rbdc::datetime::DateTime;
use serde_json::json;

/// table
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
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<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}

impl_insert!(BizActivity{});

#[tokio::main]
async fn main() {
    /// enable log crate to show sql logs
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    /// initialize rbatis. also you can call rb.clone(). this is  an Arc point
    let rb = RBatis::new();
    /// connect to database 

    //init() just set driver
    //rb.init(rbdc_sqlite::driver::SqliteDriver {}, "sqlite://target/sqlite.db" ).unwrap();
    
    // link() will set driver and try use acquire() link database
    // sqlite 
    rb.link(SqliteDriver {}, "sqlite://target/sqlite.db").await.unwrap();
    // mysql 
    // rb.link(MysqlDriver{},"mysql://root:123456@localhost:3306/test").await.unwrap();
    // postgresql 
    // rb.link(PgDriver{},"postgres://postgres:123456@localhost:5432/postgres").await.unwrap();
    // mssql/sqlserver
    // rb.link(MssqlDriver{},"jdbc:sqlserver://localhost:1433;User=SA;Password={TestPass!123456};Database=test").await.unwrap();

    let table = BizActivity {
        id: Some("2".into()),
        name: Some("2".into()),
        pc_link: Some("2".into()),
        h5_link: Some("2".into()),
        pc_banner_img: None,
        h5_banner_img: None,
        sort: Some("2".to_string()),
        status: Some(2),
        remark: Some("2".into()),
        create_time: Some(DateTime::now()),
        version: Some(1),
        delete_flag: Some(1),
    };
    let tables = [table.clone(), {
        let mut t3 = table.clone();
        t3.id = "3".to_string().into();
        t3
    }];

    let data = BizActivity::insert(&rb, &table).await;
    println!("insert = {}", json!(data));

    let data = BizActivity::insert_batch(&rb, &tables, 10).await;
    println!("insert_batch = {}", json!(data));
}
macros-update
//#[macro_use] define in 'root crate' or 'mod.rs' or 'main.rs'
#[macro_use]
extern crate rbatis;


use rbatis::rbdc::datetime::DateTime;
use serde_json::json;

#[derive(Clone, Debug, Serialize, Deserialize)]
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<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}
crud!(BizActivity{});
impl_update!(BizActivity{update_by_name(name:&str) => "`where id = '2'`"});

#[tokio::main]
async fn main() {
    /// enable log crate to show sql logs
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    /// initialize rbatis. also you can call rb.clone(). this is  an Arc point
    let rb = RBatis::new();
    /// connect to database 

    //init() just set driver
    //rb.init(rbdc_sqlite::driver::SqliteDriver {}, "sqlite://target/sqlite.db" ).unwrap();
    
    // link() will set driver and try use acquire() link database
    // sqlite 
    rb.link(SqliteDriver {}, "sqlite://target/sqlite.db").await.unwrap();
    // mysql 
    // rb.link(MysqlDriver{},"mysql://root:123456@localhost:3306/test").await.unwrap();
    // postgresql 
    // rb.link(PgDriver{},"postgres://postgres:123456@localhost:5432/postgres").await.unwrap();
    // mssql/sqlserver
    // rb.link(MssqlDriver{},"jdbc:sqlserver://localhost:1433;User=SA;Password={TestPass!123456};Database=test").await.unwrap();

    let table = BizActivity {
        id: Some("2".into()),
        name: Some("2".into()),
        pc_link: Some("2".into()),
        h5_link: Some("2".into()),
        pc_banner_img: None,
        h5_banner_img: None,
        sort: None,
        status: Some(2),
        remark: Some("2".into()),
        create_time: Some(DateTime::now()),
        version: Some(1),
        delete_flag: Some(1),
    };

    let data = BizActivity::update_by_map(&rb, &table, value!{"id":&table.id}).await;
    println!("update_by_map = {}", json!(data));

    let data = BizActivity::update_by_name(&rb, &table, "2").await;
    println!("update_by_name = {}", json!(data));

}
macros-select
//#[macro_use] define in 'root crate' or 'mod.rs' or 'main.rs'
#[macro_use]
extern crate rbatis;


use rbatis::rbdc::datetime::DateTime;
use serde_json::json;

#[derive(Clone, Debug, Serialize, Deserialize)]
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<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}
crud!(BizActivity{});//crud = insert+select_by_map+update_by_map+delete_by_map
impl_select!(BizActivity{select_all_by_id(id:&str,name:&str) => "`where id = #{id} and name = #{name}`"});
impl_select!(BizActivity{select_by_id(id:&str) -> Option => "`where id = #{id} limit 1`"});


#[tokio::main]
async fn main() {
    /// enable log crate to show sql logs
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    /// initialize rbatis. also you can call rb.clone(). this is  an Arc point
    let rb = RBatis::new();
    /// connect to database 

    //init() just set driver
    //rb.init(rbdc_sqlite::driver::SqliteDriver {}, "sqlite://target/sqlite.db" ).unwrap();
    
    // link() will set driver and try use acquire() link database
    // sqlite 
    rb.link(SqliteDriver {}, "sqlite://target/sqlite.db").await.unwrap();
    // mysql 
    // rb.link(MysqlDriver{},"mysql://root:123456@localhost:3306/test").await.unwrap();
    // postgresql 
    // rb.link(PgDriver{},"postgres://postgres:123456@localhost:5432/postgres").await.unwrap();
    // mssql/sqlserver
    // rb.link(MssqlDriver{},"jdbc:sqlserver://localhost:1433;User=SA;Password={TestPass!123456};Database=test").await.unwrap();

    let data = BizActivity::select_by_map(&rb, value!{"id":"1"}).await;
    println!("select_by_map = {}", json!(data));
    
    let data = BizActivity::select_all_by_id(&rb, "1", "1").await;
    println!("select_all_by_id = {}", json!(data));

    let data = BizActivity::select_by_id(&rb, "1").await;
    println!("select_by_id = {}", json!(data));
}
macros-select-page
//#[macro_use] define in 'root crate' or 'mod.rs' or 'main.rs'
#[macro_use]
extern crate rbatis;

use rbatis::rbdc::datetime::DateTime;
use rbatis::sql::page::PageRequest;
use serde_json::json;

#[derive(Clone, Debug, Serialize, Deserialize)]
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<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}

impl_select_page!(BizActivity{select_page() =>"
     if !sql.contains('count(1)'):
       `order by create_time desc`"});
impl_select_page!(BizActivity{select_page_by_name(name:&str) =>"
     if name != null && name != '':
       `where name != #{name}`
     if name == '':
       `where name != ''`"});

/// postgres/mssql database not support `limit 0,10`,you should use limit_sql:&str and set `limit 10 offset 0`
impl_select_page!(BizActivity{select_page_by_limit(name:&str,limit_sql:&str) => "`where name != #{name}`"});

#[tokio::main]
async fn main() {
    /// enable log crate to show sql logs
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    /// initialize rbatis. also you can call rb.clone(). this is  an Arc point
    let rb = RBatis::new();
    /// connect to database 

    //init() just set driver
    //rb.init(rbdc_sqlite::driver::SqliteDriver {}, "sqlite://target/sqlite.db" ).unwrap();
    
    // link() will set driver and try use acquire() link database
    // sqlite 
    rb.link(SqliteDriver {}, "sqlite://target/sqlite.db").await.unwrap();
    // mysql 
    // rb.link(MysqlDriver{},"mysql://root:123456@localhost:3306/test").await.unwrap();
    // postgresql 
    // rb.link(PgDriver{},"postgres://postgres:123456@localhost:5432/postgres").await.unwrap();
    // mssql/sqlserver
    // rb.link(MssqlDriver{},"jdbc:sqlserver://localhost:1433;User=SA;Password={TestPass!123456};Database=test").await.unwrap();

    let data = BizActivity::select_page(&rb, &PageRequest::new(1, 10)).await;
    println!("select_page = {}", json!(data));

    let data = BizActivity::select_page_by_name(&rb, &PageRequest::new(1, 10), "").await;
    println!("select_page_by_name = {}", json!(data));

    let data = BizActivity::select_page_by_limit(&rb, &PageRequest::new(1, 10), "2", " limit 0,10 ").await;
    println!("select_page_by_limit = {}", json!(data));
}
macros-delete
//#[macro_use] define in 'root crate' or 'mod.rs' or 'main.rs'
#[macro_use]
extern crate rbatis;

use rbatis::rbdc::datetime::DateTime;
use serde_json::json;

#[derive(Clone, Debug, Serialize, Deserialize)]
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<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}
crud!(BizActivity{});//crud = insert+select_by_map+update_by_map+delete_by_map

impl_delete!(BizActivity {delete_by_name(name:&str) => "`where name= '2'`"});

#[tokio::main]
async fn main() {
    /// enable log crate to show sql logs
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    /// initialize rbatis. also you can call rb.clone(). this is  an Arc point
    let rb = RBatis::new();
    /// connect to database 

    //init() just set driver
    //rb.init(rbdc_sqlite::driver::SqliteDriver {}, "sqlite://target/sqlite.db" ).unwrap();
    
    // link() will set driver and try use acquire() link database
    // sqlite 
    rb.link(SqliteDriver {}, "sqlite://target/sqlite.db").await.unwrap();
    // mysql 
    // rb.link(MysqlDriver{},"mysql://root:123456@localhost:3306/test").await.unwrap();
    // postgresql 
    // rb.link(PgDriver{},"postgres://postgres:123456@localhost:5432/postgres").await.unwrap();
    // mssql/sqlserver
    // rb.link(MssqlDriver{},"jdbc:sqlserver://localhost:1433;User=SA;Password={TestPass!123456};Database=test").await.unwrap();

    let data = BizActivity::delete_by_map(&rb, value!{"id": "2"}).await;
    println!("delete_by_map = {}", json!(data));

    let data = BizActivity::delete_by_name(&rb, "2").await;
    println!("delete_by_name = {}", json!(data));
}

debug_mode

if you open features on Cargo.toml "debug_mode", You will see the following features

  • show the project build Generated code(rbatis_codgen Generated code). you can see build log(............gen macro py_sql :............)
  • show the database rows data . you can see log(query <= len=1,rows=[{"id":1}])
  • show decoding invalid type Which field did the parsing fail. you can see error("invalid type: integer `1`, expected a string, key=`status`")

please notice, debug_mode should set log level to 'debug'

how to open debug_mode features on Cargo.toml?

rbatis = { version = "4",features = ["debug_mode"]}

need fast_log set level = Debug

#[tokio::main]
async fn main(){
    fast_log::init(fast_log::Config::new().console().level(log::LevelFilter::Debug));
}
cargo run
............gen macro py_sql :
 pub async fn do_select_all(
    rb: &dyn rbatis::executor::Executor,
    table_name: String,
) -> Result<Vec<BizActivity>, rbatis::rbdc::Error> {
    let mut rb_arg_map = rbs::value::map::ValueMap::new();
    rb_arg_map.insert(
        "table_name".to_string().into(),
        rbs::to_value(table_name).unwrap_or_default(),
    );
    {}
    use rbatis::executor::RBatisRef;
    let driver_type = rb.get_rbatis().driver_type()?;
    use rbatis::rbatis_codegen;
    pub fn do_select_all(arg: &rbs::Value, _tag: char) -> (String, Vec<rbs::Value>) {
        use rbatis_codegen::ops::*;
        let mut sql = String::with_capacity(1000);
        let mut args = Vec::with_capacity(20);
        sql.push_str(
            "select * from ${table_name}"
                .replacen("${table_name}", &{ &arg["table_name"] }.as_sql(), 1)
                .as_str(),
        );
        rbatis_codegen::sql_index!(sql, _tag);
        return (sql, args);
    }
    let (mut sql, rb_args) = do_select_all(&rbs::Value::Map(rb_arg_map), '?');
    use rbatis::executor::Executor;
    let r = rb.query(&sql, rb_args).await?;
    rbatis::decode::decode(r)
}
............gen macro py_sql end............

rbs

rbs is a specialized serialization framework written by rbatis for the ORM intermediate language html_sql,py_sql, used to conveniently use and replace JSON like objects in HTML statements instead of manipulating native structures. You can understand rbs as an intermediate structure similar to JSON Value.

  • Here we show the definition of rbs::Value
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
    /// null
    Null,
    /// true or false
    Bool(bool),
    /// Int32
    I32(i32),
    /// Int64
    I64(i64),
    /// Uint32
    U32(u32),
    /// Uint64
    U64(u64),
    /// A 32-bit float number.
    F32(f32),
    /// A 64-bit float number.
    F64(f64),
    /// String
    String(String),
    /// Binary/Bytes.
    Binary(Vec<u8>),
    /// Array/Vec.
    Array(Vec<Self>),
    /// Map<Key,Value>.
    Map(ValueMap),
    /// Extended implements Extension interface
    Ext(&'static str, Box<Self>),
}
  • rbs build a map value
fn main(){
    let v = rbs::to_value!{
        "key":"value",
        "key2":"value2"
    };
}
  • rbs encode to value
fn main(){
    let v = rbs::to_value!(1);
    let arg = vec![1,2,3];
    let v = rbs::to_value!(&arg);
    let arg = "1".to_string();
    let v = rbs::to_value!(&arg);
}
  • rbs decode from value
fn main(){
    let v:i32 = rbs::from_value(Value::I32(1)).unwrap();
}
  • display value
fn main(){
    let value = Value::I32(1);
    assert_eq!(value.to_string(),"1");
    assert_eq!(format!("{}",value),"1");
}

Transaction

The essence of a transaction is to use the SQL statements BEGIN, COMMIT, and ROLLBACK. The RBatis provides these three functions but also support defer_async() to prevent forgotten commits

example see

use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
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<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}
crud!(BizActivity{});
#[tokio::main]
pub async fn main() {
    let _ = fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    let rb = RBatis::new();
    // rb.link(MysqlDriver {},"mysql://root:123456@localhost:3306/test").await.unwrap();
    // rb.link(PgDriver {},"postgres://postgres:123456@localhost:5432/postgres").await.unwrap();
    // rb.link(MssqlDriver {},"mssql://SA:TestPass!123456@localhost:1433/test").await.unwrap();
    rb.link(
        SqliteDriver {},
        &format!("sqlite://{}target/sqlite.db", path),
    ).await.unwrap();
    let t = BizActivity {
        id: Some("2".into()),
        name: Some("2".into()),
        pc_link: Some("2".into()),
        h5_link: Some("2".into()),
        pc_banner_img: None,
        h5_banner_img: None,
        sort: None,
        status: Some(2),
        remark: Some("2".into()),
        create_time: Some(DateTime::now()),
        version: Some(1),
        delete_flag: Some(1),
    };
    let tx = rb.acquire_begin().await.unwrap();
    // defer_async will be rollback if tx drop
    // let mut tx = tx.defer_async(|mut tx| async move {
    //     if !tx.done() {
    //         tx.rollback().await.unwrap();
    //         println!("rollback");
    //     }
    // });
    //tx.exec("select 1", vec![]).await.unwrap();
    BizActivity::insert(& tx, &t).await.unwrap();

    tx.commit().await.unwrap();
    tx.rollback().await.unwrap();
}

Raw Sql

the RBatis also support Write the original statements of the database And the drivers provided by RBatis all support placeholder '?',so you can write '?' on Postgres/mssql...and more

use rbs::to_value;
use std::time::Duration;
use tokio::time::sleep;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
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<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}
#[tokio::main]
pub async fn main() {
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
     let rb = RBatis::new();
    // rb.link(MysqlDriver {},"mysql://root:123456@localhost:3306/test").await.unwrap();
    // rb.link(PgDriver {},"postgres://postgres:123456@localhost:5432/postgres").await.unwrap();
    // rb.link(MssqlDriver {},"mssql://SA:TestPass!123456@localhost:1433/test").await.unwrap();
    rb.link(
        SqliteDriver {},
        &format!("sqlite://{}target/sqlite.db", path),
    ).await.unwrap();
    let table: Option<BizActivity> = rb
        .query_decode("select * from biz_activity limit ?", vec![to_value!(1)])
        .await
        .unwrap();
    let count: u64 = rb
        .query_decode("select count(1) as count from biz_activity", vec![])
        .await
        .unwrap();
    sleep(Duration::from_secs(1)).await;
    println!(">>>>> table={:?}", table);
    println!(">>>>> count={}", count);
}

HtmlSql

It is implemented by RBatis a set of compatible MyBtais3 SQL editing language, support common such as if, Foreach, string interpolation

  • When the RBatis dependency in Cargo.toml turns on the debug_mode feature, the generated function implementation code is printed

  • Language parsing -> Lexical analysis -> Syntax analysis -> generation of abstract syntax trees -> translation to Rust code。Have the performance of native Rust

  • Of course, PySql is also a syntax tree using HtmlSql,PySql will be Convert to HtmlSql

  • It uses crates rbs of rbs::Value as the base object and operates on and any func

  • you can call any method/trait on rbs::Value such as #{1 + 1}, #{arg}, #{arg [0]}, #{arg [0] + 'string'} or if sql.contans('count'):

  • Strings can be reserved for Spaces using ` such as ` select * from table where `

  • method will create 2 variable on method body.So you can determine whether the variable SQL contains a COUNT statement or a SELECT statement in a paging operation

  • HtmlSql Syntax tree

Syntax/method Generated Rust code
<trim prefixOverrides=" and">` and name != '' `</trim> sql.trim(" and")
<if test="key == 'id'">`select * from table`</if> if key == "id"{sql.push_str("select * from table");}
<foreach collection="arg" index="key" item="item" open="(" close=")" separator=","/> for (key,item) in arg{}
<continue/> for (key,item) in arg{ continue;}
<set> sql.trim("set ").push_str(" set ");
<set collection="arg"> sql.trim("set ").push_str(" set name=?,age=? "); //notice collection={name:"",age:""};
<choose> match {}
<when test="true"> match true{ true=>{} _ => {} }
<otherwise> match { _ =>{} }
<where> sql.push_str("WHERE").trim("WHERE");
<bind name="a" value="1+1"></bind> let a = rbs::Value::I32(1 + 1)
`select * from table` sql.push_str("select * from table");
`#{name}` sql.push_str("?");args.push(rbs::Value::String(name));
`${name}` sql.push_str(&format!("{}",name));
`${1 + 1}` sql.push_str(&format!("{}", 1 + 1));
`#{1 + 1}` sql.push_str("?");args.push(rbs::Value::from(1+1));
`${name + '_tag'}` sql.push_str(&format!("{}",name + "_tag"));
`#{name + '_tag'}` sql.push_str("?");args.push(rbs::Value::from(format!("{}",name + "_tag")));
`${age + 1}` sql.push_str(&format!("{}", age + 1));
`#{age + 1}` sql.push_str("?");args.push(rbs::Value::from(age+1));
`${true & true}` sql.push_str(&format!("{}", true & true));
`#{true & true}` sql.push_str("?");args.push(rbs::Value::from(true & true));
`${2 > 1}` sql.push_str(&format!("{}",2 > 1));
`${2 / 1}` sql.push_str(&format!("{}", 2 / 1));
`${2 == 1}` sql.push_str(&format!("{}", 2 == 1));
`${2 * 1}` sql.push_str(&format!("{}", 2 * 1));
`${ !false }` sql.push_str(&format!("{}", !false));
`${ 2 % 1 }` sql.push_str(&format!("{}", 2 % 1));
`${ 2 - 1 }` sql.push_str(&format!("{}", 2 - 1));
  • define on Rust code see
// Clion Smart tips: click code, choose 'Inject Language or Reference', and then choose html
#[html_sql(r#"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://raw.githubusercontent.com/rbatis/rbatis/master/rbatis-codegen/mybatis-3-mapper.dtd">
  <select id="select_by_condition">
        `select * from biz_activity`
        <where>
         <if test="a">
                ` and name like #{name}`
            </if>
            <if test="name != ''">
                ` and name like #{name}`
            </if>
            <if test="dt >= '2009-12-12 00:00:00'">
                ` and create_time < #{dt}`
            </if>
            <choose>
                <when test="true">
                    ` and id != '-1'`
                </when>
                <otherwise>and id != -2</otherwise>
            </choose>
            ` and `
            <trim prefixOverrides=" and">
                ` and name != '' `
            </trim>
        </where>
  </select>"#)]
async fn select_by_condition(rb: & dyn Executor, name: &str, dt: &DateTime) -> Vec<BizActivity> {
    impled!()
}
  • define on Rust from file see

example/example.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://raw.githubusercontent.com/rbatis/rbatis/master/rbatis-codegen/mybatis-3-mapper.dtd">
<mapper>
  <insert id="insert">
    `insert into biz_activity`
    <foreach collection="arg" index="key" item="item" open="(" close=")" separator=",">
      <if test="key == 'id'">
        <continue/>
      </if>
      ${key}
    </foreach>
    ` values `
    <foreach collection="arg" index="key" item="item" open="(" close=")" separator=",">
      ${item}
    </foreach>
  </insert>
  <select id="select_by_condition">
    `select * from biz_activity`
    <where>
      <if test="name != ''">
        ` and name like #{name}`
      </if>
      <if test="dt >= '2009-12-12 00:00:00'">
        ` and create_time < #{dt}`
      </if>
      <choose>
        <when test="true">
          ` and id != '-1'`
        </when>
        <otherwise>and id != -2</otherwise>
      </choose>
      ` and `
      <trim prefixOverrides=" and">
        ` and name != '' `
      </trim>
    </where>
  </select>
</mapper>

rust code

#[html_sql("example/example.html")]
async fn select_by_condition(rb: & dyn Executor, name: &str, dt: &DateTime) -> Vec<BizActivity> {
    impled!()
}

rust code

htmlsql!(select_by_condition(rb: & dyn Executor, name: &str, dt: &DateTime) -> rbatis::Result<Vec<BizActivity>> => "example.html");
Page

impl html_sql select page.

you must deal with 3 param: (do_count:bool,page_no:u64,page_size:u64)

you must deal with sql: return Vec(if param do_count = false) return u64(if param do_count = true)

just like this exmaple:

<select id="select_page_data">
        `select`
        <if test="do_count == true">
            ` count(1) from table`
        </if>
        <if test="do_count == false">
            ` * from table limit ${page_no},${page_size}`
        </if>
  </select>
#[macro_use]
extern crate rbatis;
use rbatis::rbatis::RBatis;
use rbatis::rbdc::datetime::DateTime;
use rbatis::sql::PageRequest;
use rbdc_sqlite::driver::SqliteDriver;

htmlsql_select_page!(select_page_data(name: &str, dt: &DateTime) -> BizActivity => "example/example.html");

#[tokio::main]
pub async fn main() {
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    let rb = RBatis::new();
    rb.link(SqliteDriver {}, &format!("sqlite://target/sqlite.db"))
        .await
        .unwrap();
    let a = select_page_data(&rb,&PageRequest::new(1, 10),"test",&DateTime::now().set_micro(0))
        .await
        .unwrap();
    println!("{:?}", a);
}
Include

<include> allows reference SQL blocks and even SQL blocks from xxxx.html files, requiring refid to be specified for proper reference

step1.define <sql id="a">` and id != '' `</sql>

step2.use <include refid="a"></include> or <include refid="file://../rbatis/example/example.html?refid=a"></include>

for example:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://raw.githubusercontent.com/rbatis/rbatis/master/rbatis-codegen/mybatis-3-mapper.dtd">
<mapper>
    <sql id="a">` and id != '' `</sql>
    <select id="select_by_condition">
        `select * from biz_activity`
        <where>
            <include refid="a"></include>
            <include refid="file://../rbatis/example/example.html?refid=a"></include>
            <if test="name != ''">
                ` and name like #{name}`
            </if>
            <if test="dt >= '2009-12-12 00:00:00'">
                ` and create_time < #{dt}`
            </if>
            <choose>
                <when test="true">
                    ` and id != '-1'`
                </when>
                <otherwise>and id != -2</otherwise>
            </choose>
            ` and `
            <trim prefixOverrides=" and">
                ` and name != '' `
            </trim>
        </where>
    </select>
</mapper>

PySql

  • It is a Python-like syntax, a language for manipulating SQL statements and inserting SQL parameters
  • Syntax tree
Syntax/method Generated Rust code
trim 'AND ': sql.trim_end_matches("AND ").trim_start_matches("AND ")
trim start='AND ': sql.trim_start_matches("AND ")
trim end='AND ': sql.trim_end_matches("AND ")
if arg!=1: if arg !=1 {}
if true:
`select * from table`
if true { sql.push_str("select * from table");}
for key,item in arg: for (key,item) in arg{ }
for key,item in arg:
`and name = ${name}`
for (key,item) in arg{ sql.push_str(&format!("and name = {}",name)); }
for key,item in arg:
`continue:`
for (key,item) in arg{ continue; }
set : sql.push_str("SET")
set collection='ids': sql.trim("set ").push_str(" set name=?,age=? "); //let collection={name:"",age:""};
choose : match {}
when : match true{ true=>{} _ => {} }
otherwise : match { _ =>{} }
_: match { _ =>{} }(v1.8.54 later)
where : sql.push_str("WHERE").trim("WHERE")
bind a=1+1: let a = rbs::Value::I32(1 + 1)
let a=1+1: let a = rbs::Value::I32(1 + 1) (v1.8.54 later)
`select * from table` sql.push_str("select * from table");
`#{name}` sql.push_str("?");args.push(rbs::Value::String(name));
`${name}` sql.push_str(&format!("{}",name));
`${1 + 1}` sql.push_str(&format!("{}", 1 + 1));
`#{1 + 1}` sql.push_str("?");args.push(rbs::Value::from(1+1));
`${name + '_tag'}` sql.push_str(&format!("{}",name.to_string() + "_tag"));
`#{name + '_tag'}` sql.push_str("?");args.push(rbs::Value::from(format!("{}",name + "_tag")));
`${age + 1}` sql.push_str(&format!("{}", age + 1));
`#{age + 1}` sql.push_str("?");args.push(rbs::Value::from(age+1));
`${true & true}` sql.push_str(&format!("{}", true & true));
`#{true & true}` sql.push_str("?");args.push(rbs::Value::from(true & true));
`${2 > 1}` sql.push_str(&format!("{}",2 > 1));
`${2 / 1}` sql.push_str(&format!("{}", 2 / 1));
`${2 == 1}` sql.push_str(&format!("{}", 2 == 1));
`${2 * 1}` sql.push_str(&format!("{}", 2 * 1));
`${ !false }` sql.push_str(&format!("{}", !false));
`${ 2 % 1 }` sql.push_str(&format!("{}", 2 % 1));
`${ 2 - 1 }` sql.push_str(&format!("{}", 2 - 1));
pub struct User{
    pub delete_flag:i32,
    pub name:String
}

#[py_sql(
    "`select * from user where delete_flag = 0`
                  if name != '':
                    ` and name=#{name}`"
)]
async fn py_select(rb: & dyn Executor, name: &str) -> Result<Vec<User>, Error> {
    impled!()
}
pub struct User{
    pub delete_flag:i32,
    pub name:String
}

pysql!(user_delete_by_name(rb: &dyn Executor, name: &str) -> Result<ExecResult, Error> =>
    "`delete from user where delete_flag = 0`
                   if name != '':
                     ` and name=#{name}`" );

impl User{
    pysql!(user_delete_by_name(rb: &dyn Executor, name: &str) -> Result<ExecResult, Error> =>
    "`delete from user where delete_flag = 0`
                   if name != '':
                     ` and name=#{name}`" );
}

plugin: table-sync

This IS a PLUGIN THAT SYNCHRONIZES THE TABLE STRUCTURE WITH THE TABLE STRUCTURE IN THE code, which I believe is VERY important in MOBILE DEVELOPMENT. Note that it does not change the table structure.

  • If the table does not exist, it is created
  • If the table exists but a column is missing, increment the column of the missing section
use rbatis::rbatis::RBatis;
use rbatis::rbdc::datetime::DateTime;
use rbatis::table_sync;
use rbatis::table_sync::SqliteTableMapper;
use rbdc_sqlite::driver::SqliteDriver;
use rbs::to_value;

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct RBUser {
    pub id: i32,
    pub name: Option<String>,
    pub remark: Option<String>,
    pub create_time: Option<DateTime>,
    pub version: Option<i64>,
    pub delete_flag: Option<i32>,
}

#[tokio::main]
pub async fn main() {
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    let rb = RBatis::new();
    // ------------choose database driver------------
    //rb.init(rbdc_mysql::driver::MysqlDriver {}, "mysql://root:123456@localhost:3306/test").unwrap();
    // rb.init(rbdc_pg::driver::PgDriver {}, "postgres://postgres:123456@localhost:5432/postgres").unwrap();
    // rb.init(rbdc_mssql::driver::MssqlDriver {}, "mssql://SA:TestPass!123456@localhost:1433/test").unwrap();
    rb.init(SqliteDriver {}, &format!("sqlite://target/sqlite.db"))
        .unwrap();
    // ------------choose database column mapper------------
    let mapper = &table_sync::SqliteTableMapper{} as &dyn ColumMapper;
    // let mapper = &table_sync::PGTableMapper{} as &dyn ColumMapper;
    //let mapper = &table_sync::MysqlTableMapper{} as &dyn ColumMapper;
    // let mapper = &table_sync::MssqlTableMapper{} as &dyn ColumMapper;

    let map = rbs::to_value!{
            "id":"INT",
            "name":"TEXT",
     };
    let _ = RBatis::sync(&rb,mapper,&map,"rb_user").await;


    RBatis::sync(
        &rb.acquire().await.unwrap(),
        mapper,
        &RBUser {
            id: 0,
            //// Custom String Database Type
            //name: Some("TEXT".to_string()),
            name: Some("".to_string()),
            //// Custom String Database Type
            //remark: Some("TEXT".to_string()),
            remark: Some("".to_string()),
            create_time: Some(DateTime::utc()),
            version: Some(1),
            delete_flag: Some(1),
        },
        "rb_user",
    )
        .await
        .unwrap();
}

plugin: Intercept

Implementing an interface

use rbatis::{Error, RBatis};
use rbatis::executor::Executor;
use rbatis::intercept::{Intercept, ResultType};
use rbdc::db::ExecResult;
use rbs::Value;
#[derive(Debug)]
pub struct MyInterceptor{}

impl Intercept for MyInterceptor {
    /// task_id maybe is conn_id or tx_id,
    /// is_prepared_sql = !args.is_empty(),
    ///
    /// if return None will be return result
    /// if return Some(true) will be run next intercept
    /// if return Some(false) will be break
    fn before(
        &self,
        _task_id: i64,
        _rb: &dyn Executor,
        _sql: &mut String,
        _args: &mut Vec<Value>,
        _result: ResultType<&mut Result<ExecResult, Error>, &mut Result<Vec<Value>, Error>>,
    ) -> Result<Option<bool>, Error> {
        Ok(Some(true))
    }

    /// task_id maybe is conn_id or tx_id,
    /// is_prepared_sql = !args.is_empty(),
    ///
    /// if return None will be return result
    /// if return Some(true) will be run next intercept
    /// if return Some(false) will be break
    fn after(
        &self,
        _task_id: i64,
        _rb: &dyn Executor,
        _sql: &mut String,
        _args: &mut Vec<Value>,
        _result: ResultType<&mut Result<ExecResult, Error>, &mut Result<Vec<Value>, Error>>,
    ) -> Result<Option<bool>, Error> {
        Ok(Some(true))
    }
}
//push into RBatis
fn main(){
    let mut rb=RBatis::new();
    rb.intercepts.push(Arc::new(MyInterceptor{}) as Arc<dyn Intercept>);
}

Plug-in: distributed unique ID (snowflake algorithm(i64))

    use rbatis::plugin::snowflake::new_snowflake_id;
    #[test]
    fn test_new_async_id() {
         //Snowflake::new()  //Snowflake::new(Must be a singleton or global variable)
         //default use
         println!("{}", new_snowflake_id().to_string());
    }

Plug-in: distributed unique ID (MongoDB object id algorithm(String/u128))

    #[test]
    async fn test_new_async_id() {
       println!("{}", rbatis::plugin::object_id::ObjectId::new().to_string());
    }

macro-built-in

  • make_table Simplifies table construction by relying on the Default trait
  • make_table_field_vec take the target Vec member attribute Vec collection
  • make_table_field_map Gets the HashMap collection of member attributes of the target Vec

for example:

    use rbatis::rbdc::datetime::DateTime;
    use serde::{Deserialize, Serialize};
    #[derive(Clone, Debug, Deserialize, Serialize)]
    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<DateTime>,
        pub version: Option<BigDecimal>,
        pub delete_flag: Option<i32>,
    }

    impl Default for BizActivity {
        fn default() -> Self {
            Self {
                id: None,
                name: None,
                pc_link: None,
                h5_link: None,
                pc_banner_img: None,
                h5_banner_img: None,
                sort: None,
                status: None,
                remark: None,
                create_time: None,
                version: None,
                delete_flag: None,
            }
        }
    }

    #[test]
    fn test_make_table() {
        let table = rbatis::make_table!(BizActivity{
              id:"1".to_string(),
        });
        println!("{:#?}", table);
    }

    #[test]
    fn test_table_field_map() {
        let table = rbatis::make_table!(BizActivity{
              id:"1".to_string(),
              name:"a".to_string()
        });
        let table_vec = vec![table];
        let map = rbatis::make_table_field_map!(&table_vec,name);
        println!("{:#?}", map);
        assert_eq!(map.len(), table_vec.len());
    }

    #[test]
    fn test_table_field_vec() {
        let table = rbatis::make_table!(BizActivity{
              id:"1".to_string(),
              name:"a".to_string()
        });
        let table_vec = vec![table];
        let names = rbatis::make_table_field_vec!(&table_vec,name);
        println!("{:#?}", names);
        assert_eq!(names.len(), table_vec.len());
    }

design-driver

  • This doc is used to design a new database driver to join into rbatis

  • example see rbdc-mssql

  • step0: create your cargo project,and add 'rbdc = "4.6"' on Cargo.toml

cargo new mock_driver --lib
  • step1: add Depend,or add your database driver crates depend.
rbdc = "4.6"
rbs  = "4.6"
fastdate = { version = "0.1" }
# xx_driver = {version = "xxx"}
  • step2: define you driver struct
#[derive(Debug, Clone)]
struct MockDriver {}
#[derive(Clone, Debug)]
struct MockRowMetaData {}
#[derive(Clone, Debug)]
struct MockRow {}
#[derive(Clone, Debug)]
struct MockConnection {}
#[derive(Clone, Debug)]
struct MockConnectOptions {}
  • step3: impl trait rbdc::db::{Driver, MetaData, Row, Connection, ConnectOptions, Placeholder};
use std::any::Any;
use futures_core::future::BoxFuture;
use rbdc::db::{Driver, MetaData, Row, Connection, ConnectOptions, Placeholder, ExecResult};
use rbdc::Error;
use rbs::Value;

impl Driver for MockDriver {
    fn name(&self) -> &str {
        "MockDriver"
    }
    fn connect(&self, url: &str) -> BoxFuture<Result<Box<dyn Connection>, Error>> {
        let url = url.to_owned();
        Box::pin(async move {
            let conn = MockConnection {};
            Ok(Box::new(conn) as Box<dyn Connection>)
        })
    }

    fn connect_opt<'a>(
        &'a self,
        opt: &'a dyn ConnectOptions,
    ) -> BoxFuture<Result<Box<dyn Connection>, Error>> {
        let opt = opt.downcast_ref::<MockConnectOptions>().unwrap();
        Box::pin(async move {
            let conn = MockConnection {};
            Ok(Box::new(conn) as Box<dyn Connection>)
        })
    }

    fn default_option(&self) -> Box<dyn ConnectOptions> {
        Box::new(MockConnectOptions {})
    }
}

impl MetaData for MockRowMetaData {
    fn column_len(&self) -> usize {  todo!() }

    fn column_name(&self, i: usize) -> String {  todo!() }

    fn column_type(&self, i: usize) -> String {  todo!() }
}

impl Row for MockRow {
    fn meta_data(&self) -> Box<dyn MetaData> {  todo!() }

    fn get(&mut self, i: usize) -> Result<Value, Error> {  todo!() }
}

impl Connection for MockConnection {
    fn get_rows(&mut self, sql: &str, params: Vec<Value>) -> BoxFuture<Result<Vec<Box<dyn Row>>, Error>> {  todo!() }

    fn exec(&mut self, sql: &str, params: Vec<Value>) -> BoxFuture<Result<ExecResult, Error>> {  todo!() }

    fn close(&mut self) -> BoxFuture<Result<(), Error>> {  todo!() }

    fn ping(&mut self) -> BoxFuture<Result<(), Error>> {  todo!() }
}

impl ConnectOptions for MockConnectOptions {
    fn connect(&self) -> BoxFuture<Result<Box<dyn Connection>, Error>> {  todo!() }

    fn set_uri(&mut self, uri: &str) -> Result<(), Error> {  todo!() }
}

impl Placeholder for MockDriver {
    fn exchange(&self, sql: &str) -> String {
        //return rbdc::impl_exchange("@P", 1, sql); //TODO if database not support sql Placeholder '?',replace '@1' to '?'
        return sql.to_string();//if database is support sql Placeholder '?'
    }
}
  • step4: load your driver on rbatis
#[tokio::main]
async fn main(){
    let mut rb = RBatis::new();
    rb.init(MockDriver {}, "xxx://xxx.db").unwrap();
    rb.acquire().await.expect("connect database fail");
    println!("connect database successful");
}