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

View file

@ -1721,7 +1721,6 @@ data class GroupInfo (
val businessChat: BusinessChatInfo? = null, val businessChat: BusinessChatInfo? = null,
val fullGroupPreferences: FullGroupPreferences, val fullGroupPreferences: FullGroupPreferences,
val membership: GroupMember, val membership: GroupMember,
val hostConnCustomUserProfileId: Long? = null,
val chatSettings: ChatSettings, val chatSettings: ChatSettings,
override val createdAt: Instant, override val createdAt: Instant,
override val updatedAt: Instant, override val updatedAt: Instant,
@ -1770,7 +1769,6 @@ data class GroupInfo (
groupProfile = GroupProfile.sampleData, groupProfile = GroupProfile.sampleData,
fullGroupPreferences = FullGroupPreferences.sampleData, fullGroupPreferences = FullGroupPreferences.sampleData,
membership = GroupMember.sampleData, membership = GroupMember.sampleData,
hostConnCustomUserProfileId = null,
chatSettings = ChatSettings(enableNtfs = MsgFilter.All, sendRcpts = null, favorite = false), chatSettings = ChatSettings(enableNtfs = MsgFilter.All, sendRcpts = null, favorite = false),
createdAt = Clock.System.now(), createdAt = Clock.System.now(),
updatedAt = 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.M20250122_chat_items_include_in_history
Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions
Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts
Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes
other-modules: other-modules:
Paths_simplex_chat Paths_simplex_chat
hs-source-dirs: hs-source-dirs:

View file

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

View file

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

View file

@ -134,7 +134,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
SELECT SELECT
-- GroupInfo -- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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} -- 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, 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, deleteContactFiles,
deleteContact, deleteContact,
deleteContactWithoutGroups, deleteContactWithoutGroups,
setContactDeleted,
getDeletedContacts, getDeletedContacts,
getContactByName, getContactByName,
getContact, getContact,
@ -294,7 +293,7 @@ deleteContact db user@User {userId} ct@Contact {contactId, localDisplayName, act
assertNotUser db user ct assertNotUser db user ct
liftIO $ do liftIO $ do
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND contact_id = ?" (userId, contactId) 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 if isNothing ctMember
then do then do
deleteContactProfile_ db userId contactId deleteContactProfile_ db userId contactId
@ -322,13 +321,7 @@ deleteContactWithoutGroups db user@User {userId} ct@Contact {contactId, localDis
forM_ customUserProfileId $ \profileId -> forM_ customUserProfileId $ \profileId ->
deleteUnusedIncognitoProfileById_ db user profileId deleteUnusedIncognitoProfileById_ db user profileId
setContactDeleted :: DB.Connection -> User -> Contact -> ExceptT StoreError IO () -- TODO remove in future versions: only used for legacy contact cleanup
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)
getDeletedContacts :: DB.Connection -> VersionRangeChat -> User -> IO [Contact] getDeletedContacts :: DB.Connection -> VersionRangeChat -> User -> IO [Contact]
getDeletedContacts db vr user@User {userId} = do 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) 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, getGroupModerators,
getGroupMembersForExpiration, getGroupMembersForExpiration,
getGroupCurrentMembersCount, getGroupCurrentMembersCount,
deleteGroupConnectionsAndFiles, deleteGroupChatItems,
deleteGroupItemsAndMembers, deleteGroupMembers,
cleanupHostGroupLinkConn,
deleteGroup, deleteGroup,
getUserGroups,
getUserGroupsToSubscribe, getUserGroupsToSubscribe,
getUserGroupDetails, getUserGroupDetails,
getUserGroupsWithSummary, getUserGroupsWithSummary,
getGroupSummary, getGroupSummary,
getContactGroupPreferences, getContactGroupPreferences,
checkContactHasGroups,
getGroupInvitation, getGroupInvitation,
createNewContactMember, createNewContactMember,
createNewContactMemberAsync, createNewContactMemberAsync,
@ -275,7 +274,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = do
SELECT SELECT
-- GroupInfo -- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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} -- 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, 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, businessChat = Nothing,
fullGroupPreferences, fullGroupPreferences,
membership, membership,
hostConnCustomUserProfileId = Nothing,
chatSettings, chatSettings,
createdAt = currentTs, createdAt = currentTs,
updatedAt = 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 -- | 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 :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName 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 liftIO getInvitationGroupId_ >>= \case
Nothing -> createGroupInvitation_ Nothing -> createGroupInvitation_
Just gId -> do Just gId -> do
@ -399,11 +397,11 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
db db
[sql| [sql|
INSERT INTO groups 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) 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 insertedRowId db
let hostVRange = adjustedMemberVRange vr peerChatVRange let hostVRange = adjustedMemberVRange vr peerChatVRange
GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange 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, businessChat = Nothing,
fullGroupPreferences, fullGroupPreferences,
membership, membership,
hostConnCustomUserProfileId = customUserProfileId,
chatSettings, chatSettings,
createdAt = currentTs, createdAt = currentTs,
updatedAt = currentTs, updatedAt = currentTs,
@ -546,11 +543,11 @@ createGroupInvitedViaLink
db db
[sql| [sql|
INSERT INTO groups 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) 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 insertedRowId db
insertHost_ currentTs groupId = do insertHost_ currentTs groupId = do
let fromMemberProfile = profileFromName fromMemberName let fromMemberProfile = profileFromName fromMemberName
@ -639,33 +636,75 @@ getGroupToSubscribe db User {userId, userContactId} groupId = do
toShortMember (groupMemberId, localDisplayName, agentConnId) = toShortMember (groupMemberId, localDisplayName, agentConnId) =
ShortGroupMember groupMemberId groupId localDisplayName agentConnId ShortGroupMember groupMemberId groupId localDisplayName agentConnId
deleteGroupConnectionsAndFiles :: DB.Connection -> User -> GroupInfo -> [GroupMember] -> IO () deleteGroupChatItems :: DB.Connection -> User -> GroupInfo -> IO ()
deleteGroupConnectionsAndFiles db User {userId} GroupInfo {groupId} members = do deleteGroupChatItems db User {userId} GroupInfo {groupId} =
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
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND group_id = ?" (userId, 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) DB.execute db "DELETE FROM group_members WHERE user_id = ? AND group_id = ?" (userId, groupId)
forM_ members $ cleanupMemberProfileAndName_ db user DB.execute
forM_ (incognitoMembershipProfile g) $ deleteUnusedIncognitoProfileById_ db user . localProfileId db
where [sql|
cleanupHostGroupLinkConn_ = do DELETE FROM contact_profiles
hostId <- getHostMemberId_ db user groupId WHERE
liftIO $ user_id = ?
DB.execute AND (contact_profile_id IN (SELECT contact_profile_id FROM temp_delete_members)
db OR contact_profile_id IN (SELECT member_profile_id FROM temp_delete_members WHERE member_profile_id IS NOT NULL))
[sql| AND contact_profile_id NOT IN (SELECT contact_profile_id FROM group_members)
UPDATE connections SET via_contact_uri_hash = NULL, xcontact_id = NULL AND contact_profile_id NOT IN (SELECT member_profile_id FROM group_members)
WHERE user_id = ? AND via_group_link = 1 AND contact_id IN ( AND contact_profile_id NOT IN (SELECT contact_profile_id FROM contacts)
SELECT contact_id AND contact_profile_id NOT IN (SELECT contact_profile_id FROM contact_requests)
FROM group_members AND contact_profile_id NOT IN (SELECT custom_user_profile_id FROM connections)
WHERE user_id = ? AND group_member_id = ? |]
) (Only userId)
|] DB.execute
(userId, userId, hostId) 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.Connection -> User -> GroupInfo -> IO ()
deleteGroup db user@User {userId} g@GroupInfo {groupId, localDisplayName} = do deleteGroup db user@User {userId} g@GroupInfo {groupId, localDisplayName} = do
@ -688,11 +727,6 @@ deleteGroupProfile_ db userId groupId =
|] |]
(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.Connection -> User -> IO [ShortGroup]
getUserGroupsToSubscribe db user@User {userId} = do getUserGroupsToSubscribe db user@User {userId} = do
groupIds <- map fromOnly <$> DB.query db "SELECT group_id FROM groups WHERE user_id = ?" (Only userId) 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| [sql|
SELECT SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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.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 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) (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.Connection -> VersionRangeChat -> User -> GroupName -> ExceptT StoreError IO GroupInfo
getGroupInfoByName db vr user gName = do getGroupInfoByName db vr user gName = do
gId <- getGroupIdByName db user gName 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.Connection -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO CIMention
getMentionedGroupMember db User {userId} groupId gmId = getMentionedGroupMember db User {userId} groupId gmId =
ExceptT $ firstRow toMentionedMember (SEGroupMemberNotFound gmId) $ ExceptT $
DB.query firstRow toMentionedMember (SEGroupMemberNotFound gmId) $
db DB.query
(mentionedMemberQuery <> " WHERE m.group_id = ? AND m.group_member_id = ? AND m.user_id = ?") db
(groupId, gmId, userId) (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.Connection -> User -> GroupId -> MsgMention -> IO CIMention
getMentionedMemberByMemberId db User {userId} groupId MsgMention {memberId} = getMentionedMemberByMemberId db User {userId} groupId MsgMention {memberId} =
fmap (fromMaybe mentionedMember) $ maybeFirstRow toMentionedMember $ fmap (fromMaybe mentionedMember) $
DB.query maybeFirstRow toMentionedMember $
db DB.query
(mentionedMemberQuery <> " WHERE m.group_id = ? AND m.member_id = ? AND m.user_id = ?") db
(groupId, memberId, userId) (mentionedMemberQuery <> " WHERE m.group_id = ? AND m.member_id = ? AND m.user_id = ?")
(groupId, memberId, userId)
where where
mentionedMember = CIMention {memberId, memberRef = Nothing} mentionedMember = CIMention {memberId, memberRef = Nothing}
@ -1470,7 +1502,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do
SELECT SELECT
-- GroupInfo -- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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} -- 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, 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, updated_at TIMESTAMPTZ NOT NULL,
chat_item_id BIGINT DEFAULT NULL, chat_item_id BIGINT DEFAULT NULL,
enable_ntfs SMALLINT, enable_ntfs SMALLINT,
host_conn_custom_user_profile_id BIGINT REFERENCES contact_profiles ON DELETE SET NULL,
unread_chat SMALLINT NOT NULL DEFAULT 0, unread_chat SMALLINT NOT NULL DEFAULT 0,
chat_ts TIMESTAMPTZ, chat_ts TIMESTAMPTZ,
favorite SMALLINT NOT NULL DEFAULT 0, 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_contact_requests_xcontact_id ON contact_requests(xcontact_id);
CREATE INDEX idx_contacts_xcontact_id ON contacts(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_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( CREATE UNIQUE INDEX idx_chat_items_direct_shared_msg_id ON chat_items(
user_id, user_id,
contact_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_user_id ON group_members(user_id);
CREATE INDEX idx_group_members_invited_by ON group_members(invited_by); 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_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_chat_item_id ON groups(chat_item_id);
CREATE INDEX idx_groups_group_profile_id ON groups(group_profile_id); CREATE INDEX idx_groups_group_profile_id ON groups(group_profile_id);
CREATE INDEX idx_messages_group_id ON messages(group_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 group_member_id
); );
CREATE INDEX idx_chat_item_mentions_group_id ON chat_item_mentions(group_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 INDEX idx_chat_item_mentions_chat_item_id ON chat_item_mentions(
CREATE UNIQUE INDEX idx_chat_item_mentions_display_name ON chat_item_mentions(chat_item_id, display_name); chat_item_id
CREATE UNIQUE INDEX idx_chat_item_mentions_member_id ON chat_item_mentions(chat_item_id, member_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.M20250122_chat_items_include_in_history
import Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions import Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions
import Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts 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 (..)) import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)] schemaMigrations :: [(String, Query, Maybe Query)]
@ -253,7 +254,8 @@ schemaMigrations =
("20250115_chat_ttl", m20250115_chat_ttl, Just down_m20250115_chat_ttl), ("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), ("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), ("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 -- | 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)) 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 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 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 contact_profile_id FROM contact_requests)
AND contact_profile_id NOT IN (SELECT custom_user_profile_id FROM connections); AND contact_profile_id NOT IN (SELECT custom_user_profile_id FROM connections);
DELETE FROM display_names DELETE FROM display_names
WHERE local_display_name IN (SELECT local_display_name FROM temp_delete_contacts) 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 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 users)
AND local_display_name NOT IN (SELECT local_display_name FROM groups) 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 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: Query:
INSERT INTO groups 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) created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
@ -17,9 +17,9 @@ Plan:
Query: Query:
INSERT INTO groups 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) created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?)
Plan: Plan:
@ -35,7 +35,7 @@ Query:
SELECT SELECT
-- GroupInfo -- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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} -- 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, 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 g USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH h USING INDEX idx_sent_probe_hashes_sent_probe_id (sent_probe_id=?) 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: Query:
DELETE FROM chat_item_reactions DELETE FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? AND reaction = ? 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 f USING INDEX idx_files_chat_item_id (chat_item_id=?) LEFT-JOIN
SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH p 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 rm USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH rp 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 SEARCH dbm USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
@ -795,7 +782,7 @@ Query:
SELECT SELECT
-- GroupInfo -- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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} -- 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, 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: Query:
SELECT SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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.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 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 LIMIT 1
Plan: 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: Query:
SELECT chat_item_id SELECT chat_item_id
@ -961,8 +948,7 @@ Query:
LIMIT 1 LIMIT 1
Plan: Plan:
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?) SEARCH chat_items USING INDEX idx_chat_items_group_id (group_id=?)
USE TEMP B-TREE FOR ORDER BY
Query: Query:
SELECT chat_item_id, contact_id, group_id, note_folder_id SELECT chat_item_id, contact_id, group_id, note_folder_id
@ -1115,7 +1101,7 @@ Query:
Plan: Plan:
SEARCH m USING INDEX sqlite_autoindex_group_members_1 (group_id=? AND member_id=?) 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: Query:
SELECT i.chat_item_id SELECT i.chat_item_id
@ -1126,9 +1112,8 @@ Query:
LIMIT 1 LIMIT 1
Plan: 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=?) SEARCH m USING INTEGER PRIMARY KEY (rowid=?)
USE TEMP B-TREE FOR ORDER BY
Query: Query:
SELECT i.chat_item_id, i.contact_id, i.group_id, i.note_folder_id 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 m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH g 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: Query:
UPDATE contacts UPDATE contacts
SET local_display_name = ?, contact_profile_id = ?, updated_at = ? SET local_display_name = ?, contact_profile_id = ?, updated_at = ?
@ -2847,8 +2845,7 @@ Query:
LIMIT 1 LIMIT 1
Plan: Plan:
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=?) SEARCH chat_items USING INDEX idx_chat_items_group_id (group_id=?)
USE TEMP B-TREE FOR ORDER BY
Query: Query:
SELECT chat_item_id 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 messages USING COVERING INDEX idx_messages_connection_id (connection_id=?)
SEARCH snd_files USING COVERING INDEX idx_snd_files_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: Query:
DELETE FROM contact_profiles DELETE FROM contact_profiles
WHERE contact_profile_id in ( 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 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_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_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=?) SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query: 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 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_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_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=?) SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query: 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 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_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_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=?) SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query: 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 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_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_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=?) SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query: 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 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_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_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=?) SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query: 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 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_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_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=?) SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_profile_id=?)
Query: 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 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_member_profile_id (member_profile_id=?)
SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_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=?) 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: Query:
DELETE FROM display_names DELETE FROM display_names
WHERE user_id = ? WHERE user_id = ?
@ -3931,6 +3983,13 @@ Query:
Plan: 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: Query:
SELECT chat_item_id, timed_ttl SELECT chat_item_id, timed_ttl
FROM chat_items FROM chat_items
@ -4341,7 +4400,7 @@ Query:
SELECT SELECT
-- GroupInfo -- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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 -- 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, 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 SELECT
-- GroupInfo -- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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 -- 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, 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) 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 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 Query: DELETE FROM app_settings
Plan: 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 messages USING COVERING INDEX idx_messages_connection_id (connection_id=?)
SEARCH snd_files USING COVERING INDEX idx_snd_files_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 = ? Query: DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?
Plan: Plan:
SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?) 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 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=?) 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 = ? Query: DELETE FROM group_members WHERE user_id = ? AND group_id = ?
Plan: Plan:
SEARCH group_members USING COVERING INDEX idx_group_members_group_id (user_id=? AND group_id=?) 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 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_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=?) 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 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 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=?) 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 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_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=?) 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 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 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=?) 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_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_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=?) 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 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 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=?) 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 Query: DROP TABLE IF EXISTS temp_conn_ids
Plan: Plan:
Query: DROP TABLE IF EXISTS temp_delete_members
Plan:
Query: DROP TABLE temp_conn_ids Query: DROP TABLE temp_conn_ids
Plan: Plan:
Query: DROP TABLE temp_delete_members
Plan:
Query: INSERT INTO app_settings (app_settings) VALUES (?) Query: INSERT INTO app_settings (app_settings) VALUES (?)
Plan: Plan:
@ -5399,9 +5449,9 @@ Query: SELECT contact_id FROM contacts WHERE user_id = ? AND local_display_name
Plan: Plan:
SEARCH contacts USING INDEX sqlite_autoindex_contacts_1 (user_id=? AND local_display_name=?) 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: 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 = ? Query: SELECT contact_id, group_id FROM chat_items WHERE user_id = ? AND chat_item_id = ?
Plan: Plan:
@ -5451,10 +5501,6 @@ Query: SELECT group_id FROM user_contact_links WHERE user_id = ? AND user_contac
Plan: Plan:
SEARCH user_contact_links USING INTEGER PRIMARY KEY (rowid=?) 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 = ? Query: SELECT group_member_id FROM group_members WHERE user_id = ? AND group_id = ? AND local_display_name = ?
Plan: Plan:
SEARCH group_members USING INDEX idx_group_members_group_id (user_id=? AND group_id=?) 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 Query: UPDATE connections SET conn_status='deleted' WHERE group_member_id = 3
Plan: 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 = ? Query: UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?
Plan: Plan:

View file

@ -120,7 +120,6 @@ CREATE TABLE groups(
updated_at TEXT CHECK(updated_at NOT NULL), updated_at TEXT CHECK(updated_at NOT NULL),
chat_item_id INTEGER DEFAULT NULL REFERENCES chat_items ON DELETE SET NULL, chat_item_id INTEGER DEFAULT NULL REFERENCES chat_items ON DELETE SET NULL,
enable_ntfs INTEGER, 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), unread_chat INTEGER DEFAULT 0 CHECK(unread_chat NOT NULL),
chat_ts TEXT, chat_ts TEXT,
favorite INTEGER NOT NULL DEFAULT 0, 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_contact_requests_xcontact_id ON contact_requests(xcontact_id);
CREATE INDEX idx_contacts_xcontact_id ON contacts(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_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( CREATE UNIQUE INDEX idx_chat_items_direct_shared_msg_id ON chat_items(
user_id, user_id,
contact_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_user_id ON group_members(user_id);
CREATE INDEX idx_group_members_invited_by ON group_members(invited_by); 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_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_chat_item_id ON groups(chat_item_id);
CREATE INDEX idx_groups_group_profile_id ON groups(group_profile_id); CREATE INDEX idx_groups_group_profile_id ON groups(group_profile_id);
CREATE INDEX idx_messages_group_id ON messages(group_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, item_status,
user_mention 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 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)) 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 :: 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} let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
fullGroupPreferences = mergeGroupPreferences groupPreferences fullGroupPreferences = mergeGroupPreferences groupPreferences
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences}
businessChat = toBusinessChatInfo businessRow 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 :: 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)) = 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 SELECT
-- GroupInfo -- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, 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, 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 -- 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, 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, businessChat :: Maybe BusinessChatInfo,
fullGroupPreferences :: FullGroupPreferences, fullGroupPreferences :: FullGroupPreferences,
membership :: GroupMember, membership :: GroupMember,
hostConnCustomUserProfileId :: Maybe ProfileId,
chatSettings :: ChatSettings, chatSettings :: ChatSettings,
createdAt :: UTCTime, createdAt :: UTCTime,
updatedAt :: UTCTime, updatedAt :: UTCTime,

View file

@ -41,19 +41,6 @@ testAgentDB = "tests/tmp/test_agent.db"
appSchema :: FilePath appSchema :: FilePath
appSchema = "src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql" 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 :: FilePath
appLint = "src/Simplex/Chat/Store/SQLite/Migrations/chat_lint.sql" 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 -- sequence table moves down to the end of the file
"20241023_chat_item_autoincrement_id", "20241023_chat_item_autoincrement_id",
-- indexes move down to the end of the file -- 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 getSchema :: FilePath -> FilePath -> IO String
@ -158,7 +147,10 @@ saveQueryPlans = it "verify and overwrite query plans" $ \TestParams {chatQueryS
appChatQueryPlans appChatQueryPlans
chatQueryStats chatQueryStats
(createChatStore (DBOpts testDB "" False True TQOff) MCError) (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') <- (agentSavedPlans, agentSavedPlans') <-
updatePlans updatePlans
appAgentQueryPlans appAgentQueryPlans