Skip to content

feat: Add full text search over chat messages - #27126

Merged
johnstcn merged 15 commits into
mainfrom
cj/codagt-721
Jul 16, 2026
Merged

feat: Add full text search over chat messages#27126
johnstcn merged 15 commits into
mainfrom
cj/codagt-721

Conversation

@johnstcn

@johnstcn johnstcn commented Jul 9, 2026

Copy link
Copy Markdown
Member

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.

Notes:

  1. Search index is initially empty but populated by dbpurge (sorry). Newest messages get indexed first.
  • I had originally planned to do a CREATE INDEX CONCURRENTLY, but this approach turned out to be way nicer.
  • This also meant we had to materialize the tsvector instead of just having it as a GIN expression index.
  • Additionally, a partial index for search_tsv IS NOT NULL was needed to make the backfill query not be terrible.
  1. I had to modify some of the chatd-specific triggers to not fire when search_tsv is populated. Otherwise this would mean that the FTS indexing would cause in-progress chats to interrupt due to their revision being bumped.
  2. Because dbpurge runs every 10 minutes, you will only be able to search for messages at least 10 minutes old. Right now we don't surface the age of the search index. It would be possible to do this as a follow-up, but leaving it until explicitly requested.

Note to the reader: This PR is a "squashed stack", where each commit is a squashed PR.
I would therefore recommend you read this PR commit by commit.
If you want to see more of the gory details behind a single part of the implementation, that respective PR is the place to look:

Agents were most definitely used in this PR.

johnstcn added 5 commits July 9, 2026 14:34
This commit adds the required schema for chat search:

- search_tsv tsvector column on chat_messages (default NULL).
  It is not queried or referenced directly in Go code.
- Partial GIN full-text search index on chat_messages.search_tsv where non-null
- btree index on chat_messages.search_tsv where null
- GIN full-text-search indexes on chats.title and
  chat_diff_statuses.pull_request_title
- chat_message_search_text stored function to extract message
  content

It also modifies the existing trigger functions
set_chat_message_revision_before and
update_chat_history_after_message_update to disregard search_tsv. This
is needed to avoid spurious chat history revision updates.
- Adds a new query `BackfillChatMessagesSearchTsv` to populate
  search_tsv for a batch of chat_messages rows. This leverages
  idx_chat_messages_search_tsv_pending added in the previous commit.
- Adds Prometheus metric coderd_dbpurge_chat_search_rows_backfilled_total
  so operators can track chat message indexing progress.
- Adds a background task to dbpurge to index up to 50,000 rows
  in 10,000 row batches.
- Adds a `search` parameter to the `GetChats` query that matches over
  chat title, PR title, or message body. PostgreSQL full-text search
  is used for all matches.
- Adds database-level tests for chat FTS.

NB: a search query that is completely numeric is treated as a PR
number search (exact match).
- Adds `search:` to `searchquery.Chats` that accepts a single search
  parameter (multiple terms in quotes).
- Mutually exclusive with `title`, `pr_title`, `pr`.
- Validation: only non-empty values are supported.
- Wires the `search` filter through to `GetChats`
- Adds a preflight query to check if the tokenized query evaluates
  to an empty tsquery, returns an 400 error if so.
- Adds corresponding API tests.
@johnstcn johnstcn self-assigned this Jul 9, 2026
Copilot AI review requested due to automatic review settings July 9, 2026 14:59
@linear-code

linear-code Bot commented Jul 9, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Docs preview

📖 View docs preview for docs/admin/integrations/prometheus.md

@johnstcn johnstcn changed the title feat(coderd): Add full text search over chat messages feat: Add full text search over chat messages Jul 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds end-to-end support for full-text search over chat history, spanning schema, backfill mechanics, query/filter plumbing, API validation, and documentation/metrics updates.

Changes:

  • Introduces chat_messages.search_tsv plus supporting indexes and trigger adjustments to avoid chatd state bumps during backfill.
  • Adds a dbpurge-driven backfill (BackfillChatMessagesSearchTsv) and a query preflight (ChatSearchQueryIsEmpty) to reject searches that tokenize to nothing.
  • Wires a search: filter through the searchquery parser, GetChats SQL, and the experimental chats API, with tests, metrics, and docs updates.

Reviewed changes

Copilot reviewed 21 out of 26 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
scripts/metricsdocgen/generated_metrics Registers the new dbpurge backfill counter in generated metrics output.
docs/reference/api/chats.md Documents search: in the experimental chats endpoint query syntax.
docs/admin/integrations/prometheus.md Adds the new backfill counter to the Prometheus metrics reference table.
coderd/x/chatd/chatstate/trigger_test.go Adds regression coverage ensuring search_tsv updates do not perturb chat state/history fields.
coderd/searchquery/search.go Parses search: and enforces mutual exclusion with title:, pr_title:, and pr:.
coderd/searchquery/search_test.go Adds table-driven test coverage for search: parsing and validation.
coderd/exp_chats.go Validates search text, passes Search through to GetChats, and updates swagger param docs.
coderd/exp_chats_test.go Adds handler-level tests covering matches, composition, empty-token searches, and mutual exclusion errors.
coderd/database/queries/chats.sql Adds backfill and preflight sqlc queries; extends GetChats with full-text search logic.
coderd/database/queries.sql.go Regenerates sqlc output for new queries and the updated GetChats signature/SQL.
coderd/database/querier.go Extends the sqlc querier interface with backfill + preflight methods.
coderd/database/querier_test.go Adds database-level tests verifying GetChats search behavior across title/PR/message arms and composition.
coderd/database/models.go Extends ChatMessage model with the new search_tsv column.
coderd/database/modelqueries.go Threads the new Search param through the authorized chat listing path.
coderd/database/migrations/migrate_test.go Adds migration tests for extraction function behavior and index presence/queue semantics.
coderd/database/migrations/000541_chat_search_schema.up.sql Adds chat_message_search_text, search_tsv, indexes, and trigger changes to ignore search_tsv updates.
coderd/database/migrations/000541_chat_search_schema.down.sql Reverts trigger changes and removes new schema objects.
coderd/database/dump.sql Updates schema dump with new function/column/indexes and trigger logic.
coderd/database/dbpurge/dbpurge.go Adds incremental per-tick backfill loop and a dedicated metric counter.
coderd/database/dbpurge/dbpurge_test.go Adds integration tests for backfill convergence, ordering, sentinel behavior, per-tick bounds, and metrics.
coderd/database/dbmock/dbmock.go Regenerates mocks to include the new store methods.
coderd/database/dbmetrics/querymetrics.go Adds query latency/count instrumentation for the new queries.
coderd/database/dbauthz/dbauthz.go Adds authz gating for backfill (chat:update) and preflight (chat:read).
coderd/database/dbauthz/dbauthz_test.go Adds method-level authz tests for the new queries.
coderd/apidoc/swagger.json Regenerates swagger with updated q parameter documentation.
coderd/apidoc/docs.go Regenerates embedded swagger template with updated q parameter documentation.
Files not reviewed (5)
  • coderd/apidoc/docs.go: Generated file
  • coderd/database/dbmetrics/querymetrics.go: Generated file
  • coderd/database/dbmock/dbmock.go: Generated file
  • coderd/database/models.go: Generated file
  • coderd/database/querier.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread coderd/database/queries/chats.sql Outdated
Comment thread coderd/database/queries.sql.go
Comment thread coderd/database/migrations/migrate_test.go Outdated

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came out simpler than I had feared, nice work. Some feedback inline.

Comment thread coderd/database/dbauthz/dbauthz.go Outdated
rbac.ResourceWorkspaceBuildOrchestration.Type: {policy.ActionDelete},
// Chat auto-archive sets archived=true on inactive chats.
// Chat auto-archive sets archived=true on inactive chats; the
// search_tsv backfill also updates chat messages.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This being a part of dbpurge feels a bit off.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Danny has an RFC in the works but I didn't want to block this until that got finalized.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we have a timeline for when/if it will be actually implemented. It might be worthwhile making a copy of dbpurge, say dblivemigrate and plopping the implementation there.

If not, then at least make it very explicit with comments and possibly file naming that it's in the wrong place and meant to be moved once XXX lands.

Comment thread coderd/database/dbauthz/dbauthz.go Outdated
Comment thread coderd/database/dbpurge/dbpurge.go Outdated
Comment thread coderd/database/dbpurge/dbpurge.go Outdated
Comment thread coderd/database/dbpurge/dbpurge.go Outdated
Comment thread coderd/database/migrations/000543_chat_search_schema.up.sql Outdated
Comment thread coderd/database/queries/chats.sql Outdated
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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you looked at how well the query planner can optimize these SELECT 1 queries? IIRC it isn't always very efficient.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't. I think we may want to do an EXPLAIN on the query once the indexes are in place on dogfood.

Comment thread coderd/database/queries/chats.sql Outdated
Comment thread coderd/database/queries/chats.sql Outdated
@johnstcn
johnstcn merged commit f7481c5 into main Jul 16, 2026
32 of 33 checks passed
@johnstcn
johnstcn deleted the cj/codagt-721 branch July 16, 2026 14:22
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants