Skip to content

Commit

Permalink
Search suggest (#1)
Browse files Browse the repository at this point in the history
add option search.suggest
  • Loading branch information
moguchev authored Feb 19, 2024
1 parent 10c4eca commit 1afb9e8
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 2 deletions.
13 changes: 13 additions & 0 deletions effdsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type (
RewriteParameter = objs.RewriteParameter

SortOrder = objs.SortOrder

SuggestSort = objs.SuggestSort
SuggestMode = objs.SuggestMode
)

//--------------------------------------------------------------------------------------//
Expand Down Expand Up @@ -101,6 +104,16 @@ var (
WithIncludes = objs.WithIncludes
WithExcludes = objs.WithExcludes
SourceFilter = objs.SourceFilter

// search_source_filtering.go
Suggesters = objs.Suggesters
WithSuggest = objs.WithSuggest
TermSuggester = objs.TermSuggester
Term = objs.Term
WithTermSuggestAnalyzer = objs.WithTermSuggestAnalyzer
WithTermSuggestSize = objs.WithTermSuggestSize
WithTermSuggestSort = objs.WithTermSuggestSort
WithTermSuggestMode = objs.WithTermSuggestMode
)

//--------------------------------------------------------------------------------------//
Expand Down
28 changes: 27 additions & 1 deletion objects/body.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type SearchBody struct {
SearchAfter SearchAfterType `json:"search_after,omitempty"`
Collapse json.Marshaler `json:"collapse,omitempty"`
PIT json.Marshaler `json:"pit,omitempty"`
Suggest Suggest `json:"suggest,omitempty"`
}

type BodyOption func(*SearchBody) error
Expand Down Expand Up @@ -174,7 +175,7 @@ func WithCollpse(field string) BodyOption {
//--------------------------------------------------------------------------------------//

// You can use the _source parameter to select what fields of the source are returned. This is called source filtering.
//The following search API request sets the _source request body parameter to false. The document source is not included in the response.
// The following search API request sets the _source request body parameter to false. The document source is not included in the response.
// [Source filtering]: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html#source-filtering
func WithSourceFilter(opts ...SourceFitlerOption) BodyOption {
sourceFilter := SourceFilter(opts...)
Expand All @@ -200,3 +201,28 @@ func Define(opts ...BodyOption) (body *SearchBody, err error) {
}
return body, nil
}

//--------------------------------------------------------------------------------------//
// Suggest //
//--------------------------------------------------------------------------------------//

type Suggest interface {
SuggestInfo() string
json.Marshaler
}

type SuggestResult struct {
Ok Suggest
Err error
}

// WithSuggest - allows you to use suggest
func WithSuggest(suggestResult SuggestResult) BodyOption {
suggest := suggestResult.Ok
err := suggestResult.Err
// Type assertion
return func(b *SearchBody) error {
b.Suggest = suggest
return err
}
}
2 changes: 1 addition & 1 deletion objects/q_range_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func RangeQuery(field string, opts ...RangeQueryOption) QueryResult {
if rangeQuery.GT == nil && rangeQuery.GTE == nil && rangeQuery.LT == nil && rangeQuery.LTE == nil {
return QueryResult{
Ok: rangeQuery,
Err: errors.New("One of WithGT, WithGTE, WithLT, WithLTE should be proveded for range query"),
Err: errors.New("one of WithGT, WithGTE, WithLT, WithLTE should be proveded for range query"),
}
}
return QueryResult{
Expand Down
152 changes: 152 additions & 0 deletions objects/search_suggest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package objects

import "encoding/json"

type SuggestS struct {
GlobalText string `json:"text,omitempty"` // To avoid repetition of the suggest text, it is possible to define a global text.
Suggester
}

func (s SuggestS) MarshalJSON() ([]byte, error) {
type SuggesterBase SuggestS
return json.Marshal((SuggesterBase)(s))
}

func (s SuggestS) SuggestInfo() string {
return "Suggest"
}

// Suggests similar looking terms based on a provided text by using a suggester.
// [Suggesters]: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html#search-suggesters
func Suggesters(globalText string, s Suggester) SuggestResult {
sugest := SuggestS{
GlobalText: globalText,
Suggester: s,
}

return SuggestResult{
Ok: sugest,
Err: nil,
}
}

// ----------------------------------------------------

type Suggester interface {
json.Marshaler
_type() string
}

// ----------------------------------------------------

// TermSuggester - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html#term-suggester
type TermsSuggest []TermSuggest

func (ts TermsSuggest) MarshalJSON() ([]byte, error) {
type TermSuggesterBase TermSuggest

terms := make(M, len(ts))
for i := range ts {
terms[ts[i].GetName()] = (TermSuggesterBase)(ts[i])
}

return json.Marshal(terms)
}

func (ts TermsSuggest) _type() string {
return "term suggester"
}

type TermSuggesterS struct {
Name string `json:"-"` //
Text string `json:"text,omitempty"` // The suggest text. The suggest text is a required option that needs to be set globally or per suggestion.
Term TermS `json:"term"` // (Required) The term suggester suggests terms based on edit distance. The provided suggest text is analyzed before terms are suggested. The suggested terms are provided per analyzed suggest text token. The term suggester doesn’t take the query into account that is part of request.
}

func (t TermSuggesterS) GetName() string {
return t.Name
}

type TermS struct {
Field string `json:"field"` // The field to fetch the candidate suggestions from. This is a required option that either needs to be set globally or per suggestion.
Analyzer string `json:"analyzer,omitempty"` // The analyzer to analyse the suggest text with. Defaults to the search analyzer of the suggest field.
Size int `json:"size,omitempty"` // The maximum corrections to be returned per suggest text token.
Sort SuggestSort `json:"sort,omitempty"` // Defines how suggestions should be sorted per suggest text term. Two possible values: score, frequency
SuggestMode SuggestMode `json:"suggest_mode,omitempty"`
}

// SuggestSort - Defines how suggestions should be sorted per suggest text term.
type SuggestSort string

const (
// SortScore - Sort by score first, then document frequency and then the term itself.
SortScore SuggestSort = "score"
// FrequencyScore - Sort by document frequency first, then similarity score and then the term itself.
FrequencyScore SuggestSort = "frequency"
)

// SuggestMode - The suggest mode controls what suggestions are included or controls
// for what suggest text terms, suggestions should be suggested.
type SuggestMode string

const (
// SuggestModeMissing - Only provide suggestions for suggest text terms that are not in the index (default).
SuggestModeMissing SuggestMode = "score"
// SuggestModePopular - Only suggest suggestions that occur in more docs than the original suggest text term.
SuggestModePopular SuggestMode = "popular"
// Suggest any matching suggestions based on terms in the suggest text.
SuggestModeAlways SuggestMode = "always"
)

type TermSuggestOption func(*TermS)

func WithTermSuggestAnalyzer(analyzer string) TermSuggestOption {
return func(termSuggest *TermS) {
termSuggest.Analyzer = analyzer
}
}

func WithTermSuggestSize(size int) TermSuggestOption {
return func(termSuggest *TermS) {
termSuggest.Size = size
}
}

func WithTermSuggestSort(sort SuggestSort) TermSuggestOption {
return func(termSuggest *TermS) {
termSuggest.Sort = sort
}
}

func WithTermSuggestMode(mode SuggestMode) TermSuggestOption {
return func(termSuggest *TermS) {
termSuggest.SuggestMode = mode
}
}

func Term(name, text, field string, opts ...TermSuggestOption) TermSuggest {
s := TermSuggesterS{
Name: name,
Text: text,
Term: TermS{
Field: field,
},
}
for _, opt := range opts {
opt(&s.Term)
}
return s
}

type TermSuggest interface {
GetName() string
}

// TermSuggester - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html#term-suggester
func TermSuggester(terms ...TermSuggest) Suggester {
v := make(TermsSuggest, 0, len(terms))
for i := range terms {
v = append(v, terms[i])
}
return v
}
25 changes: 25 additions & 0 deletions objects/search_suggest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package objects

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_Searhc_Suggest_MarshalJSON(t *testing.T) {
s := Suggesters("test", TermSuggester(
Term("my-suggestion-1", "tring out Elasticsearch", "message"),
Term("my-suggestion-2", "tring out Elasticsearch", "message",
WithTermSuggestMode(SuggestModeAlways),
WithTermSuggestAnalyzer("test"),
WithTermSuggestSize(1),
WithTermSuggestSort(SortScore),
),
))

body, err := s.Ok.MarshalJSON()
require.NoError(t, err)

const expected = `{"suggest":{"my-suggestion-1":{"text":"tring out Elasticsearch","term":{"field":"message"}},"my-suggestion-2":{"text":"tring out Elasticsearch","term":{"field":"message","analyzer":"test","size":1,"sort":"score","suggest_mode":"always"}}}}`
require.Equal(t, expected, string(body))
}

0 comments on commit 1afb9e8

Please sign in to comment.