diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h @@ -194,7 +194,8 @@ std::optional timestampCursor, std::optional messageIDCursor) const = 0; virtual std::vector getRelatedMessagesForSearch( - const std::vector &messageIDs) const = 0; + const std::vector &messageIDs, + bool backupItem) const = 0; virtual void replaceDMOperation(const DMOperation &operation) const = 0; virtual void removeAllDMOperations() const = 0; virtual void diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -186,7 +186,8 @@ std::optional timestampCursor, std::optional messageIDCursor) const override; std::vector getRelatedMessagesForSearch( - const std::vector &messageIDs) const override; + const std::vector &messageIDs, + bool backupItem) const override; void replaceDMOperation(const DMOperation &operation) const override; void removeAllDMOperations() const override; void removeDMOperations(const std::vector &ids) const override; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -1509,18 +1509,28 @@ std::string threadID, std::optional timestampCursor, std::optional messageIDCursor) const { + bool backupItem = !threadIDMatchesKeyserverProtocol(threadID); + std::string messagesTable = backupItem ? "backup_messages" : "messages"; + std::string mediaTable = backupItem ? "backup_media" : "media"; + std::stringstream searchMessagesSQL; searchMessagesSQL << "SELECT " " m.id, m.local_id, m.thread, m.user, m.type, m.future_type, " - " m.content, m.time, media.id, media.container, media.thread, " - " media.uri, media.type, media.extras " + " m.content, m.time, md.id, md.container, md.thread, " + " md.uri, md.type, md.extras " "FROM message_search AS s " - "LEFT JOIN messages AS m " + "LEFT JOIN " + << messagesTable + << " AS m " " ON m.id = s.original_message_id " - "LEFT JOIN media " - " ON m.id = media.container " - "LEFT JOIN messages AS m2 " + "LEFT JOIN " + << mediaTable + << " AS md " + " ON m.id = md.container " + "LEFT JOIN " + << messagesTable + << " AS m2 " " ON m2.target_message = m.id " " AND m2.type = ? AND m2.thread = ? " "WHERE s.processed_content MATCH ? " @@ -1560,7 +1570,7 @@ messageIDs.push_back(message.message.id); } std::vector relatedMessages = - this->getRelatedMessagesForSearch(messageIDs); + this->getRelatedMessagesForSearch(messageIDs, backupItem); for (auto &entity : relatedMessages) { messages.push_back(std::move(entity)); @@ -1569,17 +1579,24 @@ } std::vector SQLiteQueryExecutor::getRelatedMessagesForSearch( - const std::vector &messageIDs) const { + const std::vector &messageIDs, + bool backupItem) const { + std::string messagesTable = backupItem ? "backup_messages" : "messages"; + std::string mediaTable = backupItem ? "backup_media" : "media"; std::stringstream selectRelatedMessagesSQLStream; selectRelatedMessagesSQLStream << "SELECT " " m.id, m.local_id, m.thread, m.user, " " m.type, m.future_type, m.content, " - " m.time, media.id, media.container, " - " media.thread, media.uri, media.type, " - " media.extras " - "FROM messages AS m " - "LEFT JOIN media " - " ON m.id = media.container " + " m.time, md.id, md.container, " + " md.thread, md.uri, md.type, " + " md.extras " + "FROM " + << messagesTable + << " AS m " + "LEFT JOIN " + << mediaTable + << " AS md " + " ON m.id = md.container " "WHERE m.target_message IN " << getSQLStatementArray(messageIDs.size()) << "ORDER BY m.time DESC"; diff --git a/web/shared-worker/_generated/comm_query_executor.wasm b/web/shared-worker/_generated/comm_query_executor.wasm index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ { let queryExecutor; let dbModule; - const threadID = '100'; + const userID = '111'; const futureType = null; const localID = null; + const testCases = [ + { + threadID: '40db5619-feb2-4e5f-bd0c-1f9a709d366e', + description: 'with thick thread', + }, + { + threadID: '7A8BC7B3-BBBE-4980-8737-E3E67A26FBD2|115614', + description: 'with thin thread', + }, + ]; + beforeAll(async () => { dbModule = getDatabaseModule(); }); @@ -34,31 +46,72 @@ clearSensitiveData(dbModule, FILE_PATH, queryExecutor); }); - it('should find messages matching provided query', () => { - const text = 'test text'; - const id = '1'; - const message: WebMessage = { - id, - localID, - thread: threadID, - user: userID, - type: messageTypes.TEXT, - futureType, - content: text, - time: BigInt(123), - }; - queryExecutor.replaceMessage(message, false); - queryExecutor.updateMessageSearchIndex(id, id, text); - const result = queryExecutor.searchMessages('test', threadID, null, null); - expect(result.length).toBe(1); - expect(result[0].message).toStrictEqual(message); - }); - - it('should find all messages matching provided query', () => { - const text1 = 'test text'; - const id1 = '1'; - queryExecutor.replaceMessage( - { + testCases.forEach(({ threadID, description }) => { + it(`should find messages matching provided query ${description}`, () => { + const text = 'test text'; + const id = '1'; + const message: WebMessage = { + id, + localID, + thread: threadID, + user: userID, + type: messageTypes.TEXT, + futureType, + content: text, + time: BigInt(123), + }; + queryExecutor.replaceMessage( + message, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id, id, text); + const result = queryExecutor.searchMessages('test', threadID, null, null); + expect(result.length).toBe(1); + expect(result[0].message).toStrictEqual(message); + }); + + it(`should find all messages matching provided query ${description}`, () => { + const text1 = 'test text'; + const id1 = '1'; + queryExecutor.replaceMessage( + { + id: id1, + localID, + thread: threadID, + user: userID, + type: messageTypes.TEXT, + futureType, + content: text1, + time: BigInt(1), + }, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id1, id1, text1); + + const text2 = 'I am test'; + const id2 = '2'; + queryExecutor.replaceMessage( + { + id: id2, + localID, + thread: threadID, + user: userID, + type: messageTypes.TEXT, + futureType, + content: text2, + time: BigInt(1), + }, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id2, id2, text2); + const result = queryExecutor.searchMessages('test', threadID, null, null); + expect(result.length).toBe(2); + }); + + it(`should ignore messages not matching provided query ${description}`, () => { + const text1 = 'test text'; + const id1 = '1'; + const matchingMessage: WebMessage = { id: id1, localID, thread: threadID, @@ -66,281 +119,266 @@ type: messageTypes.TEXT, futureType, content: text1, - time: BigInt(1), - }, - false, - ); - queryExecutor.updateMessageSearchIndex(id1, id1, text1); - - const text2 = 'I am test'; - const id2 = '2'; - queryExecutor.replaceMessage( - { - id: id2, + time: BigInt(123), + }; + queryExecutor.replaceMessage( + matchingMessage, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id1, id1, text1); + + const text2 = 'I am text'; + const id2 = '2'; + queryExecutor.replaceMessage( + { + id: id2, + localID, + thread: threadID, + user: userID, + type: messageTypes.TEXT, + futureType, + content: text2, + time: BigInt(1), + }, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id2, id2, text2); + const result = queryExecutor.searchMessages('test', threadID, null, null); + expect(result.length).toBe(1); + expect(result[0].message).toStrictEqual(matchingMessage); + }); + + it(`should match message edits ${description}`, () => { + const text1 = 'I am text'; + const id1 = '1'; + const matchingMessage: WebMessage = { + id: id1, localID, thread: threadID, user: userID, type: messageTypes.TEXT, futureType, - content: text2, + content: text1, time: BigInt(1), - }, - false, - ); - queryExecutor.updateMessageSearchIndex(id2, id2, text2); - const result = queryExecutor.searchMessages('test', threadID, null, null); - expect(result.length).toBe(2); - }); - - it('should ignore messages not matching provided query', () => { - const text1 = 'test text'; - const id1 = '1'; - const matchingMessage: WebMessage = { - id: id1, - localID, - thread: threadID, - user: userID, - type: messageTypes.TEXT, - futureType, - content: text1, - time: BigInt(123), - }; - queryExecutor.replaceMessage(matchingMessage, false); - queryExecutor.updateMessageSearchIndex(id1, id1, text1); - - const text2 = 'I am text'; - const id2 = '2'; - queryExecutor.replaceMessage( - { - id: id2, + }; + queryExecutor.replaceMessage( + matchingMessage, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id1, id1, text1); + + const text2 = 'I am test'; + const id2 = '2'; + queryExecutor.replaceMessage( + { + id: id2, + localID, + thread: threadID, + user: userID, + type: messageTypes.EDIT_MESSAGE, + futureType, + content: JSON.stringify({ targetMessageID: id1 }), + time: BigInt(5), + }, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id1, id2, text2); + const result = queryExecutor.searchMessages('test', threadID, null, null); + + expect(result.length).toBe(2); + expect(result[0].message).toStrictEqual(matchingMessage); + }); + + it(`should return only messages with time equal or smaller than timestampCursor ${description}`, () => { + const timeOlderThanSearchedFor = BigInt(1); + const timeSearchedFor = '1000'; + const timeNewerThanSearchedFor = BigInt(2000); + + const text = 'test'; + + const id1 = '1'; + const matchingMessage: WebMessage = { + id: id1, localID, thread: threadID, user: userID, type: messageTypes.TEXT, futureType, - content: text2, - time: BigInt(1), - }, - false, - ); - queryExecutor.updateMessageSearchIndex(id2, id2, text2); - const result = queryExecutor.searchMessages('test', threadID, null, null); - expect(result.length).toBe(1); - expect(result[0].message).toStrictEqual(matchingMessage); - }); - - it('should match message edits', () => { - const text1 = 'I am text'; - const id1 = '1'; - const matchingMessage: WebMessage = { - id: id1, - localID, - thread: threadID, - user: userID, - type: messageTypes.TEXT, - futureType, - content: text1, - time: BigInt(1), - }; - queryExecutor.replaceMessage(matchingMessage, false); - queryExecutor.updateMessageSearchIndex(id1, id1, text1); - - const text2 = 'I am test'; - const id2 = '2'; - queryExecutor.replaceMessage( - { - id: id2, + content: text, + time: timeOlderThanSearchedFor, + }; + queryExecutor.replaceMessage( + matchingMessage, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id1, id1, text); + + const id2 = '2'; + queryExecutor.replaceMessage( + { + id: id2, + localID, + thread: threadID, + user: userID, + type: messageTypes.TEXT, + futureType, + content: text, + time: timeNewerThanSearchedFor, + }, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id2, id2, text); + const result = queryExecutor.searchMessages( + text, + threadID, + timeSearchedFor, + '0', + ); + expect(result.length).toBe(1); + expect(result[0].message).toStrictEqual(matchingMessage); + }); + + it(`should correctly return messages with regards to messageIDCursor ${description}`, () => { + const text = 'test'; + const time = BigInt(1); + + const id1 = '1'; + const matchingMessage: WebMessage = { + id: id1, localID, thread: threadID, user: userID, - type: messageTypes.EDIT_MESSAGE, + type: messageTypes.TEXT, futureType, - content: JSON.stringify({ targetMessageID: id1 }), - time: BigInt(5), - }, - false, - ); - queryExecutor.updateMessageSearchIndex(id1, id2, text2); - const result = queryExecutor.searchMessages('test', threadID, null, null); - - expect(result.length).toBe(2); - expect(result[0].message).toStrictEqual(matchingMessage); - }); - - it('should return only messages with time equal or smaller than timestampCursor', () => { - const timeOlderThanSearchedFor = BigInt(1); - const timeSearchedFor = '1000'; - const timeNewerThanSearchedFor = BigInt(2000); - - const text = 'test'; - - const id1 = '1'; - const matchingMessage: WebMessage = { - id: id1, - localID, - thread: threadID, - user: userID, - type: messageTypes.TEXT, - futureType, - content: text, - time: timeOlderThanSearchedFor, - }; - queryExecutor.replaceMessage(matchingMessage, false); - queryExecutor.updateMessageSearchIndex(id1, id1, text); - - const id2 = '2'; - queryExecutor.replaceMessage( - { - id: id2, + content: text, + time, + }; + queryExecutor.replaceMessage( + matchingMessage, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id1, id1, text); + + const id2 = '2'; + queryExecutor.replaceMessage( + { + id: id2, + localID, + thread: threadID, + user: userID, + type: messageTypes.TEXT, + futureType, + content: text, + time, + }, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id2, id2, text); + + const result = queryExecutor.searchMessages( + text, + threadID, + time.toString(), + id2, + ); + expect(result.length).toBe(1); + expect(result[0].message).toStrictEqual(matchingMessage); + }); + + it(`should prioritize timestampCursor over messageIDCursor ${description}`, () => { + const text = 'text'; + + const greaterID = '600'; + const smallerID = '2'; + const intBetweenIDs = '100'; + + const olderTimestamp = BigInt(1); + const youngerTimestamp = BigInt(1000); + const timeBetweenTimestamps = '500'; + + const matchingMessage: WebMessage = { + id: greaterID, localID, thread: threadID, user: userID, type: messageTypes.TEXT, futureType, content: text, - time: timeNewerThanSearchedFor, - }, - false, - ); - queryExecutor.updateMessageSearchIndex(id2, id2, text); - const result = queryExecutor.searchMessages( - text, - threadID, - timeSearchedFor, - '0', - ); - expect(result.length).toBe(1); - expect(result[0].message).toStrictEqual(matchingMessage); - }); - - it('should correctly return messages with regards to messageIDCursor', () => { - const text = 'test'; - const time = BigInt(1); - - const id1 = '1'; - const matchingMessage: WebMessage = { - id: id1, - localID, - thread: threadID, - user: userID, - type: messageTypes.TEXT, - futureType, - content: text, - time, - }; - queryExecutor.replaceMessage(matchingMessage, false); - queryExecutor.updateMessageSearchIndex(id1, id1, text); - - const id2 = '2'; - queryExecutor.replaceMessage( - { - id: id2, + time: olderTimestamp, + }; + queryExecutor.replaceMessage( + matchingMessage, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(greaterID, greaterID, text); + + queryExecutor.replaceMessage( + { + id: smallerID, + localID, + thread: threadID, + user: userID, + type: messageTypes.TEXT, + futureType, + content: text, + time: youngerTimestamp, + }, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(smallerID, smallerID, text); + const result = queryExecutor.searchMessages( + text, + threadID, + timeBetweenTimestamps, + intBetweenIDs, + ); + + expect(result.length).toBe(1); + expect(result[0].message.id).toBe(greaterID); + expect(result[0].message).toStrictEqual(matchingMessage); + }); + + it(`should return messages in correct order ${description}`, () => { + const text = 'test'; + + const id1 = '1'; + const secondMessage: WebMessage = { + id: id1, localID, thread: threadID, user: userID, type: messageTypes.TEXT, futureType, content: text, - time, - }, - false, - ); - queryExecutor.updateMessageSearchIndex(id2, id2, text); - - const result = queryExecutor.searchMessages( - text, - threadID, - time.toString(), - id2, - ); - expect(result.length).toBe(1); - expect(result[0].message).toStrictEqual(matchingMessage); - }); - - it('should prioritize timestampCursor over messageIDCursor', () => { - const text = 'text'; - - const greaterID = '600'; - const smallerID = '2'; - const intBetweenIDs = '100'; - - const olderTimestamp = BigInt(1); - const youngerTimestamp = BigInt(1000); - const timeBetweenTimestamps = '500'; - - const matchingMessage: WebMessage = { - id: greaterID, - localID, - thread: threadID, - user: userID, - type: messageTypes.TEXT, - futureType, - content: text, - time: olderTimestamp, - }; - queryExecutor.replaceMessage(matchingMessage, false); - queryExecutor.updateMessageSearchIndex(greaterID, greaterID, text); - - queryExecutor.replaceMessage( - { - id: smallerID, + time: BigInt(1), + }; + queryExecutor.replaceMessage( + secondMessage, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id1, id1, text); + + const id2 = '2'; + const firstMessage: WebMessage = { + id: id2, localID, thread: threadID, user: userID, type: messageTypes.TEXT, futureType, content: text, - time: youngerTimestamp, - }, - false, - ); - queryExecutor.updateMessageSearchIndex(smallerID, smallerID, text); - const result = queryExecutor.searchMessages( - text, - threadID, - timeBetweenTimestamps, - intBetweenIDs, - ); - - expect(result.length).toBe(1); - expect(result[0].message.id).toBe(greaterID); - expect(result[0].message).toStrictEqual(matchingMessage); - }); - - it('should return messages in correct order', () => { - const text = 'test'; - - const id1 = '1'; - const secondMessage: WebMessage = { - id: id1, - localID, - thread: threadID, - user: userID, - type: messageTypes.TEXT, - futureType, - content: text, - time: BigInt(1), - }; - queryExecutor.replaceMessage(secondMessage, false); - queryExecutor.updateMessageSearchIndex(id1, id1, text); - - const id2 = '2'; - const firstMessage: WebMessage = { - id: id2, - localID, - thread: threadID, - user: userID, - type: messageTypes.TEXT, - futureType, - content: text, - time: BigInt(2), - }; - queryExecutor.replaceMessage(firstMessage, false); - queryExecutor.updateMessageSearchIndex(id2, id2, text); - - const result = queryExecutor.searchMessages(text, threadID, null, null); - expect(result.length).toBe(2); - expect(result[0].message).toStrictEqual(firstMessage); - expect(result[1].message).toStrictEqual(secondMessage); + time: BigInt(2), + }; + queryExecutor.replaceMessage( + firstMessage, + !!getProtocolByThreadID(threadID)?.dataIsBackedUp, + ); + queryExecutor.updateMessageSearchIndex(id2, id2, text); + + const result = queryExecutor.searchMessages(text, threadID, null, null); + expect(result.length).toBe(2); + expect(result[0].message).toStrictEqual(firstMessage); + expect(result[1].message).toStrictEqual(secondMessage); + }); }); });