mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 02:08:57 +00:00
Allow poll questions to be searchable
This commit is contained in:
@@ -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;
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
50
ts/sql/migrations/1500-search-polls.std.ts
Normal file
50
ts/sql/migrations/1500-search-polls.std.ts
Normal 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);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user