Feature/sort (#36)
* feat: add sort value initialization via beforeCreate hook * fix: after reinitialization, hooks are lost * test: temp test for hook * fix: hooks defined in the table options does not work * refactor: change sort config into sort type field and fix updateAssociations to create with foreignKey * refactor: abstract utility functions * fix: type definition * fix: type and where value type Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
6b84446697
commit
841249f58c
@ -47,7 +47,7 @@ describe('create', () => {
|
|||||||
{ content: 'comment2', status: 'draft' },
|
{ content: 'comment2', status: 'draft' },
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
expect(response.body.sort).toBe(null);
|
expect(response.body.sort).toBe(1);
|
||||||
expect(response.body.user_id).toBe(1);
|
expect(response.body.user_id).toBe(1);
|
||||||
|
|
||||||
const postWithUser = await agent
|
const postWithUser = await agent
|
||||||
|
@ -8,7 +8,7 @@ describe('list', () => {
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
resourcer.define({
|
resourcer.define({
|
||||||
name: 'posts',
|
name: 'articles',
|
||||||
middlewares: [
|
middlewares: [
|
||||||
jsonReponse,
|
jsonReponse,
|
||||||
],
|
],
|
||||||
@ -16,8 +16,8 @@ describe('list', () => {
|
|||||||
});
|
});
|
||||||
db = await initDatabase();
|
db = await initDatabase();
|
||||||
db.table({
|
db.table({
|
||||||
name: 'posts',
|
name: 'articles',
|
||||||
tableName: 'actions__m__posts',
|
tableName: 'actions__articles',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -48,7 +48,7 @@ describe('list', () => {
|
|||||||
|
|
||||||
it('create', async () => {
|
it('create', async () => {
|
||||||
const response = await agent
|
const response = await agent
|
||||||
.post('/posts')
|
.post('/articles')
|
||||||
.send({
|
.send({
|
||||||
title: 'title1',
|
title: 'title1',
|
||||||
});
|
});
|
||||||
@ -56,7 +56,7 @@ describe('list', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('list', async () => {
|
it('list', async () => {
|
||||||
const response = await agent.get('/posts?fields=title&page=1');
|
const response = await agent.get('/articles?fields=title&page=1');
|
||||||
expect(response.body).toEqual({
|
expect(response.body).toEqual({
|
||||||
data: [ { title: 'title1' } ],
|
data: [ { title: 'title1' } ],
|
||||||
meta: { count: 1, page: 1, per_page: 20 }
|
meta: { count: 1, page: 1, per_page: 20 }
|
||||||
|
@ -6,13 +6,12 @@ describe('get', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = await initDatabase();
|
db = await initDatabase();
|
||||||
const User = db.getModel('users');
|
const User = db.getModel('users');
|
||||||
const users = await User.bulkCreate('abcdefg'.split('').map(name => ({ name })));
|
const users = await User.bulkCreate(Array.from('abcdefg').map(name => ({ name })));
|
||||||
|
|
||||||
const Post = db.getModel('posts');
|
const Post = db.getModel('posts');
|
||||||
const posts = await Post.bulkCreate(Array(22).fill(null).map((_, i) => ({
|
const posts = await Post.bulkCreate(Array(10).fill(null).map((_, i) => ({
|
||||||
title: `title_${i}`,
|
title: `title_${i}`,
|
||||||
status: i % 2 ? 'publish' : 'draft',
|
status: i % 2 ? 'publish' : 'draft',
|
||||||
sort: i,
|
|
||||||
user_id: users[i % users.length].id
|
user_id: users[i % users.length].id
|
||||||
})));
|
})));
|
||||||
|
|
||||||
@ -20,58 +19,76 @@ describe('get', () => {
|
|||||||
comments: Array(post.sort % 5).fill(null).map((_, index) => ({
|
comments: Array(post.sort % 5).fill(null).map((_, index) => ({
|
||||||
content: `content_${index}`,
|
content: `content_${index}`,
|
||||||
status: index % 2 ? 'published' : 'draft',
|
status: index % 2 ? 'published' : 'draft',
|
||||||
user_id: users[index % users.length].id,
|
user_id: users[index % users.length].id
|
||||||
sort: index
|
|
||||||
}))
|
}))
|
||||||
})), Promise.resolve());
|
})), Promise.resolve());
|
||||||
// [
|
|
||||||
// { id: 1, post_id: 2, sort: 0 },
|
|
||||||
// { id: 2, post_id: 3, sort: 0 },
|
|
||||||
// { id: 3, post_id: 3, sort: 1 },
|
|
||||||
// { id: 4, post_id: 4, sort: 0 },
|
|
||||||
// { id: 5, post_id: 4, sort: 1 },
|
|
||||||
// { id: 6, post_id: 4, sort: 2 },
|
|
||||||
// { id: 7, post_id: 5, sort: 0 },
|
|
||||||
// { id: 8, post_id: 5, sort: 1 },
|
|
||||||
// { id: 9, post_id: 5, sort: 2 },
|
|
||||||
// { id: 10, post_id: 5, sort: 3 },
|
|
||||||
// { id: 11, post_id: 7, sort: 0 },
|
|
||||||
// { id: 12, post_id: 8, sort: 0 },
|
|
||||||
// { id: 13, post_id: 8, sort: 1 },
|
|
||||||
// { id: 14, post_id: 9, sort: 0 },
|
|
||||||
// { id: 15, post_id: 9, sort: 1 },
|
|
||||||
// { id: 16, post_id: 9, sort: 2 },
|
|
||||||
// { id: 17, post_id: 10, sort: 0 },
|
|
||||||
// { id: 18, post_id: 10, sort: 1 },
|
|
||||||
// { id: 19, post_id: 10, sort: 2 },
|
|
||||||
// { id: 20, post_id: 10, sort: 3 },
|
|
||||||
// { id: 21, post_id: 15, sort: 0 },
|
|
||||||
// { id: 22, post_id: 15, sort: 1 },
|
|
||||||
// { id: 23, post_id: 15, sort: 2 },
|
|
||||||
// { id: 24, post_id: 15, sort: 3 },
|
|
||||||
// { id: 25, post_id: 12, sort: 0 },
|
|
||||||
// { id: 26, post_id: 13, sort: 0 },
|
|
||||||
// { id: 27, post_id: 13, sort: 1 },
|
|
||||||
// { id: 28, post_id: 14, sort: 0 },
|
|
||||||
// { id: 29, post_id: 14, sort: 1 },
|
|
||||||
// { id: 30, post_id: 14, sort: 2 },
|
|
||||||
// { id: 31, post_id: 17, sort: 0 },
|
|
||||||
// { id: 32, post_id: 18, sort: 0 },
|
|
||||||
// { id: 33, post_id: 18, sort: 1 },
|
|
||||||
// { id: 34, post_id: 19, sort: 0 },
|
|
||||||
// { id: 35, post_id: 19, sort: 1 },
|
|
||||||
// { id: 36, post_id: 19, sort: 2 },
|
|
||||||
// { id: 37, post_id: 20, sort: 0 },
|
|
||||||
// { id: 38, post_id: 20, sort: 1 },
|
|
||||||
// { id: 39, post_id: 20, sort: 2 },
|
|
||||||
// { id: 40, post_id: 20, sort: 3 },
|
|
||||||
// { id: 41, post_id: 22, sort: 0 }
|
|
||||||
// ]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => db.close());
|
afterAll(() => db.close());
|
||||||
|
|
||||||
|
describe.only('sort value initialization', () => {
|
||||||
|
it('initialization by bulkCreate', async () => {
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const posts = await Post.findAll({
|
||||||
|
order: [['id', 'ASC']]
|
||||||
|
});
|
||||||
|
expect(posts.map(({ id, sort, sort_in_status, sort_in_user }) => ({ id, sort, sort_in_status, sort_in_user }))).toEqual([
|
||||||
|
{ id: 1, sort: 1, sort_in_status: 1, sort_in_user: 1 },
|
||||||
|
{ id: 2, sort: 2, sort_in_status: 1, sort_in_user: 1 },
|
||||||
|
{ id: 3, sort: 3, sort_in_status: 2, sort_in_user: 1 },
|
||||||
|
{ id: 4, sort: 4, sort_in_status: 2, sort_in_user: 1 },
|
||||||
|
{ id: 5, sort: 5, sort_in_status: 3, sort_in_user: 1 },
|
||||||
|
{ id: 6, sort: 6, sort_in_status: 3, sort_in_user: 1 },
|
||||||
|
{ id: 7, sort: 7, sort_in_status: 4, sort_in_user: 1 },
|
||||||
|
{ id: 8, sort: 8, sort_in_status: 4, sort_in_user: 2 },
|
||||||
|
{ id: 9, sort: 9, sort_in_status: 5, sort_in_user: 2 },
|
||||||
|
{ id: 10, sort: 10, sort_in_status: 5, sort_in_user: 2 }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initialization by updateAssociations', async () => {
|
||||||
|
const Comment = db.getModel('comments');
|
||||||
|
const comments = await Comment.findAll({
|
||||||
|
order: [['id', 'ASC']]
|
||||||
|
});
|
||||||
|
expect(comments.map(({ id, sort, sort_in_status, sort_in_post }) => ({ id, sort, sort_in_status, sort_in_post }))).toEqual([
|
||||||
|
{ id: 1, sort: 1, sort_in_status: 1, sort_in_post: 1 },
|
||||||
|
{ id: 2, sort: 2, sort_in_status: 2, sort_in_post: 1 },
|
||||||
|
{ id: 3, sort: 3, sort_in_status: 1, sort_in_post: 2 },
|
||||||
|
{ id: 4, sort: 4, sort_in_status: 3, sort_in_post: 1 },
|
||||||
|
{ id: 5, sort: 5, sort_in_status: 2, sort_in_post: 2 },
|
||||||
|
{ id: 6, sort: 6, sort_in_status: 4, sort_in_post: 3 },
|
||||||
|
{ id: 7, sort: 7, sort_in_status: 5, sort_in_post: 1 },
|
||||||
|
{ id: 8, sort: 8, sort_in_status: 3, sort_in_post: 2 },
|
||||||
|
{ id: 9, sort: 9, sort_in_status: 6, sort_in_post: 3 },
|
||||||
|
{ id: 10, sort: 10, sort_in_status: 4, sort_in_post: 4 },
|
||||||
|
{ id: 11, sort: 11, sort_in_status: 7, sort_in_post: 1 },
|
||||||
|
{ id: 12, sort: 12, sort_in_status: 8, sort_in_post: 1 },
|
||||||
|
{ id: 13, sort: 13, sort_in_status: 5, sort_in_post: 2 },
|
||||||
|
{ id: 14, sort: 14, sort_in_status: 9, sort_in_post: 1 },
|
||||||
|
{ id: 15, sort: 15, sort_in_status: 6, sort_in_post: 2 },
|
||||||
|
{ id: 16, sort: 16, sort_in_status: 10, sort_in_post: 3 },
|
||||||
|
{ id: 17, sort: 17, sort_in_status: 11, sort_in_post: 1 },
|
||||||
|
{ id: 18, sort: 18, sort_in_status: 7, sort_in_post: 2 },
|
||||||
|
{ id: 19, sort: 19, sort_in_status: 12, sort_in_post: 3 },
|
||||||
|
{ id: 20, sort: 20, sort_in_status: 8, sort_in_post: 4 }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sort value of append item', async () => {
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const post = await Post.create({ user_id: 1 });
|
||||||
|
expect(post.sort).toBe(11);
|
||||||
|
expect(post.sort_in_status).toBe(6);
|
||||||
|
expect(post.sort_in_user).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('sort in whole table', () => {
|
describe('sort in whole table', () => {
|
||||||
|
it('init sort value', async () => {
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
});
|
||||||
|
|
||||||
it('move id=1 by offset=1', async () => {
|
it('move id=1 by offset=1', async () => {
|
||||||
const Post = db.getModel('posts');
|
const Post = db.getModel('posts');
|
||||||
await agent
|
await agent
|
||||||
@ -165,10 +182,10 @@ describe('get', () => {
|
|||||||
expect(post1.get('sort')).toBe(0);
|
expect(post1.get('sort')).toBe(0);
|
||||||
|
|
||||||
const post2 = await Post.findByPk(2);
|
const post2 = await Post.findByPk(2);
|
||||||
expect(post2.get('sort')).toBe(22);
|
expect(post2.get('sort')).toBe(10);
|
||||||
|
|
||||||
const post22 = await Post.findByPk(22);
|
const post10 = await Post.findByPk(10);
|
||||||
expect(post22.get('sort')).toBe(21);
|
expect(post10.get('sort')).toBe(9);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('move id=2 by offset=-Infinity', async () => {
|
it('move id=2 by offset=-Infinity', async () => {
|
||||||
@ -222,7 +239,7 @@ describe('get', () => {
|
|||||||
|
|
||||||
const Post = db.getModel('posts');
|
const Post = db.getModel('posts');
|
||||||
const post2 = await Post.findByPk(2);
|
const post2 = await Post.findByPk(2);
|
||||||
expect(post2.get('sort')).toBe(22);
|
expect(post2.get('sort')).toBe(10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,8 +21,18 @@ export default {
|
|||||||
name: 'user',
|
name: 'user',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'integer',
|
type: 'sort',
|
||||||
name: 'sort'
|
name: 'sort'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'sort',
|
||||||
|
name: 'sort_in_status',
|
||||||
|
scope: ['status']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'sort',
|
||||||
|
name: 'sort_in_post',
|
||||||
|
scope: ['post']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
scopes: {
|
scopes: {
|
||||||
@ -31,6 +41,5 @@ export default {
|
|||||||
status: 'published'
|
status: 'published'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
sortable: true
|
|
||||||
} as TableOptions;
|
} as TableOptions;
|
||||||
|
@ -30,18 +30,24 @@ export default {
|
|||||||
name: 'tags',
|
name: 'tags',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'integer',
|
type: 'sort',
|
||||||
name: 'sort'
|
name: 'sort'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'sort',
|
||||||
|
name: 'sort_in_status',
|
||||||
|
scope: ['status']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'sort',
|
||||||
|
name: 'sort_in_user',
|
||||||
|
scope: ['user']
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'json',
|
type: 'json',
|
||||||
name: 'meta'
|
name: 'meta'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
hooks: {
|
|
||||||
beforeCreate(model, options) {
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scopes: {
|
scopes: {
|
||||||
customTitle: (title, ctx) => {
|
customTitle: (title, ctx) => {
|
||||||
return {
|
return {
|
||||||
@ -49,7 +55,6 @@ export default {
|
|||||||
title: title,
|
title: title,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
sortable: true
|
|
||||||
} as TableOptions;
|
} as TableOptions;
|
||||||
|
@ -158,7 +158,7 @@ describe('hooks', () => {
|
|||||||
expect(test.get('arr')).toEqual([ 1, 3, 2, 4 ]);
|
expect(test.get('arr')).toEqual([ 1, 3, 2, 4 ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only('add hook in custom field', async () => {
|
it('add hook in custom field', async () => {
|
||||||
const table = db.table({
|
const table = db.table({
|
||||||
name: 'test3',
|
name: 'test3',
|
||||||
fields: [
|
fields: [
|
||||||
|
38
packages/database/src/__tests__/model/getScopeWhere.test.ts
Normal file
38
packages/database/src/__tests__/model/getScopeWhere.test.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { getDatabase } from '..';
|
||||||
|
import Database from '../../database';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe('getScopeWhere', () => {
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = getDatabase();
|
||||||
|
db.table({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'status'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await db.sync({ force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => db.close());
|
||||||
|
|
||||||
|
it('exist column', async () => {
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const post = await Post.create({ status: 'published' });
|
||||||
|
const where = post.getScopeWhere(['status']);
|
||||||
|
expect(where).toEqual({ status: 'published' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('non-exist column', async () => {
|
||||||
|
const Post = db.getModel('posts');
|
||||||
|
const post = await Post.create({});
|
||||||
|
const where = post.getScopeWhere(['whatever']);
|
||||||
|
expect(where).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
@ -367,7 +367,6 @@ describe('model', () => {
|
|||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
const updatedComments = await Comment.findAll();
|
const updatedComments = await Comment.findAll();
|
||||||
console.log(updatedComments);
|
|
||||||
const post1CommentsCount = await Comment.count({
|
const post1CommentsCount = await Comment.count({
|
||||||
where: { post_id: post.id }
|
where: { post_id: post.id }
|
||||||
});
|
});
|
||||||
|
@ -9,13 +9,15 @@ import {
|
|||||||
BelongsToManyOptions,
|
BelongsToManyOptions,
|
||||||
ThroughOptions,
|
ThroughOptions,
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
|
import { template, get, toNumber } from 'lodash';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
import * as Options from './option-types';
|
import * as Options from './option-types';
|
||||||
import { getDataTypeKey } from '.';
|
import { getDataTypeKey } from '.';
|
||||||
import Table from '../table';
|
import Table from '../table';
|
||||||
import Database from '../database';
|
import Database from '../database';
|
||||||
import Model, { ModelCtor } from '../model';
|
import Model, { ModelCtor } from '../model';
|
||||||
import { template, isArray, map, get, toNumber } from 'lodash';
|
import { whereCompare } from '../utils';
|
||||||
import bcrypt from 'bcrypt';
|
|
||||||
|
|
||||||
export interface IField {
|
export interface IField {
|
||||||
|
|
||||||
@ -681,7 +683,7 @@ export class BELONGSTOMANY extends Relation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getAssociationOptions(): BelongsToManyOptions {
|
public getAssociationOptions(): BelongsToManyOptions {
|
||||||
const { name, ...restOptions }= this.options;
|
const { name, ...restOptions } = this.options;
|
||||||
return {
|
return {
|
||||||
as: name,
|
as: name,
|
||||||
through: this.getThroughModel(),
|
through: this.getThroughModel(),
|
||||||
@ -689,3 +691,74 @@ export class BELONGSTOMANY extends Relation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SORT extends NUMBER {
|
||||||
|
|
||||||
|
public readonly options: Options.SortOptions;
|
||||||
|
|
||||||
|
static async beforeCreateHook(this: SORT, model, options) {
|
||||||
|
const { transaction } = options;
|
||||||
|
const Model = model.constructor;
|
||||||
|
const { name, scope = [], next = 'max' } = this.options;
|
||||||
|
const where = model.getScopeWhere(scope);
|
||||||
|
const extremum: number = await Model[next](name, { where, transaction }) || 0;
|
||||||
|
model.set(name, extremum + (next === 'max' ? 1 : -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async beforeBulkCreateHook(this: SORT, models, options) {
|
||||||
|
const { transaction } = options;
|
||||||
|
const table = this.context.sourceTable;
|
||||||
|
const Model = table.getModel();
|
||||||
|
const { name, scope = [], next = 'max' } = this.options;
|
||||||
|
// 如果未配置范围限定,则可以进行性能优化处理(常用情况)。
|
||||||
|
if (!scope.length) {
|
||||||
|
const extremum: number = await Model[next](name, { transaction }) || 0;
|
||||||
|
models.forEach((model, i: number) => {
|
||||||
|
model.setDataValue(name, extremum + (i + 1) * (next === 'max' ? 1 : -1));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用于存放 where 条件与计算极值
|
||||||
|
const groups = new Map<{ [key: string]: any }, number>();
|
||||||
|
await models.reduce((promise, model) => promise.then(async () => {
|
||||||
|
const where = model.getScopeWhere(scope);
|
||||||
|
|
||||||
|
let extremum: number;
|
||||||
|
// 以 map 作为 key
|
||||||
|
let combo;
|
||||||
|
// 查找与 where 值相等的组合
|
||||||
|
for (combo of groups.keys()) {
|
||||||
|
if (whereCompare(combo, where)) {
|
||||||
|
// 如果找到的话则以之前储存的值作为基础极值
|
||||||
|
extremum = groups.get(combo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如未找到组合
|
||||||
|
if (typeof extremum === 'undefined') {
|
||||||
|
// 则使用 where 条件查询极值
|
||||||
|
extremum = await Model[next](name, { where, transaction }) || 0;
|
||||||
|
// 且使用 where 条件创建组合
|
||||||
|
combo = where;
|
||||||
|
}
|
||||||
|
const nextValue = extremum + (next === 'max' ? 1 : -1);
|
||||||
|
// 设置数据行的排序值
|
||||||
|
model.setDataValue(name, nextValue);
|
||||||
|
// 保存新的排序值为对应 where 组合的极值,以供下次计算
|
||||||
|
groups.set(combo, nextValue);
|
||||||
|
}), Promise.resolve());
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(options: Options.SortOptions, context: FieldContext) {
|
||||||
|
super(options, context);
|
||||||
|
const Model = context.sourceTable.getModel();
|
||||||
|
// TODO(feature): 可考虑策略模式,以在需要时对外提供接口
|
||||||
|
Model.addHook('beforeCreate', SORT.beforeCreateHook.bind(this));
|
||||||
|
Model.addHook('beforeBulkCreate', SORT.beforeBulkCreateHook.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDataType(): Function {
|
||||||
|
return DataTypes.INTEGER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -174,6 +174,25 @@ export interface BelongsToManyOptions extends Omit<SequelizeBelongsToManyOptions
|
|||||||
otherKey?: string;
|
otherKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SortOptions extends NumberOptions {
|
||||||
|
type: 'sort';
|
||||||
|
/**
|
||||||
|
* 排序限定范围
|
||||||
|
*
|
||||||
|
* 在同表的限定范围内的字段值相等的数据行中排序
|
||||||
|
*/
|
||||||
|
scope?: string[];
|
||||||
|
/**
|
||||||
|
* 新值创建策略
|
||||||
|
*
|
||||||
|
* max: 使用最大值
|
||||||
|
* min: 使用最小值
|
||||||
|
*
|
||||||
|
* Defaults to 'max'
|
||||||
|
*/
|
||||||
|
next?: 'min' | 'max';
|
||||||
|
}
|
||||||
|
|
||||||
export type ColumnOptions = AbstractFieldOptions
|
export type ColumnOptions = AbstractFieldOptions
|
||||||
| BooleanOptions
|
| BooleanOptions
|
||||||
| NumberOptions
|
| NumberOptions
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
Model as SequelizeModel, Op, Sequelize, ProjectionAlias, Utils, SaveOptions,
|
Model as SequelizeModel, Op, Sequelize, ProjectionAlias, Utils, SaveOptions
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import Database from './database';
|
import Database from './database';
|
||||||
import {
|
import {
|
||||||
@ -259,6 +259,27 @@ export abstract class Model extends SequelizeModel {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getScopeWhere(scope: string[]) {
|
||||||
|
const Model = this.constructor as ModelCtor<Model>;
|
||||||
|
const table = this.database.getTable(this.constructor.name);
|
||||||
|
const associations = table.getAssociations();
|
||||||
|
const where = {};
|
||||||
|
scope.forEach(col => {
|
||||||
|
const association = associations.get(col);
|
||||||
|
const dataKey = association && association instanceof BELONGSTO
|
||||||
|
? association.options.foreignKey
|
||||||
|
: col;
|
||||||
|
if (!Model.rawAttributes[dataKey]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = this.getDataValue(dataKey);
|
||||||
|
if (typeof value !== 'undefined') {
|
||||||
|
where[dataKey] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return where;
|
||||||
|
}
|
||||||
|
|
||||||
async updateSingleAssociation(key: string, data: any, options: SaveOptions<any> & { context?: any; } = {}) {
|
async updateSingleAssociation(key: string, data: any, options: SaveOptions<any> & { context?: any; } = {}) {
|
||||||
const {
|
const {
|
||||||
fields,
|
fields,
|
||||||
@ -399,27 +420,29 @@ export abstract class Model extends SequelizeModel {
|
|||||||
for (const item of toUpsertObjects) {
|
for (const item of toUpsertObjects) {
|
||||||
let target;
|
let target;
|
||||||
if (typeof item[targetKey] === 'undefined') {
|
if (typeof item[targetKey] === 'undefined') {
|
||||||
// TODO(optimize): 不确定 bulkCreate 的结果是否能保证顺序,能保证的话这里可以优化为批量处理
|
target = await this[accessors.create](item, opts);
|
||||||
target = await Target.create(item, opts);
|
|
||||||
} else {
|
} else {
|
||||||
let created: boolean;
|
target = await Target.findOne({
|
||||||
[target, created] = await Target.findOrCreate({
|
...opts,
|
||||||
where: { [targetKey]: item[targetKey] },
|
where: { [targetKey]: item[targetKey] },
|
||||||
defaults: item,
|
|
||||||
transaction
|
|
||||||
});
|
});
|
||||||
if (!created) {
|
if (!target) {
|
||||||
|
target = await this[accessors.create](item, opts);
|
||||||
|
} else {
|
||||||
await target.update(item, opts);
|
await target.update(item, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO(optimize): 此处添加的对象其实已经创建了关联,
|
||||||
|
// 但考虑到单条 create 的 hook 要求带上关联键,且后面的 set,
|
||||||
|
// 所以仍然交给 set 再调用关联一次。
|
||||||
|
toSetItems.add(target);
|
||||||
|
|
||||||
if (association instanceof BELONGSTOMANY) {
|
if (association instanceof BELONGSTOMANY) {
|
||||||
belongsToManyList.push({
|
belongsToManyList.push({
|
||||||
item,
|
item,
|
||||||
target
|
target
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
toSetItems.add(target);
|
|
||||||
|
|
||||||
await target.updateAssociations(item, opts);
|
await target.updateAssociations(item, opts);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
Relation,
|
Relation,
|
||||||
BELONGSTO,
|
BELONGSTO,
|
||||||
BELONGSTOMANY,
|
BELONGSTOMANY,
|
||||||
|
SORT
|
||||||
} from './fields';
|
} from './fields';
|
||||||
import Database from './database';
|
import Database from './database';
|
||||||
import { Model, ModelCtor } from './model';
|
import { Model, ModelCtor } from './model';
|
||||||
@ -127,6 +128,10 @@ export class Table {
|
|||||||
|
|
||||||
public relationTables = new Set<string>();
|
public relationTables = new Set<string>();
|
||||||
|
|
||||||
|
get sortable(): boolean {
|
||||||
|
return Array.from(this.fields.values()).some(field => field instanceof SORT);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(options: TableOptions, context: TabelContext) {
|
constructor(options: TableOptions, context: TabelContext) {
|
||||||
const { database } = context;
|
const { database } = context;
|
||||||
const {
|
const {
|
||||||
|
@ -290,6 +290,10 @@ export function toInclude(options: any, context: ToIncludeContext = {}) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function whereCompare(a: any, b: any): boolean {
|
||||||
|
return _.isEqual(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
export function requireModule(module: any) {
|
export function requireModule(module: any) {
|
||||||
if (typeof module === 'string') {
|
if (typeof module === 'string') {
|
||||||
module = require(module);
|
module = require(module);
|
||||||
|
Loading…
Reference in New Issue
Block a user