From c254eab90c2995f8701181c227745f985668065e Mon Sep 17 00:00:00 2001 From: trevor-signal <131492920+trevor-signal@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:27:07 -0500 Subject: [PATCH] Simplify getUnreadReactionsAndMarkRead query --- ts/sql/Interface.std.ts | 2 +- ts/sql/Server.node.ts | 60 +++++++------------ ts/sql/main.main.ts | 11 ++-- ts/test-electron/sql/markRead_test.preload.ts | 27 ++------- 4 files changed, 36 insertions(+), 64 deletions(-) diff --git a/ts/sql/Interface.std.ts b/ts/sql/Interface.std.ts index c0444e12a8..c4c70ef338 100644 --- a/ts/sql/Interface.std.ts +++ b/ts/sql/Interface.std.ts @@ -488,7 +488,7 @@ export type StoryReadType = Readonly<{ export type ReactionResultType = Pick< ReactionType, 'targetAuthorAci' | 'targetTimestamp' | 'messageId' -> & { rowid: number }; +>; export type PollVoteReadResultType = { id: string; diff --git a/ts/sql/Server.node.ts b/ts/sql/Server.node.ts index 53ee22d738..18288ad408 100644 --- a/ts/sql/Server.node.ts +++ b/ts/sql/Server.node.ts @@ -3504,46 +3504,30 @@ function getUnreadReactionsAndMarkRead( storyId?: string; } ): Array { - return db.transaction(() => { - const unreadMessages: Array = db - .prepare( - ` - SELECT reactions.rowid, targetAuthorAci, targetTimestamp, messageId - FROM reactions - INDEXED BY reactions_unread - JOIN messages on messages.id IS reactions.messageId - WHERE - reactions.conversationId IS $conversationId AND - reactions.unread > 0 AND - messages.received_at <= $readMessageReceivedAt AND - messages.storyId IS $storyId - ORDER BY messageReceivedAt DESC; + return db + .prepare( ` - ) - .all({ - conversationId, - readMessageReceivedAt, - storyId: storyId || null, - }); - - const idsToUpdate = unreadMessages.map(item => item.rowid); - batchMultiVarQuery( - db, - idsToUpdate, - (ids: ReadonlyArray, persistent: boolean): void => { - db.prepare( - ` - UPDATE reactions + UPDATE reactions + INDEXED BY reactions_unread SET unread = 0 - WHERE rowid IN ( ${ids.map(() => '?').join(', ')} ); - `, - { persistent } - ).run(ids); - } - ); - - return unreadMessages; - })(); + WHERE + conversationId = $conversationId AND + unread >= 1 AND + EXISTS ( + SELECT 1 + FROM messages + WHERE messages.id = reactions.messageId + AND messages.received_at <= $readMessageReceivedAt + AND messages.storyId IS $storyId + ) + RETURNING targetAuthorAci, targetTimestamp, messageId; + ` + ) + .all({ + conversationId, + readMessageReceivedAt, + storyId: storyId || null, + }); } function markReactionAsRead( diff --git a/ts/sql/main.main.ts b/ts/sql/main.main.ts index 9a08b997ae..a955d27e51 100644 --- a/ts/sql/main.main.ts +++ b/ts/sql/main.main.ts @@ -133,7 +133,7 @@ export class MainSQL { // eslint-disable-next-line @typescript-eslint/no-explicit-any #onResponse = new Map>(); - #shouldTimeQueries = false; + #shouldLogQueryTime: (queryName: string) => boolean; #shouldTrackQueryStats = false; #queryStats?: { @@ -150,6 +150,11 @@ export class MainSQL { exitPromises.push(onExit); } this.#onExit = Promise.all(exitPromises); + + const timeQueryEnvVar = process.env.TIME_QUERIES; + this.#shouldLogQueryTime = (queryName: string) => { + return timeQueryEnvVar === queryName || timeQueryEnvVar === '1'; + }; } public async initialize({ @@ -162,8 +167,6 @@ export class MainSQL { throw new Error('Already initialized'); } - this.#shouldTimeQueries = Boolean(process.env.TIME_QUERIES); - this.#logger = logger; this.#onReady = (async () => { @@ -461,7 +464,7 @@ export class MainSQL { currentStats.max = Math.max(currentStats.max, duration); } - if (this.#shouldTimeQueries && !app.isPackaged) { + if (this.#shouldLogQueryTime(method) && !app.isPackaged) { const twoDecimals = this.#roundDuration(duration); this.#logger?.info(`MainSQL query: ${method}, duration=${twoDecimals}ms`); } diff --git a/ts/test-electron/sql/markRead_test.preload.ts b/ts/test-electron/sql/markRead_test.preload.ts index 726a408323..ab7df8b0b8 100644 --- a/ts/test-electron/sql/markRead_test.preload.ts +++ b/ts/test-electron/sql/markRead_test.preload.ts @@ -598,17 +598,9 @@ describe('sql/markRead', () => { }); assert.lengthOf(markedRead, 2, 'two reactions marked read'); - - // Sorted in descending order - assert.strictEqual( - markedRead[0].messageId, - reaction4.messageId, - 'first should be reaction4' - ); - assert.strictEqual( - markedRead[1].messageId, - reaction1.messageId, - 'second should be reaction1' + assert.sameMembers( + markedRead.map(read => read.messageId), + [reaction4.messageId, reaction1.messageId] ); const markedRead2 = await getUnreadReactionsAndMarkRead({ @@ -755,16 +747,9 @@ describe('sql/markRead', () => { assert.lengthOf(markedRead, 2, 'two reactions marked read'); - // Sorted in descending order - assert.strictEqual( - markedRead[0].messageId, - reaction4.messageId, - 'first should be reaction4' - ); - assert.strictEqual( - markedRead[1].messageId, - reaction1.messageId, - 'second should be reaction1' + assert.sameMembers( + markedRead.map(read => read.messageId), + [reaction4.messageId, reaction1.messageId] ); const markedRead2 = await getUnreadReactionsAndMarkRead({