Skip to content

Commit c6e5ec5

Browse files
authored
Meilisearch double quote on "match" query (#29740)
make `nonFuzzyWorkaround` unessesary cc @Kerollmops
1 parent 3cd6494 commit c6e5ec5

File tree

2 files changed

+37
-70
lines changed

2 files changed

+37
-70
lines changed

modules/indexer/issues/meilisearch/meilisearch.go

Lines changed: 25 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package meilisearch
66
import (
77
"context"
88
"errors"
9+
"fmt"
910
"strconv"
1011
"strings"
1112

@@ -217,7 +218,14 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
217218

218219
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
219220

220-
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(options.Keyword, &meilisearch.SearchRequest{
221+
keyword := options.Keyword
222+
if !options.IsFuzzyKeyword {
223+
// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s)
224+
// https://www.meilisearch.com/docs/reference/api/search#phrase-search
225+
keyword = doubleQuoteKeyword(keyword)
226+
}
227+
228+
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(keyword, &meilisearch.SearchRequest{
221229
Filter: query.Statement(),
222230
Limit: int64(limit),
223231
Offset: int64(skip),
@@ -228,7 +236,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
228236
return nil, err
229237
}
230238

231-
hits, err := nonFuzzyWorkaround(searchRes, options.Keyword, options.IsFuzzyKeyword)
239+
hits, err := convertHits(searchRes)
232240
if err != nil {
233241
return nil, err
234242
}
@@ -247,73 +255,32 @@ func parseSortBy(sortBy internal.SortBy) string {
247255
return field + ":asc"
248256
}
249257

250-
// nonFuzzyWorkaround is needed as meilisearch does not have an exact search
251-
// and you can only change "typo tolerance" per index. So we have to post-filter the results
252-
// https://www.meilisearch.com/docs/learn/configuration/typo_tolerance#configuring-typo-tolerance
253-
// TODO: remove once https://github.com/orgs/meilisearch/discussions/377 is addressed
254-
func nonFuzzyWorkaround(searchRes *meilisearch.SearchResponse, keyword string, isFuzzy bool) ([]internal.Match, error) {
258+
func doubleQuoteKeyword(k string) string {
259+
kp := strings.Split(k, " ")
260+
parts := 0
261+
for i := range kp {
262+
part := strings.Trim(kp[i], "\"")
263+
if part != "" {
264+
kp[parts] = fmt.Sprintf(`"%s"`, part)
265+
parts++
266+
}
267+
}
268+
return strings.Join(kp[:parts], " ")
269+
}
270+
271+
func convertHits(searchRes *meilisearch.SearchResponse) ([]internal.Match, error) {
255272
hits := make([]internal.Match, 0, len(searchRes.Hits))
256273
for _, hit := range searchRes.Hits {
257274
hit, ok := hit.(map[string]any)
258275
if !ok {
259276
return nil, ErrMalformedResponse
260277
}
261278

262-
if !isFuzzy {
263-
keyword = strings.ToLower(keyword)
264-
265-
// declare a anon func to check if the title, content or at least one comment contains the keyword
266-
found, err := func() (bool, error) {
267-
// check if title match first
268-
title, ok := hit["title"].(string)
269-
if !ok {
270-
return false, ErrMalformedResponse
271-
} else if strings.Contains(strings.ToLower(title), keyword) {
272-
return true, nil
273-
}
274-
275-
// check if content has a match
276-
content, ok := hit["content"].(string)
277-
if !ok {
278-
return false, ErrMalformedResponse
279-
} else if strings.Contains(strings.ToLower(content), keyword) {
280-
return true, nil
281-
}
282-
283-
// now check for each comment if one has a match
284-
// so we first try to cast and skip if there are no comments
285-
comments, ok := hit["comments"].([]any)
286-
if !ok {
287-
return false, ErrMalformedResponse
288-
} else if len(comments) == 0 {
289-
return false, nil
290-
}
291-
292-
// now we iterate over all and report as soon as we detect one match
293-
for i := range comments {
294-
comment, ok := comments[i].(string)
295-
if !ok {
296-
return false, ErrMalformedResponse
297-
}
298-
if strings.Contains(strings.ToLower(comment), keyword) {
299-
return true, nil
300-
}
301-
}
302-
303-
// we got no match
304-
return false, nil
305-
}()
306-
307-
if err != nil {
308-
return nil, err
309-
} else if !found {
310-
continue
311-
}
312-
}
313279
issueID, ok := hit["id"].(float64)
314280
if !ok {
315281
return nil, ErrMalformedResponse
316282
}
283+
317284
hits = append(hits, internal.Match{
318285
ID: int64(issueID),
319286
})

modules/indexer/issues/meilisearch/meilisearch_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,10 @@ func TestMeilisearchIndexer(t *testing.T) {
5353
tests.TestIndexer(t, indexer)
5454
}
5555

56-
func TestNonFuzzyWorkaround(t *testing.T) {
57-
// get unexpected return
58-
_, err := nonFuzzyWorkaround(&meilisearch.SearchResponse{
56+
func TestConvertHits(t *testing.T) {
57+
_, err := convertHits(&meilisearch.SearchResponse{
5958
Hits: []any{"aa", "bb", "cc", "dd"},
60-
}, "bowling", false)
59+
})
6160
assert.ErrorIs(t, err, ErrMalformedResponse)
6261

6362
validResponse := &meilisearch.SearchResponse{
@@ -82,14 +81,15 @@ func TestNonFuzzyWorkaround(t *testing.T) {
8281
},
8382
},
8483
}
85-
86-
// nonFuzzy
87-
hits, err := nonFuzzyWorkaround(validResponse, "bowling", false)
88-
assert.NoError(t, err)
89-
assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}}, hits)
90-
91-
// fuzzy
92-
hits, err = nonFuzzyWorkaround(validResponse, "bowling", true)
84+
hits, err := convertHits(validResponse)
9385
assert.NoError(t, err)
9486
assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}, {ID: 33}}, hits)
9587
}
88+
89+
func TestDoubleQuoteKeyword(t *testing.T) {
90+
assert.EqualValues(t, "", doubleQuoteKeyword(""))
91+
assert.EqualValues(t, `"a" "b" "c"`, doubleQuoteKeyword("a b c"))
92+
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g"))
93+
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g"))
94+
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword(`a "" "d" """g`))
95+
}

0 commit comments

Comments
 (0)