2021.10 v1.14

Leoric 经历了一个忙碌的十月份,当月 pr 数量首次溢出一页,达到 27 个,改动比较大的有:

TypeScript 声明文件

Leoric v0.x 版本就开始提供 d.ts,但只是一个非常粗浅的版本,并没有在真正的 TypeScript 项目中实验过。十月份我们给一个内部应用作 TypeScript 改造,给这个应用的数据模型层生成了 d.ts,增加完整的类型关联,才发现原先的声明方式有不少错漏。

先看应用中实际使用时所能取得的效果,静态方法、查询条件、数据模型属性名等提示一应俱全:

output.gif

我们将 TypeScript 放到和 JavaScript 相同的位置来支持,所以在 test/types 下面基本罗列了目前实际 TypeScript 项目中使用 Leoric 所遇到过的全部 case。

显然未来需要补充的还有很多,而且由于 Leoric 的查询结果类型多变,也给声明文件的编写带来了难度,比如下面这种查询就比较难在 TypeScript 中一次性搞定类型,必须借助一些额外的条件分支来缩小类型范围:

const result = await User.average('age').average('height').group('sex');
// 实际返回类型 ResultSet<{ age, height, sex }>
// TypeScript 认为的返回类型 ResultSet<Record<string, Literal>> | number

model.update()

在已经拿到数据模型实例的情况下,通常我们可以用 model.update(values) 来快速完成数据模型的变更和持久化。在 Sequelize 模式下,这个方法与我们所直观理解的实现方式有差异,主要表现在:

因此在十月份我们给 sequelize 模式增加了兼容,在这个模式下切换到 Sequelize 的实现逻辑。

入参转换

在应用中使用数据模型查询时,容易出现不经意传错入参类型的情况,比如下面几种:

User.findOne(ctx.query.id);  // '1'
User.findOne({ name: 9527 });

对应如下 SQL:

SELECT * FROM users WHERE id = '1';
SELECT * FROM users WHERE name = 9527;

MySQL 会在执行这些 SQL 的时候自动做类型转换,确保 id=1 或者 name='9527' 的记录能够被查询到。但这些查询会有性能问题,甚至可能导致索引不能命中,在我们的应用中实际观察,耗时存在量级上的差距。

因此在十月份中的改动中,我们重新实现了数据模型的属性类型转换机制,下面这些查询里的入参都将根据属性类型做自动类型转换:

User.findOne('name = ?', name);
User.findOne({ name });
User.findOne({ name: { $ne: name });

其中对日期的处理方式比较特殊,如果表结构定义中某个字段的日期类型有限制,比如抹掉毫秒,或者只需要日期,那么下面这些查询也会对入参做相应处理:

// birthday DATE -- 只需要日期,格式为 YYYY-MM-DD
User.findOne({ birthday: new Date() });
// SELECT * FROM users WHERE birthday = '2021-11-10';

// created_at DATETIME(0) -- 去掉毫秒位,格式为 YYYY-MM-DD hh:mm:dd
User.findOne({ createdAt: new Date() });
// SELECT * FROM users WHERE created_at = '2021-11-10 07:35:00.000';

查询结果分发策略

执行 Model.where() 或者 sequelize 模式 Model.findAll() 返回的查询结果有如下两种类型:

两种类型都是数组,前者扩展了一些辅助方法比如 toJSON,但两者的主要差异还是在元素类型上面,即前者返回数据模型实例,后者返回普通 js 对象。

如果查询语句只是单个字段的聚合,也可能直接返回 number:

const count = await User.count();
const age = await User.average('age');

具体按照如下分发策略实现:

一个完整的例子:

const users = await User.findAll();
// Collection<User>

const averageHeights = await User.average('height').group('sex');
// ResultSet<{ sex, height }>

const generations = await User.select('DISTINCT YEAR(birthday) AS year');
// ResultSet<{ year }>

更具体的分发策略讨论详见《查询结果分发策略》一文。

Changelog

可以访问我们的 Releases 页面查看完整的 changelog,或者直接查看 v1.12.0…v1.14.2 的变更汇总 https://github.com/cyjake/leoric/compare/v1.12.0…v1.14.2

v2.x 里程碑预告

Leoric 计划在 2022.1.1 发布 v2.x 版本,这个版本将引入一些不兼容的变更,以解决当前版本在实际使用中遇到的问题,比如危险的 realm.sync()

在 v1.x 版本中,默认的 realm.sync() 逻辑和 v2.x 的 realm.sync({ alter: true }) 相仿,会删改表结构中的字段。在 v2.x 中,如果不传 force 或者 alter,这个方法默认将只增加字段或者增加表结构,不会删改字段。如果传了 force,将直接删除、重建表结构而不是通过一个复杂的 ALTER TABLE