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
评论区
写评论前几天试了一下toasty orm,感觉挺不错。今晚有空试一下TaiTan orm。
作者上次在论坛发这个,我就很感兴趣,看GitHub的提交频率,看得出真的是很用心在做这个,而且各个时段的提交都有,让我不禁想问:师父你是做什么工作的~~
目前我的Rust项目没用到数据库,期待有机会可以深入使用这个库。
我看到大家都反馈rbatis的模板其实也挺好,仅仅只是一个习惯问题。我觉得可能这篇推文的陈述上,让大家觉得TaiTan-ORM和rbatis直接的区别只是模板语法和Error设计。反而弱化了api简洁度上优势,在api设计上,TaiTan-ORM和Sea-ORM才是一个水准的。 就拿search方法举例:
这个心智模型是被反复打磨过的,其中selection和location都是trait,可以灵活应对各种情况,location又极度灵活,每个entity的字段可以是location,and/or/not组合后的location还是一个location,导致心智负担极低,同时这个api又无比强大,这样强大的api设计,才是TaiTan的核心特色。但是因为这篇文章是说0.1.10更新的,没有强调这个部分,但是如果大家要全面比较TaiTan-ORM和rbatis可以看看更多细节,相信大家会有更多惊喜。
确实,用过python的ninja和java的mybatis,感觉用法体验没啥大区别纯看个人习惯,甚至可能mybatis模板文件是xml遇到的多感觉可读性更熟悉
--
👇
langzi.me: rbatis的模板是来自其他语言的 orm 框架的。 别的语言的人,还是很亲切的。
rbatis的模板是来自其他语言的 orm 框架的。 别的语言的人,还是很亲切的。