Skip to content

Commit 6ffd0e6

Browse files
authored
fix(uid-search): ZMSA-67: improve UID SEARCH performance by reducing calls to imap-compiler (#955)
* ZMSA-67: improve UID SEARCH performance by reducing calls to imap-compiler * mongodb, when fetching contiguous uid then use range query
1 parent dec117d commit 6ffd0e6

File tree

3 files changed

+54
-26
lines changed

3 files changed

+54
-26
lines changed

imap-core/lib/commands/search.js

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = {
3333
let parsed;
3434

3535
try {
36-
parsed = parseQueryTerms(terms, this.selected.uidList);
36+
parsed = parseQueryTerms(terms, this.selected.uidList, isUid);
3737
} catch (E) {
3838
return callback(E);
3939
}
@@ -90,24 +90,34 @@ module.exports = {
9090
if (Array.isArray(matches) && matches.length) {
9191
matches.sort((a, b) => a - b);
9292

93-
matches.forEach(nr => {
94-
let seq;
93+
if (isUid) {
94+
response.attributes.push({
95+
type: 'TEXT',
96+
value: matches.join(' ')
97+
});
98+
} else {
99+
let uidList = this.selected.uidList || [];
100+
let uidIndex = new Map();
101+
let seqList = [];
102+
103+
for (let i = 0; i < uidList.length; i++) {
104+
uidIndex.set(uidList[i], i + 1);
105+
}
95106

96-
if (!isUid) {
97-
seq = this.selected.uidList.indexOf(nr) + 1;
107+
for (let i = 0; i < matches.length; i++) {
108+
let seq = uidIndex.get(matches[i]);
98109
if (seq) {
99-
response.attributes.push({
100-
type: 'atom',
101-
value: String(seq)
102-
});
110+
seqList.push(seq);
103111
}
104-
} else {
112+
}
113+
114+
if (seqList.length) {
105115
response.attributes.push({
106-
type: 'atom',
107-
value: String(nr)
116+
type: 'TEXT',
117+
value: seqList.join(' ')
108118
});
109119
}
110-
});
120+
}
111121
}
112122

113123
// append (MODSEQ 123) for queries that include MODSEQ criteria
@@ -136,8 +146,14 @@ module.exports = {
136146
parseQueryTerms // expose for testing
137147
};
138148

139-
function parseQueryTerms(terms, uidList) {
149+
function isFixedRange(value) {
150+
value = (value || '').toString();
151+
return value.indexOf(':') >= 0 && value.indexOf(',') < 0;
152+
}
153+
154+
function parseQueryTerms(terms, uidList, isUidSearch) {
140155
terms = [].concat(terms || []);
156+
isUidSearch = !!isUidSearch;
141157

142158
let pos = 0;
143159
let term;
@@ -168,8 +184,12 @@ function parseQueryTerms(terms, uidList) {
168184
if (!termType) {
169185
// try if it is a sequence set
170186
if (imapTools.validateSequence(term)) {
187+
let messageRange = imapTools.getMessageRange(uidList, term, isUidSearch);
188+
if (isUidSearch && isFixedRange(term)) {
189+
messageRange.isContiguous = true;
190+
}
171191
// resolve sequence list to an array of UID values
172-
curTerm = ['uid', imapTools.getMessageRange(uidList, term, false)];
192+
curTerm = ['uid', messageRange];
173193
} else {
174194
// no idea what the term is for
175195
throw new Error('Unknown search term ' + term.toUpperCase());
@@ -183,7 +203,14 @@ function parseQueryTerms(terms, uidList) {
183203
throw new Error('Invalid sequence set for ' + term.toUpperCase());
184204
}
185205
// resolve sequence list to an array of UID values
186-
curTerm.push(imapTools.getMessageRange(uidList, terms[pos++], true));
206+
{
207+
let sequence = terms[pos++];
208+
let messageRange = imapTools.getMessageRange(uidList, sequence, true);
209+
if (isFixedRange(sequence)) {
210+
messageRange.isContiguous = true;
211+
}
212+
curTerm.push(messageRange);
213+
}
187214
} else {
188215
curTerm.push(terms[pos++]);
189216
}

lib/handlers/on-search.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ module.exports = server => (mailbox, options, session, callback) => {
105105
if (term.value.length !== session.selected.uidList.length) {
106106
// not 1:*
107107
parent.push({
108-
uid: tools.checkRangeQuery(term.value, ne)
108+
uid: tools.checkRangeQuery(term.value, ne, term.value?.isContiguous)
109109
});
110110
} else if (ne) {
111111
parent.push({

lib/tools.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,23 @@ const https = require('https');
2020

2121
let templates = false;
2222

23-
function checkRangeQuery(uids, ne) {
24-
// check if uids is a straight continuous array and if such then return a range query,
25-
// otherwise return a $in query
23+
function checkRangeQuery(uids, ne, isContiguous = false) {
24+
// If isContiguous is true, assume the original criteria was a fixed range and skip gap checks.
2625

2726
if (uids.length === 1) {
2827
return {
2928
[!ne ? '$eq' : '$ne']: uids[0]
3029
};
3130
}
3231

33-
for (let i = 1, len = uids.length; i < len; i++) {
34-
if (uids[i] !== uids[i - 1] + 1) {
35-
// TODO: group into AND conditions, otherwise expands too much!
36-
return {
37-
[!ne ? '$in' : '$nin']: uids
38-
};
32+
if (!isContiguous) {
33+
for (let i = 1, len = uids.length; i < len; i++) {
34+
if (uids[i] !== uids[i - 1] + 1) {
35+
// TODO: group into AND conditions, otherwise expands too much!
36+
return {
37+
[!ne ? '$in' : '$nin']: uids
38+
};
39+
}
3940
}
4041
}
4142

0 commit comments

Comments
 (0)