mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 02:08:57 +00:00
Optimize getUnreadByConversationAndMarkRead
This commit is contained in:
@@ -3334,16 +3334,32 @@ export function _storyIdPredicate(
|
|||||||
storyId: string | undefined,
|
storyId: string | undefined,
|
||||||
includeStoryReplies: boolean
|
includeStoryReplies: boolean
|
||||||
): QueryFragment {
|
): QueryFragment {
|
||||||
|
return _storyIdPredicateAndInfo(storyId, includeStoryReplies).predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _storyIdPredicateAndInfo(
|
||||||
|
storyId: string | undefined,
|
||||||
|
includeStoryReplies: boolean
|
||||||
|
): {
|
||||||
|
predicate: QueryFragment;
|
||||||
|
isFilteringOnStoryId: boolean;
|
||||||
|
} {
|
||||||
// This is unintuitive, but 'including story replies' means that we need replies to
|
// This is unintuitive, but 'including story replies' means that we need replies to
|
||||||
// lots of different stories. So, we remove the storyId check with a clause that will
|
// lots of different stories. So, we remove the storyId check with a clause that will
|
||||||
// always be true. We don't just return TRUE because we want to use our passed-in
|
// always be true. We don't just return TRUE because we want to use our passed-in
|
||||||
// $storyId parameter.
|
// $storyId parameter.
|
||||||
if (includeStoryReplies && storyId === undefined) {
|
if (includeStoryReplies && storyId === undefined) {
|
||||||
return sqlFragment`NULL IS NULL`;
|
return {
|
||||||
|
predicate: sqlFragment`NULL IS NULL`,
|
||||||
|
isFilteringOnStoryId: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// In contrast to: replies to a specific story
|
// In contrast to: replies to a specific story
|
||||||
return sqlFragment`storyId IS ${storyId ?? null}`;
|
return {
|
||||||
|
predicate: sqlFragment`storyId IS ${storyId ?? null}`,
|
||||||
|
isFilteringOnStoryId: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUnreadByConversationAndMarkRead(
|
function getUnreadByConversationAndMarkRead(
|
||||||
@@ -3367,6 +3383,9 @@ function getUnreadByConversationAndMarkRead(
|
|||||||
return db.transaction(() => {
|
return db.transaction(() => {
|
||||||
const expirationStartTimestamp = Math.min(now, readAt ?? Infinity);
|
const expirationStartTimestamp = Math.min(now, readAt ?? Infinity);
|
||||||
|
|
||||||
|
const { predicate: storyReplyFilter, isFilteringOnStoryId } =
|
||||||
|
_storyIdPredicateAndInfo(storyId, includeStoryReplies);
|
||||||
|
|
||||||
const updateExpirationFragment = sqlFragment`
|
const updateExpirationFragment = sqlFragment`
|
||||||
UPDATE messages
|
UPDATE messages
|
||||||
INDEXED BY messages_conversationId_expirationStartTimestamp
|
INDEXED BY messages_conversationId_expirationStartTimestamp
|
||||||
@@ -3374,7 +3393,7 @@ function getUnreadByConversationAndMarkRead(
|
|||||||
expirationStartTimestamp = ${expirationStartTimestamp}
|
expirationStartTimestamp = ${expirationStartTimestamp}
|
||||||
WHERE
|
WHERE
|
||||||
conversationId = ${conversationId} AND
|
conversationId = ${conversationId} AND
|
||||||
(${_storyIdPredicate(storyId, includeStoryReplies)}) AND
|
${storyReplyFilter} AND
|
||||||
type IN ('incoming', 'poll-terminate') AND
|
type IN ('incoming', 'poll-terminate') AND
|
||||||
hasExpireTimer IS 1 AND
|
hasExpireTimer IS 1 AND
|
||||||
received_at <= ${readMessageReceivedAt}
|
received_at <= ${readMessageReceivedAt}
|
||||||
@@ -3403,56 +3422,68 @@ function getUnreadByConversationAndMarkRead(
|
|||||||
updateLateExpirationStartParams
|
updateLateExpirationStartParams
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const indexToUse = isFilteringOnStoryId
|
||||||
|
? sqlFragment`messages_unseen_with_story`
|
||||||
|
: sqlFragment`messages_unseen_no_story`;
|
||||||
|
|
||||||
const [selectQuery, selectParams] = sql`
|
const [selectQuery, selectParams] = sql`
|
||||||
SELECT
|
SELECT
|
||||||
${sqlJoin(MESSAGE_COLUMNS_FRAGMENTS)}
|
id, readStatus, expirationStartTimestamp, sent_at, source, sourceServiceId, type
|
||||||
FROM messages
|
FROM messages
|
||||||
|
INDEXED BY ${indexToUse}
|
||||||
WHERE
|
WHERE
|
||||||
conversationId = ${conversationId} AND
|
conversationId = ${conversationId} AND
|
||||||
seenStatus = ${SeenStatus.Unseen} AND
|
seenStatus = ${SeenStatus.Unseen} AND
|
||||||
isStory = 0 AND
|
isStory = 0 AND
|
||||||
(${_storyIdPredicate(storyId, includeStoryReplies)}) AND
|
${storyReplyFilter} AND
|
||||||
received_at <= ${readMessageReceivedAt}
|
received_at <= ${readMessageReceivedAt}
|
||||||
ORDER BY received_at DESC, sent_at DESC;
|
ORDER BY received_at DESC, sent_at DESC;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const rows = db
|
const rows = db
|
||||||
.prepare(selectQuery)
|
.prepare(selectQuery)
|
||||||
.all<MessageTypeUnhydrated>(selectParams);
|
.all<
|
||||||
|
Pick<
|
||||||
const statusJsonPatch = JSON.stringify({
|
MessageTypeUnhydrated,
|
||||||
readStatus: ReadStatus.Read,
|
| 'id'
|
||||||
seenStatus: SeenStatus.Seen,
|
| 'readStatus'
|
||||||
});
|
| 'expirationStartTimestamp'
|
||||||
|
| 'sent_at'
|
||||||
|
| 'source'
|
||||||
|
| 'sourceServiceId'
|
||||||
|
| 'type'
|
||||||
|
>
|
||||||
|
>(selectParams);
|
||||||
|
|
||||||
const [updateStatusQuery, updateStatusParams] = sql`
|
const [updateStatusQuery, updateStatusParams] = sql`
|
||||||
UPDATE messages
|
UPDATE messages
|
||||||
|
INDEXED BY ${indexToUse}
|
||||||
SET
|
SET
|
||||||
readStatus = ${ReadStatus.Read},
|
readStatus = ${ReadStatus.Read},
|
||||||
seenStatus = ${SeenStatus.Seen},
|
seenStatus = ${SeenStatus.Seen}
|
||||||
json = json_patch(json, ${statusJsonPatch})
|
|
||||||
WHERE
|
WHERE
|
||||||
conversationId = ${conversationId} AND
|
conversationId = ${conversationId} AND
|
||||||
seenStatus = ${SeenStatus.Unseen} AND
|
seenStatus = ${SeenStatus.Unseen} AND
|
||||||
isStory = 0 AND
|
isStory = 0 AND
|
||||||
(${_storyIdPredicate(storyId, includeStoryReplies)}) AND
|
${storyReplyFilter} AND
|
||||||
received_at <= ${readMessageReceivedAt};
|
received_at <= ${readMessageReceivedAt};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
db.prepare(updateStatusQuery).run(updateStatusParams);
|
db.prepare(updateStatusQuery).run(updateStatusParams);
|
||||||
return hydrateMessages(db, rows).map(msg => {
|
return rows.map(msg => {
|
||||||
return {
|
return {
|
||||||
originalReadStatus: msg.readStatus,
|
originalReadStatus:
|
||||||
|
msg.readStatus == null ? undefined : (msg.readStatus as ReadStatus),
|
||||||
readStatus: ReadStatus.Read,
|
readStatus: ReadStatus.Read,
|
||||||
seenStatus: SeenStatus.Seen,
|
seenStatus: SeenStatus.Seen,
|
||||||
...pick(msg, [
|
id: msg.id,
|
||||||
'expirationStartTimestamp',
|
expirationStartTimestamp: dropNull(msg.expirationStartTimestamp),
|
||||||
'id',
|
sent_at: msg.sent_at || 0,
|
||||||
'sent_at',
|
source: dropNull(msg.source),
|
||||||
'source',
|
sourceServiceId: dropNull(msg.sourceServiceId) as
|
||||||
'sourceServiceId',
|
| ServiceIdString
|
||||||
'type',
|
| undefined,
|
||||||
]),
|
type: msg.type as MessageType['type'],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user