diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 58db98c537..1997d0b02d 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -149,7 +149,7 @@ module.exports = function (module) { }; module.deleteObjectFields = async function (key, fields) { - if (!key || !Array.isArray(fields) || !fields.length) { + if (!key || (Array.isArray(key) && !key.length) || !Array.isArray(fields) || !fields.length) { return; } fields = fields.filter(Boolean); @@ -162,8 +162,12 @@ module.exports = function (module) { field = helpers.fieldToString(field); data[field] = ''; }); + if (Array.isArray(key)) { + await module.client.collection('objects').updateMany({ _key: { $in: key } }, { $unset: data }); + } else { + await module.client.collection('objects').updateOne({ _key: key }, { $unset: data }); + } - await module.client.collection('objects').updateOne({ _key: key }, { $unset: data }); cache.del(key); }; diff --git a/src/database/postgres/hash.js b/src/database/postgres/hash.js index 6094c3bb72..a1f91d5dab 100644 --- a/src/database/postgres/hash.js +++ b/src/database/postgres/hash.js @@ -247,20 +247,26 @@ SELECT (h."data" ? $2::TEXT AND h."data"->>$2::TEXT IS NOT NULL) b }; module.deleteObjectFields = async function (key, fields) { - if (!key || !Array.isArray(fields) || !fields.length) { + if (!key || (Array.isArray(key) && !key.length) || !Array.isArray(fields) || !fields.length) { return; } - - await module.pool.query({ - name: 'deleteObjectFields', - text: ` -UPDATE "legacy_hash" - SET "data" = COALESCE((SELECT jsonb_object_agg("key", "value") - FROM jsonb_each("data") - WHERE "key" <> ALL ($2::TEXT[])), '{}') - WHERE "_key" = $1::TEXT`, - values: [key, fields], - }); + async function delKey(key, fields) { + await module.pool.query({ + name: 'deleteObjectFields', + text: ` + UPDATE "legacy_hash" + SET "data" = COALESCE((SELECT jsonb_object_agg("key", "value") + FROM jsonb_each("data") + WHERE "key" <> ALL ($2::TEXT[])), '{}') + WHERE "_key" = $1::TEXT`, + values: [key, fields], + }); + } + if (Array.isArray(key)) { + await Promise.all(key.map(k => delKey(k, fields))); + } else { + await delKey(key, fields); + } }; module.incrObjectField = async function (key, field) { diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index f32f7be0e5..b495c7d44a 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -150,14 +150,21 @@ module.exports = function (module) { }; module.deleteObjectFields = async function (key, fields) { - if (!key || !Array.isArray(fields) || !fields.length) { + if (!key || (Array.isArray(key) && !key.length) || !Array.isArray(fields) || !fields.length) { return; } fields = fields.filter(Boolean); if (!fields.length) { return; } - await module.client.async.hdel(key, fields); + if (Array.isArray(key)) { + const batch = module.client.batch(); + key.forEach(k => batch.hdel(k, fields)); + await helpers.execBatch(batch); + } else { + await module.client.async.hdel(key, fields); + } + cache.del(key); }; diff --git a/src/posts/delete.js b/src/posts/delete.js index 0504eec5f4..ed85a73110 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -122,13 +122,18 @@ module.exports = function (Posts) { } async function deletePostFromReplies(postData) { - if (!parseInt(postData.toPid, 10)) { - return; + const replyPids = await db.getSortedSetMembers('pid:' + postData.pid + ':replies'); + const promises = [ + db.deleteObjectFields( + replyPids.map(pid => 'post:' + pid), ['toPid'] + ), + db.delete('pid:' + postData.pid + ':replies'), + ]; + if (parseInt(postData.toPid, 10)) { + promises.push(db.sortedSetRemove('pid:' + postData.toPid + ':replies', postData.pid)); + promises.push(db.decrObjectField('post:' + postData.toPid, 'replies')); } - await Promise.all([ - db.sortedSetRemove('pid:' + postData.toPid + ':replies', postData.pid), - db.decrObjectField('post:' + postData.toPid, 'replies'), - ]); + await Promise.all(promises); } async function deletePostFromGroups(postData) { diff --git a/src/upgrades/1.15.4/clear_purged_replies.js b/src/upgrades/1.15.4/clear_purged_replies.js new file mode 100644 index 0000000000..df4655116d --- /dev/null +++ b/src/upgrades/1.15.4/clear_purged_replies.js @@ -0,0 +1,33 @@ +'use strict'; + +const _ = require('lodash'); +const db = require('../../database'); + +const batch = require('../../batch'); + +module.exports = { + name: 'Clear purged replies and toPid', + timestamp: Date.UTC(2020, 10, 26), + method: async function () { + const progress = this.progress; + + await batch.processSortedSet('posts:pid', async function (pids) { + progress.incr(pids.length); + let postData = await db.getObjects(pids.map(pid => 'post:' + pid)); + postData = postData.filter(p => p && parseInt(p.toPid, 10)); + if (!postData.length) { + return; + } + const toPids = postData.map(p => p.toPid); + const exists = await db.exists(toPids.map(pid => 'post:' + pid)); + const pidsToDelete = postData.filter((p, index) => !exists[index]).map(p => p.pid); + await db.deleteObjectFields(pidsToDelete.map(pid => 'post:' + pid), ['toPid']); + + const repliesToDelete = _.uniq(toPids.filter((pid, index) => !exists[index])); + await db.deleteAll(repliesToDelete.map(pid => 'pid:' + pid + ':replies')); + }, { + progress: progress, + batchSize: 500, + }); + }, +}; diff --git a/test/database/hash.js b/test/database/hash.js index d3a5535d5d..123967b1da 100644 --- a/test/database/hash.js +++ b/test/database/hash.js @@ -415,6 +415,16 @@ describe('Hash methods', function () { }); }); + it('should delete multiple fields of multiple objects', async function () { + await db.setObject('deleteFields1', { foo: 'foo1', baz: '2' }); + await db.setObject('deleteFields2', { foo: 'foo2', baz: '3' }); + await db.deleteObjectFields(['deleteFields1', 'deleteFields2'], ['baz']); + const obj1 = await db.getObject('deleteFields1'); + const obj2 = await db.getObject('deleteFields2'); + assert.deepStrictEqual(obj1, { foo: 'foo1' }); + assert.deepStrictEqual(obj2, { foo: 'foo2' }); + }); + it('should not error if fields is empty array', async () => { await db.deleteObjectFields('someKey', []); }); diff --git a/test/topics.js b/test/topics.js index 6ef9d4a4b8..239cf055d5 100644 --- a/test/topics.js +++ b/test/topics.js @@ -190,6 +190,22 @@ describe('Topic\'s', function () { done(); }); }); + + it('should delete nested relies properly', async function () { + const result = await topics.post({ uid: fooUid, title: 'nested test', content: 'main post', cid: topic.categoryId }); + const reply1 = await topics.reply({ uid: fooUid, content: 'reply post 1', tid: result.topicData.tid }); + const reply2 = await topics.reply({ uid: fooUid, content: 'reply post 2', tid: result.topicData.tid, toPid: reply1.pid }); + let replies = await socketPosts.getReplies({ uid: fooUid }, reply1.pid); + assert.strictEqual(replies.length, 1); + assert.strictEqual(replies[0].content, 'reply post 2'); + let toPid = await posts.getPostField(reply2.pid, 'toPid'); + assert.strictEqual(parseInt(toPid, 10), parseInt(reply1.pid, 10)); + await posts.purge(reply1.pid, fooUid); + replies = await socketPosts.getReplies({ uid: fooUid }, reply1.pid); + assert.strictEqual(replies.length, 0); + toPid = await posts.getPostField(reply2.pid, 'toPid'); + assert.strictEqual(toPid, null); + }); }); describe('Get methods', function () {