Optimize loading stories

This commit is contained in:
Fedor Indutny 2022-11-28 09:19:48 -08:00 committed by GitHub
parent a827cb7c4e
commit d6d53f9d18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 86 deletions

View file

@ -7,6 +7,7 @@ import type { StoryDataType } from '../state/ducks/stories';
import * as durations from '../util/durations';
import * as log from '../logging/log';
import dataInterface from '../sql/Client';
import type { GetAllStoriesResultType } from '../sql/Interface';
import {
getAttachmentsForMessage,
getPropsForAttachment,
@ -18,32 +19,10 @@ import { dropNull } from '../util/dropNull';
import { DurationInSeconds } from '../util/durations';
import { SIGNAL_ACI } from '../types/SignalConversation';
let storyData:
| Array<
MessageAttributesType & {
hasReplies?: boolean;
hasRepliesFromSelf?: boolean;
}
>
| undefined;
let storyData: GetAllStoriesResultType | undefined;
export async function loadStories(): Promise<void> {
const stories = await dataInterface.getAllStories({});
storyData = await Promise.all(
stories.map(async story => {
const [hasReplies, hasRepliesFromSelf] = await Promise.all([
dataInterface.hasStoryReplies(story.id),
dataInterface.hasStoryRepliesFromSelf(story.id),
]);
return {
...story,
hasReplies,
hasRepliesFromSelf,
};
})
);
storyData = await dataInterface.getAllStories({});
await repairUnexpiredStories();
}

View file

@ -353,6 +353,13 @@ export type GetKnownMessageAttachmentsResultType = Readonly<{
attachments: ReadonlyArray<string>;
}>;
export type GetAllStoriesResultType = ReadonlyArray<
MessageType & {
hasReplies: boolean;
hasRepliesFromSelf: boolean;
}
>;
export type DataInterface = {
close: () => Promise<void>;
removeDB: () => Promise<void>;
@ -520,9 +527,7 @@ export type DataInterface = {
getAllStories: (options: {
conversationId?: string;
sourceUuid?: UUIDStringType;
}) => Promise<Array<MessageType>>;
hasStoryReplies: (storyId: string) => Promise<boolean>;
hasStoryRepliesFromSelf: (storyId: string) => Promise<boolean>;
}) => Promise<GetAllStoriesResultType>;
// getNewerMessagesByConversation is JSON on server, full message on Client
getMessageMetricsForConversation: (
conversationId: string,

View file

@ -78,6 +78,7 @@ import type {
DeleteSentProtoRecipientOptionsType,
DeleteSentProtoRecipientResultType,
EmojiType,
GetAllStoriesResultType,
GetConversationRangeCenteredOnMessageResultType,
GetKnownMessageAttachmentsResultType,
GetUnreadByConversationAndMarkReadResultType,
@ -248,8 +249,6 @@ const dataInterface: ServerInterface = {
getTapToViewMessagesNeedingErase,
getOlderMessagesByConversation,
getAllStories,
hasStoryReplies,
hasStoryRepliesFromSelf,
getNewerMessagesByConversation,
getTotalUnreadForConversation,
getMessageMetricsForConversation,
@ -1770,22 +1769,21 @@ async function getMessageCount(conversationId?: string): Promise<number> {
function hasUserInitiatedMessages(conversationId: string): boolean {
const db = getInstance();
const row: { count: number } = db
const exists: number = db
.prepare<Query>(
`
SELECT COUNT(*) as count FROM
(
SELECT 1 FROM messages
WHERE
conversationId = $conversationId AND
isUserInitiatedMessage = 1
LIMIT 1
);
SELECT EXISTS(
SELECT 1 FROM messages
WHERE
conversationId = $conversationId AND
isUserInitiatedMessage = 1
);
`
)
.pluck()
.get({ conversationId });
return row.count !== 0;
return exists !== 0;
}
function saveMessageSync(
@ -2515,12 +2513,29 @@ async function getAllStories({
}: {
conversationId?: string;
sourceUuid?: UUIDStringType;
}): Promise<Array<MessageType>> {
}): Promise<GetAllStoriesResultType> {
const db = getInstance();
const rows: JSONRows = db
const rows: ReadonlyArray<{
json: string;
hasReplies: number;
hasRepliesFromSelf: number;
}> = db
.prepare<Query>(
`
SELECT json
SELECT
json,
(SELECT EXISTS(
SELECT 1
FROM messages as replies
WHERE replies.storyId IS messages.id
)) as hasReplies,
(SELECT EXISTS(
SELECT 1
FROM messages AS selfReplies
WHERE
selfReplies.storyId IS messages.id AND
selfReplies.type IS 'outgoing'
)) as hasRepliesFromSelf
FROM messages
WHERE
type IS 'story' AND
@ -2534,39 +2549,11 @@ async function getAllStories({
sourceUuid: sourceUuid || null,
});
return rows.map(row => jsonToObject(row.json));
}
async function hasStoryReplies(storyId: string): Promise<boolean> {
const db = getInstance();
const row: { count: number } = db
.prepare<Query>(
`
SELECT COUNT(*) as count
FROM messages
WHERE storyId IS $storyId;
`
)
.get({ storyId });
return row.count !== 0;
}
async function hasStoryRepliesFromSelf(storyId: string): Promise<boolean> {
const db = getInstance();
const sql = `
SELECT COUNT(*) as count
FROM messages
WHERE
storyId IS $storyId AND
type IS 'outgoing'
`;
const row: { count: number } = db.prepare<Query>(sql).get({ storyId });
return row.count !== 0;
return rows.map(row => ({
...jsonToObject(row.json),
hasReplies: row.hasReplies !== 0,
hasRepliesFromSelf: row.hasRepliesFromSelf !== 0,
}));
}
async function getNewerMessagesByConversation(
@ -3016,26 +3003,25 @@ async function hasGroupCallHistoryMessage(
): Promise<boolean> {
const db = getInstance();
const row: { 'count(*)': number } | undefined = db
const exists: number = db
.prepare<Query>(
`
SELECT count(*) FROM messages
WHERE conversationId = $conversationId
AND type = 'call-history'
AND json_extract(json, '$.callHistoryDetails.callMode') = 'Group'
AND json_extract(json, '$.callHistoryDetails.eraId') = $eraId
LIMIT 1;
SELECT EXISTS(
SELECT 1 FROM messages
WHERE conversationId = $conversationId
AND type = 'call-history'
AND json_extract(json, '$.callHistoryDetails.callMode') = 'Group'
AND json_extract(json, '$.callHistoryDetails.eraId') = $eraId
);
`
)
.pluck()
.get({
conversationId,
eraId,
});
if (row) {
return Boolean(row['count(*)']);
}
return false;
return exists !== 0;
}
async function migrateConversationMessages(

View file

@ -0,0 +1,29 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { Database } from 'better-sqlite3';
import type { LoggerType } from '../../types/Logging';
export default function updateToSchemaVersion70(
currentVersion: number,
db: Database,
logger: LoggerType
): void {
if (currentVersion >= 70) {
return;
}
db.transaction(() => {
// Used in `getAllStories`.
db.exec(
`
CREATE INDEX messages_by_storyId ON messages (storyId);
`
);
db.pragma('user_version = 70');
})();
logger.info('updateToSchemaVersion70: success!');
}

View file

@ -45,6 +45,7 @@ import updateToSchemaVersion66 from './66-add-pni-signature-to-sent-protos';
import updateToSchemaVersion67 from './67-add-story-to-unprocessed';
import updateToSchemaVersion68 from './68-drop-deprecated-columns';
import updateToSchemaVersion69 from './69-group-call-ring-cancellations';
import updateToSchemaVersion70 from './70-story-reply-index';
function updateToSchemaVersion1(
currentVersion: number,
@ -1952,6 +1953,7 @@ export const SCHEMA_VERSIONS = [
updateToSchemaVersion67,
updateToSchemaVersion68,
updateToSchemaVersion69,
updateToSchemaVersion70,
];
export function updateSchema(db: Database, logger: LoggerType): void {