-
Notifications
You must be signed in to change notification settings - Fork 164
Home
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_sqlquery lang andhtml_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
Rustwith#![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)
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
-
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"-
tomlnative-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"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!()macroimpl_*!()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 macrosimpl_*!()
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 {}); 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_columnrbatis allow custom your table_column,it's support any
rbatis::impl_*!()macros
just like sqlselect ${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`"});//#[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));
}//#[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));
}//#[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));
}//#[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));
}//#[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));
}if you open features on Cargo.toml "debug_mode", You will see the following features
- show the project build Generated code(
rbatis_codgenGenerated code). you can see build log(............gen macro py_sql :............) - show the database
rowsdata . 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 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");
}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();
}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);
}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_modefeature, the generated function implementation code is printed -
Language parsing -> Lexical analysis -> Syntax analysis -> generation of abstract syntax trees -> translation to
Rustcode。Have the performance of nativeRust -
Of course, PySql is also a syntax tree using HtmlSql,PySql will be Convert to HtmlSql
-
It uses crates rbs of
rbs::Valueas the base object and operates on and any func -
you can call any method/trait on
rbs::Valuesuch as#{1 + 1}, #{arg}, #{arg [0]}, #{arg [0] + 'string'}orif 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
Rustcode 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
Rustfrom 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");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> 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>- 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}`" );
}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();
}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>);
} 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());
} #[test]
async fn test_new_async_id() {
println!("{}", rbatis::plugin::object_id::ObjectId::new().to_string());
}-
make_tableSimplifies table construction by relying on the Default trait -
make_table_field_vectake the target Vec member attribute Vec collection -
make_table_field_mapGets 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());
}-
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");
}