一个基于sqlx的极简orm,本项目将sql代码以derive属性方式和结构体进行关联,并自动生成相关CRUD相关API,可根据需要实现任意复杂度的数据获取。
本项目开发主要原因:
因此,是否可以将数据查询的sql和rust结合,由程序员自行控制sql的表现,这样在rust的高性能助力下,我们可以写成性能超级赞的数据库应用,并实现如下功能:
本项目由两个库组成,分别为:tinyormcore和tinyormmacroderive,其中tinyormcore为项目核心库,tinyormmacroderive为项目派生宏库
根据你的数据库类型选择不同特性,缺省为mysql
结构体对应数据表的相关配置信息,一般由派生宏进行自动生成,并提供如下方法
trait,实现了db_query创建数据库查询器,方法定义如下:
fn db_query(sql: &str) -> Query
trait,实现了各种数据库交互方法,均以db_开头,该trait中的方法不自动进行数据转换.
在派生宏无法满足需求的情况下,可使用该trait中的方法构建灵活的crud方法。
trait,实现了与结构体关联的相关数据库交互放阿飞,均以orm开头,该trait中的查询类方法均实现数据库行与struct的转换,转换时调用如下方法: fn ormrow_map(row: TinyOrmSqlRow) -> Self; 该方法需要自行实现。
在派生宏无法满足需求的情况下,可使用该trait中的方法构建灵活的crud方法。
为结构体自动实现TinyOrmDbModel数据库模型
本宏定义了如下属性,用于设置数据映射接口和参数
可选属性,可设置多个。
自定义数据库表名称。 未设置则表名称为结构名称的蛇形命名, 例如:TestUser转换为test_user。
可选属性,可设置多个。
为自动生成的数据库表名称增加前缀。 例如:TestUser转换为coretestuser.
可选属性,可设置多个。
生成Self::ormquerywith开头的获取多条记录的查询方法。
有三个子属性:
例如:
```ignore
name = "nameandtel",
sqlwhere = "user.name = ? and mobilephone = ?",
args = "name:&str,mobilephone:&str",
doc = "根据姓名和手机号查询用户",
)]
自动生成如下函数:
/// 根据姓名和手机号查询用户
pub async fn ormquerywithnameandtel(pool: &TinyOrmDbPool, name:&str,mobile_phone:&str) ->AnyhowResult
可选属性,可设置多个。
生成Self::ormupdatewith开头的将更新记录方法。
有三个子属性:
例如:
```ignore
name = "nameandtel", sqlset = "user.name = ? ,user.mobilephone = ?", sqlwhere = "id = ?", args = "id:u32,name:&str,mobilephone:&str", doc = "根据id更新姓名和手机号", )] 自动生成如下更新函数: /// 根据id更新姓名和手机号 pub async ormupdatewithnameandtel(pool: &TinyOrmDbPool, name: &str,mobilephone: &str) -> AnyhowResult<()>{...} ```
可选属性,可设置多个。
生成Self::ormdeletewith开头的删除记录方法。
有三个子属性:
例如:
```ignore
name = "nameandtel", sqlwhere = "user.name = ? and mobilephone = ?" args = "name:&str,mobilephone:&str", doc = "根据姓名和手机号删除用户" )] 自动生成如下函数: /// 根据姓名和手机号查询用户 pub async fn ormdeletewithnameandtel(pool: &TinyOrmDbPool, name:&str,mobile_phone:&str) ->AnyhowResult<()>{...} ```
可选属性,可设置多个。
生成Self::ormexistwith开头的删除记录方法。
有三个子属性:
例如:
```ignore
name = "id",
sqlwhere = "id = ?"
args = "id:u32",
doc = "根据id查询记录是否存在"
)]
自动生成如下函数:
/// 根据id查询记录是否存在
pub async fn ormexistwithnameandtel(pool: &TinyOrmDbPool, id: u32) ->AnyhowResult
至少需要设置1个,可设置多个。
设置当前字段为主键字段
有两个子属性:
该属性会自动生成如下方法:
多个主键会自动汇聚为一个方法,并将主键设置为对应的函数方法的参数
```ignore struct TestUser { #[ormpk(name="userid",auto="true")] id:u32, #[orm_pk] tel:String, }
//以上会自动生成如下方法
pub async fn ormgetwithpk(pool: &TinyOrmDbPool,id:u32,tel:&str) -> AnyhowResult
```
可选属性,可设置多个。
设置对应字段为关联其他表的join字段
有六个子属性:
如为设置skip_method="true",该属性会自动生成如下方法:
struct User {
#[orm_join(
name="user_type_id",
select_field="user_type.name as user_type_name, user_type.template",
join="JOIN user_type ON user_type.id = user_type_id",
link_id="id",
link_id_type="u32",
)]
user_type:UserType
}
// 会自动生成如下方法
pub async fn orm_query_join_with_user_type(pool: &TinyOrmDbPool,user_type:UserType) -> AnyhowResult<Vec<Self>>{...}
pub async fn orm_query_join_with_user_type_id(pool: &TinyOrmDbPool,query_value:u32) -> AnyhowResult<Vec<Self>>{...}
pub async fn orm_delete_join_with_user_type(pool: &TinyOrmDbPool,user_type:UserType) -> AnyhowResult<()>{...}
pub async fn orm_delete_join_with_user_type_id(pool: &TinyOrmDbPool,query_value:u32) -> AnyhowResult<()>{...}
pub async fn orm_update_join_with_user_type(&self,pool: &TinyOrmDbPool) -> AnyhowResult<()>{...}
可选属性,可设置多个。
设置对应数据库表的字段名称
有1个子属性:
可选属性,可设置多个。
默认情况下,结构体的每个字段均对应一个数据库表字段。 设置该属性后,可忽略对应字段,不对应数据库表的字段名称
生成更新数据库中的对应字段值的方法,方法定义如下: pub async fn ormupdate字段名称(&self,pool: &TinyOrmDbPool) ->AnyhowResult<()>
生成使用该字段查询数据库记录的方法,方法定义如下:
* pub async fn ormquerywith字段名称(pool: &TinyOrmDbPool,queryvalue:字段类型) -> AnyhowResult
生成使用该字段获取单条数据库记录的方法,方法定义如下:
* pub async fn ormgetwith字段名称(pool: &TinyOrmDbPool,queryvalue:字段类型) -> AnyhowResult
将当前结构体实例数据插入到数据库表中,用于新增记录的保存
方法定义如下: pub async fn orm_insert(&mut self, pool: &TinyOrmDbPool) -> AnyhowResult<()>
将当前结构体实例数据更新到数据库表中,用于变更记录的保存
方法定义如下: pub async fn ormupdateall(&self,pool: &TinyOrmDbPool) -> AnyhowResult<()>
自动实现TinyOrmQuery trait宏
修改Cargo.toml,增加
``` [dependencies] async-trait = "^0.1" tinyormmacro_derive="^0.3"
[dependencies.tinyormcore] version = "^0.3" features = [ "mysql", ] ```
ignore
use tiny_orm_core::prelude::*;
struct User{
#[orm_pk(auto="true")]
id: u32,
name: String,
}
``` //! 数据库模型的测试 use sqlx::mysql::MySqlPoolOptions; use sqlx::Row; use tinyormmacro_derive::{TinyOrm, TinyOrmQuery};
use super::*;
pub struct UserType {
/// 类型编号
pub id: Option
pub struct Organization { /// 行号 pub id: String, /// 机构名称 pub name: String, }
// 自动生成表格名称时附加前缀,生成表名称为:coretestuser //#[ormtablenamepref = "core"] // 指定表名称,未指定则自动生成,规则结构名称转换为蛇形命名,如:testuser
/// 自动生成ormquerywith查询方法,生成后的函数定义如下
/// /// 根据姓名和手机号查询用户
/// pub async fn ormquerywithnameandtel(pool: &TinyOrmDbPool, name:&str,mobilephone:&str) ->AnyhowResult
name = "name_and_tel",
sql_where = "user.name = ? and mobile_phone = ?",
args = "name:&str,mobile_phone:&str",
doc = "根据姓名和手机号查询用户"
)]
/// 自动生成ormdeletewith删除方法,生成后的函数定义如下
/// /// 根据姓名和手机号删除用户
/// pub async fn ormdeletewithnameandtel(pool: &TinyOrmDbPool, name:&str,mobilephone:&str) ->AnyhowResult
name = "name_and_tel",
sql_where = "user.name = ? and mobile_phone = ?",
args = "name:&str,mobile_phone:&str",
doc = "根据姓名和手机号删除用户"
)] /// 生成ormexistwith_name方法
name = "name",
sql_where = "user.name like ?",
args = "name:&str",
doc = "根据姓名查询用户是否存在"
)] /// 生成ormupdatewith_name方法
name = "name_and_tel",
sql_set = "name = ? , mobile_phone = ?",
sql_where = "id = ?",
args = "name:&str,mobile_phone:&str,id:u32",
doc = "根据id更新姓名和手机号"
)]
pub struct TestUser {
/// 类型编号
/// 生成的ormgetbypk函数参数中,id转换为u32
/// 会自动将多个pk字段合并为一个方法的参数,生成如下方法
/// /// ormpk自动实现:通过主键获取记录
/// pub async fn ormgetbypk(pool: &TinyOrmDbPool, id:u32,mobilephone:&str) -> AnyhowResult
impl TestUser {
/// 完整创建器
///
/// # 参数说明
///
/// * id 编号
/// * name 姓名
/// * mobilephone 手机
/// * password 密码
/// * usertype 用户类型
/// * org 对应机构
pub fn new(
id: u32,
name: &str,
mobilephone: &str,
password: &str,
usertype: UserType,
org: Organization,
) -> Self {
Self {
id: Some(id),
name: name.into(),
mobilephone: mobilephone.into(),
password: password.into(),
usertype,
org,
ignorefield: 0,
}
}
/// 完整创建器
///
/// # 参数说明
///
/// * id 编号
/// * name 姓名
/// * mobilephone 手机
/// * password 密码
/// * usertype 用户类型
/// * org 对应机构
pub fn newnoid(
name: &str,
mobilephone: &str,
password: &str,
usertype: UserType,
org: Organization,
) -> Self {
Self {
id: None,
name: name.into(),
mobilephone: mobilephone.into(),
password: password.into(),
usertype,
org,
ignorefield: 0,
}
}
}
/// 实现数据获取接口
impl TinyOrmData for TestUser {
/// 将sql返回数据映射为TestUser
fn ormrowmap(row: TinyOrmSqlRow) -> Self {
TestUser::new(
row.get::
async fn getpool() -> AnyhowResult
/// .测试SQL生成
fn testuser() { println!("user sql : \n{}", TestUser::DBMETA.select_sql); }
/// .测试SQL生成
fn testdbquery() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let data = TestUser::ormget_all(&pool).await.unwrap(); dbg!(data); }); }
/// 测试根据姓名和手机获取用户
fn testormquerywithnameandmobile() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let user = TestUser::ormquerywithnameandtel(&pool, "张三", "1850703xxxx") .await .unwrap(); dbg!(user); }); } /// 测试根据主键获取获取用户
fn testormgetwithpk() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let user = TestUser::ormgetwithpk(&pool, 2, "1387038xxxx") .await .unwrap(); dbg!(user); }); }
/// 测试根据主键删除用户
fn testormdelete() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let user = TestUser::ormgetwithpk(&pool, 15, "1867930xxxx") .await .unwrap(); user.orm_delete(&pool).await.unwrap(); dbg!(user); }); }
/// 测试根据个性删除,根据名称和手机号删除用户
fn testormdeletewithnameandtel() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); TestUser::ormdeletewithnameandtel(&pool, "盛XX", "1507031xxxx") .await .unwrap(); }); } /// 测试根据姓名和手机获取用户
fn testormquerywithname() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let user = TestUser::ormquerywithname(&pool, "张三").await.unwrap(); dbg!(user); }); } /// 测试是否存在
fn testormexistwithpk() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let isexist = TestUser::ormexistwithpk(&pool, 8, "18607031111") .await .unwrap(); dbg!(isexist); }); }
/// 测试是否存在
fn testormexistwithname() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let isexist = TestUser::ormexistwithname(&pool, "王xx") .await .unwrap(); dbg!(isexist); }); } /// 测试根据姓名和手机获取用户
fn testormupdatewithnameandtel() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let user = TestUser::ormupdatewithnameandtel(&pool, "张三", "18507032200", 4) .await .unwrap(); dbg!(user); }); } /// 测试filter
fn testdbfilter() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let sql = TestUser::DBMETA.buildselectsql("user.name like ? "); println!("{sql}"); let key = String::from("%李%"); let data = TestUser::ormfilterwith_sql(&pool, &sql, &key) .await .unwrap(); dbg!(data); }); }
/// 测试join字段查询和更新
fn testormqueryjoinwithusertype() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let usertype = UserType::new(1, "支行", ""); let mut data = TestUser::ormqueryjoinwithusertype(&pool, usertype) .await .unwrap(); let mut ygl = data.pop().unwrap(); let usertype2 = UserType::new(2, "", ""); ygl.usertype = usertype2; ygl.ormupdatejoinwithusertype(&pool).await.unwrap(); dbg!(ygl); }); }
/// 测试join字段查询和更新
fn testormqueryjoinwithorgid() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let data = TestUser::ormqueryjoinwithorgid(&pool, "14H700") .await .unwrap(); dbg!(data); }); } /// 测试join字段查询和更新
fn testormdeletejoinwithusertype() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let usertype = UserType::new(2, "支行", ""); TestUser::ormdeletejoinwithusertype(&pool, usertype) .await .unwrap(); }); } /// 测试更新所有信息
fn testormupdateall() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let mut user = TestUser::ormgetwithpk(&pool, 2, "13807931111") .await .unwrap(); let usertype = UserType::new(2, "支行", ""); user.name = "王2".into(); user.usertype = usertype; user.ormupdateall(&pool).await.unwrap(); dbg!(user); }); }
/// 测试插入信息
fn testorminsert() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let org = Organization { id: "14H700".into(), name: "上饶分行".into(), }; let usertype = UserType::new(2, "管理员", ""); let mut user = TestUser::newnoid("李四", "1850703xxxx", "sss", usertype, org); user.orminsert(&pool).await.unwrap(); dbg!(user); }); } /// 测试根据姓名和手机获取用户
fn testormupdatepassword() { tokio::runtime::Builder::newcurrentthread() .enableall() .build() .unwrap() .blockon(async { let pool = getpool().await.unwrap(); let mut user:TestUser = TestUser::ormquerywithname(&pool, "张三").await.unwrap().pop().unwrap(); user.password="this is a pass".into(); user.ormupdate_password(&pool).await.unwrap(); dbg!(user); }); }
```