diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 7af9bdaee..2dbbe8d1a 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -393,7 +393,7 @@ export class MessageModel extends window.Backbone.Model { strictAssert(isAciString(authorAci), 'Story message from pni'); this.set({ storyReplyContext: { - attachment, + attachment: omit(attachment, 'screenshotData'), authorAci, messageId: message.id, }, diff --git a/ts/sql/migrations/90-delete-story-reply-screenshot.ts b/ts/sql/migrations/90-delete-story-reply-screenshot.ts new file mode 100644 index 000000000..6c1314823 --- /dev/null +++ b/ts/sql/migrations/90-delete-story-reply-screenshot.ts @@ -0,0 +1,45 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { Database } from '@signalapp/better-sqlite3'; + +import type { LoggerType } from '../../types/Logging'; +import { sql } from '../util'; + +export default function updateToSchemaVersion90( + currentVersion: number, + db: Database, + logger: LoggerType +): void { + if (currentVersion >= 90) { + return; + } + + let numChanges = 0; + db.transaction(() => { + const [updateQuery, updateParams] = sql` + UPDATE messages + SET json = json_remove(json, '$.storyReplyContext.attachment.screenshotData') + WHERE isStory = 0 + + /* we want to find all messages with a non-null storyId, but using string + comparison (instead of a non-null check) here causes Sqlite to use the + storyId index */ + AND storyId > '0' + + AND json->'storyReplyContext.attachment.screenshotData' IS NOT NULL; + `; + + const info = db.prepare(updateQuery).run(updateParams); + numChanges = info.changes; + + db.pragma('user_version = 90'); + })(); + + logger.info( + `updateToSchemaVersion90: removed screenshotData from ${numChanges} ` + + `message${numChanges > 1 ? 's' : ''}` + ); + + logger.info('updateToSchemaVersion90: success!'); +} diff --git a/ts/sql/migrations/index.ts b/ts/sql/migrations/index.ts index 07cf3f6ea..38a816971 100644 --- a/ts/sql/migrations/index.ts +++ b/ts/sql/migrations/index.ts @@ -64,6 +64,7 @@ import updateToSchemaVersion85 from './85-add-kyber-keys'; import updateToSchemaVersion86 from './86-story-replies-index'; import updateToSchemaVersion88 from './88-service-ids'; import updateToSchemaVersion89 from './89-call-history'; +import updateToSchemaVersion90 from './90-delete-story-reply-screenshot'; function updateToSchemaVersion1( currentVersion: number, @@ -1999,6 +2000,7 @@ export const SCHEMA_VERSIONS = [ (_v: number, _i: Database, _l: LoggerType): void => undefined, // version 87 was dropped updateToSchemaVersion88, updateToSchemaVersion89, + updateToSchemaVersion90, ]; export function updateSchema(db: Database, logger: LoggerType): void { diff --git a/ts/test-node/sql/migration_90_test.ts b/ts/test-node/sql/migration_90_test.ts new file mode 100644 index 000000000..4e8a3ba5b --- /dev/null +++ b/ts/test-node/sql/migration_90_test.ts @@ -0,0 +1,107 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { Database } from '@signalapp/better-sqlite3'; +import SQL from '@signalapp/better-sqlite3'; +import { assert } from 'chai'; + +import { updateToVersion, insertData, getTableData } from './helpers'; + +describe('SQL/updateToSchemaVersion90', () => { + let db: Database; + + beforeEach(() => { + db = new SQL(':memory:'); + }); + + afterEach(() => { + db.close(); + }); + + it('should delete screenshot.data but leave remainder', () => { + updateToVersion(db, 89); + insertData(db, 'messages', [ + { + id: 'message_with_screenshot_data', + json: { + id: 'message_with_screenshot_data', + storyReplyContext: { + attachment: { + contentType: 'video/mp4', + filename: 'filename', + path: 'path', + screenshot: { + contentType: 'image/png', + path: 'screenshotPath', + width: 1920, + height: 1080, + }, + screenshotData: { + '4389410': 144, + '4389411': 255, + '4389412': 75, + '4389413': 168, + '4389414': 81, + '4389415': 142, + '4389416': 120, + '4389417': 150, + '4389418': 14, + }, + size: 100, + }, + messageId: 'story_id', + }, + }, + storyId: 'story_id', + }, + ]); + + updateToVersion(db, 90); + + assert.deepStrictEqual( + getTableData(db, 'messages').map(msg => msg.json), + [ + { + id: 'message_with_screenshot_data', + storyReplyContext: { + attachment: { + contentType: 'video/mp4', + filename: 'filename', + path: 'path', + screenshot: { + contentType: 'image/png', + path: 'screenshotPath', + width: 1920, + height: 1080, + }, + size: 100, + }, + messageId: 'story_id', + }, + }, + ] + ); + }); + + it('should use storyId index', () => { + updateToVersion(db, 90); + + const details = db + .prepare( + ` + EXPLAIN QUERY PLAN + UPDATE messages + SET json = json_remove(json, '$.storyReplyContext.attachment.screenshotData') + WHERE isStory = 0 + AND storyId > '0' + AND json->'storyReplyContext.attachment.screenshotData' IS NOT NULL; + ` + ) + .all() + .map(({ detail }) => detail) + .join('\n'); + + assert.include(details, 'USING INDEX messages_by_storyId'); + assert.notInclude(details, 'SCAN'); + }); +});