Leoric 经历了一个忙碌的十月份,当月 pr 数量首次溢出一页,达到 27 个,改动比较大的有:
model.update()
、Model.findAndCountAll()
Leoric v0.x 版本就开始提供 d.ts,但只是一个非常粗浅的版本,并没有在真正的 TypeScript 项目中实验过。十月份我们给一个内部应用作 TypeScript 改造,给这个应用的数据模型层生成了 d.ts,增加完整的类型关联,才发现原先的声明方式有不少错漏。
先看应用中实际使用时所能取得的效果,静态方法、查询条件、数据模型属性名等提示一应俱全:
我们将 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');
具体按照如下分发策略实现:
GROUP
,不管查询结果是否仅包含数据模型已有属性,均返回 ResultSet
类型SELECT
包含函数或者别名,返回 ResultSet
类型Collection<Bone>
一个完整的例子:
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 }>
更具体的分发策略讨论详见《查询结果分发策略》一文。
可以访问我们的 Releases 页面查看完整的 changelog,或者直接查看 v1.12.0…v1.14.2 的变更汇总 https://github.com/cyjake/leoric/compare/v1.12.0…v1.14.2
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
。