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:
Cian Johnston
2026-07-16 15:21:57 +01:00
committed by GitHub
parent 9862f10484
commit f7481c5d08
26 changed files with 1756 additions and 35 deletions
+16 -1
View File
@@ -697,7 +697,8 @@ var (
rbac.ResourceApiKey.Type: {policy.ActionDelete},
rbac.ResourceAibridgeInterception.Type: {policy.ActionDelete},
rbac.ResourceWorkspaceBuildOrchestration.Type: {policy.ActionDelete},
// Chat auto-archive sets archived=true on inactive chats.
// Chat auto-archive sets archived=true on inactive chats and computes
// search_tsv tsvector for chat_messages.
rbac.ResourceChat.Type: {policy.ActionRead, policy.ActionUpdate},
// Purge old boundary logs past the retention period.
rbac.ResourceBoundaryLog.Type: {policy.ActionDelete},
@@ -1743,6 +1744,13 @@ func (q *querier) AutoArchiveInactiveChats(ctx context.Context, arg database.Aut
return q.db.AutoArchiveInactiveChats(ctx, arg)
}
func (q *querier) BackfillChatMessagesSearchTsv(ctx context.Context, batchSize int32) (int64, error) {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceChat); err != nil {
return 0, err
}
return q.db.BackfillChatMessagesSearchTsv(ctx, batchSize)
}
func (q *querier) BackoffChatDiffStatus(ctx context.Context, arg database.BackoffChatDiffStatusParams) error {
// This is a system-level operation used by the gitsync
// background worker to reschedule failed refreshes. Same
@@ -1820,6 +1828,13 @@ func (q *querier) CalculateAIBridgeInterceptionsTelemetrySummary(ctx context.Con
return q.db.CalculateAIBridgeInterceptionsTelemetrySummary(ctx, arg)
}
func (q *querier) ChatSearchQueryIsEmpty(ctx context.Context, search string) (bool, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChat); err != nil {
return false, err
}
return q.db.ChatSearchQueryIsEmpty(ctx, search)
}
func (q *querier) ClaimPrebuiltWorkspace(ctx context.Context, arg database.ClaimPrebuiltWorkspaceParams) (database.ClaimPrebuiltWorkspaceRow, error) {
empty := database.ClaimPrebuiltWorkspaceRow{}
+8
View File
@@ -1002,6 +1002,14 @@ func (s *MethodTestSuite) TestChats() {
dbm.EXPECT().DeleteOldChats(gomock.Any(), database.DeleteOldChatsParams{}).Return(int64(0), nil).AnyTimes()
check.Args(database.DeleteOldChatsParams{}).Asserts(rbac.ResourceSystem, policy.ActionDelete)
}))
s.Run("BackfillChatMessagesSearchTsv", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().BackfillChatMessagesSearchTsv(gomock.Any(), int32(100)).Return(int64(0), nil).AnyTimes()
check.Args(int32(100)).Asserts(rbac.ResourceChat, policy.ActionUpdate)
}))
s.Run("ChatSearchQueryIsEmpty", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().ChatSearchQueryIsEmpty(gomock.Any(), "!!!").Return(true, nil).AnyTimes()
check.Args("!!!").Asserts(rbac.ResourceChat, policy.ActionRead)
}))
s.Run("GetChatRetentionDays", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetChatRetentionDays(gomock.Any()).Return(int32(30), nil).AnyTimes()
check.Args().Asserts()