elastic / go-elasticsearch

The official Go client for Elasticsearch
https://github.com/elastic/go-elasticsearch#go-elasticsearch
Apache License 2.0
5.67k stars 616 forks source link

[ENHANCEMENT] Search results helper #186

Open karmi opened 4 years ago

karmi commented 4 years ago

The client should provide a high-level helper component for convenient, efficient handling of search results.

It should provide the following facilities:

Example:

res, err := es.Search(
// ...

results, _ := NewSearchResponse(res.Body)

log.Println("Total hits:", results.Hits.Total())

for results.Hits.Next() {
    item := results.Hits.Item()
    fmt.Printf("* %s \n", item.Source["title"])
}
Example implementation

```golang package main import ( "context" "encoding/json" "fmt" "io" "log" "os" "strings" "github.com/elastic/go-elasticsearch/v8" "github.com/elastic/go-elasticsearch/v8/estransport" "github.com/elastic/go-elasticsearch/v8/esutil" ) func main() { log.SetFlags(0) var indexName = "test-search" es, _ := elasticsearch.NewClient(elasticsearch.Config{ Logger: &estransport.ColorLogger{ Output: os.Stdout, EnableRequestBody: false, EnableResponseBody: false, }, }) bi, err := esutil.NewBulkIndexer(esutil.BulkIndexerConfig{ Index: indexName, Client: es, }) if err != nil { log.Fatalf("Error creating the indexer: %s", err) } log.Println("Indexing the documents...") es.Indices.Delete([]string{indexName}) for i := 1; i <= 15; i++ { bi.Add( context.Background(), esutil.BulkIndexerItem{ Action: "index", Body: strings.NewReader(fmt.Sprintf(`{"title" : "Test %03d"}`, i)), }, ) } bi.Close(context.Background()) es.Indices.Refresh(es.Indices.Refresh.WithIndex(indexName)) log.Println(strings.Repeat("-", 80)) log.Println("Searching the index...") res, err := es.Search( es.Search.WithIndex(indexName), es.Search.WithSize(12), ) if err != nil { log.Fatalf("ERROR: %s", err) } defer res.Body.Close() if res.IsError() { log.Fatalf("ERROR: %s", res.Status()) } results, err := NewSearchResponse(res.Body) if err != nil { log.Fatalf("ERROR: %s", err) } log.Println("Total hits:", results.Hits.Total()) for results.Hits.Next() { item := results.Hits.Item() fmt.Printf("* %s \n", item.Source["title"]) } } // ---------------------------------------------------------------------------- func NewSearchResponse(body io.Reader) (SearchResponse, error) { var response = SearchResponse{ body: body, Hits: &SearchResponseHits{}, } var r envelopeResponse if err := json.NewDecoder(body).Decode(&r); err != nil { return response, err } response.Hits.total = r.Hits.Total.Value for _, h := range r.Hits.Hits { var hit SearchResponseHit hit.ID = h.ID hit.Index = h.Index hit.Source = make(map[string]interface{}) if err := json.Unmarshal(h.Source, &hit.Source); err != nil { return response, err } response.Hits.append(hit) } return response, nil } type SearchResponse struct { body io.Reader Hits *SearchResponseHits } type SearchResponseHits struct { hits []SearchResponseHit total int currentIndex int } type SearchResponseHit struct { Index string ID string Source map[string]interface{} } func (h *SearchResponseHits) Total() int { return h.total } func (h *SearchResponseHits) Next() bool { if h.currentIndex < len(h.hits) { h.currentIndex++ return true } h.currentIndex = 0 return false } func (h *SearchResponseHits) Item() SearchResponseHit { return h.hits[h.currentIndex-1] } func (h *SearchResponseHits) append(hit SearchResponseHit) { h.hits = append(h.hits, hit) } type envelopeResponse struct { Took int Hits struct { Total struct{ Value int } Hits []struct { Index string `json:"_index"` ID string `json:"_id"` Source json.RawMessage `json:"_source"` } } } ```

bisakhmondal commented 4 years ago

Hey @karmi, I'm interested in implementing the mentioned features. Would you please guide me from where should I start? I'm determined but new here. I'd love to work on this issue. Thanks.

karmi commented 4 years ago

Hello, thanks for the offer. I'm not sure that this issue is best for starting with adding features to the package. It assumes a lot of familiarity with Elasticsearch and common usage patterns, figuring out how to keep any supporting structures in sync with the Elasticsearch response format evolution, and so on. A more approachable "good first issue" is adding support for metadata to the bulk indexing helper, see eg. https://github.com/elastic/go-elasticsearch/issues/175 and related issues / pull requests. (That said, if you want to play with the feature, I can look at patches, of course.)

bisakhmondal commented 4 years ago

Thanks for the Suggestion. Yeah, it's better to contribute to a few good first issues first. Thanks.