因为春节长假的关系,以及部分研发时间投入在 Porter 项目,Leoric 的一月更新日志跳票到了二月。 Leoric 的功能迭代仍然在紧锣密鼓进行中,过去两个月的工作主要集中在字段类型增强、校验功能完善、以及兼容更多常用的 MySQL 特性上面。
二月份 Leoric 迎来两位新的贡献者 👏👏 @LB4027221 和 @luckydrq
v2.2.x 增加更多具体类型,例如 MySQL 中可能用到的 TINYINT、SMALLINT、或者 MEDIUMINT。这些类型在 SQLite 中名义上也支持,在 PostgreSQL 则只有 SMALLINT,我们在相应的数据库驱动中均做了兼容。使用方式和其他常见类型一致:
import { Bone, DataTypes } from 'leoric';
const { TINYINT, SMALLINT, BIGINT } = DataTypes;
export class User extends Bone {
static attributes = {
id: BIGINT,
age: SMALLINT,
sex: TINYINT(1),
}
}
对于 ActiveRecord 风格的使用方式,这次增加的具体类型也可以自动映射。例如,如果 users 表的 DDL 如下:
CREATE TABLE users (
id BIGINT PRIMARY KEY,
age SMALLINT DEFAULT 0 NOT NULL,
sex TINYINT(1) NOT NULL,
);
Leoric 从上述表结构初始化 User 数据模型时,对应的属性定义也会是具体的类型:
class User extends Bone {};
await connect({ database: 'foo', models: [ User ] });
assert.equal(User.attributes.sex.type.toSqlString(), 'TINYINT(1)');
https://github.com/cyjake/leoric/pull/266
DB/DataType (validate or not in SQL operations) | INTEGER | DATE |
---|---|---|
MySQL | 🔲query ✅insert | ✅query ✅insert |
SQLlite | 🔲query 🔲insert | 🔲query 🔲insert |
PostgresSQL | ✅query ✅insert | ✅query ✅insert |
我们在 v2.0.2 版本中增强了校验逻辑,将按照所使用的数据库校验逻辑提前校验相关操作,例如在 MySQL 中,如下 SQL 都将触发异常:
mysql> SELECT now() = '2022-13-12';
ERROR 1525 (HY000): Incorrect DATETIME value: '2022-13-12'
mysql> SELECT * FROM articles WHERE gmt_create >= '2022-13-12';
ERROR 1525 (HY000): Incorrect TIMESTAMP value: '2022-13-12'
mysql> INSERT INTO articles (gmt_create) VALUES ('2022-13-12');
ERROR 1292 (22007): Incorrect datetime value: '2022-13-12' for column 'gmt_create' at row 1
v2.0.2 将相关校验逻辑提前,便于尽早发现问题。
在之前的版本中,数据模型没有定义 Model.attributes
,由 Leoric 在启动时从数据库初始化字段信息时,Leoric 不会设置字段的缺省值,这么做的好处是生成的插入、更新 SQL 可以在数据库执行时再补充剩余信息,但坏处是如果遇到下面这种表:
CREATE TABLE users (
id BIGINT PRIMARY KEY,
level INTEGER DEFAULT VALUE 0 NOT NULL
);
然后 User 数据模型中没有专门声明字段信息:
class User extends Bone {}
const user = new User();
assert.equal(user.level, 0); // user.level -> null
v2.0.4 修复了这一问题,在应用侧也将读取到正确的缺省值。
MySQL 支持 UPDATE ... ORDER ... LIMIT
或者 DELETE ... ORDER ... LIMIT
等操作,在 Leoric v2.x 之前的版本中并没有完整支持这一特性,例如下面这种代码:
await User
.where({ status: STATUS_BLOCKED })
.update({ nickname: '*blocked*' })
.order('id', 'desc')
.limit(10);
// 更新最近十个被屏蔽用户的昵称
在之前的版本中会漏掉 ORDER 和 LIMIT 部分,v2.0.2 版本修复了这一问题;v2.1.0 则修复了 DELETE 的对应逻辑。给 UPDATE 和 DELETE 排序或者限定条数是 MySQL 特性,PostgreSQL 或者 SQLite 均不支持。
v2.0.2 修复聚合查询排序时用到别名会报错的问题,类似如下用法:
await User.count().group('sex').having('count > 0').order('count', 'desc');
如果使用 Sequelize 写法,大致是:
await User.find({
attributes: sequelize.fn('COUNT', '*'),
group: 'sex',
having: {
count: { $gt: 0 },
},
order: [[ 'count', 'desc' ]],
});
之前版本的 upsert 在执行时不会默认插入 createdAt:
Post.upsert({ title: 'yes' });
更新之前生成的 SQL:
INSERT INTO `posts`
(`title`, `updated_at`)
VALUES
('yes', `CURRENTTIME`)
ON DUPLICATE KEY UPDATE
`title` = VALUES(`title`),
`updated_at` = VALUES(`updated_at`)
这会导致 upsert 插入新数据时会可能不会插入 createdAt,导致字段缺失或者数据库报错
修复后生成 SQL 为:
INSERT INTO `posts`
(`title`, `created_at`, `updated_at`)
VALUES
('yes', `CURRENTTIME`, `CURRENTTIME`)
ON DUPLICATE KEY UPDATE
`title` = VALUES(`title`),
`updated_at` = VALUES(`updated_at`)
v2.1.1 和 v2.2.0 分别完善了 new Realm()
、realm.connect()
、realm.define()
等方法的类型定义,但预计仍然有许多其他问题,对于使用 TypeScript 编写 Egg 应用的用户,我们推荐参考 cnpm/cnpmcore 或者 eggjs/egg-orm!examples/typescript
Leoric 的类型定义还远未完善,为了进一步优化数据模型的类型声明,我们正在尝试使用装饰器来简化相关配置,目前大致思路是通过 class property 配合 @Column
装饰器完成 Model.attributes
配置:
import { DataTypes } from 'leoric';
const { MEDIUMINT } = DataTypes;
class Post extends Bone {
@Column()
id: bigint;
@Column({ name: 'gmt_create' })
createdAt: Date;
@Column({ name: 'gmt_modified'})
updatedAt: Date;
@Column({ name: 'gmt_deleted' })
deletedAt: Date;
@Column(MEDIUMINT)
wordCount: number;
}