Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion lib/search-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ const getMongoDBQuery = async (db, user, queryStr) => {
const parsed = parseSearchQuery(queryStr);

let hasTextFilter = false;
const isSingleTextQuery = entry => typeof entry?.$text?.$search === 'string' && Object.keys(entry).length === 1;

let walkTree = async node => {
if (Array.isArray(node)) {
Expand All @@ -163,6 +164,26 @@ const getMongoDBQuery = async (db, user, queryStr) => {
branch.$and = branch.$and.concat(subBranch || []);
}

// MongoDB allows a single $text expression per query. If this AND branch
// has multiple direct text clauses, merge them into one.
// Used for fulltext AND query (default) (example: `q=term1 term2`)
if (branch.$and.length > 1) {
let textTerms = [];
let nonTextTerms = [];

for (let entry of branch.$and) {
if (isSingleTextQuery(entry)) {
textTerms.push(entry.$text.$search);
} else {
nonTextTerms.push(entry);
}
}

if (textTerms.length > 1) {
branch.$and = [{ $text: { $search: textTerms.join(' ') } }].concat(nonTextTerms);
}
}

return branch;
} else if (node.$or && node.$or.length) {
let branch = {
Expand All @@ -178,7 +199,7 @@ const getMongoDBQuery = async (db, user, queryStr) => {
// MongoDB allows a single $text expression per query. If this OR branch
// only contains text queries, merge them into one $text search.
// Used for fulltext OR query search
if (branch.$or.length && branch.$or.every(entry => entry?.$text && typeof entry.$text.$search === 'string' && Object.keys(entry).length === 1)) {
if (branch.$or.length && branch.$or.every(isSingleTextQuery)) {
return {
$text: {
$search: branch.$or.map(entry => entry.$text.$search).join(' ')
Expand Down
25 changes: 24 additions & 1 deletion test/api/messages-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ describe('Messages tests', function () {
subjectAttachment: 'Search Query Attachment Marker',
body: 'searchquerybodytoken',
attachmentBody: 'searchqueryattachmenttoken',
multiTerm1: 'searchquerymultiterm1token',
multiTerm2: 'searchquerymultiterm2token',
toAddress: 'search.query.to@to.com',
ccAddress: 'search.query.cc@to.com',
fromAddress: 'messagestestsuser@web.zone.test'
Expand Down Expand Up @@ -89,7 +91,7 @@ describe('Messages tests', function () {
cc: [{ address: queryFixture.ccAddress }],
bcc: [{ address: queryFixture.ccAddress }],
subject: queryFixture.subjectKeyword,
text: `${queryFixture.body} keyword marker`
text: `${queryFixture.body} keyword marker ${queryFixture.multiTerm1} ${queryFixture.multiTerm2}`
})
.expect(200);

Expand Down Expand Up @@ -262,6 +264,27 @@ describe('Messages tests', function () {
expect(search.body.results.map(entry => entry.subject)).to.include(queryFixture.subjectExcluded);
});

it('should GET /users/:user/search expect success / q with two terms and searchable=1', async () => {
const q = `${queryFixture.multiTerm1} ${queryFixture.multiTerm2} in:${queryMailbox}`;

const search = await server
.get(`/users/${user}/search?q=${encodeURIComponent(q)}&limit=50`)
.send({})
.expect(200);

const searchWithSearchable = await server
.get(`/users/${user}/search?q=${encodeURIComponent(q)}&searchable=1&limit=50`)
.send({})
.expect(200);

expect(search.body.success).to.be.true;
expect(searchWithSearchable.body.success).to.be.true;
expect(searchWithSearchable.body.query).to.equal(q);
expect(search.body.results.map(entry => entry.subject)).to.include(queryFixture.subjectKeyword);
expect(searchWithSearchable.body.results.map(entry => entry.subject)).to.include(queryFixture.subjectKeyword);
expect(searchWithSearchable.body.results).to.deep.equal(search.body.results);
});

it('should GET /users/:user/search expect success / q supports OR groups', async () => {
const q = `(${queryFixture.body} OR ${queryFixture.attachmentBody}) in:${queryMailbox}`;
const search = await server
Expand Down