Disable message insert triggers during backup import

This commit is contained in:
trevor-signal 2024-11-27 13:34:02 -05:00 committed by GitHub
parent 7dced11b57
commit 34ef8dc2c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 101 additions and 1 deletions

View file

@ -374,7 +374,6 @@ export class BackupImportStream extends Writable {
toastType: ToastType.FailedToImportBackup,
});
}
// TODO (DESKTOP-7934): throw in tests if we cannot process a frame
} else {
log.info(`${this.logId}: successfully processed all frames.`);
}

View file

@ -305,6 +305,9 @@ export class BackupsService {
log.info(`importBackup: starting ${backupType}...`);
this.isRunning = 'import';
const importStart = Date.now();
await DataWriter.disableMessageInsertTriggers();
try {
const importStream = await BackupImportStream.create(backupType);
if (backupType === BackupType.Ciphertext) {
@ -397,6 +400,8 @@ export class BackupsService {
throw error;
} finally {
this.isRunning = false;
await DataWriter.enableMessageInsertTriggersAndBackfill();
window.IPC.stopTrackingQueryStats({ epochName: 'Backup Import' });
if (window.SignalCI) {
window.SignalCI.handleEvent('backupImportComplete', {

View file

@ -952,6 +952,10 @@ type WritableInterface = {
insertJob(job: Readonly<StoredJob>): void;
deleteJob(id: string): void;
disableMessageInsertTriggers(): void;
enableMessageInsertTriggersAndBackfill(): void;
ensureMessageInsertTriggersAreEnabled(): void;
processGroupCallRingCancellation(ringId: bigint): void;
cleanExpiredGroupCallRingCancellations(): void;
};

View file

@ -543,6 +543,10 @@ export const DataWriter: ServerWritableInterface = {
processGroupCallRingCancellation,
cleanExpiredGroupCallRingCancellations,
disableMessageInsertTriggers,
enableMessageInsertTriggersAndBackfill,
ensureMessageInsertTriggersAreEnabled,
// Server-only
removeKnownStickers,
@ -7463,3 +7467,84 @@ function getUnreadEditedMessagesAndMarkRead(
});
})();
}
function disableMessageInsertTriggers(db: WritableDB): void {
db.transaction(() => {
createOrUpdateItem(db, {
id: 'messageInsertTriggersDisabled',
value: true,
});
db.exec('DROP TRIGGER IF EXISTS messages_on_insert;');
db.exec('DROP TRIGGER IF EXISTS messages_on_insert_insert_mentions;');
})();
}
const selectMentionsFromMessages = `
SELECT messages.id, bodyRanges.value ->> 'mentionAci' as mentionAci,
bodyRanges.value ->> 'start' as start,
bodyRanges.value ->> 'length' as length
FROM messages, json_each(messages.json ->> 'bodyRanges') as bodyRanges
WHERE bodyRanges.value ->> 'mentionAci' IS NOT NULL
`;
function enableMessageInsertTriggersAndBackfill(db: WritableDB): void {
const createTriggersQuery = `
DROP TRIGGER IF EXISTS messages_on_insert;
CREATE TRIGGER messages_on_insert AFTER INSERT ON messages
WHEN new.isViewOnce IS NOT 1 AND new.storyId IS NULL
BEGIN
INSERT INTO messages_fts
(rowid, body)
VALUES
(new.rowid, new.body);
END;
DROP TRIGGER IF EXISTS messages_on_insert_insert_mentions;
CREATE TRIGGER messages_on_insert_insert_mentions AFTER INSERT ON messages
BEGIN
INSERT INTO mentions (messageId, mentionAci, start, length)
${selectMentionsFromMessages}
AND messages.id = new.id;
END;
`;
db.transaction(() => {
backfillMentionsTable(db);
backfillMessagesFtsTable(db);
db.exec(createTriggersQuery);
createOrUpdateItem(db, {
id: 'messageInsertTriggersDisabled',
value: false,
});
})();
}
function backfillMessagesFtsTable(db: WritableDB): void {
db.exec(`
DELETE FROM messages_fts;
INSERT OR REPLACE INTO messages_fts (rowid, body)
SELECT rowid, body
FROM messages
WHERE isViewOnce IS NOT 1 AND storyId IS NULL;
`);
}
function backfillMentionsTable(db: WritableDB): void {
db.exec(`
DELETE FROM mentions;
INSERT INTO mentions (messageId, mentionAci, start, length)
${selectMentionsFromMessages};
`);
}
function ensureMessageInsertTriggersAreEnabled(db: WritableDB): void {
db.transaction(() => {
const storedItem = getItemById(db, 'messageInsertTriggersDisabled');
const triggersDisabled = storedItem?.value;
if (triggersDisabled) {
logger.warn(
'Message insert triggers were disabled; reenabling and backfilling data'
);
enableMessageInsertTriggersAndBackfill(db);
}
})();
}

View file

@ -51,6 +51,8 @@ export default function updateToSchemaVersion45(
--- Message insert/update triggers to exclude stories and story replies
DROP TRIGGER messages_on_insert;
-- Note: any changes to this trigger must be reflected in
-- Server.ts: enableMessageInsertTriggersAndBackfill
CREATE TRIGGER messages_on_insert AFTER INSERT ON messages
WHEN new.isViewOnce IS NOT 1 AND new.storyId IS NULL
BEGIN

View file

@ -35,6 +35,8 @@ export default function updateToSchemaVersion84(
INSERT INTO mentions (messageId, mentionUuid, start, length)
${selectMentionsFromMessages};
-- Note: any changes to this trigger must be reflected in
-- Server.ts: enableMessageInsertTriggersAndBackfill
CREATE TRIGGER messages_on_insert_insert_mentions AFTER INSERT ON messages
BEGIN
INSERT INTO mentions (messageId, mentionUuid, start, length)

View file

@ -105,6 +105,7 @@ import {
updateToSchemaVersion1250,
version as MAX_VERSION,
} from './1250-defunct-call-links-storage';
import { DataWriter } from '../Server';
function updateToSchemaVersion1(
currentVersion: number,
@ -2132,6 +2133,7 @@ export function updateSchema(db: WritableDB, logger: LoggerType): void {
runSchemaUpdate(startingVersion, db, logger);
}
DataWriter.ensureMessageInsertTriggersAreEnabled(db);
enableFTS5SecureDelete(db, logger);
if (startingVersion !== MAX_VERSION) {

View file

@ -152,6 +152,7 @@ export type StorageAccessType = {
backupMediaDownloadPaused: boolean;
backupMediaDownloadBannerDismissed: boolean;
backupMediaDownloadIdle: boolean;
messageInsertTriggersDisabled: boolean;
setBackupMessagesSignatureKey: boolean;
setBackupMediaSignatureKey: boolean;
lastReceivedAtCounter: number;