#orm #database-driver #postgresql #sqlite #mysql #connection-pool

rbatis

Rust SQL 工具包和 ORM 库。一个支持编译时动态 SQL 的异步、纯 Rust SQL 包。

167 个稳定版本

4.5.29 2024 年 7 月 4 日
4.5.26 2024 年 6 月 30 日
4.5.21 2024 年 3 月 14 日
4.5.10 2023 年 12 月 30 日
1.0.7 2020 年 2 月 29 日

#107数据库接口

Download history 432/week @ 2024-05-03 330/week @ 2024-05-10 449/week @ 2024-05-17 492/week @ 2024-05-24 932/week @ 2024-05-31 477/week @ 2024-06-07 570/week @ 2024-06-14 513/week @ 2024-06-21 969/week @ 2024-06-28 999/week @ 2024-07-05 732/week @ 2024-07-12 552/week @ 2024-07-19 941/week @ 2024-07-26 690/week @ 2024-08-02 680/week @ 2024-08-09 457/week @ 2024-08-16

每月 2,856 次下载
用于 28 个 Crates (20 直接)

Apache-2.0

640KB
13K SLoC

网站 | 展示 | 示例

Build Status doc.rs unsafe forbidden codecov GitHub release discussions

一种编译时代码生成 ORM,在易用性、性能和健壮性之间取得平衡

它是一个 ORM、一个小型编译器、一个动态 SQL 语言

  • 高性能:编译时 动态 SQL,基于 Future/Tokio,连接池
  • 可靠性:Rust 安全代码,预编译:#{arg},直接替换:${arg},统一 ? 占位符(支持所有驱动程序)
  • 生产力:强大的 拦截器接口CRUDpy_sqlhtml_sql表同步插件abs_adminrbdc-drivers
  • 可维护性:RBDC 驱动程序支持自定义驱动程序、自定义连接池,支持第三方驱动程序包
感谢 SQLX, deadpool,mobc, Tiberius, MyBatis, xorm 等参考设计或代码实现。V4 版本的发布受到了这些框架的灵感和支持。**

性能

  • 此基准测试是 MockTable、MockDriver、MockConnection,假设网络 I/O 时间为 0
  • 运行代码 rbatis.query_decode::<Vec<i32>>("", vec![]).await; 在 benches bench_raw() 上运行
  • 运行代码 MockTable::insert(&rbatis,&t).await; 在 benches bench_insert() 上运行
  • 运行代码 MockTable::select_all(&rbatis).await.unwrap(); 在 benches bench_select() 上运行
  • 查看 bench 代码
---- bench_raw stdout ----(windows/SingleThread)
Time: 52.4187ms ,each:524 ns/op
QPS: 1906435 QPS/s

---- bench_select stdout ----(macos-M1Cpu/SingleThread)
Time: 112.927916ms ,each:1129 ns/op
QPS: 885486 QPS/s

---- bench_insert stdout ----(macos-M1Cpu/SingleThread)
Time: 346.576666ms ,each:3465 ns/op
QPS: 288531 QPS/s

Workflows CI 支持的操作系统/平台

  • Rust 编译器版本 v1.75+ 及以后版本
平台 是支持的
Linux(unbutu latest***)
Apple/MacOS(latest)
Windows(latest)

支持的数据结构

数据结构 是支持的
Option
Vec
HashMap
i32,i64,f32,f64,bool,String...更多 Rust 基础类型
rbatis::rbdc::类型::{Bytes,Date,DateTime,Time,Timestamp,Decimal,Json}
rbatis::插件::分页::{Page,PageRequest}
rbs::Value
serde_json::Value ...更多 serde 类型
rdbc-mysql::类型::*
rdbc-pg::类型::*
rdbc-sqlite::类型::*
rdbc-mssql::类型::*

支持的数据库驱动程序

数据库(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

如何为 RBatis 编写我的数据库驱动程序?

  • 首先,定义你的驱动项目,添加 Cargo.toml 依赖项
[features]
default = ["tls-rustls"]
tls-rustls=["rbdc/tls-rustls"]
tls-native-tls=["rbdc/tls-native-tls"]
[dependencies]
rbs = { version = "4.5"}
rbdc = { version = "4.5", default-features = false,  optional = true }
fastdate = { version = "0.3" }
tokio = { version = "1", features = ["full"] }
  • 然后,你应该实现 rbdc::db::{ConnectOptions, Connection, ExecResult, MetaData, Placeholder, Row} 特性
  • 完成。你的驱动程序已完成(你只需调用 RB.init() 方法)。它支持 RBatis 连接池/tls(本地,rustls)
#[tokio::main]
async fn main(){
  let rb = rbatis::RBatis::new();
  rb.init(YourDriver {}, "YourDriver://****").unwrap();
}

支持连接池

数据库(crates.io) github_link
FastPool-默认 rbatis/fast_pool
Deadpool Deadpool
MobcPool MobcPool

支持的 Web 框架

  • 任何 Web 框架,如 ntex, actix-web, axum, hyper, rocket, tide, warp, salvo 等。
快速示例:QueryWrapper 和常见用法(详细信息请参阅 example/crud_test.rs)
  • Cargo.toml

默认

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

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

(可选) 'native-tls'

rbs = { version = "4.5" }
rbdc-sqlite = { version = "4.5", default-features = false, features = ["tls-native-tls"] }
#rbdc-mysql={version="4.5", default-features = false, features = ["tls-native-tls"]}
#rbdc-pg={version="4.5", default-features = false, features = ["tls-native-tls"]}
#rbdc-mssql={version="4.5", default-features = false, features = ["tls-native-tls"]}
rbatis = { version = "4.5"}
#other deps
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
fast_log = "1.6"

默认使用

//#[macro_use] define in 'root crate' or 'mod.rs' or 'main.rs'
#[macro_use]
extern crate rbatis;
extern crate rbdc;
use rbatis::rbdc::datetime::DateTime;

#[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_column+update_by_column+delete_by_column

impl_select!(BizActivity{select_all_by_id(id:&str,name:&str) => "`where id = #{id} and name = #{name}`"});
impl_select!(BizActivity{select_by_id(id:String) -> Option => "`where id = #{id} limit 1`"});
impl_update!(BizActivity{update_by_name(name:&str) => "`where id = 1`"});
impl_delete!(BizActivity {delete_by_name(name:&str) => "`where name= '2'`"});
impl_select_page!(BizActivity{select_page(name:&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  
    // sqlite 
    rb.init(SqliteDriver {}, "sqlite://target/sqlite.db").unwrap();
    // mysql 
    // rb.init(MysqlDriver{},"mysql://root:123456@localhost:3306/test").unwrap();
    // postgresql 
    // rb.init(PgDriver{},"postgres://postgres:123456@localhost:5432/postgres").unwrap();
    // mssql/sqlserver
    // rb.init(MssqlDriver{},"jdbc:sqlserver://localhost:1433;User=SA;Password={TestPass!123456};Database=test").unwrap();

    let activity = 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::insert(&rb, &activity).await;
    println!("insert = {:?}", data);

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

    let data = BizActivity::select_by_id(&rb, "1".to_string()).await;
    println!("select_by_id = {:?}", data);

    let data = BizActivity::update_by_column(&rb, &activity, "id").await;
    println!("update_by_column = {:?}", data);

    let data = BizActivity::update_by_name(&rb, &activity, "test").await;
    println!("update_by_name = {:?}", data);

    let data = BizActivity::delete_by_column(&rb, "id", &"2".into()).await;
    println!("delete_by_column = {:?}", data);

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

    let data = BizActivity::select_page(&rb, &PageRequest::new(1, 10), "2").await;
    println!("select_page = {:?}", data);
}
///...more usage,see crud.rs
  • 原始 SQL
#[tokio::main]
pub async fn main() {
    use rbatis::RBatis;
    use rbdc_sqlite::driver::SqliteDriver;
    #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
    pub struct BizActivity {
        pub id: Option<String>,
        pub name: Option<String>,
    }
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    let rb = RBatis::new();
    rb.init(SqliteDriver {}, "sqlite://target/sqlite.db").unwrap();
    let table: Option<BizActivity> = rb
        .query_decode("select * from biz_activity limit ?", vec![rbs::to_value!(1)])
        .await
        .unwrap();
    let count: u64 = rb
        .query_decode("select count(1) as count from biz_activity", vec![])
        .await
        .unwrap();
    println!(">>>>> table={:?}", table);
    println!(">>>>> count={}", count);
}

宏定义

  • 重要更新(pysql 移除了运行时依赖,直接编译为静态 Rust 代码)这意味着使用 py_sql、html_sql 生成的 SQL 性能与手写代码大致相同。

由于编译时间的原因,需要声明所使用的数据库类型。

    #[py_sql("select * from biz_activity where delete_flag = 0
                  if name != '':
                    `and name=#{name}`")]
async fn py_sql_tx(rb: &RBatis, tx_id: &String, name: &str) -> Vec<BizActivity> { impled!() }
  • 添加了 html_sql 支持,一种类似于 MyBatis 的组织形式,以方便将 Java 系统迁移到 Rust(注意,构建时也编译为 Rust 代码,性能接近手写代码),这非常快。

由于编译时间的原因,需要声明所使用的数据库类型。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
        "https://raw.githubusercontent.com/rbatis/rbatis/master/rbatis-codegen/mybatis-3-mapper.dtd">
<mapper>
    <select id="select_by_condition">
        `select * from biz_activity where `
        <if test="name != ''">
            name like #{name}
        </if>
    </select>
</mapper>
    ///select page must have  '?:&PageRequest' arg and return 'Page<?>'
#[html_sql("example/example.html")]
async fn select_by_condition(rb: &dyn Executor, page_req: &PageRequest, name: &str) -> Page<BizActivity> { impled!() }
use once_cell::sync::Lazy;

pub static RB: Lazy<RBatis> = Lazy::new(|| RBatis::new());

/// Macro generates execution logic based on method definition, similar to @select dynamic SQL of Java/Mybatis
/// RB is the name referenced locally by RBatis, for example DAO ::RB, com:: XXX ::RB... Can be
/// The second parameter is the standard driver SQL. Note that the corresponding database parameter mysql is? , pg is $1...
/// macro auto edit method to  'pub async fn select(name: &str) -> rbatis::core::Result<BizActivity> {}'
///
#[sql("select * from biz_activity where id = ?")]
pub async fn select(rb: &RBatis, name: &str) -> BizActivity {}
//or: pub async fn select(name: &str) -> rbatis::core::Result<BizActivity> {}

#[tokio::test]
pub async fn test_macro() {
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    RB.link("mysql://root:123456@localhost:3306/test").await.unwrap();
    let a = select(&RB, "1").await.unwrap();
    println!("{:?}", a);
}

工作原理

依赖于 rbatis-codegen 在编译时从 html 文件创建对应结构的源代码(启用 debug_mode(Cargo.toml 中 rbatis = { features = ["debug_mode"]}),可以观察生成的函数代码,并在运行时直接调用生成的函数。我们知道编译通常分为三个步骤:词法分析、语法分析和语义分析,以及中间代码生成。在 rbatis 中,词法分析由 rbatis-codegen 中的依赖 func.rs 处理,依赖于 syn 和 quote。解析由 rbatis-codegen 中的 parser_html 和 parser_pysql 完成。生成的语法树是 rbatis-codegen 中的 syntax_tree 包中定义的结构。中间代码生成由 func.rs 生成函数处理,所有支持的功能都定义在 rbatis-codegen 中。

上述描述发生在 cargo 构建阶段,这是 Rust 过程宏的编译阶段,其中 rbatis-codegen 生成的代码被交回 Rust 编译器进行 LLVM 编译,以生成纯机器代码。

因此,我认为 rbatis 是真正的零开销动态 SQL 编译时 ORM。

提交 PR(Pull Requests)

欢迎提交合并请求,并确保您添加的任何功能在测试包下都有适当的模拟单元测试函数。

变更日志

路线图

  • 表同步插件,自动创建表/列(sqlite/mysql/mssql/postgres)
  • 自定义连接池,连接池添加更多动态配置参数
  • V5 版本

求助于 AI(AI帮助)

  • discussions

联系方式/捐赠,或 rbatis 点star

捐赠

zxj347284221

联系方式(添加好友请备注'rbatis') 微信群:先加微信,然后拉进群

zxj347284221

依赖

~11–22MB
~329K SLoC