From fddd4a70bcb32eaff394a2fa5399efcd1b2ae251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Garc=C3=ADa=20Montoro?= Date: Wed, 4 Feb 2026 13:00:44 +0100 Subject: [PATCH] Avoid `simple` config when doing FTS in Postgres (#35063) (#35179) This commit reverts PR #30214, which addressed bug MM-60790 but caused a performance regression tracked by MM-66782. This revert has two implications: 1. The performance issue is solved. 2. The original bug is re-introduced. Re-introducing the original bug seems not to be ideal, but I argue that the original PR did not actually fix the bug: - Before that PR, looking for a quoted string would return additional results: the UX was slightly confusing, because when the user looked for the word "stateful", the results would contain matches like "states" (see MM-60790). - After that PR, looking for a quoted string can timeout, so that the list of results becomes empty. The UX here may be less confusing, since the user simply doesn't find what they're looking for, and they may assume that string is not present in any post, but it's completely wrong: the result list is empty because the SQL query timed out and thus the endpoint returned 0 results. The solution to the original issue should be addressed via Elasticsearch, which should provide a more refined and precise search results. For more information on the investigation on this issue and the motivation behind the revert, see https://mattermost.atlassian.net/wiki/x/IYAk_w Co-authored-by: Mattermost Build --- .../channels/store/searchtest/post_layer.go | 64 ------------------- server/channels/store/sqlstore/post_store.go | 10 --- 2 files changed, 74 deletions(-) diff --git a/server/channels/store/searchtest/post_layer.go b/server/channels/store/searchtest/post_layer.go index f28196ad106..05359c59615 100644 --- a/server/channels/store/searchtest/post_layer.go +++ b/server/channels/store/searchtest/post_layer.go @@ -35,11 +35,6 @@ var searchPostStoreTests = []searchTest{ Fn: testSearchANDORQuotesCombinations, Tags: []string{EnginePostgres, EngineMySQL, EngineElasticSearch}, }, - { - Name: "Should be able to search without stemming", - Fn: testStemming, - Tags: []string{EnginePostgres, EngineMySQL}, - }, { // Postgres supports search with and without quotes Name: "Should be able to search for email addresses with or without quotes", @@ -472,65 +467,6 @@ func testSearchANDORQuotesCombinations(t *testing.T, th *SearchTestHelper) { } } -func testStemming(t *testing.T, th *SearchTestHelper) { - p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "great minds think", "", model.PostTypeDefault, 0, false) - require.NoError(t, err) - p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "mindful of what you think", "", model.PostTypeDefault, 0, false) - require.NoError(t, err) - - defer th.deleteUserPosts(th.User.Id) - - testCases := []struct { - name string - terms string - orTerms bool - expectedLen int - expectedIDs []string - }{ - { - name: "simple search, no stemming", - terms: `"minds think"`, - orTerms: false, - expectedLen: 1, - expectedIDs: []string{p1.Id}, - }, - { - name: "simple search, single word, no stemming", - terms: `"minds"`, - orTerms: false, - expectedLen: 1, - expectedIDs: []string{p1.Id}, - }, - { - name: "non-simple search, stemming", - terms: `minds think`, - orTerms: true, - expectedLen: 2, - expectedIDs: []string{p1.Id, p2.Id}, - }, - { - name: "simple search, no stemming, no results", - terms: `"mind"`, - orTerms: false, - expectedLen: 0, - expectedIDs: []string{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - params := &model.SearchParams{Terms: tc.terms, OrTerms: tc.orTerms} - results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20) - require.NoError(t, err) - - require.Len(t, results.Posts, tc.expectedLen) - for _, id := range tc.expectedIDs { - th.checkPostInSearchResults(t, id, results.Posts) - } - }) - } -} - func testSearchEmailAddresses(t *testing.T, th *SearchTestHelper) { p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "email test@test.com", "", model.PostTypeDefault, 0, false) require.NoError(t, err) diff --git a/server/channels/store/sqlstore/post_store.go b/server/channels/store/sqlstore/post_store.go index b1b823c86c9..0661e50253e 100644 --- a/server/channels/store/sqlstore/post_store.go +++ b/server/channels/store/sqlstore/post_store.go @@ -2160,7 +2160,6 @@ func (s *SqlPostStore) search(teamId string, userId string, params *model.Search terms = wildCardRegex.ReplaceAllLiteralString(terms, ":* ") excludedTerms = wildCardRegex.ReplaceAllLiteralString(excludedTerms, ":* ") - simpleSearch := false // Replace spaces with to_tsquery symbols replaceSpaces := func(input string, excludedInput bool) string { if input == "" { @@ -2172,11 +2171,6 @@ func (s *SqlPostStore) search(teamId string, userId string, params *model.Search // Replace spaces within quoted strings with '<->' input = quotedStringsRegex.ReplaceAllStringFunc(input, func(match string) string { - // If the whole search term is a quoted string, - // we don't want to do stemming. - if input == match { - simpleSearch = true - } return strings.Replace(match, " ", "<->", -1) }) @@ -2197,10 +2191,6 @@ func (s *SqlPostStore) search(teamId string, userId string, params *model.Search } textSearchCfg := s.pgDefaultTextSearchConfig - if simpleSearch { - textSearchCfg = "simple" - } - searchClause := fmt.Sprintf("to_tsvector('%[1]s', %[2]s) @@ to_tsquery('%[1]s', ?)", textSearchCfg, searchType) baseQuery = baseQuery.Where(searchClause, tsQueryClause) } else if s.DriverName() == model.DatabaseDriverMysql {