mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Add full text search over chat messages (#27126)
Closes CODAGT-721 Closes CODAGT-722 Closes CODAGT-723 Closes CODAGT-724 Closes CODAGT-725 This PR adds the database and API pieces necessary to support full-text chat message search. - Adds required chat schema for full-text search - Adds dbpurge job to populate search_tsv in the background - Adds `search` parameter to GetChats query - Adds `search` filter to `searchquery.Chats` - Wires chat search filter into chats API > Implemented by Coder Agents, reviewed and tested by a human.
This commit is contained in:
@@ -312,6 +312,32 @@ SET
|
||||
WHERE
|
||||
id = @id::bigint;
|
||||
|
||||
-- name: BackfillChatMessagesSearchTsv :execrows
|
||||
-- Backfills chat_messages.search_tsv for pending rows, newest first.
|
||||
-- The WHERE clause must match the predicate of
|
||||
-- idx_chat_messages_search_tsv_pending exactly so the partial index
|
||||
-- serves this query.
|
||||
WITH batch AS (
|
||||
SELECT id FROM chat_messages
|
||||
WHERE search_tsv IS NULL
|
||||
AND deleted = false
|
||||
AND visibility IN ('user', 'both')
|
||||
AND role IN ('user', 'assistant')
|
||||
ORDER BY id DESC
|
||||
LIMIT @batch_size::int
|
||||
)
|
||||
UPDATE chat_messages cm
|
||||
-- NULL means "pending", empty tsvector means "backfilled, no text".
|
||||
SET search_tsv = COALESCE(
|
||||
to_tsvector('simple', chat_message_search_text(cm.content)),
|
||||
''::tsvector)
|
||||
FROM batch WHERE cm.id = batch.id;
|
||||
|
||||
-- name: ChatSearchQueryIsEmpty :one
|
||||
-- Reports whether search text tokenizes to an empty tsquery (e.g. '!!!').
|
||||
-- Used to reject input that would silently match nothing.
|
||||
SELECT numnode(websearch_to_tsquery('simple', @search::text)) = 0 AS is_empty;
|
||||
|
||||
-- name: GetChatByID :one
|
||||
SELECT *
|
||||
FROM chats_expanded
|
||||
@@ -651,6 +677,46 @@ WHERE
|
||||
)
|
||||
ELSE true
|
||||
END
|
||||
-- websearch_to_tsquery accepts quoted phrases, OR, and -negation;
|
||||
-- the 'simple' config folds case and skips stemming.
|
||||
AND CASE
|
||||
WHEN @search::text != '' THEN (
|
||||
-- Served by idx_chats_title_fts.
|
||||
to_tsvector('simple', chats_expanded.title) @@ websearch_to_tsquery('simple', @search)
|
||||
-- Served by idx_chat_diff_statuses_pr_title_fts.
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM chat_diff_statuses cds
|
||||
WHERE cds.chat_id = chats_expanded.id
|
||||
AND to_tsvector('simple', cds.pull_request_title) @@ websearch_to_tsquery('simple', @search)
|
||||
)
|
||||
-- The WHERE clause must repeat the predicate of the partial index
|
||||
-- idx_chat_messages_search_tsv so the planner can use it. Additional
|
||||
-- filters should still be fine.
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM chat_messages cm
|
||||
WHERE cm.chat_id = chats_expanded.id
|
||||
AND cm.search_tsv IS NOT NULL
|
||||
AND cm.deleted = false
|
||||
AND cm.visibility IN ('user', 'both')
|
||||
AND cm.role IN ('user', 'assistant')
|
||||
AND cm.search_tsv @@ websearch_to_tsquery('simple', @search)
|
||||
)
|
||||
-- Skip an explicit pr_number lookup unless the search is a valid bigint.
|
||||
OR CASE
|
||||
WHEN @search ~ '^[0-9]{1,18}$' THEN EXISTS (
|
||||
SELECT 1
|
||||
FROM chat_diff_statuses cds
|
||||
WHERE cds.chat_id = chats_expanded.id
|
||||
AND cds.pr_number IS NOT NULL
|
||||
AND cds.pr_number = @search::bigint
|
||||
)
|
||||
ELSE false
|
||||
END
|
||||
)
|
||||
ELSE true
|
||||
END
|
||||
-- Paginate over root chats only. Children are fetched
|
||||
-- separately via GetChildChatsByParentIDs and embedded under
|
||||
-- each parent. Other callers that need the full set should
|
||||
|
||||
Reference in New Issue
Block a user