go-elasticsearch
go-elasticsearch copied to clipboard
[ENHANCEMENT] Search results helper
The client should provide a high-level helper component for convenient, efficient handling of search results.
It should provide the following facilities:
- Easy iteration over search results (metadata as well as hits)
- Easy access to the
_source
field - Support for all hit properties (
sort
,highlight
, ...) - Support for scrolling the search
- Support for injecting a custom JSON decoder
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
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"`
}
}
}
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.
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.)
Thanks for the Suggestion. Yeah, it's better to contribute to a few good first issues first. Thanks.