Allow poll questions to be searchable

This commit is contained in:
trevor-signal
2025-11-04 11:48:33 -05:00
committed by GitHub
parent 7bb7c2c67c
commit 6869245b89
4 changed files with 88 additions and 21 deletions

View File

@@ -267,6 +267,7 @@ import type {
import { sqlLogger } from './sqlLogger.node.js';
import { permissiveMessageAttachmentSchema } from './server/messageAttachments.std.js';
import { getFilePathsOwnedByMessage } from '../util/messageFilePaths.std.js';
import { createMessagesOnInsertTrigger } from './migrations/1500-search-polls.std.js';
const {
forEach,
@@ -8878,29 +8879,23 @@ function enableFSyncAndCheckpoint(db: WritableDB): void {
}
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;
db.transaction(() => {
backfillMentionsTable(db);
backfillMessagesFtsTable(db);
DROP TRIGGER IF EXISTS messages_on_insert_insert_mentions;
db.exec('DROP TRIGGER IF EXISTS messages_on_insert');
db.exec(createMessagesOnInsertTrigger);
db.exec('DROP TRIGGER IF EXISTS messages_on_insert_insert_mentions');
db.exec(`
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,
@@ -8912,9 +8907,9 @@ function backfillMessagesFtsTable(db: WritableDB): void {
db.exec(`
DELETE FROM messages_fts;
INSERT OR REPLACE INTO messages_fts (rowid, body)
SELECT rowid, body
SELECT rowid, searchableText
FROM messages
WHERE isViewOnce IS NOT 1 AND storyId IS NULL;
WHERE isSearchable = 1;
`);
}

View File

@@ -0,0 +1,50 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { WritableDB } from '../Interface.std.js';
import { sql } from '../util.std.js';
export const createMessagesOnInsertTrigger = sql`
CREATE TRIGGER messages_on_insert AFTER INSERT ON messages
WHEN new.isSearchable IS 1
BEGIN
INSERT INTO messages_fts
(rowid, body)
VALUES
(new.rowid, new.searchableText);
END;
`[0];
const createMessagesOnUpdateTrigger = sql`
CREATE TRIGGER messages_on_update AFTER UPDATE ON messages
WHEN
new.isSearchable IS 1 AND old.searchableText IS NOT new.searchableText
BEGIN
UPDATE messages_fts SET body = new.searchableText WHERE rowId = new.rowId;
END;
`[0];
export default function updateToSchemaVersion1500(db: WritableDB): void {
db.exec(
`ALTER TABLE messages ADD COLUMN isSearchable INT
GENERATED ALWAYS AS (isViewOnce IS NOT 1 AND storyId IS NULL) VIRTUAL;`
);
// Must be kept in sync with logic in getSearchableTextAndBodyRanges
db.exec(`
ALTER TABLE messages ADD COLUMN searchableText TEXT GENERATED ALWAYS AS (
CASE
WHEN json->'poll' IS NOT NULL THEN json->'poll'->>'question'
ELSE body
END
) VIRTUAL;
`);
// If the messages_on_insert query is updated, enableMessageInsertTriggersAndBackfill
// and backfillMessagesFtsTable must be as well
db.exec('DROP TRIGGER IF EXISTS messages_on_insert;');
db.exec(createMessagesOnInsertTrigger);
db.exec('DROP TRIGGER IF EXISTS messages_on_update;');
db.exec(createMessagesOnUpdateTrigger);
}

View File

@@ -125,6 +125,7 @@ import updateToSchemaVersion1460 from './1460-attachment-duration.std.js';
import updateToSchemaVersion1470 from './1470-kyber-triple.std.js';
import updateToSchemaVersion1480 from './1480-chat-folders-remove-duplicates.std.js';
import updateToSchemaVersion1490 from './1490-lowercase-notification-profiles.std.js';
import updateToSchemaVersion1500 from './1500-search-polls.std.js';
import { DataWriter } from '../Server.node.js';
@@ -1607,6 +1608,8 @@ export const SCHEMA_VERSIONS: ReadonlyArray<SchemaUpdateType> = [
{ version: 1470, update: updateToSchemaVersion1470 },
{ version: 1480, update: updateToSchemaVersion1480 },
{ version: 1490, update: updateToSchemaVersion1490 },
{ version: 1500, update: updateToSchemaVersion1500 },
];
export class DBVersionFromFutureError extends Error {

View File

@@ -30,8 +30,10 @@ import {
} from './conversations.dom.js';
import { hydrateRanges } from '../../util/BodyRange.node.js';
import type { RawBodyRange } from '../../types/BodyRange.std.js';
import { createLogger } from '../../logging/log.std.js';
import { getOwn } from '../../util/getOwn.std.js';
import type { MessageAttributesType } from '../../model-types.js';
const log = createLogger('search');
@@ -196,6 +198,23 @@ type CachedMessageSearchResultSelectorType = (
targetedMessageId?: string
) => MessageSearchResultPropsDataType;
/** Must be kept in sync with messages.searchableText virtual column */
function getSearchableTextAndBodyRanges(message: MessageAttributesType): {
text: string | undefined;
bodyRanges: ReadonlyArray<RawBodyRange> | undefined;
} {
if (message.poll) {
return {
text: message.poll.question,
bodyRanges: undefined,
};
}
return {
text: message.body,
bodyRanges: message.bodyRanges,
};
}
export const getCachedSelectorForMessageSearchResult = createSelector(
getUserConversationId,
getConversationSelector,
@@ -213,6 +232,7 @@ export const getCachedSelectorForMessageSearchResult = createSelector(
searchConversationId?: string,
targetedMessageId?: string
) => {
const { text, bodyRanges } = getSearchableTextAndBodyRanges(message);
return {
from,
to,
@@ -221,9 +241,8 @@ export const getCachedSelectorForMessageSearchResult = createSelector(
conversationId: message.conversationId,
sentAt: message.sent_at,
snippet: message.snippet || '',
bodyRanges:
hydrateRanges(message.bodyRanges, conversationSelector) || [],
body: message.body || '',
bodyRanges: hydrateRanges(bodyRanges, conversationSelector) || [],
body: text ?? '',
isSelected: Boolean(
targetedMessageId && message.id === targetedMessageId