From 4f5ac920234ba1546a8bcb9fb21f78b9c113f977 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 18 Jul 2025 17:18:30 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8`=E5=B0=86sql=E7=9A=84=E6=AF=8F=E4=B8=80=E8=A1=8C?= =?UTF-8?q?=E5=8C=85=E8=A3=B9=EF=BC=8C=E5=AF=BC=E8=87=B4=E5=8E=9F=E5=A7=8B?= =?UTF-8?q?sql=E6=9C=89=E6=8D=A2=E8=A1=8C=E6=97=B6=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=9A=84sql=E7=B2=98=E8=BF=9E=E9=97=AE=E9=A2=98=EF=BC=88?= =?UTF-8?q?=E4=BE=8B=EF=BC=9A#pysql[(?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit select user_id, user_name \n from user )], 生成的sql为 : select user_id, user_namefrom user,修改后生成的sql为:select user_id, user_name from user) --- rbatis-codegen/src/codegen/parser_html.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rbatis-codegen/src/codegen/parser_html.rs b/rbatis-codegen/src/codegen/parser_html.rs index 865348116..8916f56f0 100644 --- a/rbatis-codegen/src/codegen/parser_html.rs +++ b/rbatis-codegen/src/codegen/parser_html.rs @@ -294,7 +294,7 @@ fn remove_extra(text: &str) -> String { } } - data.trim_matches('`').replace("``", "") + data.trim_matches('`').replace("``", " ") } /// Implements HTML SQL function From a08e6b0e07c402bd93836b768ab561fddd4cf6f7 Mon Sep 17 00:00:00 2001 From: Jiajie6591 Date: Tue, 29 Jul 2025 13:17:30 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=A4=84=E7=90=86?= =?UTF-8?q?=E4=BA=86sql=E7=9A=84=E6=8B=BC=E6=8E=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rbatis-codegen/src/codegen/parser_html.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/rbatis-codegen/src/codegen/parser_html.rs b/rbatis-codegen/src/codegen/parser_html.rs index 8916f56f0..dfeabc28e 100644 --- a/rbatis-codegen/src/codegen/parser_html.rs +++ b/rbatis-codegen/src/codegen/parser_html.rs @@ -288,13 +288,22 @@ fn remove_extra(text: &str) -> String { for (i, line) in lines.iter().enumerate() { let mut line = line.trim(); line = line.trim_start_matches('`').trim_end_matches('`'); - data.push_str(line); + + let list: Vec<&str> = line.split("``").collect(); + let mut text = String::with_capacity(line.len()); + for s in list { + if !text.is_empty() && !text.ends_with(' ') && !s.starts_with(' ') { + text.push(' '); + } + text.push_str(s); + } + data.push_str(&text); if i + 1 < lines.len() { data.push('\n'); } } - data.trim_matches('`').replace("``", " ") + data.trim_matches('`').to_owned() } /// Implements HTML SQL function From 249f7f132996321b0279c4b857f5f380f00a00a7 Mon Sep 17 00:00:00 2001 From: Jiajie6591 Date: Tue, 29 Jul 2025 13:24:11 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=A4=84=E7=90=86?= =?UTF-8?q?=E4=BA=86sql=E7=9A=84=E6=8B=BC=E6=8E=A5=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E6=B5=8B=E8=AF=95=E5=85=A8=E9=83=A8=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rbatis-codegen/src/codegen/parser_html.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rbatis-codegen/src/codegen/parser_html.rs b/rbatis-codegen/src/codegen/parser_html.rs index dfeabc28e..d566a3c57 100644 --- a/rbatis-codegen/src/codegen/parser_html.rs +++ b/rbatis-codegen/src/codegen/parser_html.rs @@ -303,7 +303,7 @@ fn remove_extra(text: &str) -> String { } } - data.trim_matches('`').to_owned() + data.to_owned() } /// Implements HTML SQL function From 32dc60aaff1e78ae8f7df9474547631d1e5aeb07 Mon Sep 17 00:00:00 2001 From: Jiajie6591 Date: Tue, 29 Jul 2025 13:48:07 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E4=BA=86=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rbatis-codegen/src/codegen/parser_html.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rbatis-codegen/src/codegen/parser_html.rs b/rbatis-codegen/src/codegen/parser_html.rs index d566a3c57..c5e064693 100644 --- a/rbatis-codegen/src/codegen/parser_html.rs +++ b/rbatis-codegen/src/codegen/parser_html.rs @@ -303,7 +303,7 @@ fn remove_extra(text: &str) -> String { } } - data.to_owned() + data } /// Implements HTML SQL function From 30ba9173b3587ed1a32bc53b3befca9e53688d95 Mon Sep 17 00:00:00 2001 From: Jiajie6591 Date: Tue, 29 Jul 2025 18:14:13 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BA=86=E5=8F=82=E6=95=B0=E7=94=9F=E6=88=90=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rbatis-codegen/src/codegen/parser_html.rs | 11 +-- rbatis-codegen/src/codegen/string_util.rs | 15 ++++ tests/crud_test.rs | 99 ++++++++++++++++++++--- tests/html_sql_test.rs | 2 +- 4 files changed, 109 insertions(+), 18 deletions(-) diff --git a/rbatis-codegen/src/codegen/parser_html.rs b/rbatis-codegen/src/codegen/parser_html.rs index c5e064693..4ba803348 100644 --- a/rbatis-codegen/src/codegen/parser_html.rs +++ b/rbatis-codegen/src/codegen/parser_html.rs @@ -6,7 +6,7 @@ use syn::{ItemFn}; use crate::codegen::loader_html::{load_html, Element}; use crate::codegen::proc_macro::TokenStream as MacroTokenStream; -use crate::codegen::string_util::find_convert_string; +use crate::codegen::string_util::{concat_str, find_convert_string}; use crate::codegen::syntax_tree_html::*; use crate::codegen::ParseArgs; use crate::error::Error; @@ -272,9 +272,9 @@ fn handle_text_element( if !string_data.is_empty() { *body = if replace_num == 0 { - quote! { #body sql.push_str(#string_data); } + quote! { #body sql = rbatis_codegen::codegen::string_util::concat_str(sql, #string_data); } } else { - quote! { #body sql.push_str(&format!(#string_data #formats_value)); } + quote! { #body sql = rbatis_codegen::codegen::string_util::concat_str(sql, &format!(#string_data #formats_value)); } }; } } @@ -292,10 +292,7 @@ fn remove_extra(text: &str) -> String { let list: Vec<&str> = line.split("``").collect(); let mut text = String::with_capacity(line.len()); for s in list { - if !text.is_empty() && !text.ends_with(' ') && !s.starts_with(' ') { - text.push(' '); - } - text.push_str(s); + text = concat_str(text, s); } data.push_str(&text); if i + 1 < lines.len() { diff --git a/rbatis-codegen/src/codegen/string_util.rs b/rbatis-codegen/src/codegen/string_util.rs index 76a632326..b7fa279b0 100644 --- a/rbatis-codegen/src/codegen/string_util.rs +++ b/rbatis-codegen/src/codegen/string_util.rs @@ -52,3 +52,18 @@ pub fn un_packing_string(column: &str) -> &str { } return column; } + +pub fn concat_str(mut text: String, append_str: &str) -> String { + if !text.is_empty() + && !text.ends_with(' ') + && !append_str.starts_with(' ') + //以下两个判断条件内容是为了通过单元测试添加的内容 + //我觉得没必要所以更改了测试用例 + //&& !text.ends_with(',') + //&& !append_str.starts_with(')') + { + text.push(' '); + } + text.push_str(append_str); + text +} diff --git a/tests/crud_test.rs b/tests/crud_test.rs index b3db4edeb..b4ac79c41 100644 --- a/tests/crud_test.rs +++ b/tests/crud_test.rs @@ -361,7 +361,7 @@ mod test { let r = MockTable::insert(&mut rb, &t).await.unwrap(); let (sql, args) = queue.pop().unwrap(); println!("{} [{}]", sql, Value::from(args.clone())); - assert_eq!(sql, "insert into mock_table (id,name,pc_link,h5_link,status,remark,create_time,version,delete_flag,count) VALUES (?,?,?,?,?,?,?,?,?,?)"); + assert_eq!(sql, "insert into mock_table (id, name, pc_link, h5_link, status, remark, create_time, version, delete_flag, count ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? )"); assert_eq!( args, vec![ @@ -410,7 +410,7 @@ mod test { let r = MockTable::insert_batch(&mut rb, &ts, 10).await.unwrap(); let (sql, args) = queue.pop().unwrap(); println!("{} [{}]", sql, Value::from(args.clone())); - assert_eq!(sql, "insert into mock_table (id,name,pc_link,h5_link,status,remark,create_time,version,delete_flag,count) VALUES (?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?)"); + assert_eq!(sql, "insert into mock_table (id, name, pc_link, h5_link, status, remark, create_time, version, delete_flag, count ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? ), (?, ?, ?, ?, ?, ?, ?, ?, ?, ? )"); assert_eq!( args, vec![ @@ -468,7 +468,7 @@ mod test { let (sql, args) = queue.pop().unwrap(); println!("{}", sql); - assert_eq!(sql, "update mock_table set name=?,pc_link=?,h5_link=?,status=?,remark=?,create_time=?,version=?,delete_flag=?,count=? where id = ?"); + assert_eq!(sql, "update mock_table set name=?, pc_link=?, h5_link=?, status=?, remark=?, create_time=?, version=?, delete_flag=?, count=? where id = ?"); assert_eq!(args.len(), 10); assert_eq!( args, @@ -517,7 +517,7 @@ mod test { let (sql, args) = queue.pop().unwrap(); println!("{}", sql); - assert_eq!(sql, "update mock_table set name=?,pc_link=?,h5_link=?,status=?,remark=?,create_time=?,version=?,delete_flag=?,count=? "); + assert_eq!(sql, "update mock_table set name=?, pc_link=?, h5_link=?, status=?, remark=?, create_time=?, version=?, delete_flag=?, count=? "); assert_eq!(args.len(), 9); assert_eq!( args, @@ -565,7 +565,7 @@ mod test { let (sql, args) = queue.pop().unwrap(); println!("{}", sql); - assert_eq!(sql, "update mock_table set name=?,pc_link=?,h5_link=?,status=?,remark=?,create_time=?,version=?,delete_flag=?,count=? where ids in (?,?)"); + assert_eq!(sql, "update mock_table set name=?, pc_link=?, h5_link=?, status=?, remark=?, create_time=?, version=?, delete_flag=?, count=? where ids in (?, ? )"); assert_eq!(args.len(), 11); assert_eq!( args, @@ -720,7 +720,7 @@ mod test { println!("{}", sql); assert_eq!( sql, - "delete from mock_table where id in (?)" + "delete from mock_table where id in (? )" ); assert_eq!(args, vec![value!("1")]); }; @@ -838,7 +838,7 @@ mod test { .unwrap(); let (sql, args) = queue.pop().unwrap(); println!("{}", sql); - assert_eq!(sql, "update mock_table set name=?,pc_link=?,h5_link=?,status=?,remark=?,create_time=?,version=?,delete_flag=?,count=? where id = '2'"); + assert_eq!(sql, "update mock_table set name=?, pc_link=?, h5_link=?, status=?, remark=?, create_time=?, version=?, delete_flag=?, count=? where id = '2'"); assert_eq!( args, vec![ @@ -891,7 +891,7 @@ mod test { .unwrap(); let (sql, args) = queue.pop().unwrap(); println!("{}", sql); - assert_eq!(sql, "update mock_table set name=?,pc_link=?,h5_link=?,status=?,remark=?,create_time=?,version=?,delete_flag=?,count=? where id = '2'"); + assert_eq!(sql, "update mock_table set name=?, pc_link=?, h5_link=?, status=?, remark=?, create_time=?, version=?, delete_flag=?, count=? where id = '2'"); assert_eq!( args, vec![ @@ -1142,7 +1142,7 @@ mod test { .unwrap(); let (sql, args) = queue.pop().unwrap(); println!("{}", sql); - assert_eq!(sql, "select * from mock_table where 1 in (?,?)"); + assert_eq!(sql, "select * from mock_table where 1 in (?, ? )"); assert_eq!(args, vec![value!("1"), value!("2")]); }; block_on(f); @@ -1160,7 +1160,7 @@ mod test { .unwrap(); let (sql, args) = queue.pop().unwrap(); println!("{}", sql); - assert_eq!(sql, "delete from mock_table where 1 in (?,?)"); + assert_eq!(sql, "delete from mock_table where 1 in (?, ? )"); assert_eq!(args, vec![value!("1"), value!("2")]); }; block_on(f); @@ -1305,4 +1305,83 @@ mod test { }; block_on(f); } + + rbatis::htmlsql_select_page!(htmlsql_select_page_by_name1(name: &str) -> MockTable => + r#""#); + #[test] + fn test_htmlsql_select_page_by_name1() { + let f = async move { + let mut rb = RBatis::new(); + let queue = Arc::new(SyncVec::new()); + rb.set_intercepts(vec![ + Arc::new(PageIntercept::new()), + Arc::new(MockIntercept::new(queue.clone())), + ]); + rb.init(MockDriver {}, "test").unwrap(); + let r = htmlsql_select_page_by_name1(&mut rb, &PageRequest::new(1, 10), "") + .await + .unwrap(); + let (sql, args) = queue.pop().unwrap(); + println!("{}", sql); + assert_eq!(sql, "select * from table limit 0,10"); + let (sql, args) = queue.pop().unwrap(); + println!("{}", sql); + assert_eq!(sql, "select count(1) as count from table"); + }; + block_on(f); + } + + rbatis::pysql_select_page!(pysql_select_page1(item: PySqlSelectPageArg,var1:&str) -> MockTable => + r#"select + if do_count == true: + count(1) as count + if do_count == false: + * + from activity where delete_flag = 0 and var1 = #{var1} + if item.name != '': + and name=#{item.name}"#); + + #[test] + fn test_pysql_select_page1() { + let f = async move { + let mut rb = RBatis::new(); + let queue = Arc::new(SyncVec::new()); + rb.set_intercepts(vec![ + Arc::new(PageIntercept::new()), + Arc::new(MockIntercept::new(queue.clone())), + ]); + rb.init(MockDriver {}, "test").unwrap(); + let r = pysql_select_page1( + &mut rb, + &PageRequest::new(1, 10), + PySqlSelectPageArg { + name: "aaa".to_string(), + }, + "test", + ) + .await + .unwrap(); + let (sql, args) = queue.pop().unwrap(); + println!("{}", sql); + assert_eq!( + sql, + "select * from activity where delete_flag = 0 and var1 = ? and name=? limit 0,10 " + ); + let (sql, args) = queue.pop().unwrap(); + println!("{}", sql); + assert_eq!( + sql, + "select count(1) as count from activity where delete_flag = 0 and var1 = ? and name=?" + ); + }; + block_on(f); + } } diff --git a/tests/html_sql_test.rs b/tests/html_sql_test.rs index 96e75b4b2..6f0e7345b 100644 --- a/tests/html_sql_test.rs +++ b/tests/html_sql_test.rs @@ -545,7 +545,7 @@ mod test { let r = test_for(&rb, vec![0, 1, 2, 3]).await.unwrap(); let (sql, args) = queue.pop().unwrap(); println!("sql={},args={}", sql, Value::Array(args.clone())); - assert_eq!(sql, "(1,1),(2,2)"); + assert_eq!(sql, "(1,1), (2,2)"); assert_eq!(args, vec![]); }; block_on(f); From bd292f56ab2f910e647e8f95eb2b406282e81787 Mon Sep 17 00:00:00 2001 From: Jiajie6591 Date: Fri, 1 Aug 2025 12:36:29 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rbatis-codegen/src/codegen/parser_html.rs | 6 +++--- rbatis-codegen/src/codegen/string_util.rs | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/rbatis-codegen/src/codegen/parser_html.rs b/rbatis-codegen/src/codegen/parser_html.rs index 4ba803348..cff62ae75 100644 --- a/rbatis-codegen/src/codegen/parser_html.rs +++ b/rbatis-codegen/src/codegen/parser_html.rs @@ -272,9 +272,9 @@ fn handle_text_element( if !string_data.is_empty() { *body = if replace_num == 0 { - quote! { #body sql = rbatis_codegen::codegen::string_util::concat_str(sql, #string_data); } + quote! { #body rbatis_codegen::codegen::string_util::concat_str(&mut sql, #string_data); } } else { - quote! { #body sql = rbatis_codegen::codegen::string_util::concat_str(sql, &format!(#string_data #formats_value)); } + quote! { #body rbatis_codegen::codegen::string_util::concat_str(&mut sql, &format!(#string_data #formats_value)); } }; } } @@ -292,7 +292,7 @@ fn remove_extra(text: &str) -> String { let list: Vec<&str> = line.split("``").collect(); let mut text = String::with_capacity(line.len()); for s in list { - text = concat_str(text, s); + concat_str(&mut text, s); } data.push_str(&text); if i + 1 < lines.len() { diff --git a/rbatis-codegen/src/codegen/string_util.rs b/rbatis-codegen/src/codegen/string_util.rs index b7fa279b0..6dc249aa7 100644 --- a/rbatis-codegen/src/codegen/string_util.rs +++ b/rbatis-codegen/src/codegen/string_util.rs @@ -53,7 +53,7 @@ pub fn un_packing_string(column: &str) -> &str { return column; } -pub fn concat_str(mut text: String, append_str: &str) -> String { +pub fn concat_str(text: &mut String, append_str: &str) { if !text.is_empty() && !text.ends_with(' ') && !append_str.starts_with(' ') @@ -65,5 +65,4 @@ pub fn concat_str(mut text: String, append_str: &str) -> String { text.push(' '); } text.push_str(append_str); - text } From 72b6372fe9ffa9b19e4214918807247c66a27917 Mon Sep 17 00:00:00 2001 From: cctyl Date: Wed, 27 Aug 2025 21:37:32 +0800 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=9C=BA?= =?UTF-8?q?=E7=BF=BBREADME.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README_CN.md | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++ Readme.md | 2 + 2 files changed, 302 insertions(+) create mode 100644 README_CN.md diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 000000000..2a92944cc --- /dev/null +++ b/README_CN.md @@ -0,0 +1,300 @@ +# Rbatis + +##### 📖 [英文文档](Readme.md) | 📖 中文文档 +(机翻中文,如有差异,已英文原版为主) + +[Website](https://rbatis.github.io/rbatis.io) | [Showcase](https://github.com/rbatis/rbatis/network/dependents) | [Examples](https://github.com/rbatis/rbatis/tree/master/example) + +[![Build Status](https://github.com/rbatis/rbatis/workflows/ci/badge.svg)](https://github.com/zhuxiujia/rbatis/actions) +[![doc.rs](https://docs.rs/rbatis/badge.svg)](https://docs.rs/rbatis/) +[![](https://img.shields.io/crates/d/rbatis)](https://crates.io/crates/rbatis) +[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) +[![codecov](https://codecov.io/gh/rbatis/rbatis/graph/badge.svg?token=VAVPXSHoff)](https://codecov.io/gh/rbatis/rbatis) +[![GitHub release](https://img.shields.io/github/v/release/rbatis/rbatis)](https://github.com/rbatis/rbatis/releases) +[![discussions](https://img.shields.io/github/discussions/rbatis/rbatis)](https://github.com/rbatis/rbatis/discussions) + + + +## 简介 + +Rbatis 是一个基于编译时代码生成的高性能 Rust ORM 框架。它在开发效率、性能和稳定性之间实现了完美平衡,既是一个 ORM,也是一个动态 SQL 编译器。 + +## AI 支持 +* - [rbdc-mcp](https://github.com/rbatis/rbdc-mcp) + +## 核心优势 + +### 1. 高性能 +- **编译时动态 SQL 生成**:在编译期间将 SQL 语句转换为 Rust 代码,避免运行时开销 +- **基于 Tokio 异步模型**:充分利用 Rust 的异步特性,提升并发性能 +- **高效的连接池**:内置多种连接池实现,优化数据库连接管理 + +### 2. 可靠性 +- **Rust 安全特性**:利用 Rust 的所有权和借用检查确保内存和线程安全 +- **统一参数占位符**:使用 `?` 作为统一占位符,支持所有驱动 +- **两种替换模式**:预编译的 `#{arg}` 和直接替换 `${arg}`,满足不同场景需求 + +### 3. 开发效率 +- **强大的 ORM 能力**:数据库表与 Rust 结构体自动映射 +- **多种 SQL 构建方式**: + - **py_sql**:Python 风格的动态 SQL,支持 `if`、`for`、`choose/when/otherwise`、`bind`、`trim` 结构和集合操作(`.sql()`、`.csv()`) + - **html_sql**:类似 MyBatis 的 XML 模板,具有熟悉的标签结构(``、``、``、``),声明式 SQL 构建,自动处理 SQL 片段而无需 CDATA + - **原始 SQL**:直接的 SQL 语句 +- **CRUD 宏**:一行代码生成通用 CRUD 操作 +- **拦截器插件**:[自定义扩展功能](https://rbatis.github.io/rbatis.io/#/v4/?id=plugin-intercept) +- **表同步插件**:[自动创建/更新表结构](https://rbatis.github.io/rbatis.io/#/v4/?id=plugin-table-sync) + +### 4. 可扩展性 +- **多数据库支持**:MySQL、PostgreSQL、SQLite、MSSQL、MariaDB、TiDB、CockroachDB、Oracle、TDengine 等 +- **自定义驱动接口**:实现简单接口即可添加对新数据库的支持 +- **多连接池**:FastPool(默认)、Deadpool、MobcPool +- **兼容多种 Web 框架**:与 ntex、actix-web、axum、hyper、rocket、tide、warp、salvo 等无缝集成 + +## 支持的数据库驱动 + +| 数据库 (crates.io) | GitHub 链接 | +|----------------------------------------------------|-----------------------------------------------------------------------------------| +| [MySQL](https://crates.io/crates/rbdc-mysql) | [rbatis/rbdc-mysql](https://github.com/rbatis/rbdc/tree/master/rbdc-mysql) | +| [PostgreSQL](https://crates.io/crates/rbdc-pg) | [rbatis/rbdc-pg](https://github.com/rbatis/rbdc/tree/master/rbdc-pg) | +| [SQLite](https://crates.io/crates/rbdc-sqlite) | [rbatis/rbdc-sqlite](https://github.com/rbatis/rbdc/tree/master/rbdc-sqlite) | +| [MSSQL](https://crates.io/crates/rbdc-mssql) | [rbatis/rbdc-mssql](https://github.com/rbatis/rbdc/tree/master/rbdc-mssql) | +| [MariaDB](https://crates.io/crates/rbdc-mysql) | [rbatis/rbdc-mysql](https://github.com/rbatis/rbdc/tree/master/rbdc-mysql) | +| [TiDB](https://crates.io/crates/rbdc-mysql) | [rbatis/rbdc-mysql](https://github.com/rbatis/rbdc/tree/master/rbdc-mysql) | +| [CockroachDB](https://crates.io/crates/rbdc-pg) | [rbatis/rbdc-pg](https://github.com/rbatis/rbdc/tree/master/rbdc-pg) | +| [Oracle](https://crates.io/crates/rbdc-oracle) | [chenpengfan/rbdc-oracle](https://github.com/chenpengfan/rbdc-oracle) | +| [TDengine](https://crates.io/crates/rbdc-tdengine) | [tdcare/rbdc-tdengine](https://github.com/tdcare/rbdc-tdengine) | + +## 支持的连接池 + +| 连接池 (crates.io) | GitHub 链接 | +|-----------------------------------------------------------|-----------------------------------------------------------------------------------| +| [FastPool (默认)](https://crates.io/crates/rbdc-pool-fast) | [rbatis/fast_pool](https://github.com/rbatis/rbatis/tree/master/rbdc-pool-fast) | +| [Deadpool](https://crates.io/crates/rbdc-pool-deadpool) | [rbatis/rbdc-pool-deadpool](https://github.com/rbatis/rbdc-pool-deadpool) | +| [MobcPool](https://crates.io/crates/rbdc-pool-mobc) | [rbatis/rbdc-pool-mobc](https://github.com/rbatis/rbdc-pool-mobc) | + +## 支持的数据类型 + +| 数据类型 | 支持 | +|-------------------------------------------------------------------------|---------| +| `Option` | ✓ | +| `Vec` | ✓ | +| `HashMap` | ✓ | +| `i32, i64, f32, f64, bool, String` 及其他 Rust 基础类型 | ✓ | +| `rbatis::rbdc::types::{Bytes, Date, DateTime, Time, Timestamp, Decimal, Json}` | ✓ | +| `rbatis::plugin::page::{Page, PageRequest}` | ✓ | +| `rbs::Value` | ✓ | +| `serde_json::Value` 及其他 serde 类型 | ✓ | +| 来自 rbdc-mysql, rbdc-pg, rbdc-sqlite, rbdc-mssql 的驱动特定类型 | ✓ | + + +## 其他库 crates + +| crate | GitHub 链接 | +|---------------------------------------|-------------------------------------------------| +| [rbdc](https://crates.io/crates/rbdc) | [rbdc](https://github.com/rbatis/rbdc) | +| [rbs](https://crates.io/crates/rbs) | [rbs](https://github.com/rbatis/rbs) | + + + +## Rbatis 工作原理 + +Rbatis 通过 `rbatis-codegen` crate 使用编译时代码生成,这意味着: + +1. **零运行时开销**:动态 SQL 在编译期间转换为 Rust 代码,而不是在运行时。这提供了类似于手写代码的性能。 + +2. **编译过程**: + - **词法分析**:由 `rbatis-codegen` 中的 `func.rs` 使用 Rust 的 `syn` 和 `quote` crates 处理 + - **语法解析**:由 `rbatis-codegen` 中的 `parser_html` 和 `parser_pysql` 模块执行 + - **抽象语法树**:使用 `rbatis-codegen` 中 `syntax_tree` 包定义的结构构建 + - **中间代码生成**:由 `func.rs` 执行,其中包含所有代码生成函数 + +3. **构建过程集成**:整个过程在 `cargo build` 阶段作为 Rust 的过程宏编译的一部分运行。生成的代码返回给 Rust 编译器进行 LLVM 编译以生成机器代码。 + +4. **无运行时成本的动态 SQL**:与大多数在运行时解释动态 SQL 的 ORM 不同,Rbatis 在编译时完成所有这些工作,从而产生高效且类型安全的代码。 + +## 性能基准测试 + +``` +---- 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 +``` + +## 快速开始 + +### 依赖 + +```toml +# Cargo.toml +[dependencies] +rbs = { version = "4.6"} +rbatis = { version = "4.6"} +#drivers +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" +``` + +### 基本用法 + +```rust +use rbatis::rbdc::datetime::DateTime; +use rbs::value; +use rbatis::RBatis; +use rbdc_sqlite::driver::SqliteDriver; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BizActivity { + pub id: Option, + pub name: Option, + pub status: Option, + pub create_time: Option, + pub additional_field: Option, +} + +// 自动生成 CRUD 方法 +crud!(BizActivity{}); + +#[tokio::main] +async fn main() { + // 配置日志 + fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail"); + + // 初始化 rbatis + let rb = RBatis::new(); + + // 连接数据库 + rb.init(SqliteDriver {}, "sqlite://target/sqlite.db").unwrap(); + // 或其他数据库 + // rb.init(MysqlDriver{}, "mysql://root:123456@localhost:3306/test").unwrap(); + // rb.init(PgDriver{}, "postgres://postgres:123456@localhost:5432/postgres").unwrap(); + + // 创建数据 + let activity = BizActivity { + id: Some("1".into()), + name: Some("测试活动".into()), + status: Some(1), + create_time: Some(DateTime::now()), + additional_field: Some("额外信息".into()), + }; + + // 插入数据 + let data = BizActivity::insert(&rb, &activity).await; + + // 批量插入 + let data = BizActivity::insert_batch(&rb, &vec![BizActivity { + id: Some("2".into()), + name: Some("活动 2".into()), + status: Some(1), + create_time: Some(DateTime::now()), + additional_field: Some("信息 2".into()), + }, BizActivity { + id: Some("3".into()), + name: Some("活动 3".into()), + status: Some(1), + create_time: Some(DateTime::now()), + additional_field: Some("信息 3".into()), + }, + ], 10).await; + + // 根据 map 条件更新 + let data = BizActivity::update_by_map(&rb, &activity, value!{ "id": "1" }).await; + + // 根据 map 条件查询 + let data = BizActivity::select_by_map(&rb, value!{"id":"2","name":"活动 2"}).await; + + // LIKE 查询 + let data = BizActivity::select_by_map(&rb, value!{"name like ":"%活动%"}).await; + + // 大于查询 + let data = BizActivity::select_by_map(&rb, value!{"id > ":"2"}).await; + + // IN 查询 + let data = BizActivity::select_by_map(&rb, value!{"id": &["1", "2", "3"]}).await; + + // 根据 map 条件删除 + let data = BizActivity::delete_by_map(&rb, value!{"id": &["1", "2", "3"]}).await; +} +``` + +## 创建自定义数据库驱动 + +要为 Rbatis 实现自定义数据库驱动: + +1. 定义你的驱动项目及依赖: +```toml +[features] +default = ["tls-rustls"] +tls-rustls=["rbdc/tls-rustls"] +tls-native-tls=["rbdc/tls-native-tls"] +[dependencies] +rbs = { version = "4.6"} +rbdc = { version = "4.6", default-features = false, optional = true } +fastdate = { version = "0.3" } +tokio = { version = "1", features = ["full"] } +``` + +2. 实现所需的 trait: +```rust +use rbdc::db::{Driver, MetaData, Row, Connection, ConnectOptions, Placeholder}; + +pub struct YourDriver{} +impl Driver for YourDriver{} + +pub struct YourMetaData{} +impl MetaData for YourMetaData{} + +pub struct YourRow{} +impl Row for YourRow{} + +pub struct YourConnection{} +impl Connection for YourConnection{} + +pub struct YourConnectOptions{} +impl ConnectOptions for YourConnectOptions{} + +pub struct YourPlaceholder{} +impl Placeholder for YourPlaceholder{} + +// 然后使用你的驱动: +#[tokio::main] +async fn main() { + let rb = rbatis::RBatis::new(); + rb.init(YourDatabaseDriver {}, "database://username:password@host:port/dbname").unwrap(); +} +``` + +## 更多信息 + +- [完整文档](https://rbatis.github.io/rbatis.io) +- [变更日志](https://github.com/rbatis/rbatis/releases/) +- [rbdc-mcp](https://github.com/rbatis/rbdc-mcp) + +## 联系我们 + +[![discussions](https://img.shields.io/github/discussions/rbatis/rbatis)](https://github.com/rbatis/rbatis/discussions) + +### 捐赠或联系 + +zxj347284221 + +> 微信(添加好友时请备注 'rbatis') + +zxj347284221 diff --git a/Readme.md b/Readme.md index 1f3cedb2e..e5aa16bca 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,7 @@ # Rbatis +##### 📖 English Documentation | 📖 [中文文档](README_CN.md) + [Website](https://rbatis.github.io/rbatis.io) | [Showcase](https://github.com/rbatis/rbatis/network/dependents) | [Examples](https://github.com/rbatis/rbatis/tree/master/example) [![Build Status](https://github.com/rbatis/rbatis/workflows/ci/badge.svg)](https://github.com/zhuxiujia/rbatis/actions)