From 05f815655fc0e2275d641f36e1a8f29547fec38f Mon Sep 17 00:00:00 2001 From: Junyi Date: Sun, 6 Dec 2020 14:28:23 +0800 Subject: [PATCH] Fix: change strategy from add to set for updateAssociations (#33) * test: add belongsTo case * fix: change update strategy from add to set --- .../src/__tests__/model/model.test.ts | 53 +++++++++++++ packages/database/src/model.ts | 75 ++++++++----------- 2 files changed, 83 insertions(+), 45 deletions(-) diff --git a/packages/database/src/__tests__/model/model.test.ts b/packages/database/src/__tests__/model/model.test.ts index 0f0a04805..cd8280bf7 100644 --- a/packages/database/src/__tests__/model/model.test.ts +++ b/packages/database/src/__tests__/model/model.test.ts @@ -237,6 +237,22 @@ describe('model', () => { const authorizedPost = await Post.findByPk(post.id); expect(authorizedPost.user_id).toBe(user.id); }); + + it('update with exist model', async () => { + const [User, Post] = db.getModels(['users', 'posts']); + const user1 = await User.create(); + const user2 = await Post.create(); + const post = await Post.create(); + await post.updateAssociations({ + user: user1.id + }); + await post.updateAssociations({ + user: user2.id + }); + + const authorizedPost = await Post.findByPk(post.id); + expect(authorizedPost.user_id).toBe(user2.id); + }); }); describe('hasMany', () => { @@ -368,6 +384,26 @@ describe('model', () => { { id: 4, content: 'content4' } ]); }); + + it('update with different primaryKey/row/object', async () => { + const [Post, Comment] = db.getModels(['posts', 'comments']); + const post = await Post.create(); + const comments = await Comment.bulkCreate([{}, {}, {}, {}]); + await post.updateAssociations({ + comments + }); + await post.updateAssociations({ + comments: comments + .filter(({ id }) => Boolean(id % 2)) + .concat(...[await Comment.create()]) + }); + + const postComments = await Comment.findAll({ + where: { post_id: post.id }, + attributes: ['id'] + }); + expect(postComments.map(({ id }) => id)).toEqual([1,3,5]); + }); }); describe('belongsToMany', () => { @@ -410,6 +446,23 @@ describe('model', () => { ]); }); + it('update with exist rows/primaryKeys and new objects', async () => { + const [Post, Tag, PostTag] = db.getModels(['posts', 'tags', 'posts_tags']); + const post = await Post.create(); + const tags = await Tag.bulkCreate([{}, {}, {}, {}]); + await post.updateAssociations({ + tags: tags.map(item => item.id) + }); + await post.updateAssociations({ + tags: tags.filter(({ id }) => Boolean(id % 2)).concat(await Tag.create({})) + }); + const tagged = await PostTag.findAll({ + where: { post_id: post.id }, + attributes: ['tag_id'] + }); + expect(tagged.map(({ tag_id }) => tag_id)).toEqual([1,3,5]); + }); + it('update other with exist rows/primaryKeys', async () => { const [Post, Tag, PostTag] = db.getModels(['posts', 'tags', 'posts_tags']); const post = await Post.create(); diff --git a/packages/database/src/model.ts b/packages/database/src/model.ts index 511b81b92..09e30a24e 100644 --- a/packages/database/src/model.ts +++ b/packages/database/src/model.ts @@ -320,8 +320,8 @@ export abstract class Model extends SequelizeModel { // 准备设置的关联主键 const toSetPks = new Set(); const toSetUks = new Set(); - // 筛选后准备添加的关联主键 - const toAddItems = new Set(); + // 筛选后准备设置的关联主键 + const toSetItems = new Set(); // 准备添加的关联对象 const toUpsertObjects = []; @@ -358,9 +358,10 @@ export abstract class Model extends SequelizeModel { }); /* 仅传关联键处理开始 */ - // 查找已存在的关联数据 - const byPkExistItems = toSetPks.size ? await this[accessors.get]({ + // 查找已存在的数据 + const byPkExistItems = toSetPks.size ? await Target.findAll({ ...opts, + // @ts-ignore where: { [targetPk]: { [Op.in]: Array.from(toSetPks) @@ -368,36 +369,11 @@ export abstract class Model extends SequelizeModel { }, attributes: [targetPk] }) : []; - const pkExistItems = new Map(); byPkExistItems.forEach(item => { - pkExistItems.set(item[targetPk], item); + toSetItems.add(item); }); - for (const key of toSetPks) { - if (!pkExistItems.has(key)) { - toAddItems.add(key); - } - } - const byUkExistItems = await this[accessors.get]({ - ...opts, - where: { - [targetKey]: { - [Op.in]: Array.from(toSetUks) - } - }, - attributes: [targetPk, targetKey], - transaction - }); - const ukExistItems = new Map(); - byUkExistItems.forEach(item => { - ukExistItems.set(item[targetKey], item); - }); - for (const key of toSetUks) { - if (ukExistItems.has(key)) { - toSetUks.delete(key); - } - } - const byUkItems = toSetUks.size ? await Target.findAll({ + const byUkExistItems = toSetUks.size ? await Target.findAll({ ...opts, // @ts-ignore where: { @@ -407,11 +383,12 @@ export abstract class Model extends SequelizeModel { }, attributes: [targetPk, targetKey] }) : []; - byUkItems.forEach(item => { - toAddItems.add(item); + byUkExistItems.forEach(item => { + toSetItems.add(item); }); /* 仅传关联键处理结束 */ + const belongsToManyList = []; /* 值为对象处理开始 */ for (const item of toUpsertObjects) { let target; @@ -431,10 +408,26 @@ export abstract class Model extends SequelizeModel { } if (association instanceof BelongsToMany) { - // TODO(optimize): 这里暂时未能批量执行 - await this[accessors.add](target, opts); - const ThroughModel = association.getThroughModel(); - const throughName = association.getThroughName(); + belongsToManyList.push({ + item, + target + }); + } + toSetItems.add(target); + + await target.updateAssociations(item, opts); + } + /* 值为对象处理结束 */ + + // 添加所有计算后的关联 + await this[accessors.set](Array.from(toSetItems), opts); + + // 后处理 belongsToMany 的更新内容 + if (belongsToManyList.length) { + const ThroughModel = (association as BelongsToMany).getThroughModel(); + const throughName = (association as BelongsToMany).getThroughName(); + + for (const { item, target } of belongsToManyList) { const throughValues = item[throughName]; if (typeof throughValues === 'object') { const { foreignKey, sourceKey, otherKey } = association.options; @@ -448,16 +441,8 @@ export abstract class Model extends SequelizeModel { await through.update(throughValues, opts); await through.updateAssociations(throughValues, opts); } - } else { - toAddItems.add(target); } - - await target.updateAssociations(item, opts); } - /* 值为对象处理结束 */ - - // 添加所有计算后的关联 - await this[accessors.add](Array.from(toAddItems), opts); if (!options.transaction) { await transaction.commit();