< 返回版块

thegenius 发表于 2025-03-25 14:06

Tags:ORM

0.1.10 版本更新内容

[1] 数据库支持更精准:现在如果只需 MySQL,则不会生成 SQLite/Postgres 代码 数据库相关 trait 已重构为支持数据库泛型

pub trait Entity<DB: Database>: Parameter<DB> + Debug {  
    fn gen_insert_sql<'a>(&self) -> Cow<'a, str>;  
    fn gen_upsert_sql<'a>(&self) -> Cow<'a, str>;  
    fn gen_create_sql<'a>(&self) -> Cow<'a, str>;  
}  

[2] 模板引擎现已完整支持 Askama 的全部功能 现在可以在模板中使用任意 Ninja 语法

#[derive(Template, askama::Template, Debug)]  
#[template(  
 source = "select `id`, `name`, `age` FROM `user` where {% if age.is_some() %} age >= :{age} AND {% endif %} `name` = :{name}",  
 ext = "txt"  
)]  
pub struct UserCustomTemplate {  
 name: String,  
 age: Option<i32>,  
} 

[3] API 设计更简洁. [3.1]保留 7 个直观的写入 API

insert(entity) -> ()         // 冲突时失败  
upsert(entity) -> ()         // 冲突时更新  
create(entity) -> ()         // 冲突时失败,返回生成字段(实验性功能)  

update(mutation, unique) -> bool  // 返回是否实际更新  
change(mutation, location) -> u64 // 返回受影响行数  

delete(unique) -> bool       // 返回是否实际删除  
purify(location) -> u64      // 返回删除行数  

[3.2] 保留 4 个直观的读取 API

select       (selection, unique) -> Optional<SE>                  // 单条查询  
search       (selection, location, order, page) -> Vec<SE>       // 分页查询  
search_all   (selection, location, order) -> Vec<SE>             // 全量查询  
search_paged (selection, location, order, page) -> PagedList<SE> // 分页列表 

[3.3] 查询条件现在支持逻辑运算符组合

let location = And::new(  
    UserLocation::Id(Expr {  
        cmp: Cmp::GreaterOrEq,  
        val: Some(1),  
    }),  
    UserLocation::Age(Expr {  
        cmp: Cmp::GreaterOrEq,  
        val: Some(24),  
    }),  
);  
let location = Or::new(  
    UserLocation::Id(Expr {  
        cmp: Cmp::GreaterOrEq,  
        val: Some(1),  
    }),  
    UserLocation::Age(Expr {  
        cmp: Cmp::GreaterOrEq,  
        val: Some(24),  
   }),  
);  

这个大版本更新后,现在这个库的功能已经相对完善,在API的简洁度和性能上,对比其他常用的ORM框架,都有一定优势。

(1)和Sea-ORM的对比

Sea-ORM的API设计相对而言是比较优美的,TaiTan-ORM相对而言,在API的简洁和直观程度上基本是等价的。个人认为在语义上甚至更加直观一些,比如以insert举例:

// sea-orm insert
let apple = fruit::ActiveModel {
   name: Set("Apple".to_owned()),
   ..Default::default()
};

let _ = apple.insert(db).await?; //db作为参数其实用习惯了也能理解,但会略微奇怪

TaiTan-ORM的insert如下:

let entity = User {
        id: 1,
        name: "Allen".to_string(),
        age: Option::Some(23),
        birthday: Option::Some(datetime!(2019-01-01 0:00)),
};
let _ = db.insert(&entity).await?; // db insert entity更加符合语义逻辑

比起语义上的轻微优势,性能才是TaiTan-ORM相对于Sea-ORM的巨大优势,TaiTan大量使用了编译器宏展开,几乎等价于手写代码,和裸地使用sqlx一致,但是Sea-ORM相对而言是更加厚的封装,性能也明显弱于基础的sqlx。

(2)和rbatis对比

rbatis在性能上其实非常优秀,我最初也是直接使用rbatis做项目。感谢rbatis的作者,为我们带来了非常优秀的ORM实现。但是后续的一些个人使用习惯问题,导致了我决定开始研发TaiTan-ORM.
[1] 首先是语义相对复杂,尤其是模板上,rbatis支持的几种模板格式都没法让我有赏心悦目的感受

impl_select!(BizActivity{select_all_by_id(id: &str, name: &str) 
  => "where id=#{id} and name=#{name}");

#[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!() }

<!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>

我更加青睐Ninja类似的语法,这也是TaiTan-ORM选择支持的语法类型,如下:

#[derive(Template, askama::Template, Debug)]
#[template(
 source = "select `id`, `name`, `age` FROM `user` where {% if age.is_some() %} age >= :{age} AND {% endif %} `name` = :{name}",
 ext = "txt"
)]
pub struct UserCustomTemplate {
 name: String,
 age: Option<i32>,
}

这个模板的舒爽养眼程度我至少认为是高出一截的,同时这种在struct直接通过宏展开的方式,理论上的性能也能够更加极限。

[2]第二个促使我从rabtis离开的因素是Error. 在我大量使用rbatis的版本里面,当时为了能够精确区分主键冲突失败,曾经需要我们判断错误字符串。而我坚定地认为Error也需要被精确设计,有兴趣的同学可以看看TaiTan-ORM的Error设计,应该能够感受到对每个错误语义的反复打磨

#[derive(Error, Debug)]
pub enum TaitanOrmError {
    #[error("database connection permanent error: `{0}`")]
    PermanentConnErr(String),

    #[error("database connection temporary error: `{0}`")]
    TemporaryConnErr(String),

    #[error("sql `{0}` has syntax error")]
    SqlSyntaxErr(String),

    #[error("type `{0}` is not supported")]
    TypeNotSupportedErr(String),

    #[error("row not: `{0}`")]
    ConstraintViolationErr(String),

    #[error("arguments encode error: `{0}`")]
    EncodeError(String),

    #[error("row not found error: `{0}`")]
    RowNotFoundErr(String),

    #[error("row decode error: `{0}`")]
    DecodeError(String),

    #[error("unexpected error `{0}`")]
    UnexpectedError(String),

    #[error("execute template paged search must has count sql")]
    TemplatePagedNotHasCountSql,

    #[error("execute template paged search must has page field")]
    TemplatePageFieldNotFound,

    #[error(transparent)]
    NotValidCmpErr(#[from] NotValidCmpError),

    #[error(transparent)]
    NotValidConditionError(#[from] NotValidConditionError),

    #[error(transparent)]
    NotValidOrderByError(#[from] NotValidOrderByError),

    #[error(transparent)]
    NotImplementTrait(#[from] NotImplementError),
    // #[error(transparent)]
    // BoxDynError(#[from] Box<dyn std::error::Error + 'static + Send + Sync>),
    #[error(transparent)]
    TemplateRenderError(#[from] TemplateRenderError),

    #[error(transparent)]
    DatabaseInitFail(#[from] DatabaseInitFail)
}

而最新的0.1.10是经过二个月大量打磨后的诚意之作。作为作者,更喜欢把自己的作品当作一壶好茶,欢迎八方来客品鉴。当然也欢迎批评以及提issue。


Ext Link: https://github.com/thegenius/taitan-orm

评论区

写评论
BlackStone 2025-03-26 19:04

前几天试了一下toasty orm,感觉挺不错。今晚有空试一下TaiTan orm。

asuper 2025-03-26 16:35

作者上次在论坛发这个,我就很感兴趣,看GitHub的提交频率,看得出真的是很用心在做这个,而且各个时段的提交都有,让我不禁想问:师父你是做什么工作的~~

目前我的Rust项目没用到数据库,期待有机会可以深入使用这个库。

作者 thegenius 2025-03-26 16:00

我看到大家都反馈rbatis的模板其实也挺好,仅仅只是一个习惯问题。我觉得可能这篇推文的陈述上,让大家觉得TaiTan-ORM和rbatis直接的区别只是模板语法和Error设计。反而弱化了api简洁度上优势,在api设计上,TaiTan-ORM和Sea-ORM才是一个水准的。 就拿search方法举例:

_______________________________________
| selection | location | order | page |
---------------------------------------
     |          |         |       |--> sql{ limit 200, 100 }
     |          |         |--> sql{ order by age, id }
     |          |--> sql{ where name = '' }
     |-->sql{ select name, age, id from `user` }

这个心智模型是被反复打磨过的,其中selection和location都是trait,可以灵活应对各种情况,location又极度灵活,每个entity的字段可以是location,and/or/not组合后的location还是一个location,导致心智负担极低,同时这个api又无比强大,这样强大的api设计,才是TaiTan的核心特色。但是因为这篇文章是说0.1.10更新的,没有强调这个部分,但是如果大家要全面比较TaiTan-ORM和rbatis可以看看更多细节,相信大家会有更多惊喜。

xiaoyaou 2025-03-26 10:29

确实,用过python的ninja和java的mybatis,感觉用法体验没啥大区别纯看个人习惯,甚至可能mybatis模板文件是xml遇到的多感觉可读性更熟悉

--
👇
langzi.me: rbatis的模板是来自其他语言的 orm 框架的。 别的语言的人,还是很亲切的。

langzi.me 2025-03-25 18:37

rbatis的模板是来自其他语言的 orm 框架的。 别的语言的人,还是很亲切的。

1 共 5 条评论, 1 页