diff --git a/keyserver/src/database/search-utils.js b/keyserver/src/database/search-utils.js --- a/keyserver/src/database/search-utils.js +++ b/keyserver/src/database/search-utils.js @@ -134,9 +134,25 @@ } } +const fulltextOperands = ['+', '<', '>', '~']; + +function processQueryForSearch(query: string): string { + if (query === '') { + return ''; + } + const stemmedQuery = segmentAndStem(query); + + return stemmedQuery + .split(' ') + .filter(word => !fulltextOperands.includes(word)) + .map(segment => `+${segment}`) + .join(' '); +} + export { processMessagesForSearch, processMessagesInDBForSearch, segmentAndStem, stopwords, + processQueryForSearch, }; diff --git a/keyserver/src/database/search-utils.test.js b/keyserver/src/database/search-utils.test.js --- a/keyserver/src/database/search-utils.test.js +++ b/keyserver/src/database/search-utils.test.js @@ -1,5 +1,10 @@ // @flow -import { segmentAndStem, stopwords } from './search-utils.js'; + +import { + segmentAndStem, + stopwords, + processQueryForSearch, +} from './search-utils.js'; const alphaNumericRegex = /^[A-Za-z0-9 ]*$/; const lowerCaseRegex = /^[a-z ]*$/; @@ -50,3 +55,14 @@ ); }); }); + +describe('processQueryForSearch(query: string)', () => { + it('should add + before every word', () => { + expect(processQueryForSearch('test')).toBe('+test'); + expect(processQueryForSearch('test hello')).toBe('+test +hello'); + expect(processQueryForSearch('test \nhello')).toBe('+test +hello'); + }); + it('should remove + < > ~ from the query', () => { + expect(processQueryForSearch('+ < > ~')).toBe(''); + }); +}); diff --git a/keyserver/src/fetchers/message-fetchers.js b/keyserver/src/fetchers/message-fetchers.js --- a/keyserver/src/fetchers/message-fetchers.js +++ b/keyserver/src/fetchers/message-fetchers.js @@ -26,6 +26,7 @@ type FetchPinnedMessagesResult, isMessageSidebarSourceReactionOrEdit, } from 'lib/types/message-types.js'; +import { defaultNumberPerThread } from 'lib/types/message-types.js'; import { threadPermissions } from 'lib/types/thread-types.js'; import { ServerError } from 'lib/utils/errors.js'; @@ -39,6 +40,7 @@ mergeOrConditions, mergeAndConditions, } from '../database/database.js'; +import { processQueryForSearch } from '../database/search-utils.js'; import type { SQLStatementType } from '../database/types.js'; import type { PushInfo } from '../push/send.js'; import type { Viewer } from '../session/viewer.js'; @@ -906,6 +908,60 @@ return [...rawMessageInfos, ...rawRelatedMessageInfos]; } +const searchMessagesPageSize: number = defaultNumberPerThread + 1; + +async function searchMessagesInSingleChat( + inputQuery: string, + threadID: string, + viewer?: Viewer, + cursor?: string, +): Promise<$ReadOnlyArray> { + if (inputQuery === '') { + console.warn('received empty search query'); + return []; + } + const pattern = processQueryForSearch(inputQuery); + + const query = SQL` + SELECT m.id, m.thread AS threadID, m.content, m.time, m.type, m.creation, + m.user AS creatorID, m.target_message as targetMessageID, + stm.permissions AS subthread_permissions, up.id AS uploadID, + up.type AS uploadType, up.secret AS uploadSecret, up.extra AS uploadExtra + FROM message_search s + LEFT JOIN messages m ON m.id = s.original_message_id + LEFT JOIN memberships stm ON m.type = ${messageTypes.CREATE_SUB_THREAD} + AND stm.thread = m.content AND stm.user = m.user + LEFT JOIN uploads up ON up.container = m.id + LEFT JOIN messages m2 ON m2.target_message = m.id + AND m2.type = ${messageTypes.SIDEBAR_SOURCE} AND m2.thread = ${threadID} + WHERE MATCH(s.processed_content) AGAINST(${pattern} IN BOOLEAN MODE) + AND (m.thread = ${threadID} OR m2.id IS NOT NULL) + `; + + if (cursor) { + query.append(SQL`AND m.id < ${cursor} `); + } + query.append(SQL` + ORDER BY m.time DESC, m.id DESC + LIMIT ${searchMessagesPageSize} + `); + + const [results] = await dbQuery(query); + if (results.length === 0) { + return []; + } + + const rawMessageInfos = await rawMessageInfoForRowsAndRelatedMessages( + results, + viewer, + ); + + return shimUnsupportedRawMessageInfos( + rawMessageInfos, + viewer?.platformDetails, + ); +} + export { fetchCollapsableNotifs, fetchMessageInfos, @@ -919,4 +975,6 @@ fetchPinnedMessageInfos, fetchRelatedMessages, rawMessageInfoForRowsAndRelatedMessages, + searchMessagesInSingleChat, + searchMessagesPageSize, };