Skip to content

Commit

Permalink
fix: crash when self user is mentioned multiple times in last message…
Browse files Browse the repository at this point in the history
… [WPB-15157] 🍒 🍒 (#3234)

* Commit with unresolved merge conflicts

* conflicts resolved

* change order of sql migrations to match RC

* reorder migration

* reorder migration

* add migration to match RC

---------

Co-authored-by: Michał Saleniuk <[email protected]>
Co-authored-by: Michał Saleniuk <[email protected]>
  • Loading branch information
3 people authored Jan 22, 2025
1 parent 01522be commit d60b3da
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ SELECT
MemberChangeContent.member_change_list AS lastMessageMemberChangeList,
MemberChangeContent.member_change_type AS lastMessageMemberChangeType,
ConversationNameChangedContent.conversation_name AS lastMessageUpdateConversationName,
(Mention.user_id IS NOT NULL) AS lastMessageIsMentioningSelfUser,
COUNT(Mention.user_id) > 0 AS lastMessageIsMentioningSelfUser,
TextContent.is_quoting_self AS lastMessageIsQuotingSelfUser,
TextContent.text_body AS lastMessageText,
AssetContent.asset_mime_type AS lastMessageAssetMimeType
Expand Down Expand Up @@ -76,7 +76,8 @@ WHERE
OR (ConversationDetails.type IS 'CONNECTION_PENDING' AND ConversationDetails.otherUserId IS NOT NULL) -- show connection requests even without metadata
)
AND (ConversationDetails.protocol IS 'PROTEUS' OR ConversationDetails.protocol IS 'MIXED' OR (ConversationDetails.protocol IS 'MLS' AND ConversationDetails.mls_group_state IS 'ESTABLISHED'))
AND ConversationDetails.isActive;
AND ConversationDetails.isActive
GROUP BY ConversationDetails.qualifiedId;

selectAllConversationDetailsWithEvents:
SELECT * FROM ConversationDetailsWithEvents
Expand Down
209 changes: 80 additions & 129 deletions persistence/src/commonMain/db_user/migrations/95.sqm
Original file line number Diff line number Diff line change
@@ -1,131 +1,82 @@
DROP VIEW IF EXISTS ConversationDetails;
DROP VIEW IF EXISTS ConversationDetailsWithEvents;

CREATE VIEW IF NOT EXISTS ConversationDetails AS
CREATE VIEW IF NOT EXISTS ConversationDetailsWithEvents AS
SELECT
Conversation.qualified_id AS qualifiedId,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.name
WHEN 'CONNECTION_PENDING' THEN connection_user.name
ELSE Conversation.name
END AS name,
Conversation.type,
Call.status AS callStatus,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.preview_asset_id
WHEN 'CONNECTION_PENDING' THEN connection_user.preview_asset_id
END AS previewAssetId,
Conversation.muted_status AS mutedStatus,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.team
ELSE Conversation.team_id
END AS teamId,
CASE (Conversation.type)
WHEN 'CONNECTION_PENDING' THEN Connection.last_update_date
ELSE Conversation.last_modified_date
END AS lastModifiedDate,
Conversation.last_read_date AS lastReadDate,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.user_availability_status
WHEN 'CONNECTION_PENDING' THEN connection_user.user_availability_status
END AS userAvailabilityStatus,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.user_type
WHEN 'CONNECTION_PENDING' THEN connection_user.user_type
END AS userType,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.bot_service
WHEN 'CONNECTION_PENDING' THEN connection_user.bot_service
END AS botService,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.deleted
WHEN 'CONNECTION_PENDING' THEN connection_user.deleted
END AS userDeleted,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.defederated
WHEN 'CONNECTION_PENDING' THEN connection_user.defederated
END AS userDefederated,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.supported_protocols
WHEN 'CONNECTION_PENDING' THEN connection_user.supported_protocols
END AS userSupportedProtocols,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.connection_status
WHEN 'CONNECTION_PENDING' THEN connection_user.connection_status
END AS connectionStatus,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.qualified_id
WHEN 'CONNECTION_PENDING' THEN connection_user.qualified_id
END AS otherUserId,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.active_one_on_one_conversation_id
WHEN 'CONNECTION_PENDING' THEN connection_user.active_one_on_one_conversation_id
END AS otherUserActiveConversationId,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN coalesce(User.active_one_on_one_conversation_id = Conversation.qualified_id, 0)
ELSE 1
END AS isActive,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.accent_id
ELSE 0
END AS accentId,
Conversation.last_notified_date AS lastNotifiedMessageDate,
memberRole. role AS selfRole,
Conversation.protocol,
Conversation.mls_cipher_suite,
Conversation.mls_epoch,
Conversation.mls_group_id,
Conversation.mls_last_keying_material_update_date,
Conversation.mls_group_state,
Conversation.access_list,
Conversation.access_role_list,
Conversation.mls_proposal_timer,
Conversation.muted_time,
Conversation.creator_id,
Conversation.receipt_mode,
Conversation.message_timer,
Conversation.user_message_timer,
Conversation.incomplete_metadata,
Conversation.archived,
Conversation.archived_date_time,
Conversation.verification_status AS mls_verification_status,
Conversation.proteus_verification_status,
Conversation.legal_hold_status,
SelfUser.id AS selfUserId,
CASE
WHEN Conversation.type = 'GROUP' THEN
CASE
WHEN memberRole.role IS NOT NULL THEN 1
ELSE 0
END
WHEN Conversation.type = 'ONE_ON_ONE' THEN
CASE
WHEN User.defederated = 1 THEN 0
WHEN User.deleted = 1 THEN 0
WHEN User.connection_status = 'BLOCKED' THEN 0
WHEN Conversation.legal_hold_status = 'DEGRADED' THEN 0
ELSE 1
END
ELSE 0
END AS interactionEnabled,
LabeledConversation.folder_id IS NOT NULL AS isFavorite,
CurrentFolder.id AS folderId,
CurrentFolder.name AS folderName
FROM Conversation
LEFT JOIN SelfUser
LEFT JOIN Member ON Conversation.qualified_id = Member.conversation
AND Conversation.type IS 'ONE_ON_ONE'
AND Member.user IS NOT SelfUser.id
LEFT JOIN Member AS memberRole ON Conversation.qualified_id = memberRole.conversation
AND memberRole.user IS SelfUser.id
LEFT JOIN User ON User.qualified_id = Member.user
LEFT JOIN Connection ON Connection.qualified_conversation = Conversation.qualified_id
AND (Connection.status = 'SENT'
OR Connection.status = 'PENDING'
OR Connection.status = 'NOT_CONNECTED'
AND Conversation.type IS 'CONNECTION_PENDING')
LEFT JOIN User AS connection_user ON Connection.qualified_to = connection_user.qualified_id
LEFT JOIN Call ON Call.id IS (SELECT id FROM Call WHERE Call.conversation_id = Conversation.qualified_id AND Call.status IS 'STILL_ONGOING' ORDER BY created_at DESC LIMIT 1)
LEFT JOIN ConversationFolder AS FavoriteFolder ON FavoriteFolder.folder_type IS 'FAVORITE'
LEFT JOIN LabeledConversation ON LabeledConversation.conversation_id = Conversation.qualified_id AND LabeledConversation.folder_id = FavoriteFolder.id
LEFT JOIN LabeledConversation AS ConversationLabel ON ConversationLabel.conversation_id = Conversation.qualified_id AND ConversationLabel.folder_id IS NOT FavoriteFolder.id
LEFT JOIN ConversationFolder AS CurrentFolder ON CurrentFolder.id = ConversationLabel.folder_id AND CurrentFolder.folder_type IS NOT 'FAVORITE';
ConversationDetails.*,
-- unread events
UnreadEventCountsGrouped.knocksCount AS unreadKnocksCount,
UnreadEventCountsGrouped.missedCallsCount AS unreadMissedCallsCount,
UnreadEventCountsGrouped.mentionsCount AS unreadMentionsCount,
UnreadEventCountsGrouped.repliesCount AS unreadRepliesCount,
UnreadEventCountsGrouped.messagesCount AS unreadMessagesCount,
CASE
WHEN ConversationDetails.callStatus = 'STILL_ONGOING' AND ConversationDetails.type = 'GROUP' THEN 1 -- if ongoing call in a group, move it to the top
WHEN ConversationDetails.mutedStatus = 'ALL_ALLOWED' THEN
CASE
WHEN (UnreadEventCountsGrouped.knocksCount + UnreadEventCountsGrouped.missedCallsCount + UnreadEventCountsGrouped.mentionsCount + UnreadEventCountsGrouped.repliesCount + UnreadEventCountsGrouped.messagesCount) > 0 THEN 1 -- if any unread events, move it to the top
WHEN ConversationDetails.type = 'CONNECTION_PENDING' AND ConversationDetails.connectionStatus = 'PENDING' THEN 1 -- if received connection request, move it to the top
ELSE 0
END
WHEN ConversationDetails.mutedStatus = 'ONLY_MENTIONS_AND_REPLIES_ALLOWED' THEN
CASE
WHEN (UnreadEventCountsGrouped.mentionsCount + UnreadEventCountsGrouped.repliesCount) > 0 THEN 1 -- only if unread mentions or replies, move it to the top
WHEN ConversationDetails.type = 'CONNECTION_PENDING' AND ConversationDetails.connectionStatus = 'PENDING' THEN 1 -- if received connection request, move it to the top
ELSE 0
END
ELSE 0
END AS hasNewActivitiesToShow,
-- draft message
MessageDraft.text AS messageDraftText,
MessageDraft.edit_message_id AS messageDraftEditMessageId,
MessageDraft.quoted_message_id AS messageDraftQuotedMessageId,
MessageDraft.mention_list AS messageDraftMentionList,
-- last message
Message.id AS lastMessageId,
Message.content_type AS lastMessageContentType,
Message.creation_date AS lastMessageDate,
Message.visibility AS lastMessageVisibility,
Message.sender_user_id AS lastMessageSenderUserId,
(Message.expire_after_millis IS NOT NULL) AS lastMessageIsEphemeral,
User.name AS lastMessageSenderName,
User.connection_status AS lastMessageSenderConnectionStatus,
User.deleted AS lastMessageSenderIsDeleted,
(Message.sender_user_id IS NOT NULL AND Message.sender_user_id == ConversationDetails.selfUserId) AS lastMessageIsSelfMessage,
MemberChangeContent.member_change_list AS lastMessageMemberChangeList,
MemberChangeContent.member_change_type AS lastMessageMemberChangeType,
ConversationNameChangedContent.conversation_name AS lastMessageUpdateConversationName,
COUNT(Mention.user_id) > 0 AS lastMessageIsMentioningSelfUser,
TextContent.is_quoting_self AS lastMessageIsQuotingSelfUser,
TextContent.text_body AS lastMessageText,
AssetContent.asset_mime_type AS lastMessageAssetMimeType
FROM ConversationDetails
LEFT JOIN UnreadEventCountsGrouped
ON UnreadEventCountsGrouped.conversationId = ConversationDetails.qualifiedId
LEFT JOIN MessageDraft
ON ConversationDetails.qualifiedId = MessageDraft.conversation_id AND ConversationDetails.archived = 0 -- only return message draft for non-archived conversations
LEFT JOIN LastMessage
ON LastMessage.conversation_id = ConversationDetails.qualifiedId AND ConversationDetails.archived = 0 -- only return last message for non-archived conversations
LEFT JOIN Message
ON LastMessage.message_id = Message.id AND LastMessage.conversation_id = Message.conversation_id
LEFT JOIN User
ON Message.sender_user_id = User.qualified_id
LEFT JOIN MessageMemberChangeContent AS MemberChangeContent
ON LastMessage.message_id = MemberChangeContent.message_id AND LastMessage.conversation_id = MemberChangeContent.conversation_id
LEFT JOIN MessageMention AS Mention
ON LastMessage.message_id == Mention.message_id AND ConversationDetails.selfUserId == Mention.user_id
LEFT JOIN MessageConversationChangedContent AS ConversationNameChangedContent
ON LastMessage.message_id = ConversationNameChangedContent.message_id AND LastMessage.conversation_id = ConversationNameChangedContent.conversation_id
LEFT JOIN MessageAssetContent AS AssetContent
ON LastMessage.message_id = AssetContent.message_id AND LastMessage.conversation_id = AssetContent.conversation_id
LEFT JOIN MessageTextContent AS TextContent
ON LastMessage.message_id = TextContent.message_id AND LastMessage.conversation_id = TextContent.conversation_id
WHERE
ConversationDetails.type IS NOT 'SELF'
AND (
ConversationDetails.type IS 'GROUP'
OR (ConversationDetails.type IS 'ONE_ON_ONE' AND (ConversationDetails.name IS NOT NULL AND ConversationDetails.otherUserId IS NOT NULL)) -- show 1:1 convos if they have user metadata
OR (ConversationDetails.type IS 'ONE_ON_ONE' AND ConversationDetails.userDeleted = 1) -- show deleted 1:1 convos to maintain prev, logic
OR (ConversationDetails.type IS 'CONNECTION_PENDING' AND ConversationDetails.otherUserId IS NOT NULL) -- show connection requests even without metadata
)
AND (ConversationDetails.protocol IS 'PROTEUS' OR ConversationDetails.protocol IS 'MIXED' OR (ConversationDetails.protocol IS 'MLS' AND ConversationDetails.mls_group_state IS 'ESTABLISHED'))
AND ConversationDetails.isActive
GROUP BY ConversationDetails.qualifiedId;
13 changes: 11 additions & 2 deletions persistence/src/commonMain/db_user/migrations/96.sqm
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
DROP TABLE LabeledConversation;
import com.wire.kalium.persistence.dao.QualifiedIDEntity;
import com.wire.kalium.persistence.dao.conversation.folder.ConversationFolderTypeEntity;

CREATE TABLE LabeledConversation (
CREATE TABLE IF NOT EXISTS ConversationFolder (
id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
folder_type TEXT AS ConversationFolderTypeEntity NOT NULL
);


CREATE TABLE IF NOT EXISTS LabeledConversation (
conversation_id TEXT AS QualifiedIDEntity NOT NULL,
folder_id TEXT NOT NULL,

FOREIGN KEY (conversation_id) REFERENCES Conversation(qualified_id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (folder_id) REFERENCES ConversationFolder(id) ON DELETE CASCADE ON UPDATE CASCADE,

PRIMARY KEY (folder_id, conversation_id)
Expand Down
131 changes: 131 additions & 0 deletions persistence/src/commonMain/db_user/migrations/97.sqm
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
DROP VIEW IF EXISTS ConversationDetails;

CREATE VIEW IF NOT EXISTS ConversationDetails AS
SELECT
Conversation.qualified_id AS qualifiedId,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.name
WHEN 'CONNECTION_PENDING' THEN connection_user.name
ELSE Conversation.name
END AS name,
Conversation.type,
Call.status AS callStatus,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.preview_asset_id
WHEN 'CONNECTION_PENDING' THEN connection_user.preview_asset_id
END AS previewAssetId,
Conversation.muted_status AS mutedStatus,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.team
ELSE Conversation.team_id
END AS teamId,
CASE (Conversation.type)
WHEN 'CONNECTION_PENDING' THEN Connection.last_update_date
ELSE Conversation.last_modified_date
END AS lastModifiedDate,
Conversation.last_read_date AS lastReadDate,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.user_availability_status
WHEN 'CONNECTION_PENDING' THEN connection_user.user_availability_status
END AS userAvailabilityStatus,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.user_type
WHEN 'CONNECTION_PENDING' THEN connection_user.user_type
END AS userType,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.bot_service
WHEN 'CONNECTION_PENDING' THEN connection_user.bot_service
END AS botService,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.deleted
WHEN 'CONNECTION_PENDING' THEN connection_user.deleted
END AS userDeleted,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.defederated
WHEN 'CONNECTION_PENDING' THEN connection_user.defederated
END AS userDefederated,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.supported_protocols
WHEN 'CONNECTION_PENDING' THEN connection_user.supported_protocols
END AS userSupportedProtocols,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.connection_status
WHEN 'CONNECTION_PENDING' THEN connection_user.connection_status
END AS connectionStatus,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.qualified_id
WHEN 'CONNECTION_PENDING' THEN connection_user.qualified_id
END AS otherUserId,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.active_one_on_one_conversation_id
WHEN 'CONNECTION_PENDING' THEN connection_user.active_one_on_one_conversation_id
END AS otherUserActiveConversationId,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN coalesce(User.active_one_on_one_conversation_id = Conversation.qualified_id, 0)
ELSE 1
END AS isActive,
CASE (Conversation.type)
WHEN 'ONE_ON_ONE' THEN User.accent_id
ELSE 0
END AS accentId,
Conversation.last_notified_date AS lastNotifiedMessageDate,
memberRole. role AS selfRole,
Conversation.protocol,
Conversation.mls_cipher_suite,
Conversation.mls_epoch,
Conversation.mls_group_id,
Conversation.mls_last_keying_material_update_date,
Conversation.mls_group_state,
Conversation.access_list,
Conversation.access_role_list,
Conversation.mls_proposal_timer,
Conversation.muted_time,
Conversation.creator_id,
Conversation.receipt_mode,
Conversation.message_timer,
Conversation.user_message_timer,
Conversation.incomplete_metadata,
Conversation.archived,
Conversation.archived_date_time,
Conversation.verification_status AS mls_verification_status,
Conversation.proteus_verification_status,
Conversation.legal_hold_status,
SelfUser.id AS selfUserId,
CASE
WHEN Conversation.type = 'GROUP' THEN
CASE
WHEN memberRole.role IS NOT NULL THEN 1
ELSE 0
END
WHEN Conversation.type = 'ONE_ON_ONE' THEN
CASE
WHEN User.defederated = 1 THEN 0
WHEN User.deleted = 1 THEN 0
WHEN User.connection_status = 'BLOCKED' THEN 0
WHEN Conversation.legal_hold_status = 'DEGRADED' THEN 0
ELSE 1
END
ELSE 0
END AS interactionEnabled,
LabeledConversation.folder_id IS NOT NULL AS isFavorite,
CurrentFolder.id AS folderId,
CurrentFolder.name AS folderName
FROM Conversation
LEFT JOIN SelfUser
LEFT JOIN Member ON Conversation.qualified_id = Member.conversation
AND Conversation.type IS 'ONE_ON_ONE'
AND Member.user IS NOT SelfUser.id
LEFT JOIN Member AS memberRole ON Conversation.qualified_id = memberRole.conversation
AND memberRole.user IS SelfUser.id
LEFT JOIN User ON User.qualified_id = Member.user
LEFT JOIN Connection ON Connection.qualified_conversation = Conversation.qualified_id
AND (Connection.status = 'SENT'
OR Connection.status = 'PENDING'
OR Connection.status = 'NOT_CONNECTED'
AND Conversation.type IS 'CONNECTION_PENDING')
LEFT JOIN User AS connection_user ON Connection.qualified_to = connection_user.qualified_id
LEFT JOIN Call ON Call.id IS (SELECT id FROM Call WHERE Call.conversation_id = Conversation.qualified_id AND Call.status IS 'STILL_ONGOING' ORDER BY created_at DESC LIMIT 1)
LEFT JOIN ConversationFolder AS FavoriteFolder ON FavoriteFolder.folder_type IS 'FAVORITE'
LEFT JOIN LabeledConversation ON LabeledConversation.conversation_id = Conversation.qualified_id AND LabeledConversation.folder_id = FavoriteFolder.id
LEFT JOIN LabeledConversation AS ConversationLabel ON ConversationLabel.conversation_id = Conversation.qualified_id AND ConversationLabel.folder_id IS NOT FavoriteFolder.id
LEFT JOIN ConversationFolder AS CurrentFolder ON CurrentFolder.id = ConversationLabel.folder_id AND CurrentFolder.folder_type IS NOT 'FAVORITE';
10 changes: 10 additions & 0 deletions persistence/src/commonMain/db_user/migrations/98.sqm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
DROP TABLE LabeledConversation;

CREATE TABLE LabeledConversation (
conversation_id TEXT AS QualifiedIDEntity NOT NULL,
folder_id TEXT NOT NULL,

FOREIGN KEY (folder_id) REFERENCES ConversationFolder(id) ON DELETE CASCADE ON UPDATE CASCADE,

PRIMARY KEY (folder_id, conversation_id)
);

0 comments on commit d60b3da

Please sign in to comment.