#orm #sqlx #tiny

macro tiny_orm_macro_derive

基于sqlx的将SQL和ORM结合的简易ORM实现的相关辅助宏

18 releases

0.4.5 Aug 18, 2023
0.4.4 Aug 18, 2023
0.3.1 Nov 18, 2022
0.3.0 Oct 6, 2022
0.2.10 Sep 25, 2022

#2895 in Database interfaces

Download history 10/week @ 2024-07-25 2/week @ 2024-09-19 4/week @ 2024-09-26 2/week @ 2024-10-03

103 downloads per month
Used in tiny_orm_core

MIT/Apache

130KB
1.5K SLoC

Rust Tiny Orm

一个基于sqlx的极简orm,本项目将sql代码以derive属性方式和结构体进行关联,并自动生成相关CRUD相关API,可根据需要实现任意复杂度的数据获取。

本项目开发主要原因:

  1. 目前rust的orm产品都太大而全了,习惯了Python Django的自动化的实现,对DSL相当不习惯,个人觉得太繁琐了;
  2. 实际orm就是api和sql的转换,因为orm的实现限制,sql的复杂查询的实现都比较别扭,为了与数据库交互还得额外学习大量orm dsl的用法,为什么不直接用sql查询呢?!
  3. orm为了满足各种需要,实现均较为复杂,带来了大量的学习成本。但是,实际工程角度来看,实际我们项目与数据库的交互是有限的,基本就是根据特定字段查询数据,新增数据和更新数据等,各种查询的逻辑也基本是固定的,并不需要高度灵活性的查询接口,且一般ORM实现高度灵活的查询,是以增加运行时消耗为代价的;
  4. 实际orm的核心生成sql交互的代码,整个和rust的宏高度契合,因此使用宏生成我们工程需要的数据库接口,是ORM的最佳实践!

因此,是否可以将数据查询的sql和rust结合,由程序员自行控制sql的表现,这样在rust的高性能助力下,我们可以写成性能超级赞的数据库应用,并实现如下功能:

  1. 自动实现rust结构体到数据库表的映射;
  2. 自动生成数据库交互相关的接口,如insert、update、query、delete和exist类方法;
  3. 在自动生成方法基础上,可根据sql自动生成具有高度灵活性的数据库CRUD方法;
  4. 在以上基础上最大化的减少手写sql的工作量,减少数据库交互逻辑的处理工作量。

项目组成

本项目由两个库组成,分别为:tiny_orm_core和tiny_orm_macro_derive,其中tiny_orm_core为项目核心库,tiny_orm_macro_derive为项目派生宏库

特性

根据你的数据库类型选择不同特性,缺省为mysql

  1. mysql
  2. postgres
  3. sqlite
  4. mssql
  5. any

核心库说明

  1. tiny_orm_core::TinyOrmDbMeta

结构体对应数据表的相关配置信息,一般由派生宏进行自动生成,并提供如下方法

  • pub fn build_select_sql(&self, where_sql: &str) -> String
    • 创建select语句
  • pub fn build_exist_sql(&self, where_sql: &str) -> String
    • 创建exist语句
  • pub fn build_insert_sql(&self, insert_field_value: &str) -> String
    • 创建insert语句
  • pub fn build_delete_sql(&self, delete_where_sql: &str) -> String
    • 创建where语句
  • pub fn build_update_sql(&self, set_sql: &str, where_sql: Option<&str>) -> String
    • 创建update语句
  • pub fn build_update_sql_with_pk(&self, set_sql: &str) -> String
    • 创建通过主键更新update语句
  1. tiny_orm_core::TinyOrmDbModel

trait,实现了db_query创建数据库查询器,方法定义如下:

fn db_query(sql: &str) -> Query<MySql, MySqlArguments>

  1. tiny_orm_core::TinyOrmDbQuery

trait,实现了各种数据库交互方法,均以db_开头,该trait中的方法不自动进行数据转换.

在派生宏无法满足需求的情况下,可使用该trait中的方法构建灵活的crud方法。

  1. tiny_orm_core::TinyOrmData

trait,实现了与结构体关联的相关数据库交互放阿飞,均以orm_开头,该trait中的查询类方法均实现数据库行与struct的转换,转换时调用如下方法: fn orm_row_map(row: TinyOrmSqlRow) -> Self; 该方法需要自行实现。

在派生宏无法满足需求的情况下,可使用该trait中的方法构建灵活的crud方法。

派生宏说明

  1. #[derive(TinyOrm)]

为结构体自动实现TinyOrmDbModel数据库模型

用法

本宏定义了如下属性,用于设置数据映射接口和参数

应用于结构体的属性

  1. #[orm_table_name = "core_user"]

可选属性,可设置多个。

自定义数据库表名称。 未设置则表名称为结构名称的蛇形命名, 例如:TestUser转换为test_user。

  1. #[orm_table_name_pref = "core"]

可选属性,可设置多个。

为自动生成的数据库表名称增加前缀。 例如:TestUser转换为core_test_user.

  1. #[orm_query]

可选属性,可设置多个。

生成Self::orm_query_with开头的获取多条记录的查询方法。

有三个子属性:

  • name
    • 生成的函数名称后缀
    • 如果name设置为tel,实际生成的方法名称为:orm_query_with_tel
  • sql_where
    • 数据库select语句的where部分
    • 可选,未设置则为获取所有记录
  • order_by
    • 数据库select语句的order by部分
    • 可选,未设置则为默认排序
  • args
    • 生成方法的参数部分定义
    • 参数数量应与sql_where定义的?参数数量一致
    • 与实际函数定义的写法一致,如定义2个参数,格式为:arg1:type1,arg2:type2
    • 实际生成的函数,会自动添加pool: &TinyOrmDbPool
  • doc
    • 生成方法的文档说明

例如:

#[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>>{...}
  1. #[orm_update]

可选属性,可设置多个。

生成Self::orm_update_with开头的将更新记录方法。

有三个子属性:

  • name
    • 生成的函数名称后缀
    • 如果name设置为tel,实际生成的方法名称为:orm_update_with_tel
  • sql_set
    • 数据库update语句的set部分
  • sql_where
    • 数据库update语句的where部分
    • 可选,未设置则为更新所有记录
  • args
    • 生成方法的参数部分定义
    • 与实际函数定义的写法一致,如定义2个参数,格式为:arg1:type1,arg2:type2
    • 参数数量应与sql_set+sql_where定义的?参数数量一致
    • 实际生成的函数,会自动添加pool: &TinyOrmDbPool
  • doc
    • 生成方法的文档说明

例如:

#[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<()>{...}
  1. #[orm_delete]

可选属性,可设置多个。

生成Self::orm_delete_with开头的删除记录方法。

有三个子属性:

  • name
    • 生成的函数名称后缀
    • 如果name设置为tel,实际生成的方法名称为:orm_delete_with_tel
  • sql_where
    • 数据库delete语句的where部分
    • 可选,未设置则为删除所有记录
  • args
    • 生成方法的参数部分定义
    • 参数数量应与sql_where定义的?参数数量一致
    • 与实际函数定义的写法一致,如定义2个参数,格式为:arg1:type1,arg2:type2
    • 实际生成的函数,会自动添加pool: &TinyOrmDbPool
  • doc
    • 生成方法的文档说明

例如:

#[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<()>{...}
  1. #[orm_exist]

可选属性,可设置多个。

生成Self::orm_exist_with开头的删除记录方法。

有三个子属性:

  • name
    • 生成的函数名称后缀
    • 如果name设置为tel,实际生成的方法名称为:orm_exist_with_tel
  • sql_where
    • 数据库select语句的where部分
      • 实际生成的sql为:select count(1) from table_name where sql_where
    • 可选,未设置则为所有记录
  • args
    • 生成方法的参数部分定义
    • 参数数量应与sql_where定义的?参数数量一致
    • 与实际函数定义的写法一致,如定义2个参数,格式为:arg1:type1,arg2:type2
    • 实际生成的函数,会自动添加pool: &TinyOrmDbPool
  • doc
    • 生成方法的文档说明

例如:

#[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>
  1. #[orm_join]

可选属性,可设置多个。

设置不与其他struct model关联join信息

有二个子属性:

  • select_field
    • 从join表select数据库字段的对应sql语句
    • 该语句会与结构体自身对应数据库表的字段合并为select的选择字段清单
  • join
    • 连接表的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. #[orm_pk(name="id", auto="true")]

至少需要设置1个,可设置多个。

设置当前字段为主键字段

有两个子属性:

  • name
    • 设置对应数据库表中的字段名称
    • 可选属性,如果未设置,数据库中的字段名称和结构体字段名称一致
  • auto
    • 设置为true,则标识这是个自动增长的主键,在插入时不设置值
    • 可选属性

该属性会自动生成如下方法:

多个主键会自动汇聚为一个方法,并将主键设置为对应的函数方法的参数

  • Self::orm_get_with_pk
    • 通过主键获取单条记录
    • 如果设置了多个主键,会将按照字段定义顺序,合并到该方法的参数中
    • 函数定义
      • pub async fn orm_get_with_pk(pool: &TinyOrmDbPool,主键字段1:字段类型1,主键字段2:字段类型2,...) -> AnyhowResult
      • 结构体字段类型自动转换
        • Option 自动转换为 T
        • String 自动转换为 &str
  • Self::orm_delete_with_pk
    • 通过主键删除记录
    • 如果设置了多个主键,会将按照字段定义顺序,合并到该方法的参数中
    • 函数定义
      • pub async fn orm_delete_with_pk(pool: &TinyOrmDbPool,主键字段1:字段类型1,主键字段2:字段类型2,...) -> AnyhowResult<()>
      • 结构体字段类型自动转换
        • Option 自动转换为 T
        • String 自动转换为 &str
  • self::orm_delete
    • 通过主键删除当前实例在数据库记录
    • 如果设置了多个主键,会将按照字段定义顺序,合并到该方法的参数中
    • 函数定义
      • pub async fn orm_delete(&self,pool: &TinyOrmDbPool) -> AnyhowResult
  • Self::orm_exist_with_pk
    • 通过主键查询记录是否存在
    • 如果设置了多个主键,会将按照字段定义顺序,合并到该方法的参数中
    • 函数定义
      • pub async fn orm_exists_with_pk(pool: &TinyOrmDbPool,主键字段1:字段类型1,主键字段2:字段类型2,...) -> AnyhowResult
      • 结构体字段类型自动转换
        • Option 自动转换为 T
        • String 自动转换为 &str
  • Self::orm_exist
    • 通过主键查询当前实例在数据库记录是否存在
    • 如果设置了多个主键,会将按照字段定义顺序,合并到该方法的参数中
    • 函数定义
      • pub async fn orm_exists_with_pk(&self,pool: &TinyOrmDbPool) -> AnyhowResult

示例

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>{...}

  1. #[orm_join]

可选属性,可设置多个。

设置对应字段为关联其他表的join字段

有六个子属性:

  • name
    • 设置对应数据库表中的字段名称
    • 可选属性,如果未设置,数据库中的字段名称和结构体字段名称一致
  • select_field
    • 从join表select数据库字段的对应sql语句
    • 该语句会与结构体自身对应数据库表的字段合并为select的选择字段清单
  • join
    • 连接表的join语句
  • link_id
    • 字段的rust类型的对应连接字段
    • 例如,如某结构体User
      • 其中某一字段定义为:user_type:UserType,设置link_id="id"
      • 写入User表记录时,会自动读取self.user_type.id为对应数据库字段的值
  • link_id_type
    • 连接字段的rust类型
    • 如以上UserType::id为u32类型,则link_id_type="u32"
  • skip_method
    • 是否不自动生成相关join方法
    • true,则不自动生成

如为设置skip_method="true",该属性会自动生成如下方法:

  • Self::orm_query_join_with_字段名称
    • 使用join字段查询记录
    • 函数定义
      • pub async fn orm_query_join_with_字段名称(pool: &TinyOrmDbPool,字段名称:字段类型) -> AnyhowResult<Vec>
      • 结构体字段类型自动转换
        • Option 自动转换为 T
        • String 自动转换为 &str
  • Self::orm_query_join_with_字段名称_{link_id名称}
    • 使用join字段的link_id查询记录
    • 函数定义
      • pub async fn orm_query_join_with_字段名称_{link_id名称}(pool: &TinyOrmDbPool,link_id名称:link_id_type名称) -> AnyhowResult<Vec>
  • Self::orm_delete_join_with_字段名称
    • 使用join字段删除记录
    • 函数定义
      • pub async fn orm_delete_join_with_字段名称(pool: &TinyOrmDbPool,字段名称:字段类型) -> AnyhowResult<()>
      • 结构体字段类型自动转换
        • Option 自动转换为 T
        • String 自动转换为 &str
  • Self::orm_delete_join_with_字段名称_{link_id名称}
    • 使用join字段的link_id删除记录
    • 函数定义
      • pub async fn orm_delete_join_with_字段名称_{link_id名称}(pool: &TinyOrmDbPool,link_id名称:link_id_type名称) -> AnyhowResult<()>
  • self::orm_update_join_with_字段名称
    • 使用当前实例的对应join字段.link_id保存到数据库中对应字段中
    • 函数定义
      • pub async fn orm_update_join_with_字段名称(&self,pool: &TinyOrmDbPool) -> AnyhowResult<()>

示例

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. #[orm_field(name = "custom_table_field_name")]

可选属性,可设置多个。

设置对应数据库表的字段名称

有1个子属性:

  • name
    • 设置对应数据库表中的字段名称
    • 可选属性,如果未设置,数据库中的字段名称和结构体字段名称一致
  1. #[orm_ignore]

可选属性,可设置多个。

默认情况下,结构体的每个字段均对应一个数据库表字段。 设置该属性后,可忽略对应字段,不对应数据库表的字段名称

  1. #[orm_update]

生成更新数据库中的对应字段值的方法,方法定义如下: pub async fn orm_update_字段名称(&self,pool: &TinyOrmDbPool) ->AnyhowResult<()>

  1. #[orm_query]
  • order_by
    • 数据库select语句的order by部分
    • 可选,未设置则为默认排序

生成使用该字段查询数据库记录的方法,方法定义如下:

  • pub async fn orm_query_with_字段名称(pool: &TinyOrmDbPool,query_value:字段类型) -> AnyhowResult<Vec>
  • 结构体字段类型自动转换
    • Option 自动转换为 T
    • String 自动转换为 &str

示例

struct User {
   id:String,
   #[orm_query(
      order_by="user.id", 
   )]
   name:String,

   #[orm_query]
   sex:String,
   user_type:UserType,
}
  1. #[orm_get]

生成使用该字段获取单条数据库记录的方法,方法定义如下:

  • pub async fn orm_get_with_字段名称(pool: &TinyOrmDbPool,query_value:字段类型) -> AnyhowResult
  • 结构体字段类型自动转换
    • Option 自动转换为 T
    • String 自动转换为 &str

其他自动生成方法

  1. self.orm_insert方法

将当前结构体实例数据插入到数据库表中,用于新增记录的保存

方法定义如下: pub async fn orm_insert(&mut self, pool: &TinyOrmDbPool) -> AnyhowResult<()>

  1. self.orm_update_all方法

将当前结构体实例数据更新到数据库表中,用于变更记录的保存

方法定义如下: pub async fn orm_update_all(&self,pool: &TinyOrmDbPool) -> AnyhowResult<()>

  1. #[derive(TinyOrmQuery)]

自动实现TinyOrmQuery trait宏

使用说明

  1. 添加依赖

修改Cargo.toml,增加

[dependencies]
async-trait = "^0.1"
tiny_orm_macro_derive="^0.3"

[dependencies.tiny_orm_core]
version = "^0.3"
features = [
    "mysql",
]
  1. 引入相关trait
use tiny_orm_core::prelude::*;
  1. 在结构体中使用派生属性
struct User{
    #[orm_pk(auto="true")]
    id: u32,
    name: String,
}
  1. 使用自动生成的相关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);
        });
}

Dependencies

~1.5MB
~37K SLoC