Compare commits

...

4 Commits

Author SHA1 Message Date
spaced4ndy
7bfb196e2a plan queries (to delete later) 2024-01-10 17:28:53 +04:00
spaced4ndy
1127b11bba indexes wip 2024-01-10 17:27:41 +04:00
spaced4ndy
8d66750f20 Merge branch 'master' into f/mark-read-all 2024-01-09 15:33:35 +04:00
spaced4ndy
50b71b3045 ios: fix "mark read" chat preview button not always marking unread items as read 2024-01-09 13:18:01 +04:00
5 changed files with 209 additions and 5 deletions

View File

@@ -844,7 +844,7 @@ func apiRejectContactRequest(contactReqId: Int64) async throws {
throw r
}
func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws {
func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)? = nil) async throws {
try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange))
}
@@ -1044,7 +1044,24 @@ func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async {
await markChatUnread(chat, unreadChat: false)
}
} catch {
logger.error("markChatRead apiChatRead error: \(responseError(error))")
logger.error("markChatRead error: \(responseError(error))")
}
}
func markChatReadAll(_ chat: Chat) async {
do {
if chat.chatStats.unreadCount > 0 {
let cInfo = chat.chatInfo
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId)
await MainActor.run {
withAnimation { ChatModel.shared.markChatItemsRead(cInfo) }
}
}
if chat.chatStats.unreadChat {
await markChatUnread(chat, unreadChat: false)
}
} catch {
logger.error("markChatReadAll error: \(responseError(error))")
}
}

View File

@@ -210,7 +210,7 @@ struct ChatListNavLink: View {
@ViewBuilder private func markReadButton() -> some View {
if chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat {
Button {
Task { await markChatRead(chat) }
Task { await markChatReadAll(chat) }
} label: {
Label("Read", systemImage: "checkmark")
}

View File

@@ -115,7 +115,7 @@ public enum ChatCommand {
case apiGetCallInvitations
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
case apiGetNetworkStatuses
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)?)
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
case receiveFile(fileId: Int64, encrypted: Bool?, inline: Bool?)
case setFileToReceive(fileId: Int64, encrypted: Bool?)
@@ -265,7 +265,11 @@ public enum ChatCommand {
case .apiGetCallInvitations: return "/_call get"
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
case .apiGetNetworkStatuses: return "/_network_statuses"
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
case let .apiChatRead(type, id, itemRange): if let (from, to) = itemRange {
return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
} else {
return "/_read chat \(ref(type, id))"
}
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
case let .receiveFile(fileId, encrypt, inline): return "/freceive \(fileId)\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
case let .setFileToReceive(fileId, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("encrypt", encrypt))"

View File

@@ -0,0 +1,20 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20240110_indexes where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20240110_indexes :: Query
m20240110_indexes =
[sql|
CREATE INDEX idx_chat_items_direct_item_status ON chat_items(user_id, contact_id, item_status);
CREATE INDEX idx_chat_items_group_item_status ON chat_items(user_id, group_id, item_status);
|]
down_m20240110_indexes :: Query
down_m20240110_indexes =
[sql|
DROP INDEX idx_chat_items_group_item_status;
DROP INDEX idx_chat_items_direct_item_status;
|]

View File

@@ -0,0 +1,163 @@
plan
-- direct read
UPDATE chat_items SET item_status = ?, updated_at = ?
WHERE user_id = ? AND contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
EXPLAIN QUERY PLAN UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?;
UPDATE chat_items SET item_status = ?, updated_at = ?
WHERE user_id = ? AND contact_id = ? AND item_status = ?
EXPLAIN QUERY PLAN UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND contact_id = ? AND item_status = ?;
-- direct get chat
-- last
SELECT
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live,
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol,
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id
WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%'
ORDER BY i.created_at DESC, i.chat_item_id DESC
LIMIT ?
EXPLAIN QUERY PLAN SELECT i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol, ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM chat_items i LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' ORDER BY i.created_at DESC, i.chat_item_id DESC LIMIT ?;
-- before
SELECT
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live,
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol,
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id
WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%'
AND i.chat_item_id < ?
ORDER BY i.created_at DESC, i.chat_item_id DESC
LIMIT ?
EXPLAIN QUERY PLAN SELECT i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol, ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent FROM chat_items i LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' AND i.chat_item_id < ? ORDER BY i.created_at DESC, i.chat_item_id DESC LIMIT ?;
-- direct get previews
-- last
SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat
FROM contacts ct
LEFT JOIN (
SELECT contact_id, chat_item_id, MAX(created_at)
FROM chat_items
GROUP BY contact_id
) LastItems ON LastItems.contact_id = ct.contact_id
LEFT JOIN (
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
FROM chat_items
WHERE item_status = :rcv_new
GROUP BY contact_id
) ChatStats ON ChatStats.contact_id = ct.contact_id
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
ORDER BY ts DESC LIMIT :count
EXPLAIN QUERY PLAN SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat FROM contacts ct LEFT JOIN ( SELECT contact_id, chat_item_id, MAX(created_at) FROM chat_items GROUP BY contact_id ) LastItems ON LastItems.contact_id = ct.contact_id LEFT JOIN ( SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE item_status = :rcv_new GROUP BY contact_id ) ChatStats ON ChatStats.contact_id = ct.contact_id WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used ORDER BY ts DESC LIMIT :count;
-- before
SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat
FROM contacts ct
LEFT JOIN (
SELECT contact_id, chat_item_id, MAX(created_at)
FROM chat_items
GROUP BY contact_id
) LastItems ON LastItems.contact_id = ct.contact_id
LEFT JOIN (
SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
FROM chat_items
WHERE item_status = :rcv_new
GROUP BY contact_id
) ChatStats ON ChatStats.contact_id = ct.contact_id
WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used
AND ts < :ts ORDER BY ts DESC LIMIT :count
EXPLAIN QUERY PLAN SELECT ct.contact_id, ct.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), ct.unread_chat FROM contacts ct LEFT JOIN ( SELECT contact_id, chat_item_id, MAX(created_at) FROM chat_items GROUP BY contact_id ) LastItems ON LastItems.contact_id = ct.contact_id LEFT JOIN ( SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE item_status = :rcv_new GROUP BY contact_id ) ChatStats ON ChatStats.contact_id = ct.contact_id WHERE ct.user_id = :user_id AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used AND ts < :ts ORDER BY ts DESC LIMIT :count;
-- group read
UPDATE chat_items SET item_status = ?, updated_at = ?
WHERE user_id = ? AND group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
EXPLAIN QUERY PLAN UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?;
UPDATE chat_items SET item_status = ?, updated_at = ?
WHERE user_id = ? AND group_id = ? AND item_status = ?
EXPLAIN QUERY PLAN UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND group_id = ? AND item_status = ?;
-- group get chat
-- last
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY item_ts DESC, chat_item_id DESC
LIMIT ?
EXPLAIN QUERY PLAN SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?;
-- before
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?))
ORDER BY item_ts DESC, chat_item_id DESC
LIMIT ?
EXPLAIN QUERY PLAN SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?)) ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?;
-- get group previews
-- last
SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat
FROM groups g
LEFT JOIN (
SELECT group_id, chat_item_id, MAX(item_ts)
FROM chat_items
GROUP BY group_id
) LastItems ON LastItems.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
FROM chat_items
WHERE item_status = :rcv_new
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
WHERE g.user_id = :user_id
ORDER BY ts DESC LIMIT :count
EXPLAIN QUERY PLAN SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( SELECT group_id, chat_item_id, MAX(item_ts) FROM chat_items GROUP BY group_id ) LastItems ON LastItems.group_id = g.group_id LEFT JOIN ( SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE item_status = :rcv_new GROUP BY group_id ) ChatStats ON ChatStats.group_id = g.group_id WHERE g.user_id = :user_id ORDER BY ts DESC LIMIT :count;
-- before
SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat
FROM groups g
LEFT JOIN (
SELECT group_id, chat_item_id, MAX(item_ts)
FROM chat_items
GROUP BY group_id
) LastItems ON LastItems.group_id = g.group_id
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread
FROM chat_items
WHERE item_status = :rcv_new
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
WHERE g.user_id = :user_id
AND ts < :ts ORDER BY ts DESC LIMIT :count
EXPLAIN QUERY PLAN SELECT g.group_id, g.chat_ts as ts, LastItems.chat_item_id, COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat FROM groups g LEFT JOIN ( SELECT group_id, chat_item_id, MAX(item_ts) FROM chat_items GROUP BY group_id ) LastItems ON LastItems.group_id = g.group_id LEFT JOIN ( SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread FROM chat_items WHERE item_status = :rcv_new GROUP BY group_id ) ChatStats ON ChatStats.group_id = g.group_id WHERE g.user_id = :user_id AND ts < :ts ORDER BY ts DESC LIMIT :count;