Open
Description
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"`
}
}
}