mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-27 13:39:54 +00:00
core: open chat on "new" unread items (contigous unread aread in the end) (#6724)
* core: open chat on "new" unread items (contigous unread aread in the end) * move * ui: match first unread logic with core --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
co-authored by
Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
parent
f0a3d163dd
commit
f51d8a5a87
@@ -351,6 +351,13 @@ struct ChatView: View {
|
||||
|
||||
if let openAround = chatModel.openAroundItemId, let index = mergedItems.boxedValue.indexInParentItems[openAround] {
|
||||
scrollView.scrollToItem(index)
|
||||
} else if let viewedIdx = mergedItems.boxedValue.items.firstIndex(where: { !$0.hasUnread() }) {
|
||||
// scroll to first unread after last viewed item (items reversed: 0 = newest)
|
||||
if viewedIdx > 0 {
|
||||
scrollView.scrollToItem(viewedIdx - 1)
|
||||
} else {
|
||||
scrollView.scrollToBottom()
|
||||
}
|
||||
} else if let unreadIndex = mergedItems.boxedValue.items.lastIndex(where: { $0.hasUnread() }) {
|
||||
scrollView.scrollToItem(unreadIndex)
|
||||
} else {
|
||||
|
||||
+11
-1
@@ -1753,7 +1753,17 @@ fun BoxScope.ChatItemsList(
|
||||
val hoveredItemId = remember { mutableStateOf(null as Long?) }
|
||||
val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, resetListState.value, saver = LazyListState.Saver) {
|
||||
val openAroundItemId = chatModel.openAroundItemId.value
|
||||
val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: mergedItems.value.items.indexOfLast { it.hasUnread() }
|
||||
val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: run {
|
||||
// scroll to first unread after last viewed item (items reversed: 0 = newest)
|
||||
val viewedIdx = mergedItems.value.items.indexOfFirst { !it.hasUnread() }
|
||||
if (viewedIdx > 0) {
|
||||
viewedIdx - 1
|
||||
} else if (viewedIdx < 0) {
|
||||
mergedItems.value.items.indexOfLast { it.hasUnread() }
|
||||
} else {
|
||||
0 // viewed is bottom item, scroll to bottom
|
||||
}
|
||||
}
|
||||
val reportsState = reportsListState
|
||||
if (openAroundItemId != null) {
|
||||
highlightedItems.value += openAroundItemId
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
# Initial chat open: jump to last unread block
|
||||
|
||||
## Problem
|
||||
|
||||
When opening a chat with unread messages, the app always scrolls to the oldest unread message (`minUnreadItemId`). For casual group members with hundreds of unreads, this forces them to scroll through the entire backlog to reach new messages.
|
||||
|
||||
The bottom circle scrolls to the latest messages without marking all as read (by design — moderators use this to reply quickly, then return to the top circle to read sequentially). But the next time the chat opens, it jumps back to the oldest unread.
|
||||
|
||||
Users want the initial open to skip old unreads and land on the "new" ones — messages that arrived after their last interaction.
|
||||
|
||||
## Design
|
||||
|
||||
Change `CPInitial` to use a different pivot for `getDirectChatAround'` / `getGroupChatAround'`.
|
||||
|
||||
Currently the pivot is `minUnreadItemId` (absolute first unread). Instead, try `maxViewedItemId` (last non-unread item in sort order) first. If not found, fall back to `minUnreadItemId`. The `getAround'` function is unchanged — it always loads `CRBefore`/`CRAfter` around the pivot and includes it.
|
||||
|
||||
**maxViewedItemId**: the last item in sort order that is not `CISRcvNew`.
|
||||
- "Viewed" means received read or sent — any `item_status != CISRcvNew`.
|
||||
|
||||
### Why include works for both pivots
|
||||
|
||||
`getDirectChatAround'` always includes the pivot in the result. When the pivot is maxViewed (a read item), including it adds one extra read item — harmless. The client scrolls to the first unread in the loaded items, which is the first item in `afterCIs` (the new unreads after the gap). The include/exclude distinction is unnecessary.
|
||||
|
||||
### Sort order
|
||||
|
||||
- Groups: `(item_ts, chat_item_id)`
|
||||
- Direct chats: `(created_at, chat_item_id)`
|
||||
|
||||
### Cases
|
||||
|
||||
Items in display order (left = oldest/top, right = newest/bottom). U = unread (`CISRcvNew`), R = not unread (read, sent, event).
|
||||
|
||||
**Case 1: Unreads contiguous from bottom, no gap**
|
||||
```
|
||||
R...R U...U
|
||||
↑ maxViewed (last R) used as pivot
|
||||
```
|
||||
`afterCIs` = the unreads. Same as current behavior.
|
||||
|
||||
**Case 2: Gap, then new unreads at bottom**
|
||||
```
|
||||
R...R U...U R...R U...U
|
||||
↑ maxViewed used as pivot
|
||||
```
|
||||
`afterCIs` = new unreads only. Skips old unreads. This is the desired improvement.
|
||||
|
||||
**Case 3: Gap at bottom, no new unreads**
|
||||
```
|
||||
R...R U...U R...R
|
||||
↑ maxViewed used as pivot
|
||||
```
|
||||
`afterCIs` = empty. Items loaded are the latest. Old unreads reachable via top circle.
|
||||
|
||||
**Case 4: All unread**
|
||||
```
|
||||
U...U
|
||||
```
|
||||
maxViewed = NULL. Fall back to `minUnreadItemId` as pivot. Current behavior.
|
||||
|
||||
**Case 5: No unreads**
|
||||
|
||||
`maxViewedItemId` returns some item but `minUnreadItemId` returns `Nothing` — no unreads exist. Handled by stats showing zero unreads. `getAround'` loads items around maxViewed, which are the latest items.
|
||||
|
||||
Actually: maxViewed is always found when items exist (every chat has at least sent items or read items unless case 4). So the flow is: maxViewed found → load around it → stats show 0 unreads → client shows latest items.
|
||||
|
||||
### No UI changes needed
|
||||
|
||||
Only `CPInitial` backend logic changes. The top circle, unread counter, unread separator, and all pagination continue to use `minUnreadItemId` as before.
|
||||
|
||||
### Out-of-order delivery
|
||||
|
||||
A late-arriving group message with old `item_ts` but recent `created_at` sorts into the old unread block in display order and gets skipped on initial open. This is acceptable — the top circle still reaches it.
|
||||
|
||||
### Notes (local chat)
|
||||
|
||||
Notes (`getLocalChatInitial_`) have unread handling in code but it's dead — all items are sent, `CISRcvNew` never occurs. No change needed.
|
||||
|
||||
### Open concern
|
||||
|
||||
In case 2, `CRBefore(maxViewed)` loads items before the gap, which may include old unreads. The client's scroll logic finds the first unread in loaded items (`lastIndex(where: hasUnread)` in reversed list), which could be an old unread from `beforeCIs` rather than a new unread from `afterCIs`. To be validated during testing — if problematic, may need client-side adjustment or limiting `beforeCIs` count.
|
||||
|
||||
## Implementation
|
||||
|
||||
### Files to modify
|
||||
|
||||
`src/Simplex/Chat/Store/Messages.hs` — all changes are here.
|
||||
|
||||
### New functions
|
||||
|
||||
#### Direct chats
|
||||
|
||||
```haskell
|
||||
-- max viewed item: received read or sent (any item_status != CISRcvNew)
|
||||
getContactMaxViewedItemId_ :: DB.Connection -> User -> Contact -> IO (Maybe ChatItemId)
|
||||
```
|
||||
|
||||
Query:
|
||||
```sql
|
||||
SELECT chat_item_id
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND contact_id = ? AND item_status != ?
|
||||
ORDER BY created_at DESC, chat_item_id DESC
|
||||
LIMIT 1
|
||||
```
|
||||
|
||||
#### Groups
|
||||
|
||||
```haskell
|
||||
-- max viewed item: received read or sent (any item_status != CISRcvNew)
|
||||
getGroupMaxViewedItemId_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ExceptT StoreError IO (Maybe ChatItemId)
|
||||
```
|
||||
|
||||
Mirrors `queryUnreadGroupItems` structure but with `item_status != ?` instead of `item_status = ?`. Handles the same 4-case scope/content filter dispatch. New function `queryViewedGroupItems`.
|
||||
|
||||
Query (for the no-scope, no-content-filter case):
|
||||
```sql
|
||||
SELECT chat_item_id
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL
|
||||
AND item_status != ?
|
||||
ORDER BY item_ts DESC, chat_item_id DESC
|
||||
LIMIT 1
|
||||
```
|
||||
|
||||
### Modified functions
|
||||
|
||||
#### `getDirectChatInitial_`
|
||||
|
||||
Current:
|
||||
```haskell
|
||||
getDirectChatInitial_ db user ct contentFilter count = do
|
||||
liftIO (getContactMinUnreadId_ db user ct) >>= \case
|
||||
Just minUnreadItemId -> do
|
||||
unreadCount <- liftIO $ getContactUnreadCount_ db user ct
|
||||
let stats = emptyChatStats {unreadCount, minUnreadItemId}
|
||||
getDirectChatAround' db user ct contentFilter minUnreadItemId count "" stats
|
||||
Nothing -> (,Just $ NavigationInfo 0 0) <$> getDirectChatLast_ db user ct contentFilter count ""
|
||||
```
|
||||
|
||||
New — only the pivot source changes, rest stays the same:
|
||||
```haskell
|
||||
getDirectChatInitial_ db user ct contentFilter count = do
|
||||
liftIO (getContactMaxViewedItemId_ db user ct >>= maybe (getContactMinUnreadId_ db user ct) (pure . Just)) >>= \case
|
||||
Just pivotId -> do
|
||||
unreadCount <- liftIO $ getContactUnreadCount_ db user ct
|
||||
minUnreadItemId <- fromMaybe 0 <$> liftIO (getContactMinUnreadId_ db user ct)
|
||||
let stats = emptyChatStats {unreadCount, minUnreadItemId}
|
||||
getDirectChatAround' db user ct contentFilter pivotId count "" stats
|
||||
Nothing -> (,Just $ NavigationInfo 0 0) <$> getDirectChatLast_ db user ct contentFilter count ""
|
||||
```
|
||||
|
||||
#### `getGroupChatInitial_`
|
||||
|
||||
Same minimal change — only the pivot source:
|
||||
```haskell
|
||||
getGroupChatInitial_ db user g scopeInfo_ contentFilter count = do
|
||||
(getGroupMaxViewedItemId_ db user g scopeInfo_ contentFilter >>= maybe (getGroupMinUnreadId_ db user g scopeInfo_ contentFilter) (pure . Just)) >>= \case
|
||||
Just pivotId -> do
|
||||
stats <- getGroupStats_ db user g scopeInfo_
|
||||
getGroupChatAround' db user g scopeInfo_ contentFilter pivotId count "" stats
|
||||
Nothing -> do
|
||||
stats <- liftIO $ getStats 0 (0, 0)
|
||||
(,Just $ NavigationInfo 0 0) <$> getGroupChatLast_ db user g scopeInfo_ contentFilter count "" stats
|
||||
where
|
||||
getStats minUnreadItemId (unreadCount, unreadMentions) = do
|
||||
reportsCount <- getGroupReportsCount_ db user g False
|
||||
pure ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat = False}
|
||||
```
|
||||
|
||||
### Summary of all affected functions
|
||||
|
||||
| Function | Change |
|
||||
|----------|--------|
|
||||
| `getDirectChatInitial_` | Try maxViewed first, fall back to minUnread, same `getAround'` call |
|
||||
| `getGroupChatInitial_` | Same |
|
||||
| **New:** `getContactMaxViewedItemId_` | MAX non-unread by (created_at DESC, id DESC) |
|
||||
| **New:** `getGroupMaxViewedItemId_` | MAX non-unread with scope/filter dispatch |
|
||||
| **New:** `queryViewedGroupItems` | Like `queryUnreadGroupItems` but `item_status != ?` |
|
||||
|
||||
Nothing else changes. `getDirectChatAround'`, `getGroupChatAround'`, `getDirectChatAround_`, `getGroupChatAround_`, `getContactMinUnreadId_`, `getGroupMinUnreadId_`, `getContactStats_`, `getGroupStats_`, `getChatItemIDs`, `NavigationInfo` computation, mark-read operations, UI code — all unchanged.
|
||||
|
||||
### Performance
|
||||
|
||||
- `maxViewedItemId` query: scans backward from the largest sort key, skipping unread items. Fast when there are recent read/sent items (the common case). Worst case: all items are unread — returns `Nothing` and falls back to minUnread.
|
||||
|
||||
- All other queries are existing code, same performance.
|
||||
|
||||
- Queries run only during `CPInitial` (chat open). No writes.
|
||||
|
||||
### Testing
|
||||
|
||||
1. Open chat with unreads, no prior interaction → same as current (case 1/4)
|
||||
2. Open chat, jump to bottom (marks bottom screen read), close, reopen → lands on new unreads after the gap (case 2)
|
||||
3. Open chat, jump to bottom, reply, close, new messages arrive, reopen → lands on new messages (case 2)
|
||||
4. Open chat, jump to bottom, close, no new messages, reopen → loads around last viewed item at bottom (case 3)
|
||||
5. Open chat, read from top (marks first screen read), close, reopen → lands on next unread after first screen, same as current (case 1)
|
||||
6. Group with scope (member support chat) → same behavior with scope filter applied
|
||||
7. Group with content filter (reports) → same behavior with content filter applied
|
||||
@@ -1303,7 +1303,8 @@ getDirectChatInitial_ db user ct contentFilter count = do
|
||||
Just minUnreadItemId -> do
|
||||
unreadCount <- liftIO $ getContactUnreadCount_ db user ct
|
||||
let stats = emptyChatStats {unreadCount, minUnreadItemId}
|
||||
getDirectChatAround' db user ct contentFilter minUnreadItemId count "" stats
|
||||
pivotId <- liftIO $ fromMaybe minUnreadItemId <$> getContactMaxViewedItemId_ db user ct
|
||||
getDirectChatAround' db user ct contentFilter pivotId count "" stats
|
||||
Nothing -> (,Just $ NavigationInfo 0 0) <$> getDirectChatLast_ db user ct contentFilter count ""
|
||||
|
||||
getContactStats_ :: DB.Connection -> User -> Contact -> IO ChatStats
|
||||
@@ -1326,6 +1327,21 @@ getContactMinUnreadId_ db User {userId} Contact {contactId} =
|
||||
|]
|
||||
(userId, contactId, CISRcvNew)
|
||||
|
||||
-- max viewed item: received read or sent (any item_status != CISRcvNew)
|
||||
getContactMaxViewedItemId_ :: DB.Connection -> User -> Contact -> IO (Maybe ChatItemId)
|
||||
getContactMaxViewedItemId_ db User {userId} Contact {contactId} =
|
||||
fmap join . maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT chat_item_id
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND contact_id = ? AND item_status != ?
|
||||
ORDER BY created_at DESC, chat_item_id DESC
|
||||
LIMIT 1
|
||||
|]
|
||||
(userId, contactId, CISRcvNew)
|
||||
|
||||
getContactUnreadCount_ :: DB.Connection -> User -> Contact -> IO Int
|
||||
getContactUnreadCount_ db User {userId} Contact {contactId} =
|
||||
fromOnly . head
|
||||
@@ -1633,7 +1649,8 @@ getGroupChatInitial_ db user g scopeInfo_ contentFilter count = do
|
||||
Just minUnreadItemId -> do
|
||||
unreadCounts <- getGroupUnreadCount_ db user g scopeInfo_ Nothing
|
||||
stats <- liftIO $ getStats minUnreadItemId unreadCounts
|
||||
getGroupChatAround' db user g scopeInfo_ contentFilter minUnreadItemId count "" stats
|
||||
pivotId <- fromMaybe minUnreadItemId <$> getGroupMaxViewedItemId_ db user g scopeInfo_ contentFilter
|
||||
getGroupChatAround' db user g scopeInfo_ contentFilter pivotId count "" stats
|
||||
Nothing -> do
|
||||
stats <- liftIO $ getStats 0 (0, 0)
|
||||
(,Just $ NavigationInfo 0 0) <$> getGroupChatLast_ db user g scopeInfo_ contentFilter count "" stats
|
||||
@@ -1652,14 +1669,23 @@ getGroupStats_ db user g scopeInfo_ = do
|
||||
getGroupMinUnreadId_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ExceptT StoreError IO (Maybe ChatItemId)
|
||||
getGroupMinUnreadId_ db user g scopeInfo_ contentFilter =
|
||||
fmap join . maybeFirstRow fromOnly $
|
||||
queryUnreadGroupItems db user g scopeInfo_ contentFilter baseQuery orderLimit
|
||||
queryUnreadGroupItems db user g scopeInfo_ contentFilter " item_status = ? " baseQuery orderLimit
|
||||
where
|
||||
baseQuery = "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? "
|
||||
orderLimit = " ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1"
|
||||
|
||||
-- max viewed item: received read or sent (any item_status != CISRcvNew)
|
||||
getGroupMaxViewedItemId_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ExceptT StoreError IO (Maybe ChatItemId)
|
||||
getGroupMaxViewedItemId_ db user g scopeInfo_ contentFilter =
|
||||
fmap join . maybeFirstRow fromOnly $
|
||||
queryUnreadGroupItems db user g scopeInfo_ contentFilter " item_status != ? " baseQuery orderLimit
|
||||
where
|
||||
baseQuery = "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? "
|
||||
orderLimit = " ORDER BY item_ts DESC, chat_item_id DESC LIMIT 1"
|
||||
|
||||
getGroupUnreadCount_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ExceptT StoreError IO (Int, Int)
|
||||
getGroupUnreadCount_ db user g scopeInfo_ contentFilter =
|
||||
head <$> queryUnreadGroupItems db user g scopeInfo_ contentFilter baseQuery ""
|
||||
head <$> queryUnreadGroupItems db user g scopeInfo_ contentFilter " item_status = ? " baseQuery ""
|
||||
where
|
||||
baseQuery = "SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? "
|
||||
|
||||
@@ -1671,26 +1697,26 @@ getGroupReportsCount_ db User {userId} GroupInfo {groupId} archived =
|
||||
"SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0"
|
||||
(userId, groupId, MCReport_, BI archived)
|
||||
|
||||
queryUnreadGroupItems :: FromRow r => DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> Query -> Query -> ExceptT StoreError IO [r]
|
||||
queryUnreadGroupItems db User {userId} GroupInfo {groupId} scopeInfo_ contentFilter baseQuery orderLimit =
|
||||
queryUnreadGroupItems :: FromRow r => DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> Query -> Query -> Query -> ExceptT StoreError IO [r]
|
||||
queryUnreadGroupItems db User {userId} GroupInfo {groupId} scopeInfo_ contentFilter statusCond baseQuery orderLimit =
|
||||
case (scopeInfo_, contentFilter) of
|
||||
(Nothing, Nothing) ->
|
||||
liftIO $
|
||||
DB.query
|
||||
db
|
||||
(baseQuery <> " AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status = ? " <> orderLimit)
|
||||
(baseQuery <> " AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND " <> statusCond <> orderLimit)
|
||||
(userId, groupId, CISRcvNew)
|
||||
(Nothing, Just mcTag) ->
|
||||
liftIO $
|
||||
DB.query
|
||||
db
|
||||
(baseQuery <> " AND msg_content_tag = ? AND item_status = ? " <> orderLimit)
|
||||
(baseQuery <> " AND msg_content_tag = ? AND " <> statusCond <> orderLimit)
|
||||
(userId, groupId, mcTag, CISRcvNew)
|
||||
(Just GCSIMemberSupport {groupMember_ = m}, Nothing) ->
|
||||
liftIO $
|
||||
DB.query
|
||||
db
|
||||
(baseQuery <> " AND group_scope_tag = ? AND group_scope_group_member_id IS NOT DISTINCT FROM ? AND item_status = ? " <> orderLimit)
|
||||
(baseQuery <> " AND group_scope_tag = ? AND group_scope_group_member_id IS NOT DISTINCT FROM ? AND " <> statusCond <> orderLimit)
|
||||
(userId, groupId, GCSTMemberSupport_, groupMemberId' <$> m, CISRcvNew)
|
||||
(Just _scope, Just _mcTag) ->
|
||||
throwError $ SEInternalError "group scope and content filter are not supported together"
|
||||
|
||||
@@ -1197,10 +1197,6 @@ Query: UPDATE connections SET smp_agent_version = ? WHERE conn_id = ?
|
||||
Plan:
|
||||
SEARCH connections USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query: UPDATE deleted_snd_chunk_replicas SET delay = ?, retries = retries + 1, updated_at = ? WHERE deleted_snd_chunk_replica_id = ?
|
||||
Plan:
|
||||
SEARCH deleted_snd_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE messages SET msg_body = x'' WHERE conn_id = ? AND internal_id = ?
|
||||
Plan:
|
||||
SEARCH messages USING PRIMARY KEY (conn_id=? AND internal_id=?)
|
||||
@@ -1209,6 +1205,10 @@ Query: UPDATE ratchets SET ratchet_state = ? WHERE conn_id = ?
|
||||
Plan:
|
||||
SEARCH ratchets USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query: UPDATE rcv_file_chunk_replicas SET delay = ?, retries = retries + 1, updated_at = ? WHERE rcv_file_chunk_replica_id = ?
|
||||
Plan:
|
||||
SEARCH rcv_file_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE rcv_file_chunk_replicas SET received = 1, updated_at = ? WHERE rcv_file_chunk_replica_id = ?
|
||||
Plan:
|
||||
SEARCH rcv_file_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
@@ -3133,6 +3133,16 @@ Query:
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_contact_id (contact_id=?)
|
||||
|
||||
Query:
|
||||
SELECT chat_item_id
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND contact_id = ? AND item_status != ?
|
||||
ORDER BY created_at DESC, chat_item_id DESC
|
||||
LIMIT 1
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_contacts_created_at (user_id=? AND contact_id=?)
|
||||
|
||||
Query:
|
||||
SELECT chat_item_id
|
||||
FROM chat_items
|
||||
@@ -4733,6 +4743,14 @@ Query:
|
||||
Plan:
|
||||
SEARCH protocol_servers USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE rcv_files
|
||||
SET to_receive = 1, user_approved_relays = ?, updated_at = ?
|
||||
WHERE file_id = ?
|
||||
|
||||
Plan:
|
||||
SEARCH rcv_files USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE remote_controllers
|
||||
SET ctrl_device_name = ?, dh_priv_key = ?, prev_dh_priv_key = dh_priv_key
|
||||
@@ -6043,7 +6061,7 @@ Query: SELECT COUNT(1) FROM groups WHERE user_id = ? AND chat_item_ttl > 0
|
||||
Plan:
|
||||
SEARCH groups USING INDEX idx_groups_chat_ts (user_id=?)
|
||||
|
||||
Query: SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status = ?
|
||||
Query: SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status = ?
|
||||
Plan:
|
||||
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_stats_all (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_status=?)
|
||||
|
||||
@@ -6085,7 +6103,11 @@ Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND contact_id = ?
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_direct_shared_msg_id (user_id=? AND contact_id=? AND shared_msg_id=?)
|
||||
|
||||
Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status = ? ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1
|
||||
Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status != ? ORDER BY item_ts DESC, chat_item_id DESC LIMIT 1
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=?)
|
||||
|
||||
Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status = ? ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1
|
||||
Plan:
|
||||
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_status=?)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user