بنقرة واحدة
rbatis
Rust high-performance ORM framework - compile-time dynamic SQL, MyBatis compatible syntax
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Rust high-performance ORM framework - compile-time dynamic SQL, MyBatis compatible syntax
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | rbatis |
| description | Rust high-performance ORM framework - compile-time dynamic SQL, MyBatis compatible syntax |
Documentation: https://rbatis.github.io/rbatis.io | GitHub: https://github.com/rbatis/rbatis
html_sql (XML template) and py_sql (Python-like syntax)#{arg}, ${arg}, ?#![forbid(unsafe_code)])# Cargo.toml
[dependencies]
rbatis = { version = "4.8"}
rbs = { version = "4"}
rbdc-sqlite = { version = "4" }
# rbdc-mysql = { version = "4" }
# rbdc-pg = { version = "4" }
# rbdc-mssql = { version = "4" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
fast_log = "1.6"
With native-tls:
rbdc-sqlite = { version = "4", default-features = false, features = ["tls-native-tls"] }
# rbdc-mysql = { version = "4", default-features = false, features = ["tls-native-tls"] }
# rbdc-pg = { version = "4", default-features = false, features = ["tls-native-tls"] }
use rbatis::RBatis;
use rbdc_sqlite::driver::SqliteDriver;
#[tokio::main]
async fn main() -> rbatis::Result<()> {
fast_log::init(fast_log::Config::new().console())?;
let rb = RBatis::new();
// init() only sets the driver
// rb.init(SqliteDriver {}, "sqlite://target/sqlite.db")?;
// link() sets driver and connects to database
rb.link(SqliteDriver {}, "sqlite://target/sqlite.db").await?;
Ok(())
}
Database connection strings:
// SQLite
rb.link(SqliteDriver {}, "sqlite://target/sqlite.db").await?;
// MySQL
rb.link(MysqlDriver {}, "mysql://root:123456@localhost:3306/test").await?;
// PostgreSQL
rb.link(PgDriver {}, "postgres://postgres:123456@localhost:5432/postgres").await?;
// MSSQL
rb.link(MssqlDriver {}, "jdbc:sqlserver://localhost:1433;User=SA;Password=TestPass!123456;Database=test").await?;
The crud! macro generates: insert, insert_batch, select_by_map, update_by_map, delete_by_map
use serde::{Deserialize, Serialize};
use rbatis::rbdc::datetime::DateTime;
use rbs::value;
#[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>,
}
// Table name auto-inferred as snake_case ("biz_activity")
crud!(BizActivity {});
// Custom table name
crud!(BizActivity {}, "biz_activity");
let table = BizActivity {
id: Some("2".into()),
name: Some("2".into()),
// ... other fields
};
// Single insert
BizActivity::insert(&rb, &table).await?;
// Batch insert
let tables = [table.clone(), { /* ... */ }];
BizActivity::insert_batch(&rb, &tables, 10).await?; // batch_size=10
// Update by condition, fields in condition are used as WHERE
BizActivity::update_by_map(&rb, &table, value!{"id": &table.id}).await?;
BizActivity::update_by_map(&rb, &table, value!{"id": "1"}).await?;
// Equality condition
BizActivity::select_by_map(&rb, value!{"id":"1"}).await?;
// Multiple conditions
BizActivity::select_by_map(&rb, value!{"id":"1","name":"test"}).await?;
// IN query
BizActivity::select_by_map(&rb, value!{"id": &["1", "2", "3"]}).await?;
// LIKE query (key ends with " like ")
BizActivity::select_by_map(&rb, value!{"name like ": "%test%"}).await?;
// Comparison operators
BizActivity::select_by_map(&rb, value!{"id > ": "2"}).await?;
BizActivity::delete_by_map(&rb, value!{"id": "1"}).await?;
BizActivity::delete_by_map(&rb, value!{"id": &["1", "2", "3"]}).await?;
html_sql is a MyBatis-like XML template syntax. SQL is compiled to Rust code at compile time.
Inline HTML:
use rbatis::html_sql;
use rbatis::executor::Executor;
#[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,
a: bool,
) -> rbatis::Result<Vec<BizActivity>> {
impled!()
}
Load from file:
#[html_sql("example/example.html")]
impl BizActivity {
pub async fn select_by_condition(
rb: &dyn Executor,
name: &str,
dt: &DateTime,
) -> rbatis::Result<Vec<BizActivity>> {
impled!()
}
}
Using htmlsql! macro:
htmlsql!(select_by_condition(
rb: &dyn Executor,
name: &str,
dt: &DateTime
) -> rbatis::Result<Vec<BizActivity>> => "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>
<!-- Reusable SQL fragment -->
<sql id="a">` and id != '' `</sql>
<select id="select_by_condition">
`select * from biz_activity`
<where>
<!-- if: conditional -->
<if test="name != ''">
` and name like #{name}`
</if>
<!-- choose/when/otherwise: switch/case -->
<choose>
<when test="true">
` and id != '-1'`
</when>
<otherwise>and id != -2</otherwise>
</choose>
<!-- trim: remove/add prefix/suffix -->
<trim prefixOverrides=" and">
` and name != '' `
</trim>
</where>
</select>
<!-- foreach: iterate collection -->
<insert id="insert">
`insert into biz_activity`
<foreach collection="arg" index="key" item="item" open="(" close=")" separator=",">
<if test="key == 'id'">
<continue></continue>
</if>
${key}
</foreach>
</insert>
<!-- set: UPDATE SET -->
<update id="update_by_id">
` update biz_activity `
<set collection="arg"></set>
` where id = #{id} `
</update>
</mapper>
String concatenation (for LIKE patterns):
` and title like #{'%'+title+'%'}`
` and name like #{'%'+name+'%'}`
Date range query:
` and create_date >= #{start_date}`
` and create_date <= #{end_date}`
No escaping needed in HTML: Comparison operators like <= and >= can be written directly without XML escaping.
| Syntax | 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></continue> | continue; |
<break></break> | break; |
<set> | sql.trim("set ").push_str(" set "); |
<set collection="arg"> | sql.trim("set ").push_str(" set 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)); |
`${!false}` | sql.push_str(&format!("{}", !false)); |
`${2 % 1}` | sql.push_str(&format!("{}", 2 % 1)); |
`${2 - 1}` | sql.push_str(&format!("{}", 2 - 1)); |
<!-- Define fragment -->
<sql id="a">` and id != '' `</sql>
<!-- Reference in same file -->
<include refid="a"></include>
<!-- Reference from external file -->
<include refid="file://../rbatis/example/example.html?refid=a"></include>
${}, #{}, and test="" Use RustCore principle: Every expression inside ${}, #{}, or test="" is parsed as Rust code via syn::parse_str::<Expr> in func.rs. This is not MyBatis XML syntax.
Translation flow (from func.rs):
string → syn::parse_str::<Expr> → translate() → Rust code
Key operator mappings in translate() (func.rs:36-215):
| Rust Operator | MyBatis XML (DO NOT USE) | Notes |
|---|---|---|
&& | and | Binary operator |
|| | or | Binary operator |
! | not | Unary operator |
null | null | Becomes rbs::Value::Null |
==, !=, <, <=, >, >= | same | Direct comparison |
+, -, *, /, % | same | Arithmetic |
Examples from func.rs:
// Line 58-73: && becomes bool::op_from() wrapper
BinOp::And(_) => {
b.left = Box::new(syn::parse_str::<Expr>(&format!(
"bool::op_from({})", b.left.to_token_stream()
)));
// ... same for right
}
// Line 74-88: || becomes bool::op_from() wrapper
BinOp::Or(_) => {
// ... same pattern
}
// Line 11-14: null becomes rbs::Value::Null
Expr::Path(b) => {
if token == "null" {
return syn::parse_str::<Expr>("rbs::Value::Null")...
}
}
What NOT to write (will cause parse error):
test="name != null and name != ''" → use &&test="a or b" → use ||${name + '_tag'} in XML → same in html_sql (already Rust)What TO write (Rust syntax):
test="name != null && name != ''"test="a || b"${name + "_tag"}use rbatis::py_sql;
use rbatis::executor::Executor;
#[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!()
}
Using pysql! macro:
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}`" );
// In impl block
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}`" );
}
String concatenation (for LIKE patterns):
` and title like #{'%'+title+'%'}`
` and name like #{'%'+name+'%'}`
Date range query:
` and create_date >= #{start_date}`
` and create_date <= #{end_date}`
Collection to SQL list (.sql() method):
`select * from activity where delete_flag = 0`
if !ids.is_empty():
` and id in `
${ids.sql()}
Result: select * from activity where delete_flag = 0 and id in (1, 2, 3)
| Syntax | 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=? "); |
choose : | match {} |
when : | match true{ true=>{} _ => {} } |
otherwise : | match { _ =>{} } |
_: | match { _ =>{} }(v1.8.54+) |
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+) |
`#{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)); |
`${!false}` | sql.push_str(&format!("{}", !false)); |
`${2 % 1}` | sql.push_str(&format!("{}", 2 % 1)); |
`${2 - 1}` | sql.push_str(&format!("{}", 2 - 1)); |
Use native SQL directly, driver handles placeholder ? conversion automatically.
use rbs::value;
// Execute SQL (INSERT/UPDATE/DELETE)
let result = rb
.exec("update activity set status = 0 where id > 0", vec![])
.await?;
// Query and decode result
let table: Option<BizActivity> = rb
.exec_decode("select * from biz_activity limit ?", vec![value!(1)])
.await?;
let tx = rb.acquire_begin().await?;
BizActivity::insert(&tx, &table).await?;
tx.commit().await?;
tx.rollback().await?;
let tx = rb.acquire_begin().await?;
// defer_async: rollback automatically if transaction is dropped without commit
let tx = tx.defer_async(|tx| async move {
if tx.done() {
log::info!("transaction [{}] complete", tx.tx_id());
} else {
tx.rollback().await.unwrap();
log::info!("transaction [{}] rollback", tx.tx_id());
}
});
BizActivity::insert(&tx, &table).await?;
tx.commit().await?; // after commit, tx.done() == true, no rollback
When return type is Page<T>, PageIntercept handles pagination automatically.
Using impl block (recommended):
#[rbatis::html_sql("example/example.html")]
impl Activity {
// PageIntercept automatically adds LIMIT/OFFSET
pub async fn select_by_page(
rb: &dyn rbatis::Executor,
page_req: &rbatis::PageRequest,
name: &str,
) -> rbatis::Result<rbatis::Page<Activity>> {
impled!()
}
}
// Usage
let page = Activity::select_by_page(&rb, &PageRequest::new(1, 10), "test").await?;
Using htmlsql_select_page! macro:
<!-- example.html -->
<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>
use rbatis::htmlsql_select_page;
use rbatis::rbdc::datetime::DateTime;
use rbatis::PageRequest;
htmlsql_select_page!(select_page_data(name: &str, dt: &DateTime) -> BizActivity => "example/example.html");
// Usage
let page = select_page_data(
&rb,
&PageRequest::new(1, 10),
"test",
&DateTime::now(),
).await?;
use rbatis::Page;
use rbatis::PageRequest;
let page_req = PageRequest::new(1, 10);
// Query with pagination
let page: Page<Activity> = Activity::select_by_page(&rb, &page_req, "test").await?;
// Page fields
page.total; // total records
page.pages; // total pages
page.page_no; // current page
page.page_size; // page size
page.records; // data Vec<T>
Auto-sync Rust struct to database table (creates table/add columns only, never modifies existing columns).
use rbatis::table_sync;
use rbatis::RBatis;
#[tokio::main]
pub async fn main() {
let rb = RBatis::new();
rb.init(SqliteDriver {}, "sqlite://target/sqlite.db").unwrap();
// Select mapper for your database
let mapper = &table_sync::SqliteTableMapper {};
// let mapper = &table_sync::PGTableMapper{};
// let mapper = &table_sync::MysqlTableMapper{};
// let mapper = &table_sync::MssqlTableMapper{};
// Method 1: Using value! macro to define column types
let table = value! {
"id": "INTEGER",
"name": "TEXT",
"remark": "TEXT",
"create_time": "TEXT",
"version": "TEXT",
"delete_flag": "INT8"
};
RBatis::sync(&rb.acquire().await.unwrap(), mapper, &table, "rb_user").await?;
// Method 2: Using struct directly
RBatis::sync(
&rb.acquire().await.unwrap(),
mapper,
&BizActivity {
id: 0,
name: Some("".to_string()),
// ... other fields
create_time: Some(DateTime::now()),
version: Some(1),
delete_flag: Some(1),
},
"biz_activity", // table name
).await?;
}
use rbatis::async_trait;
use rbatis::executor::Executor;
use rbatis::intercept::{Intercept, ResultType};
use rbatis::rbdc::ExecResult;
use rbatis::{Action, Error};
use rbs::Value;
use std::sync::Arc;
#[derive(Debug)]
pub struct MyInterceptor {}
#[async_trait]
impl Intercept for MyInterceptor {
/// before: called before SQL execution
/// Return Action::Next to continue, Action::Stop to stop
async fn before(
&self,
_task_id: i64,
_rb: &dyn Executor,
_sql: &mut String,
_args: &mut Vec<Value>,
_result: ResultType<&mut Result<ExecResult, Error>, &mut Result<Value, Error>>,
) -> Result<Action, Error> {
Ok(Action::Next) // continue
}
/// after: called after SQL execution
async fn after(
&self,
_task_id: i64,
_rb: &dyn Executor,
_sql: &mut String,
_args: &mut Vec<Value>,
_result: ResultType<&mut Result<ExecResult, Error>, &mut Result<Value, Error>>,
) -> Result<Action, Error> {
Ok(Action::Next)
}
}
// Add to RBatis
fn main() {
let mut rb = RBatis::new();
rb.intercepts.push(Arc::new(MyInterceptor {}) as Arc<dyn Intercept>);
}
| Interceptor | Description |
|---|---|
PageIntercept | Auto pagination |
LogInterceptor | SQL logging (enabled by default) |
plus! 宏是项目自定义的扩展宏,在 crud! 基础上额外提供常用的单表操作方法。
定义位置: src/macros/rb.rs
使用方法:
use rbatis::crud;
use crate::plus; // 项目自定义宏
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Owner {
pub id: u64,
pub name: String,
pub face: Option<String>,
}
crud!(Owner {}, "owner");
plus!(Owner {});
生成的6个方法:
| 方法 | 签名 | 说明 |
|---|---|---|
select_by_id | async fn select_by_id<T>(executor, id: T) -> Result<Option<T>> | 根据ID查询单条记录 |
update_by_id | async fn update_by_id(executor, table: &Self) -> Result<ExecResult> | 根据ID更新记录(跳过id字段) |
delete_by_id | async fn delete_by_id<T>(executor, id: T) -> Result<ExecResult> | 根据ID删除记录 |
select_one_by_condition | async fn select_one_by_condition(executor, condition: Value) -> Result<Option<Self>> | 条件查询单条 |
count_by_condition | async fn count_by_condition(executor, condition: Value) -> Result<u64> | 条件计数 |
select_page_by_condition | async fn select_page_by_condition(executor, page_request, condition: Value) -> Result<Page<Self>> | 条件分页查询 |
使用示例:
use rbs::value;
// 1. select_by_id - 根据ID查询
let owner = Owner::select_by_id(&rb, 12345u64).await?;
// 2. update_by_id - 根据ID更新(id字段不参与SET)
let mut owner = Owner {
id: 12345,
name: "新名字".to_string(),
face: Some("https://...".to_string()),
};
Owner::update_by_id(&rb, &owner).await?;
// 生成SQL: update owner set name=?, face=? where id = ?
// 3. delete_by_id - 根据ID删除
Owner::delete_by_id(&rb, 12345u64).await?;
// 4. select_one_by_condition - 条件查询单条
let owner = Owner::select_one_by_condition(&rb, value! {"id": 12345}).await?;
// 5. count_by_condition - 条件计数
let count = Owner::count_by_condition(&rb, value! {"name like ": "%test%"}).await?;
// 6. select_page_by_condition - 条件分页
let page_req = PageRequest::new(1, 10);
let condition = value! {
"name like ": "%test%",
"column": ["*"],
"order_by": "created_date desc"
};
let page = Owner::select_page_by_condition(&rb, &page_req, condition).await?;
条件语法说明:
value! {"id": 123} → where id = ?value! {"name like ": "%test%"} → where name like ?value! {"id > ": 100} → where id > ?value! {"id": [1, 2, 3]} → where id in (?, ?, ?)value! {"id": 1, "name": "test"} → where id = ? and name = ?特殊参数(select_page_by_condition):
column: 指定查询列,如 ["id", "name"] 或 "*"order_by: 排序,如 "created_date desc"use rbatis::make_table;
#[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);
}
#[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);
}
Implement rbdc::db::* traits to create custom database drivers.
use rbdc::db::{Driver, MetaData, Row, Connection, ConnectOptions, Placeholder};
// step1: define structs
#[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 {}
// step2: implement traits
impl Driver for MockDriver {
fn name(&self) -> &str { "MockDriver" }
fn connect(&self, url: &str) -> BoxFuture<Result<Box<dyn Connection>, Error>> {
Box::pin(async move {
Ok(Box::new(MockConnection {}) as Box<dyn Connection>)
})
}
fn connect_opt<'a>(&'a self, opt: &'a dyn ConnectOptions) -> BoxFuture<'a, Result<Box<dyn Connection>, Error>> {
Box::pin(async move {
Ok(Box::new(MockConnection {}) 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 exec_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 {
// If database doesn't support ? placeholder, convert to database-specific format
// e.g., MSSQL: sql.replacen("?", "@P1", 1)
sql.to_string()
}
}
// step3: use the driver
#[tokio::main]
async fn main() {
let mut rb = RBatis::new();
rb.init(MockDriver {}, "xxx://xxx.db").unwrap();
rb.acquire().await.expect("connect database fail");
}
rbs is rbatis's serialization framework, similar to JSON Value.
pub enum Value {
Null,
Bool(bool),
I32(i32),
I64(i64),
U32(u32),
U64(u64),
F32(f32),
F64(f64),
String(String),
Binary(Vec<u8>),
Array(Vec<Self>),
Map(ValueMap),
Ext(&'static str, Box<Self>),
}
// Build value
let v = rbs::to_value!{ "key": "value" };
// Decode
let v: i32 = rbs::from_value(Value::I32(1)).unwrap();
| Syntax | Type | Description |
|---|---|---|
#{arg} | Prepared placeholder | Parameter bound as ?, safe from injection |
${arg} | Direct string replacement | Concatenated directly into SQL |
? | Native placeholder | Driver auto-converts to database format |
Examples:
-- #{name} generates prepared statement
` where name = #{name}` -> sql.push_str("?"); args.push(name)
-- ${name} direct replacement
` where name = ${name}` -> sql.push_str(&format!("{}", name))