• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CMU-17313Q / fall23-nodebb-just-relax / 6676852353

28 Oct 2023 12:23PM UTC coverage: 85.016% (+0.003%) from 85.013%
6676852353

push

github

web-flow
Merge pull request #79 from CMU-17313Q/integrating-jshint

Integrating the JShint Tool

10221 of 13255 branches covered (0.0%)

Branch coverage included in aggregate %.

23 of 26 new or added lines in 17 files covered. (88.46%)

3 existing lines in 2 files now uncovered.

22624 of 25379 relevant lines covered (89.14%)

3228.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.7
/src/database/mongo/sorted.js
1
'use strict';
2

3
const _ = require('lodash');
10✔
4
const utils = require('../../utils');
10✔
5

6
module.exports = function (module) {
10✔
7
    const helpers = require('./helpers');
10✔
8
    const dbHelpers = require('../helpers');
10✔
9

10
    const util = require('util');
10✔
11
    const sleep = util.promisify(setTimeout);
10✔
12

13
    require('./sorted/add')(module);
10✔
14
    require('./sorted/remove')(module);
10✔
15
    require('./sorted/union')(module);
10✔
16
    require('./sorted/intersect')(module);
10✔
17

18
    module.getSortedSetRange = async function (key, start, stop) {
10✔
19
        return await getSortedSetRange(key, start, stop, '-inf', '+inf', 1, false);
1,610✔
20
    };
21

22
    module.getSortedSetRevRange = async function (key, start, stop) {
10✔
23
        return await getSortedSetRange(key, start, stop, '-inf', '+inf', -1, false);
3,880✔
24
    };
25

26
    module.getSortedSetRangeWithScores = async function (key, start, stop) {
10✔
27
        return await getSortedSetRange(key, start, stop, '-inf', '+inf', 1, true);
1,247✔
28
    };
29

30
    module.getSortedSetRevRangeWithScores = async function (key, start, stop) {
10✔
31
        return await getSortedSetRange(key, start, stop, '-inf', '+inf', -1, true);
508✔
32
    };
33

34
    async function getSortedSetRange(key, start, stop, min, max, sort, withScores) {
35
        if (!key) {
7,612!
36
            return;
×
37
        }
38
        const isArray = Array.isArray(key);
7,612✔
39
        if ((start < 0 && start > stop) || (isArray && !key.length)) {
7,612✔
40
            return [];
12✔
41
        }
42
        const query = { _key: key };
7,600✔
43
        if (isArray) {
7,600✔
44
            if (key.length > 1) {
318✔
45
                query._key = { $in: key };
208✔
46
            } else {
47
                query._key = key[0];
110✔
48
            }
49
        }
50

51
        if (min !== '-inf') {
7,600✔
52
            query.score = { $gte: min };
340✔
53
        }
54
        if (max !== '+inf') {
7,600✔
55
            query.score = query.score || {};
79✔
56
            query.score.$lte = max;
79✔
57
        }
58

59
        if (max === min) {
7,600✔
60
            query.score = max;
50✔
61
        }
62

63
        const fields = { _id: 0, _key: 0 };
7,600✔
64
        if (!withScores) {
7,600✔
65
            fields.score = 0;
5,673✔
66
        }
67

68
        let reverse = false;
7,600✔
69
        if (start === 0 && stop < -1) {
7,600✔
70
            reverse = true;
2✔
71
            sort *= -1;
2✔
72
            start = Math.abs(stop + 1);
2✔
73
            stop = -1;
2✔
74
        } else if (start < 0 && stop > start) {
7,598✔
75
            const tmp1 = Math.abs(stop + 1);
6✔
76
            stop = Math.abs(start + 1);
6✔
77
            start = tmp1;
6✔
78
        }
79

80
        let limit = stop - start + 1;
7,600✔
81
        if (limit <= 0) {
7,600✔
82
            limit = 0;
3,562✔
83
        }
84

85
        let result = [];
7,600✔
86
        async function doQuery(_key, fields, skip, limit) {
87
            return await module.client.collection('objects').find({ ...query, ...{ _key: _key } }, { projection: fields })
7,624✔
88
                .sort({ score: sort })
89
                .skip(skip)
90
                .limit(limit)
91
                .toArray();
92
        }
93

94
        if (isArray && key.length > 100) {
7,600✔
95
            const batches = [];
8✔
96
            const batch = require('../../batch');
8✔
97
            const batchSize = Math.ceil(key.length / Math.ceil(key.length / 100));
8✔
98
            await batch.processArray(key, async currentBatch => batches.push(currentBatch), { batch: batchSize });
32✔
99
            const batchData = await Promise.all(batches.map(
8✔
100
                batch => doQuery({ $in: batch }, { _id: 0, _key: 0 }, 0, stop + 1)
32✔
101
            ));
102
            result = dbHelpers.mergeBatch(batchData, 0, stop, sort);
8✔
103
            if (start > 0) {
8✔
104
                result = result.slice(start, stop !== -1 ? stop + 1 : undefined);
2!
105
            }
106
        } else {
107
            result = await doQuery(query._key, fields, start, limit);
7,592✔
108
        }
109

110
        if (reverse) {
7,600✔
111
            result.reverse();
2✔
112
        }
113
        if (!withScores) {
7,600✔
114
            result = result.map(item => item.value);
91,088✔
115
        }
116

117
        return result;
7,600✔
118
    }
119

120
    module.getSortedSetRangeByScore = async function (key, start, count, min, max) {
10✔
121
        return await getSortedSetRangeByScore(key, start, count, min, max, 1, false);
55✔
122
    };
123

124
    module.getSortedSetRevRangeByScore = async function (key, start, count, max, min) {
10✔
125
        return await getSortedSetRangeByScore(key, start, count, min, max, -1, false);
136✔
126
    };
127

128
    module.getSortedSetRangeByScoreWithScores = async function (key, start, count, min, max) {
10✔
129
        return await getSortedSetRangeByScore(key, start, count, min, max, 1, true);
24✔
130
    };
131

132
    module.getSortedSetRevRangeByScoreWithScores = async function (key, start, count, max, min) {
10✔
133
        return await getSortedSetRangeByScore(key, start, count, min, max, -1, true);
154✔
134
    };
135

136
    async function getSortedSetRangeByScore(key, start, count, min, max, sort, withScores) {
137
        if (parseInt(count, 10) === 0) {
369✔
138
            return [];
2✔
139
        }
140
        const stop = (parseInt(count, 10) === -1) ? -1 : (start + count - 1);
367✔
141
        return await getSortedSetRange(key, start, stop, min, max, sort, withScores);
367✔
142
    }
143

144
    module.sortedSetCount = async function (key, min, max) {
10✔
145
        if (!key) {
560!
146
            return;
×
147
        }
148

149
        const query = { _key: key };
560✔
150
        if (min !== '-inf') {
560✔
151
            query.score = { $gte: min };
378✔
152
        }
153
        if (max !== '+inf') {
560✔
154
            query.score = query.score || {};
188✔
155
            query.score.$lte = max;
188✔
156
        }
157

158
        const count = await module.client.collection('objects').countDocuments(query);
560✔
159
        return count || 0;
560✔
160
    };
161

162
    module.sortedSetCard = async function (key) {
10✔
163
        if (!key) {
3,813!
164
            return 0;
×
165
        }
166
        const count = await module.client.collection('objects').countDocuments({ _key: key });
3,813✔
167
        return parseInt(count, 10) || 0;
3,813✔
168
    };
169

170
    module.sortedSetsCard = async function (keys) {
10✔
171
        if (!Array.isArray(keys) || !keys.length) {
838✔
172
            return [];
120✔
173
        }
174
        const promises = keys.map(k => module.sortedSetCard(k));
960✔
175
        return await Promise.all(promises);
718✔
176
    };
177

178
    module.sortedSetsCardSum = async function (keys) {
10✔
179
        if (!keys || (Array.isArray(keys) && !keys.length)) {
272✔
180
            return 0;
20✔
181
        }
182

183
        const count = await module.client.collection('objects').countDocuments({ _key: Array.isArray(keys) ? { $in: keys } : keys });
252✔
184
        return parseInt(count, 10) || 0;
252✔
185
    };
186

187
    module.sortedSetRank = async function (key, value) {
10✔
188
        return await getSortedSetRank(false, key, value);
138✔
189
    };
190

191
    module.sortedSetRevRank = async function (key, value) {
10✔
192
        return await getSortedSetRank(true, key, value);
8✔
193
    };
194

195
    async function getSortedSetRank(reverse, key, value) {
196
        if (!key) {
150!
197
            return;
×
198
        }
199
        value = helpers.valueToString(value);
150✔
200
        const score = await module.sortedSetScore(key, value);
150✔
201
        if (score === null) {
150✔
202
            return null;
30✔
203
        }
204

205
        return await module.client.collection('objects').countDocuments({
120✔
206
            $or: [
207
                {
208
                    _key: key,
209
                    score: reverse ? { $gt: score } : { $lt: score },
120✔
210
                },
211
                {
212
                    _key: key,
213
                    score: score,
214
                    value: reverse ? { $gt: value } : { $lt: value },
120✔
215
                },
216
            ],
217
        });
218
    }
219

220
    module.sortedSetsRanks = async function (keys, values) {
10✔
221
        return await sortedSetsRanks(module.sortedSetRank, keys, values);
2✔
222
    };
223

224
    module.sortedSetsRevRanks = async function (keys, values) {
10✔
225
        return await sortedSetsRanks(module.sortedSetRevRank, keys, values);
×
226
    };
227

228
    async function sortedSetsRanks(method, keys, values) {
229
        if (!Array.isArray(keys) || !keys.length) {
2!
230
            return [];
×
231
        }
232
        const data = new Array(values.length);
2✔
233
        for (let i = 0; i < values.length; i += 1) {
2✔
234
            data[i] = { key: keys[i], value: values[i] };
4✔
235
        }
236
        const promises = data.map(item => method(item.key, item.value));
4✔
237
        return await Promise.all(promises);
2✔
238
    }
239

240
    module.sortedSetRanks = async function (key, values) {
10✔
241
        return await sortedSetRanks(false, key, values);
12✔
242
    };
243

244
    module.sortedSetRevRanks = async function (key, values) {
10✔
245
        return await sortedSetRanks(true, key, values);
2✔
246
    };
247

248
    async function sortedSetRanks(reverse, key, values) {
249
        if (values.length === 1) {
14✔
250
            return [await getSortedSetRank(reverse, key, values[0])];
4✔
251
        }
252
        const sortedSet = await module[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](key, 0, -1);
10✔
253
        return values.map((value) => {
10✔
254
            if (!value) {
28!
255
                return null;
×
256
            }
257
            const index = sortedSet.indexOf(value.toString());
28✔
258
            return index !== -1 ? index : null;
28✔
259
        });
260
    }
261

262
    module.sortedSetScore = async function (key, value) {
10✔
263
        if (!key) {
6,213✔
264
            return null;
2✔
265
        }
266
        value = helpers.valueToString(value);
6,211✔
267
        const result = await module.client.collection('objects').findOne({ _key: key, value: value }, { projection: { _id: 0, _key: 0, value: 0 } });
6,211✔
268
        return result ? result.score : null;
6,211✔
269
    };
270

271
    module.sortedSetsScore = async function (keys, value) {
10✔
272
        if (!Array.isArray(keys) || !keys.length) {
632✔
273
            return [];
2✔
274
        }
275
        value = helpers.valueToString(value);
630✔
276
        const result = await module.client.collection('objects').find({ _key: { $in: keys }, value: value }, { projection: { _id: 0, value: 0 } }).toArray();
630✔
277
        const map = {};
630✔
278
        result.forEach((item) => {
630✔
279
            if (item) {
72!
280
                map[item._key] = item;
72✔
281
            }
282
        });
283

284
        return keys.map(key => (map[key] ? map[key].score : null));
1,386✔
285
    };
286

287
    module.sortedSetScores = async function (key, values) {
10✔
288
        if (!key) {
3,372!
289
            return null;
×
290
        }
291
        if (!values.length) {
3,372✔
292
            return [];
66✔
293
        }
294
        values = values.map(helpers.valueToString);
3,306✔
295
        const result = await module.client.collection('objects').find({ _key: key, value: { $in: values } }, { projection: { _id: 0, _key: 0 } }).toArray();
3,306✔
296

297
        const valueToScore = {};
3,306✔
298
        result.forEach((item) => {
3,306✔
299
            if (item) {
2,428!
300
                valueToScore[item.value] = item.score;
2,428✔
301
            }
302
        });
303

304
        return values.map(v => (utils.isNumber(valueToScore[v]) ? valueToScore[v] : null));
37,624✔
305
    };
306

307
    module.isSortedSetMember = async function (key, value) {
10✔
308
        if (!key) {
2,561!
309
            return;
×
310
        }
311
        value = helpers.valueToString(value);
2,561✔
312
        const result = await module.client.collection('objects').findOne({
2,561✔
313
            _key: key, value: value,
314
        }, {
315
            projection: { _id: 0, value: 1 },
316
        });
317
        return !!result;
2,561✔
318
    };
319

320
    module.isSortedSetMembers = async function (key, values) {
10✔
321
        if (!key) {
3,482!
322
            return;
×
323
        }
324
        if (!values.length) {
3,482✔
325
            return [];
146✔
326
        }
327
        values = values.map(helpers.valueToString);
3,336✔
328
        const results = await module.client.collection('objects').find({
3,336✔
329
            _key: key, value: { $in: values },
330
        }, {
331
            projection: { _id: 0, value: 1 },
332
        }).toArray();
333

334
        const isMember = {};
3,336✔
335
        results.forEach((item) => {
3,336✔
336
            if (item) {
8,063!
337
                isMember[item.value] = true;
8,063✔
338
            }
339
        });
340

341
        return values.map(value => !!isMember[value]);
12,778✔
342
    };
343

344
    module.isMemberOfSortedSets = async function (keys, value) {
10✔
345
        if (!Array.isArray(keys) || !keys.length) {
3,754✔
346
            return [];
2✔
347
        }
348
        value = helpers.valueToString(value);
3,752✔
349
        const results = await module.client.collection('objects').find({
3,752✔
350
            _key: { $in: keys }, value: value,
351
        }, {
352
            projection: { _id: 0, _key: 1, value: 1 },
353
        }).toArray();
354

355
        const isMember = {};
3,752✔
356
        results.forEach((item) => {
3,752✔
357
            if (item) {
1,931!
358
                isMember[item._key] = true;
1,931✔
359
            }
360
        });
361

362
        return keys.map(key => !!isMember[key]);
18,882✔
363
    };
364

365
    module.getSortedSetMembers = async function (key) {
10✔
366
        const data = await module.getSortedSetsMembers([key]);
2,214✔
367
        return data && data[0];
2,214✔
368
    };
369

370
    module.getSortedSetsMembers = async function (keys) {
10✔
371
        if (!Array.isArray(keys) || !keys.length) {
4,250✔
372
            return [];
44✔
373
        }
374
        const arrayOfKeys = keys.length > 1;
4,206✔
375
        const projection = { _id: 0, value: 1 };
4,206✔
376
        if (arrayOfKeys) {
4,206✔
377
            projection._key = 1;
276✔
378
        }
379
        const data = await module.client.collection('objects').find({
4,206✔
380
            _key: arrayOfKeys ? { $in: keys } : keys[0],
4,206✔
381
        }, { projection: projection }).toArray();
382

383
        if (!arrayOfKeys) {
4,206✔
384
            return [data.map(item => item.value)];
3,930✔
385
        }
386
        const sets = {};
276✔
387
        data.forEach((item) => {
276✔
388
            sets[item._key] = sets[item._key] || [];
2,513✔
389
            sets[item._key].push(item.value);
2,513✔
390
        });
391

392
        return keys.map(k => sets[k] || []);
2,304✔
393
    };
394

395
    module.sortedSetIncrBy = async function (key, increment, value) {
10✔
396
        if (!key) {
1,328!
397
            return;
×
398
        }
399
        const data = {};
1,328✔
400
        value = helpers.valueToString(value);
1,328✔
401
        data.score = parseFloat(increment);
1,328✔
402

403
        try {
1,328✔
404
            const result = await module.client.collection('objects').findOneAndUpdate({
1,328✔
405
                _key: key,
406
                value: value,
407
            }, {
408
                $inc: data,
409
            }, {
410
                returnDocument: 'after',
411
                upsert: true,
412
            });
413
            return result && result.value ? result.value.score : null;
1,328!
414
        } catch (err) {
415
            // if there is duplicate key error retry the upsert
416
            // https://github.com/NodeBB/NodeBB/issues/4467
417
            // https://jira.mongodb.org/browse/SERVER-14322
418
            // https://docs.mongodb.org/manual/reference/command/findAndModify/#upsert-and-unique-index
UNCOV
419
            if (err && err.message.startsWith('E11000 duplicate key error')) {
×
UNCOV
420
                return await module.sortedSetIncrBy(key, increment, value);
×
421
            }
422
            throw err;
×
423
        }
424
    };
425

426
    module.sortedSetIncrByBulk = async function (data) {
10✔
427
        const bulk = module.client.collection('objects').initializeUnorderedBulkOp();
34✔
428
        data.forEach((item) => {
34✔
429
            bulk.find({ _key: item[0], value: helpers.valueToString(item[2]) })
66✔
430
                .upsert()
431
                .update({ $inc: { score: parseFloat(item[1]) } });
432
        });
433
        await bulk.execute();
34✔
434
        const result = await module.client.collection('objects').find({
34✔
435
            _key: { $in: _.uniq(data.map(i => i[0])) },
66✔
436
            value: { $in: _.uniq(data.map(i => i[2])) },
66✔
437
        }, {
438
            projection: { _id: 0, _key: 1, value: 1, score: 1 },
439
        }).toArray();
440

441
        const map = {};
34✔
442
        result.forEach((item) => {
34✔
443
            map[`${item._key}:${item.value}`] = item.score;
80✔
444
        });
445
        return data.map(item => map[`${item[0]}:${item[2]}`]);
66✔
446
    };
447

448
    module.getSortedSetRangeByLex = async function (key, min, max, start, count) {
10✔
449
        return await sortedSetLex(key, min, max, 1, start, count);
44✔
450
    };
451

452
    module.getSortedSetRevRangeByLex = async function (key, max, min, start, count) {
10✔
453
        return await sortedSetLex(key, min, max, -1, start, count);
10✔
454
    };
455

456
    module.sortedSetLexCount = async function (key, min, max) {
10✔
457
        const data = await sortedSetLex(key, min, max, 1, 0, 0);
8✔
458
        return data ? data.length : null;
8!
459
    };
460

461
    async function sortedSetLex(key, min, max, sort, start, count) {
462
        const query = { _key: key };
62✔
463
        start = start !== undefined ? start : 0;
62✔
464
        count = count !== undefined ? count : 0;
62✔
465
        buildLexQuery(query, min, max);
62✔
466

467
        const data = await module.client.collection('objects').find(query, { projection: { _id: 0, value: 1 } })
62✔
468
            .sort({ value: sort })
469
            .skip(start)
470
            .limit(count === -1 ? 0 : count)
62✔
471
            .toArray();
472

473
        return data.map(item => item && item.value);
148✔
474
    }
475

476
    module.sortedSetRemoveRangeByLex = async function (key, min, max) {
10✔
477
        const query = { _key: key };
8✔
478
        buildLexQuery(query, min, max);
8✔
479

480
        await module.client.collection('objects').deleteMany(query);
8✔
481
    };
482

483
    function buildLexQuery(query, min, max) {
484
        if (min !== '-') {
70✔
485
            if (min.match(/^\(/)) {
44✔
486
                query.value = { $gt: min.slice(1) };
8✔
487
            } else if (min.match(/^\[/)) {
36✔
488
                query.value = { $gte: min.slice(1) };
8✔
489
            } else {
490
                query.value = { $gte: min };
28✔
491
            }
492
        }
493
        if (max !== '+') {
70✔
494
            query.value = query.value || {};
44!
495
            if (max.match(/^\(/)) {
44✔
496
                query.value.$lt = max.slice(1);
8✔
497
            } else if (max.match(/^\[/)) {
36✔
498
                query.value.$lte = max.slice(1);
8✔
499
            } else {
500
                query.value.$lte = max;
28✔
501
            }
502
        }
503
    }
504

505
    module.getSortedSetScan = async function (params) {
10✔
506
        const project = { _id: 0, value: 1 };
16✔
507
        if (params.withScores) {
16✔
508
            project.score = 1;
2✔
509
        }
510

511
        const match = helpers.buildMatchQuery(params.match);
16✔
512
        let regex;
513
        try {
16✔
514
            regex = new RegExp(match);
16✔
515
        } catch (err) {
516
            return [];
×
517
        }
518

519
        const cursor = module.client.collection('objects').find({
16✔
520
            _key: params.key, value: { $regex: regex },
521
        }, { projection: project });
522

523
        if (params.limit) {
16✔
524
            cursor.limit(params.limit);
10✔
525
        }
526

527
        const data = await cursor.toArray();
16✔
528
        if (!params.withScores) {
16✔
529
            return data.map(d => d.value);
24✔
530
        }
531
        return data;
2✔
532
    };
533

534
    module.processSortedSet = async function (setKey, processFn, options) {
10✔
535
        let done = false;
534✔
536
        const ids = [];
534✔
537
        const project = { _id: 0, _key: 0 };
534✔
538

539
        if (!options.withScores) {
534✔
540
            project.score = 0;
528✔
541
        }
542
        const cursor = await module.client.collection('objects').find({ _key: setKey }, { projection: project })
534✔
543
            .sort({ score: 1 })
544
            .batchSize(options.batch);
545

546
        if (processFn && processFn.constructor && processFn.constructor.name !== 'AsyncFunction') {
534✔
547
            processFn = util.promisify(processFn);
46✔
548
        }
549

550
        while (!done) {
534✔
551
            /* eslint-disable no-await-in-loop */
552
            const item = await cursor.next();
4,675✔
553
            if (item === null) {
4,675✔
554
                done = true;
534✔
555
            } else {
556
                ids.push(options.withScores ? item : item.value);
4,141✔
557
            }
558

559
            if (ids.length >= options.batch || (done && ids.length !== 0)) {
4,675✔
560
                await processFn(ids);
146✔
561

562
                ids.length = 0;
146✔
563
                if (options.interval) {
146✔
564
                    await sleep(options.interval);
48✔
565
                }
566
            }
567
        }
568
    };
569
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc