diff --git a/ts/sql/migrations/1020-self-merges.ts b/ts/sql/migrations/1020-self-merges.ts new file mode 100644 index 0000000000..184b524c06 --- /dev/null +++ b/ts/sql/migrations/1020-self-merges.ts @@ -0,0 +1,54 @@ +// Copyright 2024 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'; +import { getOurUuid } from './41-uuid-keys'; + +export const version = 1020; + +export function updateToSchemaVersion1020( + currentVersion: number, + db: Database, + logger: LoggerType +): void { + if (currentVersion >= 1020) { + return; + } + + db.transaction(() => { + const ourAci = getOurUuid(db); + + if (ourAci == null) { + logger.info('updateToSchemaVersion1020: not linked'); + return; + } + + const [selectQuery, selectParams] = sql` + SELECT id FROM conversations + WHERE serviceId IS ${ourAci} + `; + const ourConversationId = db.prepare(selectQuery).pluck().get(selectParams); + if (ourConversationId == null) { + logger.error('updateToSchemaVersion1020: no conversation'); + return; + } + + const [deleteQuery, deleteParams] = sql` + DELETE FROM messages + WHERE + conversationId IS ${ourConversationId} AND + type IS 'conversation-merge' + `; + const { changes } = db.prepare(deleteQuery).run(deleteParams); + if (changes !== 0) { + logger.warn(`updateToSchemaVersion1020: removed ${changes} self merges`); + } + })(); + + db.pragma('user_version = 1020'); + + logger.info('updateToSchemaVersion1020: success!'); +} diff --git a/ts/sql/migrations/index.ts b/ts/sql/migrations/index.ts index 7a96b30478..f0fbe7f704 100644 --- a/ts/sql/migrations/index.ts +++ b/ts/sql/migrations/index.ts @@ -76,10 +76,11 @@ import { updateToSchemaVersion970 } from './970-fts5-optimize'; import { updateToSchemaVersion980 } from './980-reaction-timestamp'; import { updateToSchemaVersion990 } from './990-phone-number-sharing'; import { updateToSchemaVersion1000 } from './1000-mark-unread-call-history-messages-as-unseen'; +import { updateToSchemaVersion1010 } from './1010-call-links-table'; import { version as MAX_VERSION, - updateToSchemaVersion1010, -} from './1010-call-links-table'; + updateToSchemaVersion1020, +} from './1020-self-merges'; function updateToSchemaVersion1( currentVersion: number, @@ -2023,6 +2024,7 @@ export const SCHEMA_VERSIONS = [ updateToSchemaVersion990, updateToSchemaVersion1000, updateToSchemaVersion1010, + updateToSchemaVersion1020, ]; export class DBVersionFromFutureError extends Error { diff --git a/ts/test-node/sql/migration_1020_test.ts b/ts/test-node/sql/migration_1020_test.ts new file mode 100644 index 0000000000..5c97bea4a8 --- /dev/null +++ b/ts/test-node/sql/migration_1020_test.ts @@ -0,0 +1,85 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; +import type { Database } from '@signalapp/better-sqlite3'; +import SQL from '@signalapp/better-sqlite3'; +import { v4 as generateGuid } from 'uuid'; + +import { normalizeAci } from '../../util/normalizeAci'; +import { insertData, getTableData, updateToVersion } from './helpers'; + +describe('SQL/updateToSchemaVersion1020', () => { + let db: Database; + + const OUR_ACI = normalizeAci( + generateGuid(), + 'updateToSchemaVersion1020 test' + ); + const THEIR_ACI = normalizeAci( + generateGuid(), + 'updateToSchemaVersion1020 test' + ); + + beforeEach(() => { + db = new SQL(':memory:'); + updateToVersion(db, 1010); + }); + + afterEach(() => { + db.close(); + }); + + it('removes self merges and nothing else', () => { + insertData(db, 'items', [ + { + id: 'uuid_id', + json: { + id: 'uuid_id', + value: `${OUR_ACI}.2`, + }, + }, + ]); + + insertData(db, 'conversations', [ + { + id: 'us', + serviceId: OUR_ACI, + }, + { + id: 'them', + serviceId: THEIR_ACI, + }, + ]); + + insertData(db, 'messages', [ + { + id: 'a', + conversationId: 'us', + type: 'conversation-merge', + }, + { + id: 'b', + conversationId: 'us', + type: 'incoming', + }, + { + id: 'c', + conversationId: 'them', + type: 'conversation-merge', + }, + { + id: 'd', + conversationId: 'them', + type: 'incoming', + }, + ]); + + updateToVersion(db, 1020); + + assert.deepStrictEqual( + getTableData(db, 'messages').map(m => m.id), + ['b', 'c', 'd'] + ); + }); +});