2021.12 v2.0

本月更新日志内容不多不少,包含 v1.15.1 和 v2.0.0 两个主要版本,后者是 breaking change,包含部分不向后兼容的改动。

v2.x 注意事项

升级 v2.0.0 版本需要留意 logQueryError(err, sql, duration, options)model.sync({ force | alter }) 接口的变化,详细的改动背景参考 2021.11 更新日志

logQueryError()

diff --git a/app.js b/app.js
index a6008a597..6a3647ec9 100644
--- a/app.js
+++ b/app.js
@@ -36,7 +36,7 @@ module.exports = class AppBootHook {
           const table = Model ? Model.table : '';
           app.logger.info(`leoric^success^${duration}^${modelName}^${table}^${command}^${sql}`);
         },
-        logQueryError(sql, err, duration, options) {
+        logQueryError(err, sql, duration, options) {
           const { command = '', Model } = options;
           const modelName = Model ? Model.name : '';
           const table = Model ? Model.table : '';

如果之前的代码中没有使用 logQueryError,忽略这项改动即可。

model.sync()

diff --git a/app.js b/app.js
index 600f40eecf..84063cb1ca 100644
--- a/app.js
+++ b/app.js
@@ -100,7 +100,7 @@ async function connect(app, db) {
   }

   try {
-    await db.sync();
+    await db.sync({ alter: true });
   } catch (err) {

默认为 model.sync({ force: false, alter: false }),因此如果按照 model.sync() 调用,会跳过已经存在的表,而不会像 v1.x 那样自动 ALTER TABLE。将对应方法调整为 model.sync({ alter: true }) 即可。

非常不建议在生产环境使用此方法,尤其不要再生产环境打开 model.sync({ force }),会一律使用 DROP TABLE IF EXISTS ... 来清理已有表,造成数据丢失。

推荐使用迁移任务来管理数据库表结构。

v1.15.1

https://github.com/cyjake/leoric/releases/tag/v1.15.1

Model.join().limit()

https://github.com/cyjake/leoric/pull/247
在之前的版本中,下面这种查询:

await Post.include('comments')
  .order('comments.id', 'desc')
  .order('posts.id', 'desc')
  .limit(1);

将生成 SQL:

SELECT posts.*, comments.*
  FROM (SELECT * FROM posts ORDER BY id DESC)
    AS posts
  LEFT JOIN comments ON posts.id = comments.post_id
 ORDER BY comments.id desc

目的是为了保证 Post.first.include('comments') 这种查询能够正确返回第一条 Post,但在实际使用中,由于这种调用存在二义性,过早提取 ORDER BYLIMIT 到子查询反而导致上面的查询在遇到下面这种数据时返回错误结果:

post_id comment_id
1 3
2 2
3 1

应该返回 Post <#id 1>,实际返回 Post <#id 3>。因此在 v1.15.1 版本中我们去掉了这个过早优化,确保正确结果能够被返回。

日期转换

当数据模型没有声明 DATETIME 的精度时,之前的版本会忽略相关字段值的精度转换。在 v1.15.1 版本中,则会使用数据库表结构信息中返回的精度作为默认值,例如:

class Post {
  static attributes = {
    createdAt: DATE
  }
}

posts.created_at 字段实际类型可能是 DATETIME(0)(MySQL 的默认精度就是 0),也可能是 TIMESTAMP(6)(使用一些 DMS 工具时推荐的默认值),v1.15.1 版本会在初始化数据模型时根据这些信息,对 Post.attributes.createdAt 的类型做补足。

信息补全之后,下面这些操作都将触发日期精度转换:

const post = new Post({ createdAt: new Date() });
post.createdAt = new Date('2021-12-31');
const post2 = await Post.create({ createdAt: new Date('2021-12-31') });

一般没有太多需要转,主要有两种情况:

options.silent

主要修复如下这种使用方式:

const post = await Post.first;
await post.update({ updatedAt: new Date('2021-10-15') }, { silent: true });