diff --git a/src/database/mongo/sorted/add.js b/src/database/mongo/sorted/add.js index c85b1d15e7..61c03602f6 100644 --- a/src/database/mongo/sorted/add.js +++ b/src/database/mongo/sorted/add.js @@ -72,8 +72,17 @@ module.exports = function (db, module) { bulk.find({ _key: keys[i], value: value }).upsert().updateOne({ $set: { score: parseFloat(isArrayOfScores ? scores[i] : scores) } }); } - bulk.execute(function (err) { - callback(err); + bulk.execute(err => callback(err)); + }; + + module.sortedSetAddBulk = function (data, callback) { + if (!Array.isArray(data) || !data.length) { + return setImmediate(callback); + } + var bulk = db.collection('objects').initializeUnorderedBulkOp(); + data.forEach(function (item) { + bulk.find({ _key: item[0], value: String(item[2]) }).upsert().updateOne({ $set: { score: parseFloat(item[1]) } }); }); + bulk.execute(err => callback(err)); }; }; diff --git a/src/database/postgres/sorted/add.js b/src/database/postgres/sorted/add.js index 5293a2635d..6fc70fe378 100644 --- a/src/database/postgres/sorted/add.js +++ b/src/database/postgres/sorted/add.js @@ -125,4 +125,37 @@ INSERT INTO "legacy_zset" ("_key", "value", "score") }); }, callback); }; + + module.sortedSetAddBulk = function (data, callback) { + if (!Array.isArray(data) || !data.length) { + return setImmediate(callback); + } + const keys = []; + const values = []; + const scores = []; + data.forEach(function (item) { + keys.push(item[0]); + scores.push(item[1]); + values.push(item[2]); + }); + module.transaction(function (tx, done) { + var query = tx.client.query.bind(tx.client); + + async.series([ + async.apply(helpers.ensureLegacyObjectsType, tx.client, keys, 'zset'), + async.apply(query, { + name: 'sortedSetAddBulk2', + text: ` +INSERT INTO "legacy_zset" ("_key", "value", "score") +SELECT k, v, s + FROM UNNEST($1::TEXT[], $2::TEXT[], $3::NUMERIC[]) vs(k, v, s) + ON CONFLICT ("_key", "value") + DO UPDATE SET "score" = EXCLUDED."score"`, + values: [keys, values, scores], + }), + ], function (err) { + done(err); + }); + }, callback); + }; }; diff --git a/src/database/redis/sorted/add.js b/src/database/redis/sorted/add.js index 47f71191e8..6cd6cc46a8 100644 --- a/src/database/redis/sorted/add.js +++ b/src/database/redis/sorted/add.js @@ -69,4 +69,15 @@ module.exports = function (redisClient, module) { callback(err); }); }; + + module.sortedSetAddBulk = function (data, callback) { + if (!Array.isArray(data) || !data.length) { + return setImmediate(callback); + } + var batch = redisClient.batch(); + data.forEach(function (item) { + batch.zadd(item[0], item[1], item[2]); + }); + batch.exec(err => callback(err)); + }; }; diff --git a/src/user/create.js b/src/user/create.js index 70dfa90fbf..00fbddaff1 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -74,25 +74,26 @@ module.exports = function (User) { db.incrObjectField('global', 'userCount', next); }, function (next) { - db.sortedSetsAdd([ - 'username:uid', 'user:' + userData.uid + ':usernames', - ], [userData.uid, timestamp], userData.username, next); - }, - function (next) { - db.sortedSetAdd('username:sorted', 0, userData.username.toLowerCase() + ':' + userData.uid, next); - }, - function (next) { - db.sortedSetAdd('userslug:uid', userData.uid, userData.userslug, next); - }, - function (next) { - var sets = ['users:joindate', 'users:online']; + const bulk = [ + ['username:uid', userData.uid, userData.username], + ['user:' + userData.uid + ':usernames', timestamp, userData.username], + ['username:sorted', 0, userData.username.toLowerCase() + ':' + userData.uid], + ['userslug:uid', userData.uid, userData.userslug], + ['users:joindate', timestamp, userData.uid], + ['users:online', timestamp, userData.uid], + ['users:postcount', 0, userData.uid], + ['users:reputation', 0, userData.uid], + ]; + if (parseInt(userData.uid, 10) !== 1) { - sets.push('users:notvalidated'); + bulk.push(['users:notvalidated', timestamp, userData.uid]); } - db.sortedSetsAdd(sets, timestamp, userData.uid, next); - }, - function (next) { - db.sortedSetsAdd(['users:postcount', 'users:reputation'], 0, userData.uid, next); + if (userData.email) { + bulk.push(['email:uid', userData.uid, userData.email.toLowerCase()]); + bulk.push(['email:sorted', 0, userData.email.toLowerCase() + ':' + userData.uid]); + bulk.push(['user:' + userData.uid + ':emails', timestamp, userData.email]); + } + db.sortedSetAddBulk(bulk, next); }, function (next) { groups.join('registered-users', userData.uid, next); @@ -101,23 +102,12 @@ module.exports = function (User) { User.notifications.sendWelcomeNotification(userData.uid, next); }, function (next) { - if (userData.email) { - async.parallel([ - async.apply(db.sortedSetAdd, 'email:uid', userData.uid, userData.email.toLowerCase()), - async.apply(db.sortedSetAdd, 'email:sorted', 0, userData.email.toLowerCase() + ':' + userData.uid), - async.apply(db.sortedSetAdd, 'user:' + userData.uid + ':emails', timestamp, userData.email), - ], next); - - if (userData.uid > 1 && meta.config.requireEmailConfirmation) { - User.email.sendValidationEmail(userData.uid, { - email: userData.email, - }); - } - } else { - next(); + if (userData.email && userData.uid > 1 && meta.config.requireEmailConfirmation) { + User.email.sendValidationEmail(userData.uid, { + email: userData.email, + }); } - }, - function (next) { + if (!data.password) { return next(); } diff --git a/test/database/sorted.js b/test/database/sorted.js index 4d17a50379..ea8a7feb25 100644 --- a/test/database/sorted.js +++ b/test/database/sorted.js @@ -120,6 +120,34 @@ describe('Sorted Set methods', function () { }); }); + describe('sortedSetAddMulti()', function () { + it('should add elements into multiple sorted sets with different scores', function (done) { + db.sortedSetAddBulk([ + ['bulk1', 1, 'item1'], + ['bulk2', 2, 'item1'], + ['bulk2', 3, 'item2'], + ['bulk3', 4, 'item3'], + ], function (err) { + assert.ifError(err); + assert.equal(arguments.length, 1); + db.getSortedSetRevRangeWithScores(['bulk1', 'bulk2', 'bulk3'], 0, -1, function (err, data) { + assert.ifError(err); + assert.deepStrictEqual(data, [{ value: 'item3', score: 4 }, + { value: 'item2', score: 3 }, + { value: 'item1', score: 2 }, + { value: 'item1', score: 1 }]); + done(); + }); + }); + }); + it('should not error if data is undefined', function (done) { + db.sortedSetAddBulk(undefined, function (err) { + assert.ifError(err); + done(); + }); + }); + }); + describe('getSortedSetRange()', function () { it('should return the lowest scored element', function (done) { db.getSortedSetRange('sortedSetTest1', 0, 0, function (err, value) {