From e20fa41fd5a79b1b49d94f6b939c88e9a536759c Mon Sep 17 00:00:00 2001 From: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:32:43 -0700 Subject: [PATCH] Drop calls from migration with the same callId and peerId --- ts/sql/migrations/87-calls-history-table.ts | 13 ++++ ts/test-node/sql_migrations_test.ts | 76 +++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/ts/sql/migrations/87-calls-history-table.ts b/ts/sql/migrations/87-calls-history-table.ts index 1097054c5715..23e26858cd91 100644 --- a/ts/sql/migrations/87-calls-history-table.ts +++ b/ts/sql/migrations/87-calls-history-table.ts @@ -77,6 +77,8 @@ export default function updateToSchemaVersion87( const rows = db.prepare(selectQuery).all(); + const uniqueConstraint = new Set(); + for (const row of rows) { const json = JSON.parse(row.json); const details = json.callHistoryDetails; @@ -153,6 +155,17 @@ export default function updateToSchemaVersion87( continue; } + // We need to ensure a call with the same callId and peerId doesn't get + // inserted twice because of the unique constraint on the table. + const uniqueKey = `${callId} -> ${peerId}`; + if (uniqueConstraint.has(uniqueKey)) { + logger.error( + `updateToSchemaVersion87: duplicate callId/peerId pair (${uniqueKey})` + ); + continue; + } + uniqueConstraint.add(uniqueKey); + const [insertQuery, insertParams] = sql` INSERT INTO callsHistory ( callId, diff --git a/ts/test-node/sql_migrations_test.ts b/ts/test-node/sql_migrations_test.ts index e024de09b293..8892d34de8ee 100644 --- a/ts/test-node/sql_migrations_test.ts +++ b/ts/test-node/sql_migrations_test.ts @@ -3658,5 +3658,81 @@ describe('SQL migrations test', () => { callHistoryDetailsSchema.parse(row); } }); + + it('handles unique constraint violations', () => { + updateToVersion(86); + + const message1Id = generateGuid(); + const message2Id = generateGuid(); + const conversationId = generateGuid(); + const callHistoryDetails = { + callId: '123', + callMode: CallMode.Direct, + wasDeclined: false, + wasDeleted: false, + wasIncoming: false, + wasVideoCall: false, + acceptedTime: Date.now(), + endedTime: undefined, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- using old types + const message1: MessageAttributesType & { callHistoryDetails: any } = { + id: message1Id, + type: 'call-history', + conversationId, + sent_at: Date.now() - 10, + received_at: Date.now() - 10, + timestamp: Date.now() - 10, + callHistoryDetails, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- using old types + const message2: MessageAttributesType & { callHistoryDetails: any } = { + id: message2Id, + type: 'call-history', + conversationId, + sent_at: Date.now(), + received_at: Date.now(), + timestamp: Date.now(), + callHistoryDetails, + }; + + const [insertQuery, insertParams] = sql` + INSERT INTO messages ( + id, + conversationId, + type, + json + ) + VALUES + ( + ${message1Id}, + ${conversationId}, + ${message1.type}, + ${JSON.stringify(message1)} + ), + ( + ${message2Id}, + ${conversationId}, + ${message2.type}, + ${JSON.stringify(message2)} + ); + `; + + db.prepare(insertQuery).run(insertParams); + + updateToVersion(87); + + const [selectHistoryQuery] = sql` + SELECT * FROM callsHistory; + `; + + const rows = db.prepare(selectHistoryQuery).all(); + for (const row of rows) { + callHistoryDetailsSchema.parse(row); + } + assert.strictEqual(rows.length, 1); + }); }); });