Crates.io | tiny_orm_core |
lib.rs | tiny_orm_core |
version | 0.4.6 |
source | src |
created_at | 2022-09-22 02:35:21.900109 |
updated_at | 2023-08-18 07:51:46.132048 |
description | 基于sqlx的将SQL和ORM结合的简易ORM实现 |
homepage | |
repository | https://gitee.com/eshangrao/tiny-orm |
max_upload_size | |
id | 671281 |
size | 69,941 |
一个基于sqlx的极简orm,本项目将sql代码以derive属性方式和结构体进行关联,并自动生成相关CRUD相关API,可根据需要实现任意复杂度的数据获取。
本项目开发主要原因:
因此,是否可以将数据查询的sql和rust结合,由程序员自行控制sql的表现,这样在rust的高性能助力下,我们可以写成性能超级赞的数据库应用,并实现如下功能:
本项目由两个库组成,分别为:tiny_orm_core和tiny_orm_macro_derive,其中tiny_orm_core为项目核心库,tiny_orm_macro_derive为项目派生宏库
根据你的数据库类型选择不同特性,缺省为mysql
结构体对应数据表的相关配置信息,一般由派生宏进行自动生成,并提供如下方法
trait,实现了db_query创建数据库查询器,方法定义如下:
fn db_query(sql: &str) -> Query<MySql, MySqlArguments>
trait,实现了各种数据库交互方法,均以db_开头,该trait中的方法不自动进行数据转换.
在派生宏无法满足需求的情况下,可使用该trait中的方法构建灵活的crud方法。
trait,实现了与结构体关联的相关数据库交互放阿飞,均以orm_开头,该trait中的查询类方法均实现数据库行与struct的转换,转换时调用如下方法: fn orm_row_map(row: TinyOrmSqlRow) -> Self; 该方法需要自行实现。
在派生宏无法满足需求的情况下,可使用该trait中的方法构建灵活的crud方法。
为结构体自动实现TinyOrmDbModel数据库模型
本宏定义了如下属性,用于设置数据映射接口和参数
可选属性,可设置多个。
自定义数据库表名称。 未设置则表名称为结构名称的蛇形命名, 例如:TestUser转换为test_user。
可选属性,可设置多个。
为自动生成的数据库表名称增加前缀。 例如:TestUser转换为core_test_user.
可选属性,可设置多个。
生成Self::orm_query_with开头的获取多条记录的查询方法。
有三个子属性:
例如:
#[orm_query(
name = "name_and_tel",
sql_where = "user.name = ? and mobile_phone = ?",
order_by = "user.id",
args = "name:&str,mobile_phone:&str",
doc = "根据姓名和手机号查询用户",
)]
自动生成如下函数:
/// 根据姓名和手机号查询用户
pub async fn orm_query_with_name_and_tel(pool: &TinyOrmDbPool, name:&str,mobile_phone:&str) ->AnyhowResult<Vec<Self>>{...}
可选属性,可设置多个。
生成Self::orm_update_with开头的将更新记录方法。
有三个子属性:
例如:
#[orm_update(
name = "name_and_tel",
sql_set = "user.name = ? ,user.mobile_phone = ?",
sql_where = "id = ?",
args = "id:u32,name:&str,mobile_phone:&str",
doc = "根据id更新姓名和手机号",
)]
自动生成如下更新函数:
/// 根据id更新姓名和手机号
pub async orm_update_with_name_and_tel(pool: &TinyOrmDbPool, name: &str,mobile_phone: &str) -> AnyhowResult<()>{...}
可选属性,可设置多个。
生成Self::orm_delete_with开头的删除记录方法。
有三个子属性:
例如:
#[orm_delete(
name = "name_and_tel",
sql_where = "user.name = ? and mobile_phone = ?"
args = "name:&str,mobile_phone:&str",
doc = "根据姓名和手机号删除用户"
)]
自动生成如下函数:
/// 根据姓名和手机号查询用户
pub async fn orm_delete_with_name_and_tel(pool: &TinyOrmDbPool, name:&str,mobile_phone:&str) ->AnyhowResult<()>{...}
可选属性,可设置多个。
生成Self::orm_exist_with开头的删除记录方法。
有三个子属性:
例如:
#[orm_exist(
name = "id",
sql_where = "id = ?"
args = "id:u32",
doc = "根据id查询记录是否存在"
)]
自动生成如下函数:
/// 根据id查询记录是否存在
pub async fn orm_exist_with_name_and_tel(pool: &TinyOrmDbPool, id: u32) ->AnyhowResult<bool>
可选属性,可设置多个。
设置不与其他struct model关联join信息
有二个子属性:
例如:
#[orm_join(
select_field="node.name AS node_name, organization.name AS org_name",
join="JOIN node ON node.id = node_id JOIN organization ON organization.id = node.org_id",
)]
struct user {
id:u32,
name:String,
/// 所属网点:连接到其他表
node_id:String,
}
至少需要设置1个,可设置多个。
设置当前字段为主键字段
有两个子属性:
该属性会自动生成如下方法:
多个主键会自动汇聚为一个方法,并将主键设置为对应的函数方法的参数
struct TestUser {
#[orm_pk(name="user_id",auto="true")]
id:u32,
#[orm_pk]
tel:String,
}
//以上会自动生成如下方法
pub async fn orm_get_with_pk(pool: &TinyOrmDbPool,id:u32,tel:&str) -> AnyhowResult<Self>{...}
pub async fn orm_delete_with_pk(pool: &TinyOrmDbPool,id:u32,tel:&str) -> AnyhowResult<()>{...}
pub async fn orm_exist_with_pk(pool: &TinyOrmDbPool,id:u32,tel:&str) -> AnyhowResult<bool>{...}
pub async fn orm_delete(self,pool: &TinyOrmDbPool) -> AnyhowResult<bool>{...}
pub async fn orm_exist(self,pool: &TinyOrmDbPool) -> AnyhowResult<bool>{...}
可选属性,可设置多个。
设置对应字段为关联其他表的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 orm_update_字段名称(&self,pool: &TinyOrmDbPool) ->AnyhowResult<()>
生成使用该字段查询数据库记录的方法,方法定义如下:
struct User {
id:String,
#[orm_query(
order_by="user.id",
)]
name:String,
#[orm_query]
sex:String,
user_type:UserType,
}
生成使用该字段获取单条数据库记录的方法,方法定义如下:
将当前结构体实例数据插入到数据库表中,用于新增记录的保存
方法定义如下: pub async fn orm_insert(&mut self, pool: &TinyOrmDbPool) -> AnyhowResult<()>
将当前结构体实例数据更新到数据库表中,用于变更记录的保存
方法定义如下: pub async fn orm_update_all(&self,pool: &TinyOrmDbPool) -> AnyhowResult<()>
自动实现TinyOrmQuery trait宏
修改Cargo.toml,增加
[dependencies]
async-trait = "^0.1"
tiny_orm_macro_derive="^0.3"
[dependencies.tiny_orm_core]
version = "^0.3"
features = [
"mysql",
]
引入相关trait
use tiny_orm_core::prelude::*;
struct User{
#[orm_pk(auto="true")]
id: u32,
name: String,
}
使用自动生成的相关orm_方法进行数据库交互
//! 数据库模型的测试
use sqlx::mysql::MySqlPoolOptions;
use sqlx::Row;
use tiny_orm_macro_derive::{TinyOrm, TinyOrmQuery};
use super::*;
#[derive(Debug, PartialEq, Eq)]
pub struct UserType {
/// 类型编号
pub id: Option<u32>,
/// 类型名称
pub name: String,
/// 中断短信模板
pub template: String,
}
impl UserType {
/// 完整创建器
///
/// # 参数说明
///
/// * id 类型编号
/// * name 类型名称
/// * template 中断短信模板
pub fn new(id: u32, name: &str, template: &str) -> Self {
UserType {
id: Some(id),
name: name.into(),
template: template.into(),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Organization {
/// 行号
pub id: String,
/// 机构名称
pub name: String,
}
#[allow(dead_code)]
#[derive(TinyOrm, TinyOrmQuery, Debug, PartialEq, Eq)]
// 自动生成表格名称时附加前缀,生成表名称为:core_test_user
//#[orm_table_name_pref = "core"]
// 指定表名称,未指定则自动生成,规则结构名称转换为蛇形命名,如:test_user
#[orm_table_name = "user"]
/// 自动生成orm_query_with查询方法,生成后的函数定义如下
/// /// 根据姓名和手机号查询用户
/// pub async fn orm_query_with_name_and_tel(pool: &TinyOrmDbPool, name:&str,mobile_phone:&str) ->AnyhowResult<Vec<Self>> {
/// let sql = Self::DB_META.build_select_sql("name = ? and mobile_phone = ?");
/// let query = Self::db_query(&sql)
/// .bind(name)
/// .bind(mobile_phone);
/// Self::db_fetch_all(pool,query,Self::orm_row_map).await
/// }
#[orm_query(
name = "name_and_tel",
sql_where = "user.name = ? and mobile_phone = ?",
args = "name:&str,mobile_phone:&str",
doc = "根据姓名和手机号查询用户"
)]
/// 自动生成orm_delete_with删除方法,生成后的函数定义如下
/// /// 根据姓名和手机号删除用户
/// pub async fn orm_delete_with_name_and_tel(pool: &TinyOrmDbPool, name:&str,mobile_phone:&str) ->AnyhowResult<Vec<Self>> {
/// let sql = Self::DB_META.build_delete_sql("name = ? and mobile_phone = ?");
/// let query = Self::db_query(&sql)
/// .bind(name)
/// .bind(mobile_phone);
/// Self::db_execute(pool, query).await
/// }
#[orm_delete(
name = "name_and_tel",
sql_where = "user.name = ? and mobile_phone = ?",
args = "name:&str,mobile_phone:&str",
doc = "根据姓名和手机号删除用户"
)]
/// 生成orm_exist_with_name方法
#[orm_exist(
name = "name",
sql_where = "user.name like ?",
args = "name:&str",
doc = "根据姓名查询用户是否存在"
)]
/// 生成orm_update_with_name方法
#[orm_update(
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 {
/// 类型编号
/// 生成的orm_get_by_pk函数参数中,id转换为u32
/// 会自动将多个pk字段合并为一个方法的参数,生成如下方法
/// /// orm_pk自动实现:通过主键获取记录
/// pub async fn orm_get_by_pk(pool: &TinyOrmDbPool, id:u32,mobile_phone:&str) -> AnyhowResult<Self>{
/// let sql = Self::DB_META.build_select_sql(#pk_where_sql);
/// let query = Self::db_query(&sql)
/// .bind(id)
/// .bind(mobile_phone);
/// Self::db_fetch_one(pool, query, Self::orm_row_map).await
/// .with_context(|| "根据主键获取记录出错!")
/// }
/// /// orm_pk自动实现:通过主键删除记录
/// pub async fn orm_delete_by_pk(pool: &TinyOrmDbPool,id:u32,mobile_phone:&str) -> AnyhowResult<()>{
/// let sql = Self::DB_META.build_delete_sql(#pk_where_sql);
/// let query = Self::db_query(&sql)
/// .bind(id)
/// .bind(mobile_phone);
/// Self::db_execute(pool, query).await
/// .with_context(|| "根据主键删除记录出错!")?;
/// Ok(())
/// }
/// /// orm_pk自动实现:通过主键删除当前记录
/// pub async fn orm_delete(&self,pool: &TinyOrmDbPool) -> AnyhowResult<()>{
/// Self::orm_delete_by_pk(pool,self.id.unwrap(),self.mobile_phone.as_ref()).await
/// }
/// /// orm_pk自动实现:通过主键查询记录是否存在
/// pub async fn orm_exist_with_pk(pool: &TinyOrmDbPool,,id:u32,mobile_phone:&str) -> AnyhowResult<bool>{
/// let sql = Self::DB_META.build_exist_sql(Self::DB_META.pk_where_sql);
/// let row: (i64,) = sqlx::query_as(&sql)
/// .bind(id)
/// .bind(mobile_phone)
/// .fetch_one(pool)
/// .await
/// .with_context(|| "通过主键查询记录是否存在时出错!")?;
/// Ok(row.0 > 0)
/// }
/// /// orm_pk自动实现:通过主键查询当前记录是否存在
/// pub async fn orm_exist(&self,pool: &TinyOrmDbPool) -> AnyhowResult<()>{
/// Self::orm_exist_with_pk(pool,self.id.unwrap(),self.mobile_phone.as_ref()).await
/// }
/// 只能设置一个auto主键
#[orm_pk(name = "id", auto = "true")]
/// insert时自动生成id
pub id: Option<u32>,
/// 类型名称
// 生成orm_query_with_name方法
#[orm_query]
pub name: String,
/// 手机号码
/// 生成的orm_get_by_pk函数参数中,mobile_phone会自动把String转换为&str
#[orm_pk]
pub mobile_phone: String,
/// 密码
/// 重命名数表字段名称,否则与字段名称一致
#[orm_field(name = "password")]
// 生成self.orm_update_password方法
#[orm_update]
password: String,
/// 用户类型
/// 定义join字段和join sql
/// 自动生成Self::orm_query_join_with_user_type,Self::orm_delete_join_with_user_type,self.orm_update_join_with_user_type方法
#[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",//update 时保存值使用的字段id,如:user_type.id
link_id_type="u32"
)]
#[allow(unused_variables)]
pub user_type: UserType,
/// 所属机构
/// 自动生成Self::orm_query_join_with_org,Self::orm_delete_join_with_org,self.orm_update_join_with_org方法
#[orm_join(
name="org_id", // 重命名表字段名称
select_field="organization.name as org_name",
join="JOIN organization ON organization.id = org_id",
link_id="id",//update 时保存值使用的字段id,如:org.id
link_id_type="&str"
)]
pub org: Organization,
/// 忽略字段,不在数据库中对应
#[orm_ignore]
pub ignore_field: u32,
}
impl TestUser {
/// 完整创建器
///
/// # 参数说明
///
/// * id 编号
/// * name 姓名
/// * mobile_phone 手机
/// * password 密码
/// * user_type 用户类型
/// * org 对应机构
pub fn new(
id: u32,
name: &str,
mobile_phone: &str,
password: &str,
user_type: UserType,
org: Organization,
) -> Self {
Self {
id: Some(id),
name: name.into(),
mobile_phone: mobile_phone.into(),
password: password.into(),
user_type,
org,
ignore_field: 0,
}
}
/// 完整创建器
///
/// # 参数说明
///
/// * id 编号
/// * name 姓名
/// * mobile_phone 手机
/// * password 密码
/// * user_type 用户类型
/// * org 对应机构
pub fn new_no_id(
name: &str,
mobile_phone: &str,
password: &str,
user_type: UserType,
org: Organization,
) -> Self {
Self {
id: None,
name: name.into(),
mobile_phone: mobile_phone.into(),
password: password.into(),
user_type,
org,
ignore_field: 0,
}
}
}
/// 实现数据获取接口
impl TinyOrmData for TestUser {
/// 将sql返回数据映射为TestUser
fn orm_row_map(row: TinyOrmSqlRow) -> Self {
TestUser::new(
row.get::<u32, _>("id"),
row.get("name"),
row.get("mobile_phone"),
row.get("password"),
UserType::new(
row.get::<u32, _>("user_type_id"),
row.get("user_type_name"),
row.get("template"),
),
Organization {
id: row.get("org_id"),
name: row.get("org_name"),
},
)
}
}
async fn get_pool() -> AnyhowResult<TinyOrmDbPool> {
let user_name = "net_guard";
let password = "some pass";
let ip = "localhost";
let port = 3306;
let db_name = "abc_net_guard";
let pool = MySqlPoolOptions::new()
.max_connections(1)
.connect(&format!(
"mysql://{}:{}@{}:{}/{}",
user_name, password, ip, port, db_name
))
.await?;
Ok(pool)
}
/// .测试SQL生成
#[test]
fn test_user() {
println!("user sql : \n{}", TestUser::DB_META.select_sql);
}
/// .测试SQL生成
#[test]
fn test_db_query() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let data = TestUser::orm_get_all(&pool).await.unwrap();
dbg!(data);
});
}
/// 测试根据姓名和手机获取用户
#[test]
fn test_orm_query_with_name_and_mobile() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let user = TestUser::orm_query_with_name_and_tel(&pool, "张三", "1850703xxxx")
.await
.unwrap();
dbg!(user);
});
}
/// 测试根据主键获取获取用户
#[test]
fn test_orm_get_with_pk() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let user = TestUser::orm_get_with_pk(&pool, 2, "1387038xxxx")
.await
.unwrap();
dbg!(user);
});
}
/// 测试根据主键删除用户
#[test]
fn test_orm_delete() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let user = TestUser::orm_get_with_pk(&pool, 15, "1867930xxxx")
.await
.unwrap();
user.orm_delete(&pool).await.unwrap();
dbg!(user);
});
}
/// 测试根据个性删除,根据名称和手机号删除用户
#[test]
fn test_orm_delete_with_name_and_tel() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
TestUser::orm_delete_with_name_and_tel(&pool, "盛XX", "1507031xxxx")
.await
.unwrap();
});
}
/// 测试根据姓名和手机获取用户
#[test]
fn test_orm_query_with_name() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let user = TestUser::orm_query_with_name(&pool, "张三").await.unwrap();
dbg!(user);
});
}
/// 测试是否存在
#[test]
fn test_orm_exist_with_pk() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let is_exist = TestUser::orm_exist_with_pk(&pool, 8, "18607031111")
.await
.unwrap();
dbg!(is_exist);
});
}
/// 测试是否存在
#[test]
fn test_orm_exist_with_name() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let is_exist = TestUser::orm_exist_with_name(&pool, "王xx")
.await
.unwrap();
dbg!(is_exist);
});
}
/// 测试根据姓名和手机获取用户
#[test]
fn test_orm_update_with_name_and_tel() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let user = TestUser::orm_update_with_name_and_tel(&pool, "张三", "18507032200", 4)
.await
.unwrap();
dbg!(user);
});
}
/// 测试filter
#[test]
fn test_db_filter() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let sql = TestUser::DB_META.build_select_sql("user.name like ? ");
println!("{sql}");
let key = String::from("%李%");
let data = TestUser::orm_filter_with_sql(&pool, &sql, &key)
.await
.unwrap();
dbg!(data);
});
}
/// 测试join字段查询和更新
#[test]
fn test_orm_query_join_with_user_type() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let user_type = UserType::new(1, "支行", "");
let mut data = TestUser::orm_query_join_with_user_type(&pool, user_type)
.await
.unwrap();
let mut ygl = data.pop().unwrap();
let user_type_2 = UserType::new(2, "", "");
ygl.user_type = user_type_2;
ygl.orm_update_join_with_user_type(&pool).await.unwrap();
dbg!(ygl);
});
}
/// 测试join字段查询和更新
#[test]
fn test_orm_query_join_with_org_id() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let data = TestUser::orm_query_join_with_org_id(&pool, "14H700")
.await
.unwrap();
dbg!(data);
});
}
/// 测试join字段查询和更新
#[test]
fn test_orm_delete_join_with_user_type() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let user_type = UserType::new(2, "支行", "");
TestUser::orm_delete_join_with_user_type(&pool, user_type)
.await
.unwrap();
});
}
/// 测试更新所有信息
#[test]
fn test_orm_update_all() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let mut user = TestUser::orm_get_with_pk(&pool, 2, "13807931111")
.await
.unwrap();
let user_type = UserType::new(2, "支行", "");
user.name = "王2".into();
user.user_type = user_type;
user.orm_update_all(&pool).await.unwrap();
dbg!(user);
});
}
/// 测试插入信息
#[test]
fn test_orm_insert() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let org = Organization {
id: "14H700".into(),
name: "上饶分行".into(),
};
let user_type = UserType::new(2, "管理员", "");
let mut user = TestUser::new_no_id("李四", "1850703xxxx", "sss", user_type, org);
user.orm_insert(&pool).await.unwrap();
dbg!(user);
});
}
/// 测试根据姓名和手机获取用户
#[test]
fn test_orm_update_password() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let pool = get_pool().await.unwrap();
let mut user:TestUser = TestUser::orm_query_with_name(&pool, "张三").await.unwrap().pop().unwrap();
user.password="this is a pass".into();
user.orm_update_password(&pool).await.unwrap();
dbg!(user);
});
}