Skip to content

Commit f94ca14

Browse files
committed
feat: allow user to configure whether synonyms should or should not be included when determining exact matches
1 parent 04e00f6 commit f94ca14

File tree

9 files changed

+50
-36
lines changed

9 files changed

+50
-36
lines changed

.golangci.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ linters:
9696
- rowserrcheck # checks whether Err of rows is checked successfully
9797
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
9898
- sloglint # A Go linter that ensures consistent code style when using log/slog
99-
- tenv # detects using os.Setenv instead of t.Setenv since Go1.17
99+
- usetesting # detects using os.Setenv instead of t.Setenv since Go1.17
100100
- testableexamples # checks if examples are testable (have an expected output)
101101
- tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes
102102
- unconvert # removes unnecessary type conversions

cmd/main.go

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const (
5252
primarySuggestMultiplier = "primary-suggest-multiplier"
5353
rankThreshold = "rank-threshold"
5454
preRankLimitMultiplier = "pre-rank-limit-multiplier"
55+
synonymsExactMatch = "synonyms-exact-match"
5556
)
5657

5758
var (
@@ -238,6 +239,13 @@ func main() {
238239
Required: false,
239240
Value: 10,
240241
},
242+
&cli.BoolFlag{
243+
Name: synonymsExactMatch,
244+
EnvVars: []string{strcase.ToScreamingSnake(synonymsExactMatch)},
245+
Usage: "When true synonyms are taken into account during exact match calculation",
246+
Required: false,
247+
Value: false,
248+
},
241249
},
242250
Action: func(c *cli.Context) error {
243251
log.Println(c.Command.Usage)
@@ -271,6 +279,7 @@ func main() {
271279
c.Float64(primarySuggestMultiplier),
272280
c.Int(rankThreshold),
273281
c.Int(preRankLimitMultiplier),
282+
c.Bool(synonymsExactMatch),
274283
)
275284
if err != nil {
276285
return err

internal/search/datasources/postgres/postgres.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ type Postgres struct {
2929
primarySuggestMultiplier float64
3030
rankThreshold int
3131
preRankLimitMultiplier int
32+
synonymsExactMatch bool
3233
}
3334

34-
func NewPostgres(dbConn string, queryTimeout time.Duration, searchIndex string, searchIndexSrid d.SRID, rankNormalization int, exactMatchMultiplier float64, primarySuggestMultiplier float64, rankThreshold int, preRankLimitMultiplier int) (*Postgres, error) {
35+
func NewPostgres(dbConn string, queryTimeout time.Duration, searchIndex string, searchIndexSrid d.SRID,
36+
rankNormalization int, exactMatchMultiplier float64, primarySuggestMultiplier float64, rankThreshold int,
37+
preRankLimitMultiplier int, synonymsExactMatch bool) (*Postgres, error) {
38+
3539
ctx := context.Background()
3640
config, err := pgxpool.ParseConfig(dbConn)
3741
if err != nil {
@@ -57,6 +61,7 @@ func NewPostgres(dbConn string, queryTimeout time.Duration, searchIndex string,
5761
primarySuggestMultiplier,
5862
rankThreshold,
5963
preRankLimitMultiplier,
64+
synonymsExactMatch,
6065
}, nil
6166
}
6267

@@ -76,7 +81,7 @@ func (p *Postgres) SearchFeaturesAcrossCollections(ctx context.Context, searchQu
7681
}
7782
sql := makeSQL(p.searchIndex, srid, bboxFilter)
7883
wildcardQuery := searchQuery.ToWildcardQuery()
79-
exactMatchQuery := searchQuery.ToExactMatchQuery()
84+
exactMatchQuery := searchQuery.ToExactMatchQuery(p.synonymsExactMatch)
8085
names, versions, relevance := collections.NamesAndVersionsAndRelevance()
8186
log.Printf("\nSEARCH QUERY (wildcard): %s\n", wildcardQuery)
8287

internal/search/domain/search.go

+16-16
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const (
1010
VersionParam = "version"
1111
RelevanceParam = "relevance"
1212
DefaultRelevance = 0.5
13-
Wildcard = ":*"
1413
)
1514

1615
// GeoJSON properties in search response
@@ -36,36 +35,37 @@ func NewSearchQuery(words []string, withoutSynonyms map[string]struct{}, withSyn
3635
}
3736

3837
func (q *SearchQuery) ToWildcardQuery() string {
39-
return q.toString(true)
38+
return q.toString(true, true)
4039
}
4140

42-
func (q *SearchQuery) ToExactMatchQuery() string {
43-
return q.toString(false)
41+
func (q *SearchQuery) ToExactMatchQuery(useSynonyms bool) string {
42+
return q.toString(false, useSynonyms)
4443
}
4544

46-
func (q *SearchQuery) toString(wildcard bool) string {
45+
func (q *SearchQuery) toString(useWildcard bool, useSynonyms bool) string {
46+
wildcard := ""
47+
if useWildcard {
48+
wildcard = ":*"
49+
}
50+
4751
sb := &strings.Builder{}
4852
for i, word := range q.words {
4953
if i > 0 {
5054
sb.WriteString(" & ")
5155
}
5256
if _, ok := q.withoutSynonyms[word]; ok {
5357
sb.WriteString(word)
54-
if wildcard {
55-
sb.WriteString(Wildcard)
56-
}
58+
sb.WriteString(wildcard)
5759
} else if synonyms, ok := q.withSynonyms[word]; ok {
5860
slices.Sort(synonyms)
5961
sb.WriteByte('(')
6062
sb.WriteString(word)
61-
if wildcard {
62-
sb.WriteString(Wildcard)
63-
}
64-
for _, synonym := range synonyms {
65-
sb.WriteString(" | ")
66-
sb.WriteString(synonym)
67-
if wildcard {
68-
sb.WriteString(Wildcard)
63+
sb.WriteString(wildcard)
64+
if useSynonyms {
65+
for _, synonym := range synonyms {
66+
sb.WriteString(" | ")
67+
sb.WriteString(synonym)
68+
sb.WriteString(wildcard)
6969
}
7070
}
7171
sb.WriteByte(')')

internal/search/main.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ type Search struct {
2626
json *jsonFeatures
2727
}
2828

29-
func NewSearch(e *engine.Engine, dbConn string, searchIndex string, searchIndexSrid int, rewritesFile string, synonymsFile string, rankNormalization int, exactMatchMultiplier float64, primarySuggestMultiplier float64, rankThreshold int, preRankLimitMultiplier int) (*Search, error) {
29+
func NewSearch(e *engine.Engine, dbConn string, searchIndex string, searchIndexSrid int, rewritesFile string,
30+
synonymsFile string, rankNormalization int, exactMatchMultiplier float64, primarySuggestMultiplier float64,
31+
rankThreshold int, preRankLimitMultiplier int, synonymsExactMatch bool) (*Search, error) {
32+
3033
queryExpansion, err := NewQueryExpansion(rewritesFile, synonymsFile)
3134
if err != nil {
3235
return nil, err
@@ -43,6 +46,7 @@ func NewSearch(e *engine.Engine, dbConn string, searchIndex string, searchIndexS
4346
primarySuggestMultiplier,
4447
rankThreshold,
4548
preRankLimitMultiplier,
49+
synonymsExactMatch,
4650
),
4751
json: newJSONFeatures(e),
4852
queryExpansion: queryExpansion,
@@ -137,7 +141,10 @@ func (s *Search) enrichFeaturesWithHref(fc *domain.FeatureCollection, outputCRS
137141
return nil
138142
}
139143

140-
func newDatasource(e *engine.Engine, dbConn string, searchIndex string, searchIndexSrid int, rankNormalization int, exactMatchMultiplier float64, primarySuggestMultiplier float64, rankThreshold int, preRankLimitMultiplier int) ds.Datasource {
144+
func newDatasource(e *engine.Engine, dbConn string, searchIndex string, searchIndexSrid int, rankNormalization int,
145+
exactMatchMultiplier float64, primarySuggestMultiplier float64, rankThreshold int,
146+
preRankLimitMultiplier int, synonymsExactMatch bool) ds.Datasource {
147+
141148
datasource, err := postgres.NewPostgres(
142149
dbConn,
143150
timeout,
@@ -148,6 +155,7 @@ func newDatasource(e *engine.Engine, dbConn string, searchIndex string, searchIn
148155
primarySuggestMultiplier,
149156
rankThreshold,
150157
preRankLimitMultiplier,
158+
synonymsExactMatch,
151159
)
152160
if err != nil {
153161
log.Fatalf("failed to create datasource: %v", err)

internal/search/main_test.go

+3-11
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,11 @@ func TestSearch(t *testing.T) {
5959
assert.NoError(t, err)
6060

6161
// given search endpoint
62-
searchEndpoint, err := NewSearch(
63-
eng,
64-
dbConn,
65-
testSearchIndex,
66-
domain.WGS84SRIDPostgis,
62+
searchEndpoint, err := NewSearch(eng, dbConn, testSearchIndex, domain.WGS84SRIDPostgis,
6763
"internal/search/testdata/rewrites.csv",
6864
"internal/search/testdata/synonyms.csv",
69-
1,
70-
3.0,
71-
1.01,
72-
4000,
73-
10,
74-
)
65+
1, 3.0, 1.01,
66+
4000, 10, false)
7567
assert.NoError(t, err)
7668

7769
// given empty search index

internal/search/query_expansion_fuzz_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func FuzzExpand(f *testing.F) {
3434
f.Fuzz(func(t *testing.T, input string) {
3535
expanded, err := queryExpansion.Expand(context.Background(), input)
3636
assert.NoError(t, err)
37-
query := expanded.ToExactMatchQuery()
37+
query := expanded.ToExactMatchQuery(true)
3838

3939
assert.Truef(t, utf8.ValidString(query), "valid string")
4040
if strings.TrimSpace(input) != "" {

internal/search/query_expansion_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ westgoeverneurstraat | westgoevstraat | westgouvstraat) & 1800
189189
if tt.args.wildcard {
190190
query = actual.ToWildcardQuery()
191191
} else {
192-
query = actual.ToExactMatchQuery()
192+
query = actual.ToExactMatchQuery(true)
193193
}
194194
assert.Equal(t, strings.ReplaceAll(tt.want, "\n", ""), query, tt.args.searchQuery)
195195
})

internal/search/testdata/expected-synonym-with-space.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"displayName": "Spui 70 2511 BT s-Gravenhage",
2020
"highlight": "<b>Spui</b> 70 2511 BT <b>s</b>-<b>Gravenhage</b>",
2121
"href": "https://example.com/ogc/v1/collections/addresses/items/154?f=json",
22-
"score": 0.038760408759117126
22+
"score": 0.029046258330345156
2323
},
2424
"geometry": {
2525
"type": "Point",
@@ -48,7 +48,7 @@
4848
"displayName": "Spui 180 2511 BW 's-Gravenhage",
4949
"highlight": "<b>Spui</b> 180 2511 BW '<b>s</b>-<b>Gravenhage</b>",
5050
"href": "https://example.com/ogc/v1/collections/addresses/items/155?f=json",
51-
"score": 0.038760408759117126
51+
"score": 0.029046258330345156
5252
},
5353
"geometry": {
5454
"type": "Point",

0 commit comments

Comments
 (0)