2022.02 v2.2

In the past two months, whilst celebrating Chinese New Year, we have most of the available work force focused on enhancements about data types, validations, and compatibility issues about MySQL.

In February, we welcome two new contributors 👏👏 @LB4027221 and @luckydrq.

Data Types Enhancements

TINYINT、SMALLINT、etc.

More data types were added in v2.2.x, for example those specific numeric types in MySQL like TINYINT, SMALLINT, or MEDIUMINT. Those types are supported in SQLite allegedly as well, whilst in PostgreSQL only SMALLINT is available, we’ve got those edge cases covered in the correponding database drivers. Hence the types can be used universally:

import { Bone, DataTypes } from 'leoric';

const { TINYINT, SMALLINT, BIGINT } = DataTypes;

export class User extends Bone {
  static attributes = {
    id: BIGINT,
    age: SMALLINT,
    sex: TINYINT(1),
  }
}

For those who prefer leaving the model attributes initialized with database metadata, the newly added data types will be recognized as well. For Example, if the DDL of users table is like below:

CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  age SMALLINT DEFAULT 0 NOT NULL,
  sex TINYINT(1) NOT NULL,
);

The corresponding model attributes will be initialized by Leoric accordingly

class User extends Bone {};
await connect({ database: 'foo', models: [ User ] });
assert.equal(User.attributes.sex.type.toSqlString(), 'TINYINT(1)');

Validataions and Default Values

Validations

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

We’ve enhanced the validation logics a lot in v2.0.2, most of the validations will be triggered in advance if the database used does so. For example, in MySQL all of the SQL below yield exception:

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

In v2.0.2 we will validate in advance to prevent the error query hit database.

DEFAULT VALUE

In previous versions, when the models have Model.attributes left blank and have Leoric to initialize them accordingly, the default values won’t be set. The advantage side of this behavior is that when generating INSERT or UPDATE queries, database can take of the default values handling, the disadvantage side is that when coming to scenarios like below:

CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  level INTEGER DEFAULT VALUE 0 NOT NULL
);

and the User model has left User.attributes blank:

class User extends Bone {}
const user = new User();
assert.equal(user.level, 0);  // user.level -> null

the newly instantiated user won’t have user.level default to 0, which is fixed in v2.0.4

Fixed Issues

UPDATE … ORDER … LIMIT

MySQL supports operations likeUPDATE ... ORDER ... LIMIT or DELETE ... ORDER ... LIMIT, which is not fully supported in versions before Leoric v2.x, such as use cases like below:

await User
  .where({ status: STATUS_BLOCKED })
  .update({ nickname: '*blocked*' })
  .order('id', 'desc')
  .limit(10);
// update the nickname of 10 users blocked recently

ORDER and LIMIT clause were omitted in previous versions, which is fixed in v2.0.2; the DELETE equivalent is fixed in v2.1.0. This feature is MySQL only, neither SQLite nor PostgreSQL supports it.

ORDER BY alias

Ordering with alias column is fixed in v2.0.2

await User.count().group('sex').having('count > 0').order('count', 'desc');

the Sequelize equivalent of the query above is like below:

await User.find({
  attributes: sequelize.fn('COUNT', '*'),
  group: 'sex',
  having: {
    count: { $gt: 0 },
  },
  order: [[ 'count', 'desc' ]],
});

UPSERT with createdAt DEFAULT

createdAt won’t be set when UPSERT in previous versions. For example,

Post.upsert({ title: 'yes' });

the SQL equivalent is like below:

INSERT INTO `posts`
  (`title`, `updated_at`)
VALUES
  ('yes', `CURRENTTIME`)
ON DUPLICATE KEY UPDATE
`title` = VALUES(`title`),
`updated_at` = VALUES(`updated_at`)

which left the createdAt attribute empty, and might cause the database to complain if createdAt can not be NULL. With the issue fixed in v2.1.1, Leoric can now generate correct SQL like below:

INSERT INTO `posts`
  (`title`, `created_at`, `updated_at`)
VALUES
  ('yes', `CURRENTTIME`, `CURRENTTIME`)
ON DUPLICATE KEY UPDATE
`title` = VALUES(`title`),
`updated_at` = VALUES(`updated_at`)

Type Declarations

Realm

The type declarations of new Realm(), realm.connect(), and realm.define() were added in v2.1.1 and v2.2.0, but there still might be some overlooked corners. We’d recommend taking cnpm/cnpmcore or eggjs/egg-orm!examples/typescript as examples of writing Egg applications in TypeScript.

[wip] Decorators

To better optimize the type declrations in models, we are experimenting TypeScript decorations. Here is what we’ve accomplished so far:

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;
}