core: optimize group deletion (#5565)

* core: optimize group deletion

* withFastStore

* fix indexes

* updated plans

* remove prints

* remove print

* undo diff

* core: optimize group delete - delayed group cleanup, delete unused contacts before deleting group (#5579)

* core: delete unused group contacts, don't create new ones

* remove from exceptions

* plans

* fix tests

* remove fixtures

* update plans

* update plans

* fix test

* remove unused functino

* update plans

* remove withFastStore

* core: time group deletion (#5596)

* core: time group deletion

* queries

* works, test fails

* fix

* update plans

* update migration, queries

* not null

* remove deleted

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* cleanup

* remove unused field

* fix

* fix

* plans

* fix plan save

* plans

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
spaced4ndy 2025-01-31 18:47:59 +04:00 committed by GitHub
parent 1332480170
commit 9e000d6bce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 298 additions and 186 deletions

View file

@ -1938,7 +1938,6 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
public var businessChat: BusinessChatInfo?
public var fullGroupPreferences: FullGroupPreferences
public var membership: GroupMember
public var hostConnCustomUserProfileId: Int64?
public var chatSettings: ChatSettings
var createdAt: Date
var updatedAt: Date
@ -1974,7 +1973,6 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
groupProfile: GroupProfile.sampleData,
fullGroupPreferences: FullGroupPreferences.sampleData,
membership: GroupMember.sampleData,
hostConnCustomUserProfileId: nil,
chatSettings: ChatSettings.defaults,
createdAt: .now,
updatedAt: .now,

View file

@ -1721,7 +1721,6 @@ data class GroupInfo (
val businessChat: BusinessChatInfo? = null,
val fullGroupPreferences: FullGroupPreferences,
val membership: GroupMember,
val hostConnCustomUserProfileId: Long? = null,
val chatSettings: ChatSettings,
override val createdAt: Instant,
override val updatedAt: Instant,
@ -1770,7 +1769,6 @@ data class GroupInfo (
groupProfile = GroupProfile.sampleData,
fullGroupPreferences = FullGroupPreferences.sampleData,
membership = GroupMember.sampleData,
hostConnCustomUserProfileId = null,
chatSettings = ChatSettings(enableNtfs = MsgFilter.All, sendRcpts = null, favorite = false),
createdAt = Clock.System.now(),
updatedAt = Clock.System.now(),

View file

@ -223,6 +223,7 @@ library
Simplex.Chat.Store.SQLite.Migrations.M20250122_chat_items_include_in_history
Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions
Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts
Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes
other-modules:
Paths_simplex_chat
hs-source-dirs:

View file

@ -1076,12 +1076,12 @@ processChatCommand' vr = \case
withFastStore' $ \db -> deletePendingContactConnection db userId chatId
pure $ CRContactConnectionDeleted user conn
CTGroup -> do
Group gInfo@GroupInfo {membership} members <- withStore $ \db -> getGroup db vr user chatId
Group gInfo@GroupInfo {membership} members <- withFastStore $ \db -> getGroup db vr user chatId
let GroupMember {memberRole = membershipMemRole} = membership
let isOwner = membershipMemRole == GROwner
canDelete = isOwner || not (memberCurrent membership)
unless canDelete $ throwChatError $ CEGroupUserRole gInfo GROwner
filesInfo <- withStore' $ \db -> getGroupFileInfo db user gInfo
filesInfo <- withFastStore' $ \db -> getGroupFileInfo db user gInfo
withGroupLock "deleteChat group" chatId . procCmd $ do
cancelFilesInProgress user filesInfo
deleteFilesLocally filesInfo
@ -1090,11 +1090,10 @@ processChatCommand' vr = \case
deleteGroupLinkIfExists user gInfo
deleteMembersConnections' user members doSendDel
updateCIGroupInvitationStatus user gInfo CIGISRejected `catchChatError` \_ -> pure ()
-- functions below are called in separate transactions to prevent crashes on android
-- (possibly, race condition on integrity check?)
withStore' $ \db -> deleteGroupConnectionsAndFiles db user gInfo members
withStore' $ \db -> deleteGroupItemsAndMembers db user gInfo members
withStore' $ \db -> deleteGroup db user gInfo
withFastStore' $ \db -> deleteGroupChatItems db user gInfo
withFastStore' $ \db -> cleanupHostGroupLinkConn db user gInfo
withFastStore' $ \db -> deleteGroupMembers db user gInfo
withFastStore' $ \db -> deleteGroup db user gInfo
pure $ CRGroupDeletedUser user gInfo
CTLocal -> pure $ chatCmdError (Just user) "not supported"
CTContactRequest -> pure $ chatCmdError (Just user) "not supported"
@ -3541,6 +3540,7 @@ cleanupManager = do
cleanupUser cleanupInterval stepDelay user = do
cleanupTimedItems cleanupInterval user `catchChatError` (toView . CRChatError (Just user))
liftIO $ threadDelay' stepDelay
-- TODO remove in future versions: legacy step - contacts are no longer marked as deleted
cleanupDeletedContacts user `catchChatError` (toView . CRChatError (Just user))
liftIO $ threadDelay' stepDelay
cleanupTimedItems cleanupInterval user = do

View file

@ -61,6 +61,7 @@ import Simplex.Chat.Operators
import Simplex.Chat.ProfileGenerator (generateRandomProfile)
import Simplex.Chat.Protocol
import Simplex.Chat.Store
import Simplex.Chat.Store.Connections
import Simplex.Chat.Store.Direct
import Simplex.Chat.Store.Files
import Simplex.Chat.Store.Groups
@ -1200,11 +1201,9 @@ deleteMembersConnections user members = deleteMembersConnections' user members F
deleteMembersConnections' :: User -> [GroupMember] -> Bool -> CM ()
deleteMembersConnections' user members waitDelivery = do
let memberConns =
filter (\Connection {connStatus} -> connStatus /= ConnDeleted) $
mapMaybe (\GroupMember {activeConn} -> activeConn) members
let memberConns = mapMaybe (\GroupMember {activeConn} -> activeConn) members
deleteAgentConnectionsAsync' user (map aConnId memberConns) waitDelivery
lift . void . withStoreBatch' $ \db -> map (\conn -> updateConnectionStatus db conn ConnDeleted) memberConns
lift . void . withStoreBatch' $ \db -> map (\Connection {connId} -> deleteConnectionRecord db user connId) memberConns
deleteMemberConnection :: User -> GroupMember -> CM ()
deleteMemberConnection user mem = deleteMemberConnection' user mem False

View file

@ -134,7 +134,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,

View file

@ -35,7 +35,6 @@ module Simplex.Chat.Store.Direct
deleteContactFiles,
deleteContact,
deleteContactWithoutGroups,
setContactDeleted,
getDeletedContacts,
getContactByName,
getContact,
@ -294,7 +293,7 @@ deleteContact db user@User {userId} ct@Contact {contactId, localDisplayName, act
assertNotUser db user ct
liftIO $ do
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND contact_id = ?" (userId, contactId)
ctMember :: (Maybe ContactId) <- maybeFirstRow fromOnly $ DB.query db "SELECT contact_id FROM group_members WHERE user_id = ? AND contact_id = ? LIMIT 1" (userId, contactId)
ctMember :: (Maybe ContactId) <- maybeFirstRow fromOnly $ DB.query db "SELECT contact_id FROM group_members WHERE contact_id = ? LIMIT 1" (Only contactId)
if isNothing ctMember
then do
deleteContactProfile_ db userId contactId
@ -322,13 +321,7 @@ deleteContactWithoutGroups db user@User {userId} ct@Contact {contactId, localDis
forM_ customUserProfileId $ \profileId ->
deleteUnusedIncognitoProfileById_ db user profileId
setContactDeleted :: DB.Connection -> User -> Contact -> ExceptT StoreError IO ()
setContactDeleted db user@User {userId} ct@Contact {contactId} = do
assertNotUser db user ct
liftIO $ do
currentTs <- getCurrentTime
DB.execute db "UPDATE contacts SET deleted = 1, updated_at = ? WHERE user_id = ? AND contact_id = ?" (currentTs, userId, contactId)
-- TODO remove in future versions: only used for legacy contact cleanup
getDeletedContacts :: DB.Connection -> VersionRangeChat -> User -> IO [Contact]
getDeletedContacts db vr user@User {userId} = do
contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 1" (Only userId)

View file

@ -55,16 +55,15 @@ module Simplex.Chat.Store.Groups
getGroupModerators,
getGroupMembersForExpiration,
getGroupCurrentMembersCount,
deleteGroupConnectionsAndFiles,
deleteGroupItemsAndMembers,
deleteGroupChatItems,
deleteGroupMembers,
cleanupHostGroupLinkConn,
deleteGroup,
getUserGroups,
getUserGroupsToSubscribe,
getUserGroupDetails,
getUserGroupsWithSummary,
getGroupSummary,
getContactGroupPreferences,
checkContactHasGroups,
getGroupInvitation,
createNewContactMember,
createNewContactMemberAsync,
@ -275,7 +274,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = do
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
@ -347,7 +346,6 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
businessChat = Nothing,
fullGroupPreferences,
membership,
hostConnCustomUserProfileId = Nothing,
chatSettings,
createdAt = currentTs,
updatedAt = currentTs,
@ -362,7 +360,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
-- | creates a new group record for the group the current user was invited to, or returns an existing one
createGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName
createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, business} incognitoProfileId = do
createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, business} incognitoProfileId = do
liftIO getInvitationGroupId_ >>= \case
Nothing -> createGroupInvitation_
Just gId -> do
@ -399,11 +397,11 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
db
[sql|
INSERT INTO groups
(group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs,
(group_profile_id, local_display_name, inv_queue_info, user_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|]
((profileId, localDisplayName, connRequest, customUserProfileId, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
((profileId, localDisplayName, connRequest, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
insertedRowId db
let hostVRange = adjustedMemberVRange vr peerChatVRange
GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange
@ -418,7 +416,6 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
businessChat = Nothing,
fullGroupPreferences,
membership,
hostConnCustomUserProfileId = customUserProfileId,
chatSettings,
createdAt = currentTs,
updatedAt = currentTs,
@ -546,11 +543,11 @@ createGroupInvitedViaLink
db
[sql|
INSERT INTO groups
(group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs,
(group_profile_id, local_display_name, user_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|]
((profileId, localDisplayName, customUserProfileId, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
((profileId, localDisplayName, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
insertedRowId db
insertHost_ currentTs groupId = do
let fromMemberProfile = profileFromName fromMemberName
@ -639,33 +636,75 @@ getGroupToSubscribe db User {userId, userContactId} groupId = do
toShortMember (groupMemberId, localDisplayName, agentConnId) =
ShortGroupMember groupMemberId groupId localDisplayName agentConnId
deleteGroupConnectionsAndFiles :: DB.Connection -> User -> GroupInfo -> [GroupMember] -> IO ()
deleteGroupConnectionsAndFiles db User {userId} GroupInfo {groupId} members = do
forM_ members $ \m -> DB.execute db "DELETE FROM connections WHERE user_id = ? AND group_member_id = ?" (userId, groupMemberId' m)
DB.execute db "DELETE FROM files WHERE user_id = ? AND group_id = ?" (userId, groupId)
deleteGroupItemsAndMembers :: DB.Connection -> User -> GroupInfo -> [GroupMember] -> IO ()
deleteGroupItemsAndMembers db user@User {userId} g@GroupInfo {groupId} members = do
deleteGroupChatItems :: DB.Connection -> User -> GroupInfo -> IO ()
deleteGroupChatItems db User {userId} GroupInfo {groupId} =
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND group_id = ?" (userId, groupId)
void $ runExceptT cleanupHostGroupLinkConn_ -- to allow repeat connection via the same group link if one was used
deleteGroupMembers :: DB.Connection -> User -> GroupInfo -> IO ()
deleteGroupMembers db User {userId} GroupInfo {groupId} = do
DB.execute_ db "DROP TABLE IF EXISTS temp_delete_members"
#if defined(dbPostgres)
DB.execute_ db "CREATE TABLE temp_delete_members (contact_profile_id BIGINT, member_profile_id BIGINT, local_display_name TEXT)"
#else
DB.execute_ db "CREATE TABLE temp_delete_members (contact_profile_id INTEGER, member_profile_id INTEGER, local_display_name TEXT)"
#endif
DB.execute
db
[sql|
INSERT INTO temp_delete_members (contact_profile_id, member_profile_id, local_display_name)
SELECT contact_profile_id, member_profile_id, local_display_name FROM group_members WHERE group_id = ?
|]
(Only groupId)
DB.execute db "DELETE FROM group_members WHERE user_id = ? AND group_id = ?" (userId, groupId)
forM_ members $ cleanupMemberProfileAndName_ db user
forM_ (incognitoMembershipProfile g) $ deleteUnusedIncognitoProfileById_ db user . localProfileId
where
cleanupHostGroupLinkConn_ = do
hostId <- getHostMemberId_ db user groupId
liftIO $
DB.execute
db
[sql|
UPDATE connections SET via_contact_uri_hash = NULL, xcontact_id = NULL
WHERE user_id = ? AND via_group_link = 1 AND contact_id IN (
SELECT contact_id
FROM group_members
WHERE user_id = ? AND group_member_id = ?
)
|]
(userId, userId, hostId)
DB.execute
db
[sql|
DELETE FROM contact_profiles
WHERE
user_id = ?
AND (contact_profile_id IN (SELECT contact_profile_id FROM temp_delete_members)
OR contact_profile_id IN (SELECT member_profile_id FROM temp_delete_members WHERE member_profile_id IS NOT NULL))
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM group_members)
AND contact_profile_id NOT IN (SELECT member_profile_id FROM group_members)
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM contacts)
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM contact_requests)
AND contact_profile_id NOT IN (SELECT custom_user_profile_id FROM connections)
|]
(Only userId)
DB.execute
db
[sql|
DELETE FROM display_names
WHERE
user_id = ?
AND local_display_name IN (SELECT local_display_name FROM temp_delete_members)
AND local_display_name NOT IN (SELECT local_display_name FROM group_members)
AND local_display_name NOT IN (SELECT local_display_name FROM contacts)
AND local_display_name NOT IN (SELECT local_display_name FROM users)
AND local_display_name NOT IN (SELECT local_display_name FROM groups)
AND local_display_name NOT IN (SELECT local_display_name FROM user_contact_links)
AND local_display_name NOT IN (SELECT local_display_name FROM contact_requests)
|]
(Only userId)
DB.execute_ db "DROP TABLE temp_delete_members"
-- to allow repeat connection via the same group link if one was used
cleanupHostGroupLinkConn :: DB.Connection -> User -> GroupInfo -> IO ()
cleanupHostGroupLinkConn db user@User {userId} GroupInfo {groupId} = do
runExceptT (getHostMemberId_ db user groupId) >>= \case
Left _ -> pure ()
Right hostId ->
DB.execute
db
[sql|
UPDATE connections SET via_contact_uri_hash = NULL, xcontact_id = NULL
WHERE user_id = ? AND via_group_link = 1 AND contact_id IN (
SELECT contact_id
FROM group_members
WHERE user_id = ? AND group_member_id = ?
)
|]
(userId, userId, hostId)
deleteGroup :: DB.Connection -> User -> GroupInfo -> IO ()
deleteGroup db user@User {userId} g@GroupInfo {groupId, localDisplayName} = do
@ -688,11 +727,6 @@ deleteGroupProfile_ db userId groupId =
|]
(userId, groupId)
getUserGroups :: DB.Connection -> VersionRangeChat -> User -> IO [Group]
getUserGroups db vr user@User {userId} = do
groupIds <- map fromOnly <$> DB.query db "SELECT group_id FROM groups WHERE user_id = ?" (Only userId)
rights <$> mapM (runExceptT . getGroup db vr user) groupIds
getUserGroupsToSubscribe :: DB.Connection -> User -> IO [ShortGroup]
getUserGroupsToSubscribe db user@User {userId} = do
groupIds <- map fromOnly <$> DB.query db "SELECT group_id FROM groups WHERE user_id = ?" (Only userId)
@ -707,7 +741,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do
[sql|
SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
@ -763,10 +797,6 @@ getContactGroupPreferences db User {userId} Contact {contactId} = do
|]
(userId, contactId)
checkContactHasGroups :: DB.Connection -> User -> Contact -> IO (Maybe GroupId)
checkContactHasGroups db User {userId} Contact {contactId} =
maybeFirstRow fromOnly $ DB.query db "SELECT group_id FROM group_members WHERE user_id = ? AND contact_id = ? LIMIT 1" (userId, contactId)
getGroupInfoByName :: DB.Connection -> VersionRangeChat -> User -> GroupName -> ExceptT StoreError IO GroupInfo
getGroupInfoByName db vr user gName = do
gId <- getGroupIdByName db user gName
@ -801,19 +831,21 @@ getGroupMember db vr user@User {userId} groupId groupMemberId =
getMentionedGroupMember :: DB.Connection -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO CIMention
getMentionedGroupMember db User {userId} groupId gmId =
ExceptT $ firstRow toMentionedMember (SEGroupMemberNotFound gmId) $
DB.query
db
(mentionedMemberQuery <> " WHERE m.group_id = ? AND m.group_member_id = ? AND m.user_id = ?")
(groupId, gmId, userId)
ExceptT $
firstRow toMentionedMember (SEGroupMemberNotFound gmId) $
DB.query
db
(mentionedMemberQuery <> " WHERE m.group_id = ? AND m.group_member_id = ? AND m.user_id = ?")
(groupId, gmId, userId)
getMentionedMemberByMemberId :: DB.Connection -> User -> GroupId -> MsgMention -> IO CIMention
getMentionedMemberByMemberId db User {userId} groupId MsgMention {memberId} =
fmap (fromMaybe mentionedMember) $ maybeFirstRow toMentionedMember $
DB.query
db
(mentionedMemberQuery <> " WHERE m.group_id = ? AND m.member_id = ? AND m.user_id = ?")
(groupId, memberId, userId)
fmap (fromMaybe mentionedMember) $
maybeFirstRow toMentionedMember $
DB.query
db
(mentionedMemberQuery <> " WHERE m.group_id = ? AND m.member_id = ? AND m.user_id = ?")
(groupId, memberId, userId)
where
mentionedMember = CIMention {memberId, memberRef = Nothing}
@ -1470,7 +1502,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,

View file

@ -128,7 +128,6 @@ CREATE TABLE groups(
updated_at TIMESTAMPTZ NOT NULL,
chat_item_id BIGINT DEFAULT NULL,
enable_ntfs SMALLINT,
host_conn_custom_user_profile_id BIGINT REFERENCES contact_profiles ON DELETE SET NULL,
unread_chat SMALLINT NOT NULL DEFAULT 0,
chat_ts TIMESTAMPTZ,
favorite SMALLINT NOT NULL DEFAULT 0,
@ -692,7 +691,6 @@ CREATE INDEX idx_groups_inv_queue_info ON groups(inv_queue_info);
CREATE INDEX idx_contact_requests_xcontact_id ON contact_requests(xcontact_id);
CREATE INDEX idx_contacts_xcontact_id ON contacts(xcontact_id);
CREATE INDEX idx_messages_shared_msg_id ON messages(shared_msg_id);
CREATE INDEX idx_chat_items_shared_msg_id ON chat_items(shared_msg_id);
CREATE UNIQUE INDEX idx_chat_items_direct_shared_msg_id ON chat_items(
user_id,
contact_id,
@ -765,9 +763,6 @@ CREATE INDEX idx_group_members_contact_profile_id ON group_members(
CREATE INDEX idx_group_members_user_id ON group_members(user_id);
CREATE INDEX idx_group_members_invited_by ON group_members(invited_by);
CREATE INDEX idx_group_profiles_user_id ON group_profiles(user_id);
CREATE INDEX idx_groups_host_conn_custom_user_profile_id ON groups(
host_conn_custom_user_profile_id
);
CREATE INDEX idx_groups_chat_item_id ON groups(chat_item_id);
CREATE INDEX idx_groups_group_profile_id ON groups(group_profile_id);
CREATE INDEX idx_messages_group_id ON messages(group_id);
@ -1034,7 +1029,27 @@ CREATE INDEX idx_group_snd_item_statuses_chat_item_id_group_member_id ON group_s
group_member_id
);
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_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
);
CREATE INDEX idx_chat_items_group_id ON chat_items(group_id);
CREATE INDEX idx_connections_group_member_id ON connections(group_member_id);
CREATE INDEX idx_chat_items_group_id_shared_msg_id ON chat_items(
group_id,
shared_msg_id
);
|]

View file

@ -127,6 +127,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250115_chat_ttl
import Simplex.Chat.Store.SQLite.Migrations.M20250122_chat_items_include_in_history
import Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions
import Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts
import Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@ -253,7 +254,8 @@ schemaMigrations =
("20250115_chat_ttl", m20250115_chat_ttl, Just down_m20250115_chat_ttl),
("20250122_chat_items_include_in_history", m20250122_chat_items_include_in_history, Just down_m20250122_chat_items_include_in_history),
("20250126_mentions", m20250126_mentions, Just down_m20250126_mentions),
("20250129_delete_unused_contacts", m20250129_delete_unused_contacts, Just down_m20250129_delete_unused_contacts)
("20250129_delete_unused_contacts", m20250129_delete_unused_contacts, Just down_m20250129_delete_unused_contacts),
("20250130_indexes", m20250130_indexes, Just down_m20250130_indexes)
]
-- | The list of migrations in ascending order by date

View file

@ -43,12 +43,14 @@ WHERE
OR contact_profile_id IN (SELECT contact_profile_id FROM temp_delete_contacts))
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM group_members)
AND contact_profile_id NOT IN (SELECT member_profile_id FROM group_members)
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM contacts)
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM contact_requests)
AND contact_profile_id NOT IN (SELECT custom_user_profile_id FROM connections);
DELETE FROM display_names
WHERE local_display_name IN (SELECT local_display_name FROM temp_delete_contacts)
AND local_display_name NOT IN (SELECT local_display_name FROM group_members)
AND local_display_name NOT IN (SELECT local_display_name FROM contacts)
AND local_display_name NOT IN (SELECT local_display_name FROM users)
AND local_display_name NOT IN (SELECT local_display_name FROM groups)
AND local_display_name NOT IN (SELECT local_display_name FROM user_contact_links)

View file

@ -0,0 +1,36 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20250130_indexes :: Query
m20250130_indexes =
[sql|
CREATE INDEX idx_chat_items_group_id ON chat_items(group_id);
CREATE INDEX idx_connections_group_member_id ON connections(group_member_id);
DROP INDEX idx_chat_items_shared_msg_id;
CREATE INDEX idx_chat_items_group_id_shared_msg_id ON chat_items(group_id, shared_msg_id);
DROP INDEX idx_groups_host_conn_custom_user_profile_id;
ALTER TABLE groups DROP COLUMN host_conn_custom_user_profile_id;
|]
down_m20250130_indexes :: Query
down_m20250130_indexes =
[sql|
ALTER TABLE groups ADD COLUMN host_conn_custom_user_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL;
CREATE INDEX idx_groups_host_conn_custom_user_profile_id ON groups(host_conn_custom_user_profile_id);
DROP INDEX idx_chat_items_group_id_shared_msg_id;
CREATE INDEX idx_chat_items_shared_msg_id ON chat_items(shared_msg_id);
DROP INDEX idx_connections_group_member_id;
DROP INDEX idx_chat_items_group_id;
|]

View file

@ -1,2 +0,0 @@
CREATE INDEX 'chat_items_group_id' ON 'chat_items'('group_id'); --> groups(group_id)
CREATE INDEX 'connections_group_member_id' ON 'connections'('group_member_id'); --> group_members(group_member_id)

View file

@ -9,7 +9,7 @@ Plan:
Query:
INSERT INTO groups
(group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs,
(group_profile_id, local_display_name, inv_queue_info, user_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
@ -17,9 +17,9 @@ Plan:
Query:
INSERT INTO groups
(group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs,
(group_profile_id, local_display_name, user_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
Plan:
@ -35,7 +35,7 @@ Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
@ -605,19 +605,6 @@ SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH g USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH h USING INDEX idx_sent_probe_hashes_sent_probe_id (sent_probe_id=?)
Query:
UPDATE connections SET via_contact_uri_hash = NULL, xcontact_id = NULL
WHERE user_id = ? AND via_group_link = 1 AND contact_id IN (
SELECT contact_id
FROM group_members
WHERE user_id = ? AND group_member_id = ?
)
Plan:
SEARCH connections USING INDEX idx_connections_updated_at (user_id=?)
LIST SUBQUERY 1
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
DELETE FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? AND reaction = ?
@ -742,7 +729,7 @@ SEARCH i USING INTEGER PRIMARY KEY (rowid=?)
SEARCH f USING INDEX idx_files_chat_item_id (chat_item_id=?) LEFT-JOIN
SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH p USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH ri USING INDEX idx_chat_items_shared_msg_id (shared_msg_id=?) LEFT-JOIN
SEARCH ri USING INDEX idx_chat_items_group_id_shared_msg_id (group_id=? AND shared_msg_id=?) LEFT-JOIN
SEARCH rm USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH rp USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH dbm USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
@ -795,7 +782,7 @@ Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
@ -838,7 +825,7 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group
Query:
SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
@ -951,7 +938,7 @@ Query:
LIMIT 1
Plan:
SEARCH chat_items USING INDEX idx_chat_items_group_member_id (group_member_id=?)
SEARCH chat_items USING INDEX idx_chat_items_group_id (group_id=?)
Query:
SELECT chat_item_id
@ -961,8 +948,7 @@ Query:
LIMIT 1
Plan:
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?)
USE TEMP B-TREE FOR ORDER BY
SEARCH chat_items USING INDEX idx_chat_items_group_id (group_id=?)
Query:
SELECT chat_item_id, contact_id, group_id, note_folder_id
@ -1115,7 +1101,7 @@ Query:
Plan:
SEARCH m USING INDEX sqlite_autoindex_group_members_1 (group_id=? AND member_id=?)
SEARCH i USING INDEX idx_chat_items_shared_msg_id (shared_msg_id=?)
SEARCH i USING INDEX idx_chat_items_group_id_shared_msg_id (group_id=? AND shared_msg_id=?)
Query:
SELECT i.chat_item_id
@ -1126,9 +1112,8 @@ Query:
LIMIT 1
Plan:
SEARCH i USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?)
SEARCH i USING INDEX idx_chat_items_group_id (group_id=?)
SEARCH m USING INTEGER PRIMARY KEY (rowid=?)
USE TEMP B-TREE FOR ORDER BY
Query:
SELECT i.chat_item_id, i.contact_id, i.group_id, i.note_folder_id
@ -1193,6 +1178,19 @@ SEARCH r USING INDEX idx_received_probes_user_id (user_id=?)
SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH g USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
Query:
UPDATE connections SET via_contact_uri_hash = NULL, xcontact_id = NULL
WHERE user_id = ? AND via_group_link = 1 AND contact_id IN (
SELECT contact_id
FROM group_members
WHERE user_id = ? AND group_member_id = ?
)
Plan:
SEARCH connections USING INDEX idx_connections_updated_at (user_id=?)
LIST SUBQUERY 1
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE contacts
SET local_display_name = ?, contact_profile_id = ?, updated_at = ?
@ -2847,8 +2845,7 @@ Query:
LIMIT 1
Plan:
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?)
USE TEMP B-TREE FOR ORDER BY
SEARCH chat_items USING INDEX idx_chat_items_group_id (group_id=?)
Query:
SELECT chat_item_id
@ -3475,6 +3472,35 @@ SEARCH commands USING COVERING INDEX idx_commands_connection_id (connection_id=?
SEARCH messages USING COVERING INDEX idx_messages_connection_id (connection_id=?)
SEARCH snd_files USING COVERING INDEX idx_snd_files_connection_id (connection_id=?)
Query:
DELETE FROM contact_profiles
WHERE
user_id = ?
AND (contact_profile_id IN (SELECT contact_profile_id FROM temp_delete_members)
OR contact_profile_id IN (SELECT member_profile_id FROM temp_delete_members WHERE member_profile_id IS NOT NULL))
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM group_members)
AND contact_profile_id NOT IN (SELECT member_profile_id FROM group_members)
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM contacts)
AND contact_profile_id NOT IN (SELECT contact_profile_id FROM contact_requests)
AND contact_profile_id NOT IN (SELECT custom_user_profile_id FROM connections)
Plan:
SEARCH contact_profiles USING COVERING INDEX idx_contact_profiles_user_id (user_id=?)
LIST SUBQUERY 1
SCAN temp_delete_members
LIST SUBQUERY 2
SCAN temp_delete_members
USING INDEX idx_group_members_contact_profile_id FOR IN-OPERATOR
USING INDEX idx_group_members_member_profile_id FOR IN-OPERATOR
USING INDEX idx_contacts_contact_profile_id FOR IN-OPERATOR
USING INDEX idx_contact_requests_contact_profile_id FOR IN-OPERATOR
USING INDEX idx_connections_custom_user_profile_id FOR IN-OPERATOR
SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?)
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query:
DELETE FROM contact_profiles
WHERE contact_profile_id in (
@ -3491,7 +3517,6 @@ SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profil
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH groups USING COVERING INDEX idx_groups_host_conn_custom_user_profile_id (host_conn_custom_user_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query:
@ -3510,7 +3535,6 @@ SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profil
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH groups USING COVERING INDEX idx_groups_host_conn_custom_user_profile_id (host_conn_custom_user_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query:
@ -3531,7 +3555,6 @@ SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profil
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH groups USING COVERING INDEX idx_groups_host_conn_custom_user_profile_id (host_conn_custom_user_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query:
@ -3552,7 +3575,6 @@ SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profil
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH groups USING COVERING INDEX idx_groups_host_conn_custom_user_profile_id (host_conn_custom_user_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query:
@ -3591,7 +3613,6 @@ SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profil
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH groups USING COVERING INDEX idx_groups_host_conn_custom_user_profile_id (host_conn_custom_user_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query:
@ -3604,7 +3625,6 @@ SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profil
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH groups USING COVERING INDEX idx_groups_host_conn_custom_user_profile_id (host_conn_custom_user_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query:
@ -3629,9 +3649,41 @@ SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profil
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH groups USING COVERING INDEX idx_groups_host_conn_custom_user_profile_id (host_conn_custom_user_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query:
DELETE FROM display_names
WHERE
user_id = ?
AND local_display_name IN (SELECT local_display_name FROM temp_delete_members)
AND local_display_name NOT IN (SELECT local_display_name FROM group_members)
AND local_display_name NOT IN (SELECT local_display_name FROM contacts)
AND local_display_name NOT IN (SELECT local_display_name FROM users)
AND local_display_name NOT IN (SELECT local_display_name FROM groups)
AND local_display_name NOT IN (SELECT local_display_name FROM user_contact_links)
AND local_display_name NOT IN (SELECT local_display_name FROM contact_requests)
Plan:
SEARCH display_names USING PRIMARY KEY (user_id=? AND local_display_name=?)
LIST SUBQUERY 1
SCAN temp_delete_members
LIST SUBQUERY 2
SCAN group_members USING COVERING INDEX idx_group_members_user_id_local_display_name
LIST SUBQUERY 3
SCAN contacts USING COVERING INDEX sqlite_autoindex_contacts_1
USING INDEX sqlite_autoindex_users_2 FOR IN-OPERATOR
LIST SUBQUERY 5
SCAN groups USING COVERING INDEX sqlite_autoindex_groups_1
LIST SUBQUERY 6
SCAN user_contact_links USING COVERING INDEX sqlite_autoindex_user_contact_links_1
LIST SUBQUERY 7
SCAN contact_requests USING COVERING INDEX sqlite_autoindex_contact_requests_1
SEARCH contact_requests USING COVERING INDEX sqlite_autoindex_contact_requests_1 (user_id=? AND local_display_name=?)
SEARCH group_members USING COVERING INDEX idx_group_members_user_id_local_display_name (user_id=? AND local_display_name=?)
SEARCH groups USING COVERING INDEX sqlite_autoindex_groups_1 (user_id=? AND local_display_name=?)
SEARCH contacts USING COVERING INDEX sqlite_autoindex_contacts_1 (user_id=? AND local_display_name=?)
SEARCH users USING INTEGER PRIMARY KEY (rowid=?)
Query:
DELETE FROM display_names
WHERE user_id = ?
@ -3931,6 +3983,13 @@ Query:
Plan:
Query:
INSERT INTO temp_delete_members (contact_profile_id, member_profile_id, local_display_name)
SELECT contact_profile_id, member_profile_id, local_display_name FROM group_members WHERE group_id = ?
Plan:
SEARCH group_members USING INDEX sqlite_autoindex_group_members_1 (group_id=?)
Query:
SELECT chat_item_id, timed_ttl
FROM chat_items
@ -4341,7 +4400,7 @@ Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
@ -4362,7 +4421,7 @@ Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
@ -4817,6 +4876,9 @@ SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=?
Query: CREATE TABLE temp_conn_ids (conn_id BLOB)
Error: SQLite3 returned ErrorError while attempting to perform prepare "explain query plan CREATE TABLE temp_conn_ids (conn_id BLOB)": table temp_conn_ids already exists
Query: CREATE TABLE temp_delete_members (contact_profile_id INTEGER, member_profile_id INTEGER, local_display_name TEXT)
Error: SQLite3 returned ErrorError while attempting to perform prepare "explain query plan CREATE TABLE temp_delete_members (contact_profile_id INTEGER, member_profile_id INTEGER, local_display_name TEXT)": table temp_delete_members already exists
Query: DELETE FROM app_settings
Plan:
@ -4932,16 +4994,6 @@ SEARCH commands USING COVERING INDEX idx_commands_connection_id (connection_id=?
SEARCH messages USING COVERING INDEX idx_messages_connection_id (connection_id=?)
SEARCH snd_files USING COVERING INDEX idx_snd_files_connection_id (connection_id=?)
Query: DELETE FROM contact_profiles WHERE user_id = ? AND contact_profile_id = ?
Plan:
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?)
SEARCH connections USING COVERING INDEX idx_connections_custom_user_profile_id (custom_user_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?)
SEARCH groups USING COVERING INDEX idx_groups_host_conn_custom_user_profile_id (host_conn_custom_user_profile_id=?)
SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query: DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?
Plan:
SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?)
@ -4982,14 +5034,6 @@ SEARCH rcv_files USING INTEGER PRIMARY KEY (rowid=?)
SEARCH snd_files USING COVERING INDEX idx_snd_files_file_id (file_id=?)
SEARCH files USING COVERING INDEX idx_files_redirect_file_id (redirect_file_id=?)
Query: DELETE FROM files WHERE user_id = ? AND group_id = ?
Plan:
SEARCH files USING INDEX idx_files_group_id (group_id=?)
SEARCH extra_xftp_file_descriptions USING COVERING INDEX idx_extra_xftp_file_descriptions_file_id (file_id=?)
SEARCH rcv_files USING INTEGER PRIMARY KEY (rowid=?)
SEARCH snd_files USING COVERING INDEX idx_snd_files_file_id (file_id=?)
SEARCH files USING COVERING INDEX idx_files_redirect_file_id (redirect_file_id=?)
Query: DELETE FROM group_members WHERE user_id = ? AND group_id = ?
Plan:
SEARCH group_members USING COVERING INDEX idx_group_members_group_id (user_id=? AND group_id=?)
@ -5005,7 +5049,7 @@ SEARCH chat_items USING COVERING INDEX idx_chat_items_group_member_id (group_mem
SEARCH pending_group_messages USING COVERING INDEX idx_pending_group_messages_group_member_id (group_member_id=?)
SEARCH messages USING COVERING INDEX idx_messages_forwarded_by_group_member_id (forwarded_by_group_member_id=?)
SEARCH messages USING COVERING INDEX idx_messages_author_group_member_id (author_group_member_id=?)
SCAN connections USING COVERING INDEX idx_connections_group_member
SEARCH connections USING COVERING INDEX idx_connections_group_member_id (group_member_id=?)
SEARCH rcv_files USING COVERING INDEX idx_rcv_files_group_member_id (group_member_id=?)
SEARCH snd_files USING COVERING INDEX idx_snd_files_group_member_id (group_member_id=?)
SEARCH group_member_intros USING COVERING INDEX idx_group_member_intros_to_group_member_id (to_group_member_id=?)
@ -5028,7 +5072,7 @@ SEARCH chat_items USING COVERING INDEX idx_chat_items_group_member_id (group_mem
SEARCH pending_group_messages USING COVERING INDEX idx_pending_group_messages_group_member_id (group_member_id=?)
SEARCH messages USING COVERING INDEX idx_messages_forwarded_by_group_member_id (forwarded_by_group_member_id=?)
SEARCH messages USING COVERING INDEX idx_messages_author_group_member_id (author_group_member_id=?)
SCAN connections USING COVERING INDEX idx_connections_group_member
SEARCH connections USING COVERING INDEX idx_connections_group_member_id (group_member_id=?)
SEARCH rcv_files USING COVERING INDEX idx_rcv_files_group_member_id (group_member_id=?)
SEARCH snd_files USING COVERING INDEX idx_snd_files_group_member_id (group_member_id=?)
SEARCH group_member_intros USING COVERING INDEX idx_group_member_intros_to_group_member_id (to_group_member_id=?)
@ -5044,7 +5088,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_user_mention
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_id (group_id=?)
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=?)
@ -5172,9 +5216,15 @@ SEARCH contact_profiles USING COVERING INDEX idx_contact_profiles_user_id (user_
Query: DROP TABLE IF EXISTS temp_conn_ids
Plan:
Query: DROP TABLE IF EXISTS temp_delete_members
Plan:
Query: DROP TABLE temp_conn_ids
Plan:
Query: DROP TABLE temp_delete_members
Plan:
Query: INSERT INTO app_settings (app_settings) VALUES (?)
Plan:
@ -5399,9 +5449,9 @@ Query: SELECT contact_id FROM contacts WHERE user_id = ? AND local_display_name
Plan:
SEARCH contacts USING INDEX sqlite_autoindex_contacts_1 (user_id=? AND local_display_name=?)
Query: SELECT contact_id FROM group_members WHERE user_id = ? AND contact_id = ? LIMIT 1
Query: SELECT contact_id FROM group_members WHERE contact_id = ? LIMIT 1
Plan:
SEARCH group_members USING INDEX idx_group_members_user_id (user_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_id (contact_id=?)
Query: SELECT contact_id, group_id FROM chat_items WHERE user_id = ? AND chat_item_id = ?
Plan:
@ -5451,10 +5501,6 @@ Query: SELECT group_id FROM user_contact_links WHERE user_id = ? AND user_contac
Plan:
SEARCH user_contact_links USING INTEGER PRIMARY KEY (rowid=?)
Query: SELECT group_member_id FROM group_members WHERE user_id = ? AND contact_profile_id = ? AND group_member_id != ? LIMIT 1
Plan:
SEARCH group_members USING INDEX idx_group_members_user_id (user_id=?)
Query: SELECT group_member_id FROM group_members WHERE user_id = ? AND group_id = ? AND local_display_name = ?
Plan:
SEARCH group_members USING INDEX idx_group_members_group_id (user_id=? AND group_id=?)
@ -5549,7 +5595,7 @@ SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
Query: UPDATE connections SET conn_status='deleted' WHERE group_member_id = 3
Plan:
SCAN connections
SEARCH connections USING INDEX idx_connections_group_member_id (group_member_id=?)
Query: UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?
Plan:

View file

@ -120,7 +120,6 @@ CREATE TABLE groups(
updated_at TEXT CHECK(updated_at NOT NULL),
chat_item_id INTEGER DEFAULT NULL REFERENCES chat_items ON DELETE SET NULL,
enable_ntfs INTEGER,
host_conn_custom_user_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL,
unread_chat INTEGER DEFAULT 0 CHECK(unread_chat NOT NULL),
chat_ts TEXT,
favorite INTEGER NOT NULL DEFAULT 0,
@ -658,7 +657,6 @@ CREATE INDEX idx_groups_inv_queue_info ON groups(inv_queue_info);
CREATE INDEX idx_contact_requests_xcontact_id ON contact_requests(xcontact_id);
CREATE INDEX idx_contacts_xcontact_id ON contacts(xcontact_id);
CREATE INDEX idx_messages_shared_msg_id ON messages(shared_msg_id);
CREATE INDEX idx_chat_items_shared_msg_id ON chat_items(shared_msg_id);
CREATE UNIQUE INDEX idx_chat_items_direct_shared_msg_id ON chat_items(
user_id,
contact_id,
@ -731,9 +729,6 @@ CREATE INDEX idx_group_members_contact_profile_id ON group_members(
CREATE INDEX idx_group_members_user_id ON group_members(user_id);
CREATE INDEX idx_group_members_invited_by ON group_members(invited_by);
CREATE INDEX idx_group_profiles_user_id ON group_profiles(user_id);
CREATE INDEX idx_groups_host_conn_custom_user_profile_id ON groups(
host_conn_custom_user_profile_id
);
CREATE INDEX idx_groups_chat_item_id ON groups(chat_item_id);
CREATE INDEX idx_groups_group_profile_id ON groups(group_profile_id);
CREATE INDEX idx_messages_group_id ON messages(group_id);
@ -1017,3 +1012,9 @@ CREATE INDEX idx_chat_items_groups_user_mention ON chat_items(
item_status,
user_mention
);
CREATE INDEX idx_chat_items_group_id ON chat_items(group_id);
CREATE INDEX idx_connections_group_member_id ON connections(group_member_id);
CREATE INDEX idx_chat_items_group_id_shared_msg_id ON chat_items(
group_id,
shared_msg_id
);

View file

@ -577,18 +577,18 @@ safeDeleteLDN db User {userId} localDisplayName = do
type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId)
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow
type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, BI favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) =
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) =
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
fullGroupPreferences = mergeGroupPreferences groupPreferences
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences}
businessChat = toBusinessChatInfo businessRow
in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, chatItemTTL, uiThemes, customData}
in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, chatItemTTL, uiThemes, customData}
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) =
@ -610,7 +610,7 @@ groupInfoQuery =
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,

View file

@ -408,7 +408,6 @@ data GroupInfo = GroupInfo
businessChat :: Maybe BusinessChatInfo,
fullGroupPreferences :: FullGroupPreferences,
membership :: GroupMember,
hostConnCustomUserProfileId :: Maybe ProfileId,
chatSettings :: ChatSettings,
createdAt :: UTCTime,
updatedAt :: UTCTime,

View file

@ -41,19 +41,6 @@ testAgentDB = "tests/tmp/test_agent.db"
appSchema :: FilePath
appSchema = "src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql"
-- Some indexes found by `.lint fkey-indexes` are not added to schema, explanation:
--
-- - CREATE INDEX 'chat_items_group_id' ON 'chat_items'('group_id'); --> groups(group_id)
--
-- Covering index is used instead. See for example:
-- EXPLAIN QUERY PLAN DELETE FROM groups;
-- (uses idx_chat_items_groups_item_status)
--
-- - CREATE INDEX 'connections_group_member_id' ON 'connections'('group_member_id'); --> group_members(group_member_id)
--
-- Covering index is used instead. See for example:
-- EXPLAIN QUERY PLAN DELETE FROM group_members;
-- (uses idx_connections_group_member)
appLint :: FilePath
appLint = "src/Simplex/Chat/Store/SQLite/Migrations/chat_lint.sql"
@ -136,7 +123,9 @@ skipComparisonForDownMigrations =
-- sequence table moves down to the end of the file
"20241023_chat_item_autoincrement_id",
-- indexes move down to the end of the file
"20241125_indexes"
"20241125_indexes",
-- indexes move down to the end of the file
"20250130_indexes"
]
getSchema :: FilePath -> FilePath -> IO String
@ -158,7 +147,10 @@ saveQueryPlans = it "verify and overwrite query plans" $ \TestParams {chatQueryS
appChatQueryPlans
chatQueryStats
(createChatStore (DBOpts testDB "" False True TQOff) MCError)
(`DB.execute_` "CREATE TABLE IF NOT EXISTS temp_conn_ids (conn_id BLOB)")
(\db -> do
DB.execute_ db "CREATE TABLE IF NOT EXISTS temp_conn_ids (conn_id BLOB)"
DB.execute_ db "CREATE TABLE IF NOT EXISTS temp_delete_members (contact_profile_id INTEGER, member_profile_id INTEGER, local_display_name TEXT)"
)
(agentSavedPlans, agentSavedPlans') <-
updatePlans
appAgentQueryPlans