diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index a8a3af5252..2682d550b7 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -894,16 +894,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = in Just (fInv, fileDescrText) | otherwise = Nothing processContentItem :: GroupMember -> ChatItem 'CTGroup d -> MsgContent -> Maybe (FileInvitation, RcvFileDescrText) -> CM [ChatMsgEvent 'Json] - processContentItem sender ChatItem {meta, quotedItem} mc fInvDescr_ = + processContentItem sender ChatItem {formattedText, meta, quotedItem, mentions} mc fInvDescr_ = if isNothing fInvDescr_ && not (msgContentHasText mc) then pure [] else do let CIMeta {itemTs, itemSharedMsgId, itemTimed} = meta quotedItemId_ = quoteItemId =<< quotedItem fInv_ = fst <$> fInvDescr_ - -- TODO [mentions] history? - -- let (_t, ft_) = msgContentTexts mc - (chatMsgEvent, _) <- withStore $ \db -> prepareGroupMsg db user gInfo mc M.empty quotedItemId_ Nothing fInv_ itemTimed False + (mc', _, mentions') = updatedMentionNames mc formattedText mentions + mentions'' = M.map (\CIMention {memberId} -> MsgMention {memberId}) mentions' + (chatMsgEvent, _) <- withStore $ \db -> prepareGroupMsg db user gInfo mc' mentions'' quotedItemId_ Nothing fInv_ itemTimed False let senderVRange = memberChatVRange' sender xMsgNewChatMsg = ChatMessage {chatVRange = senderVRange, msgId = itemSharedMsgId, chatMsgEvent} fileDescrEvents <- case (snd <$> fInvDescr_, itemSharedMsgId) of diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index ea2f94ad99..5562f016c9 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -320,6 +320,7 @@ deriving instance Show AChat data ChatStats = ChatStats { unreadCount :: Int, -- returned both in /_get chat initial API and in /_get chats API + unreadMentions :: Int, -- returned both in /_get chat initial API and in /_get chats API reportsCount :: Int, -- returned both in /_get chat initial API and in /_get chats API minUnreadItemId :: ChatItemId, unreadChat :: Bool @@ -327,7 +328,7 @@ data ChatStats = ChatStats deriving (Show) emptyChatStats :: ChatStats -emptyChatStats = ChatStats 0 0 0 False +emptyChatStats = ChatStats 0 0 0 0 False data NavigationInfo = NavigationInfo { afterUnread :: Int, diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 339e50140c..b9a227b85c 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -570,7 +570,12 @@ data AChatPreviewData = forall c. ChatTypeI c => ACPD (SChatType c) (ChatPreview type ChatStatsRow = (Int, Int, ChatItemId, BoolInt) toChatStats :: ChatStatsRow -> ChatStats -toChatStats (unreadCount, reportsCount, minUnreadItemId, BI unreadChat) = ChatStats {unreadCount, reportsCount, minUnreadItemId, unreadChat} +toChatStats (unreadCount, reportsCount, minUnreadItemId, BI unreadChat) = ChatStats {unreadCount, unreadMentions = 0, reportsCount, minUnreadItemId, unreadChat} + +type GroupStatsRow = (Int, Int, Int, ChatItemId, BoolInt) + +toGroupStats :: GroupStatsRow -> ChatStats +toGroupStats (unreadCount, unreadMentions, reportsCount, minUnreadItemId, BI unreadChat) = ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat} findDirectChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData] findDirectChatPreviews_ db User {userId} pagination clq = @@ -674,9 +679,9 @@ findGroupChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQ findGroupChatPreviews_ db User {userId} pagination clq = map toPreview <$> getPreviews where - toPreview :: (GroupId, UTCTime, Maybe ChatItemId) :. ChatStatsRow -> AChatPreviewData + toPreview :: (GroupId, UTCTime, Maybe ChatItemId) :. GroupStatsRow -> AChatPreviewData toPreview ((groupId, ts, lastItemId_) :. statsRow) = - ACPD SCTGroup $ GroupChatPD ts groupId lastItemId_ (toChatStats statsRow) + ACPD SCTGroup $ GroupChatPD ts groupId lastItemId_ (toGroupStats statsRow) baseQuery = [sql| SELECT @@ -690,12 +695,13 @@ findGroupChatPreviews_ db User {userId} pagination clq = LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -761,7 +767,7 @@ findGroupChatPreviews_ db User {userId} pagination clq = |] p = baseParams :. (userId, search, search, search, search) queryWithPagination q p - queryWithPagination :: ToRow p => Query -> p -> IO [(GroupId, UTCTime, Maybe ChatItemId) :. ChatStatsRow] + queryWithPagination :: ToRow p => Query -> p -> IO [(GroupId, UTCTime, Maybe ChatItemId) :. GroupStatsRow] queryWithPagination query params = case pagination of PTLast count -> DB.query db (query <> " ORDER BY g.chat_ts DESC LIMIT ?") (params :. Only count) PTAfter ts count -> DB.query db (query <> " AND g.chat_ts > ? ORDER BY g.chat_ts ASC LIMIT ?") (params :. (ts, count)) @@ -1353,19 +1359,19 @@ getGroupChatInitial_ db user g contentFilter count = do stats <- liftIO $ getStats minUnreadItemId =<< getGroupUnreadCount_ db user g Nothing getGroupChatAround' db user g contentFilter minUnreadItemId count "" stats Nothing -> liftIO $ do - stats <- getStats 0 0 + stats <- getStats 0 (0, 0) (,Just $ NavigationInfo 0 0) <$> getGroupChatLast_ db user g contentFilter count "" stats where - getStats minUnreadItemId unreadCount = do + getStats minUnreadItemId (unreadCount, unreadMentions) = do reportsCount <- getGroupReportsCount_ db user g False - pure ChatStats {unreadCount, reportsCount, minUnreadItemId, unreadChat = False} + pure ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat = False} getGroupStats_ :: DB.Connection -> User -> GroupInfo -> IO ChatStats getGroupStats_ db user g = do minUnreadItemId <- fromMaybe 0 <$> getGroupMinUnreadId_ db user g Nothing - unreadCount <- getGroupUnreadCount_ db user g Nothing + (unreadCount, unreadMentions) <- getGroupUnreadCount_ db user g Nothing reportsCount <- getGroupReportsCount_ db user g False - pure ChatStats {unreadCount, reportsCount, minUnreadItemId, unreadChat = False} + pure ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat = False} getGroupMinUnreadId_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> IO (Maybe ChatItemId) getGroupMinUnreadId_ db user g contentFilter = @@ -1375,11 +1381,11 @@ getGroupMinUnreadId_ db user g contentFilter = baseQuery = "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? " orderLimit = " ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1" -getGroupUnreadCount_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> IO Int +getGroupUnreadCount_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> IO (Int, Int) getGroupUnreadCount_ db user g contentFilter = - fromOnly . head <$> queryUnreadGroupItems db user g contentFilter baseQuery "" + head <$> queryUnreadGroupItems db user g contentFilter baseQuery "" where - baseQuery = "SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? " + baseQuery = "SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? " getGroupReportsCount_ :: DB.Connection -> User -> GroupInfo -> Bool -> IO Int getGroupReportsCount_ db User {userId} GroupInfo {groupId} archived = @@ -3111,10 +3117,9 @@ getGroupSndStatusCounts db itemId = (Only itemId) getGroupHistoryItems :: DB.Connection -> User -> GroupInfo -> GroupMember -> Int -> IO [Either StoreError (CChatItem 'CTGroup)] -getGroupHistoryItems db user@User {userId} GroupInfo {groupId} m count = do +getGroupHistoryItems db user@User {userId} g@GroupInfo {groupId} m count = do ciIds <- getLastItemIds_ - -- use getGroupCIWithReactions to read reactions data - reverse <$> mapM (runExceptT . getGroupChatItem db user groupId) ciIds + reverse <$> mapM (runExceptT . getGroupCIWithReactions db user g) ciIds where getLastItemIds_ :: IO [ChatItemId] getLastItemIds_ = diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250126_mentions.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250126_mentions.hs index f5f5e24fe0..a920c17859 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20250126_mentions.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250126_mentions.hs @@ -22,11 +22,15 @@ CREATE INDEX idx_chat_item_mentions_group_id ON chat_item_mentions(group_id); CREATE INDEX idx_chat_item_mentions_chat_item_id ON chat_item_mentions(chat_item_id); CREATE UNIQUE INDEX idx_chat_item_mentions_display_name ON chat_item_mentions(chat_item_id, display_name); CREATE UNIQUE INDEX idx_chat_item_mentions_member_id ON chat_item_mentions(chat_item_id, member_id); + +CREATE INDEX idx_chat_items_groups_user_mention ON chat_items(user_id, group_id, item_status, user_mention); |] down_m20250126_mentions :: Query down_m20250126_mentions = [sql| +DROP INDEX idx_chat_items_groups_user_mention; + DROP INDEX idx_chat_item_mentions_group_id; DROP INDEX idx_chat_item_mentions_chat_item_id; DROP INDEX idx_chat_item_mentions_display_name; diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index f9c19d31b5..acec82aaf4 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -157,7 +157,7 @@ Query: WHERE i.user_id = ? AND i.item_status = ? AND (g.enable_ntfs = 1 OR g.enable_ntfs IS NULL) Plan: -SEARCH i USING COVERING INDEX idx_chat_items_groups (user_id=?) +SEARCH i USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=?) SEARCH g USING INTEGER PRIMARY KEY (rowid=?) Query: @@ -480,7 +480,7 @@ Query: LIMIT ? Plan: -SEARCH chat_items USING INDEX idx_chat_items_groups_history (user_id=?) +SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=?) USE TEMP B-TREE FOR ORDER BY Query: @@ -491,7 +491,7 @@ Query: LIMIT ? Plan: -SEARCH chat_items USING INDEX idx_chat_items_groups_history (user_id=?) +SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=?) USE TEMP B-TREE FOR ORDER BY Query: @@ -985,7 +985,7 @@ Query: LIMIT 1 Plan: -SEARCH chat_items USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?) +SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?) USE TEMP B-TREE FOR ORDER BY Query: @@ -1005,7 +1005,7 @@ Query: LIMIT ? Plan: -SEARCH chat_items USING INDEX idx_chat_items_groups_history (user_id=?) +SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=?) USE TEMP B-TREE FOR ORDER BY Query: @@ -1151,7 +1151,7 @@ Query: LIMIT 1 Plan: -SEARCH i USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?) +SEARCH i USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?) SEARCH m USING INTEGER PRIMARY KEY (rowid=?) SEARCH c USING INTEGER PRIMARY KEY (rowid=?) USE TEMP B-TREE FOR ORDER BY @@ -1920,12 +1920,13 @@ Query: LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -1949,7 +1950,7 @@ Query: ORDER BY g.chat_ts DESC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=?) @@ -1971,12 +1972,13 @@ Query: LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -1995,7 +1997,7 @@ Query: ORDER BY g.chat_ts DESC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=?) @@ -2016,12 +2018,13 @@ Query: LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -2039,7 +2042,7 @@ Query: AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts ? ORDER BY g.chat_ts ASC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?) @@ -2104,12 +2108,13 @@ Query: LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -2127,7 +2132,7 @@ Query: ORDER BY g.chat_ts DESC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=?) @@ -2148,12 +2153,13 @@ Query: LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -2171,7 +2177,7 @@ Query: AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts ? ORDER BY g.chat_ts ASC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?) @@ -2236,12 +2243,13 @@ Query: LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -2259,7 +2267,7 @@ Query: ORDER BY g.chat_ts DESC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=?) @@ -2280,12 +2288,13 @@ Query: LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -2300,7 +2309,7 @@ Query: WHERE g.user_id = ? AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts ? ORDER BY g.chat_ts ASC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?) @@ -2362,12 +2372,13 @@ Query: LIMIT 1 ) AS chat_item_id, COALESCE(ChatStats.UnreadCount, 0), + COALESCE(ChatStats.UnreadMentions, 0), COALESCE(ReportCount.Count, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( - SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread + SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ? GROUP BY group_id @@ -2382,7 +2393,7 @@ Query: WHERE g.user_id = ? ORDER BY g.chat_ts DESC LIMIT ? Plan: MATERIALIZE ChatStats -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id>?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?) MATERIALIZE ReportCount SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?) SEARCH g USING INDEX idx_groups_chat_ts (user_id=?) @@ -2930,7 +2941,7 @@ Query: LIMIT 1 Plan: -SEARCH chat_items USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?) +SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?) USE TEMP B-TREE FOR ORDER BY Query: @@ -4080,7 +4091,7 @@ Query: WHERE user_id = ? AND group_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL Plan: -SEARCH chat_items USING INDEX idx_chat_items_groups (user_id=? AND group_id=? AND item_status=?) +SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?) Query: SELECT group_snd_item_status, COUNT(1) @@ -4171,7 +4182,7 @@ Query: WHERE user_id = ? AND group_id = ? AND item_status = ? Plan: -SEARCH chat_items USING INDEX idx_chat_items_groups (user_id=? AND group_id=? AND item_status=?) +SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?) Query: UPDATE chat_items SET item_status = ?, updated_at = ? @@ -4666,7 +4677,7 @@ Query: JOIN files f ON f.chat_item_id = i.chat_item_id WHERE i.user_id = ? Plan: -SEARCH i USING COVERING INDEX idx_chat_items_groups_history (user_id=?) +SEARCH i USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=?) SEARCH f USING INDEX idx_files_chat_item_id (chat_item_id=?) Query: @@ -4693,7 +4704,7 @@ Query: JOIN files f ON f.chat_item_id = i.chat_item_id WHERE i.user_id = ? AND i.group_id = ? Plan: -SEARCH i USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?) +SEARCH i USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?) SEARCH f USING INDEX idx_files_chat_item_id (chat_item_id=?) Query: @@ -5013,7 +5024,7 @@ SEARCH groups USING COVERING INDEX idx_groups_chat_item_id (chat_item_id=?) Query: DELETE FROM chat_items WHERE user_id = ? AND group_id = ? Plan: -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?) SEARCH chat_item_mentions USING COVERING INDEX idx_chat_item_mentions_chat_item_id (chat_item_id=?) SEARCH group_snd_item_statuses USING COVERING INDEX idx_group_snd_item_statuses_chat_item_id (chat_item_id=?) SEARCH chat_item_versions USING COVERING INDEX idx_chat_item_versions_chat_item_id (chat_item_id=?) @@ -5198,7 +5209,7 @@ SEARCH chat_tags_chats USING COVERING INDEX idx_chat_tags_chats_chat_tag_id_grou SEARCH chat_item_moderations USING COVERING INDEX idx_chat_item_moderations_group_id (group_id=?) SEARCH chat_item_reactions USING COVERING INDEX idx_chat_item_reactions_group_id (group_id=?) SEARCH chat_items USING COVERING INDEX idx_chat_items_fwd_from_group_id (fwd_from_group_id=?) -SCAN chat_items USING COVERING INDEX idx_chat_items_groups_item_ts +SCAN chat_items USING COVERING INDEX idx_chat_items_groups_user_mention SEARCH messages USING COVERING INDEX idx_messages_group_id (group_id=?) SEARCH user_contact_links USING COVERING INDEX idx_user_contact_links_group_id (group_id=?) SEARCH files USING COVERING INDEX idx_files_group_id (group_id=?) @@ -5310,7 +5321,7 @@ SEARCH protocol_servers USING COVERING INDEX idx_smp_servers_user_id (user_id=?) SEARCH settings USING COVERING INDEX idx_settings_user_id (user_id=?) SEARCH commands USING COVERING INDEX idx_commands_user_id (user_id=?) SEARCH calls USING COVERING INDEX idx_calls_user_id (user_id=?) -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_history (user_id=?) +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=?) SEARCH contact_requests USING COVERING INDEX sqlite_autoindex_contact_requests_2 (user_id=?) SEARCH user_contact_links USING COVERING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?) SEARCH connections USING COVERING INDEX idx_connections_group_member (user_id=?) @@ -5449,10 +5460,6 @@ Query: SELECT COUNT(1) FROM chat_item_versions WHERE chat_item_id = ? Plan: SEARCH chat_item_versions USING COVERING INDEX idx_chat_item_versions_chat_item_id (chat_item_id=?) -Query: SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? AND item_status = ? -Plan: -SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id=? AND item_status=?) - Query: SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0 Plan: SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id=? AND msg_content_tag=? AND item_deleted=? AND item_sent=?) @@ -5465,6 +5472,10 @@ Query: SELECT COUNT(1) FROM groups WHERE user_id = ? AND chat_item_ttl > 0 Plan: SEARCH groups USING INDEX idx_groups_chat_ts (user_id=?) +Query: SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? AND item_status = ? +Plan: +SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?) + Query: SELECT accepted_at FROM operator_usage_conditions WHERE server_operator_id = ? AND conditions_commit = ? Plan: SEARCH operator_usage_conditions USING INDEX idx_operator_usage_conditions_conditions_commit (conditions_commit=? AND server_operator_id=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index b1bdecd1e4..58374fe51c 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -1011,3 +1011,9 @@ CREATE UNIQUE INDEX idx_chat_item_mentions_member_id ON chat_item_mentions( chat_item_id, member_id ); +CREATE INDEX idx_chat_items_groups_user_mention ON chat_items( + user_id, + group_id, + item_status, + user_mention +); diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index dccd311f59..ee957d3daa 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -191,8 +191,9 @@ chatGroupTests = do describe "group member reports" $ do it "should send report to group owner, admins and moderators, but not other users" testGroupMemberReports describe "group member mentions" $ do - it "should send messages with member mentions" testMemberMention + it "should send and edit messages with member mentions" testMemberMention it "should forward and quote message updating mentioned member name" testForwardQuoteMention + it "should send updated mentions in history" testGroupHistoryWithMentions describe "uniqueMsgMentions" testUniqueMsgMentions describe "updatedMentionNames" testUpdatedMentionNames where @@ -6686,6 +6687,12 @@ testMemberMention = [ alice <# "#team cath> hello @Alice", bob <# "#team cath> hello @Alice" ] + cath ##> "! #team hello @alice @bob" + cath <# "#team [edited] hello @alice @bob" + concurrentlyN_ + [ alice <# "#team cath> [edited] hello @alice @bob", + bob <# "#team cath> [edited] hello @alice @bob" + ] testForwardQuoteMention :: HasCallStack => TestParams -> IO () testForwardQuoteMention = @@ -6753,6 +6760,41 @@ testForwardQuoteMention = bob <# "alice> -> forwarded" bob <## " hello @alice @alice_1" +testGroupHistoryWithMentions :: HasCallStack => TestParams -> IO () +testGroupHistoryWithMentions = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup2 "team" alice bob + + threadDelay 1000000 + + alice #> "#team hello @bob" + bob <# "#team alice!> hello @bob" + + bob ##> "/p robert" + bob <## "user profile is changed to robert (your 1 contacts are notified)" + alice <## "contact bob changed to robert" + alice <## "use @robert to send messages" + + alice ##> "/create link #team" + gLink <- getGroupLink alice "team" GRMember True + + cath ##> ("/c " <> gLink) + cath <## "connection request sent!" + alice <## "cath (Catherine): accepting request to join group #team..." + concurrentlyN_ + [ alice <## "#team: cath joined the group", + cath + <### [ "#team: joining the group...", + "#team: you joined the group", + WithTime "#team alice> hello @robert [>>]", + "#team: member robert is connected" + ], + do + bob <## "#team: alice added cath (Catherine) to the group (connecting...)" + bob <## "#team: new member cath is connected" + ] + testUniqueMsgMentions :: SpecWith TestParams testUniqueMsgMentions = do it "1 correct mention" $ \_ ->