Skip to content

Commit 1057c2b

Browse files
committed
search: add Multi Get search request
Users can now use MultiGet to retrieve several documents in one roundtrip. See `multi_get_test.go` for an example.
1 parent 3ee991c commit 1057c2b

File tree

5 files changed

+314
-6
lines changed

5 files changed

+314
-6
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Here's the current API status.
119119
* Get (ok)
120120
* Delete (ok)
121121
* Update (missing)
122-
* Multi Get (missing)
122+
* Multi Get (ok)
123123
* Bulk (ok)
124124
* Bulk UDP (missing)
125125
* Delete By Query (missing)

client.go

+6
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ func (c *Client) Get() *GetService {
165165
return builder
166166
}
167167

168+
// MultiGet retrieves multiple documents in one roundtrip.
169+
func (c *Client) MultiGet() *MultiGetService {
170+
builder := NewMultiGetService(c)
171+
return builder
172+
}
173+
168174
// Exists checks if a document exists.
169175
func (c *Client) Exists() *ExistsService {
170176
builder := NewExistsService(c)

get.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,12 @@ func (b *GetService) Do() (*GetResult, error) {
144144
// -- Result of a get request.
145145

146146
type GetResult struct {
147-
Index string `json:"_index"`
148-
Type string `json:"_type"`
149-
Id string `json:"_id"`
150-
Source *json.RawMessage `json:"_source,omitempty"`
151-
Found bool `json:"found"`
147+
Index string `json:"_index"`
148+
Type string `json:"_type"`
149+
Id string `json:"_id"`
150+
Version int64 `json:"_version,omitempty"`
151+
Source *json.RawMessage `json:"_source,omitempty"`
152+
Found bool `json:"found,omitempty"`
153+
Fields []string `json:"fields,omitempty"`
154+
Error string `json:"error,omitempty"` // used only in MultiGet
152155
}

multi_get.go

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Copyright 2014 Oliver Eilhard. All rights reserved.
2+
// Use of this source code is governed by a MIT-license.
3+
// See http://olivere.mit-license.org/license.txt for details.
4+
5+
package elastic
6+
7+
import (
8+
"encoding/json"
9+
"fmt"
10+
"net/http"
11+
"net/url"
12+
)
13+
14+
type MultiGetService struct {
15+
client *Client
16+
preference string
17+
realtime *bool
18+
refresh *bool
19+
items []*MultiGetItem
20+
}
21+
22+
func NewMultiGetService(client *Client) *MultiGetService {
23+
builder := &MultiGetService{
24+
client: client,
25+
items: make([]*MultiGetItem, 0),
26+
}
27+
return builder
28+
}
29+
30+
func (b *MultiGetService) Preference(preference string) *MultiGetService {
31+
b.preference = preference
32+
return b
33+
}
34+
35+
func (b *MultiGetService) Refresh(refresh bool) *MultiGetService {
36+
b.refresh = &refresh
37+
return b
38+
}
39+
40+
func (b *MultiGetService) Realtime(realtime bool) *MultiGetService {
41+
b.realtime = &realtime
42+
return b
43+
}
44+
45+
func (b *MultiGetService) Add(items ...*MultiGetItem) *MultiGetService {
46+
b.items = append(b.items, items...)
47+
return b
48+
}
49+
50+
func (b *MultiGetService) Source() interface{} {
51+
source := make(map[string]interface{})
52+
items := make([]interface{}, len(b.items))
53+
for i, item := range b.items {
54+
items[i] = item.Source()
55+
}
56+
source["docs"] = items
57+
return source
58+
}
59+
60+
func (b *MultiGetService) Do() (*MultiGetResult, error) {
61+
// Build url
62+
urls := "/_mget"
63+
64+
params := make(url.Values)
65+
if b.realtime != nil {
66+
params.Add("realtime", fmt.Sprintf("%v", *b.realtime))
67+
}
68+
if b.preference != "" {
69+
params.Add("preference", b.preference)
70+
}
71+
if b.refresh != nil {
72+
params.Add("refresh", fmt.Sprintf("%v", *b.refresh))
73+
}
74+
if len(params) > 0 {
75+
urls += "?" + params.Encode()
76+
}
77+
78+
// Set up a new request
79+
req, err := b.client.NewRequest("GET", urls)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
// Set body
85+
req.SetBodyJson(b.Source())
86+
87+
// Get response
88+
res, err := b.client.c.Do((*http.Request)(req))
89+
if err != nil {
90+
return nil, err
91+
}
92+
if err := checkResponse(res); err != nil {
93+
return nil, err
94+
}
95+
defer res.Body.Close()
96+
ret := new(MultiGetResult)
97+
if err := json.NewDecoder(res.Body).Decode(ret); err != nil {
98+
return nil, err
99+
}
100+
return ret, nil
101+
}
102+
103+
// -- Multi Get Item --
104+
105+
// MultiGetItem is a single document to retrieve via the MultiGetService.
106+
type MultiGetItem struct {
107+
index string
108+
typ string
109+
id string
110+
routing string
111+
fields []string
112+
version int64 // see org.elasticsearch.common.lucene.uid.Versions
113+
versionType string // see org.elasticsearch.index.VersionType
114+
fsc *FetchSourceContext
115+
}
116+
117+
func NewMultiGetItem() *MultiGetItem {
118+
return &MultiGetItem{
119+
version: -3, // MatchAny (see Version below)
120+
}
121+
}
122+
123+
func (item *MultiGetItem) Index(index string) *MultiGetItem {
124+
item.index = index
125+
return item
126+
}
127+
128+
func (item *MultiGetItem) Type(typ string) *MultiGetItem {
129+
item.typ = typ
130+
return item
131+
}
132+
133+
func (item *MultiGetItem) Id(id string) *MultiGetItem {
134+
item.id = id
135+
return item
136+
}
137+
138+
func (item *MultiGetItem) Routing(routing string) *MultiGetItem {
139+
item.routing = routing
140+
return item
141+
}
142+
143+
func (item *MultiGetItem) Fields(fields ...string) *MultiGetItem {
144+
if item.fields == nil {
145+
item.fields = make([]string, 0)
146+
}
147+
item.fields = append(item.fields, fields...)
148+
return item
149+
}
150+
151+
// Version can be MatchAny (-3), MatchAnyPre120 (0), NotFound (-1),
152+
// or NotSet (-2). These are specified in org.elasticsearch.common.lucene.uid.Versions.
153+
// The default is MatchAny (-3).
154+
func (item *MultiGetItem) Version(version int64) *MultiGetItem {
155+
item.version = version
156+
return item
157+
}
158+
159+
// VersionType can be "internal", "external", "external_gt", "external_gte",
160+
// or "force". See org.elasticsearch.index.VersionType in Elasticsearch source.
161+
// It is "internal" by default.
162+
func (item *MultiGetItem) VersionType(versionType string) *MultiGetItem {
163+
item.versionType = versionType
164+
return item
165+
}
166+
167+
func (item *MultiGetItem) FetchSource(fetchSourceContext *FetchSourceContext) *MultiGetItem {
168+
item.fsc = fetchSourceContext
169+
return item
170+
}
171+
172+
// Source returns the serialized JSON to be sent to Elasticsearch as
173+
// part of a MultiGet search.
174+
func (item *MultiGetItem) Source() interface{} {
175+
source := make(map[string]interface{})
176+
177+
if item.index != "" {
178+
source["_index"] = item.index
179+
}
180+
if item.typ != "" {
181+
source["_type"] = item.typ
182+
}
183+
source["_id"] = item.id
184+
185+
if item.fsc != nil {
186+
source["_source"] = item.fsc.Source()
187+
}
188+
189+
if item.fields != nil {
190+
source["_fields"] = item.fields
191+
}
192+
193+
if item.routing != "" {
194+
source["_routing"] = item.routing
195+
}
196+
197+
return source
198+
}
199+
200+
// -- Result of a Multi Get request.
201+
202+
type MultiGetResult struct {
203+
Docs []*GetResult `json:"docs,omitempty"`
204+
}

multi_get_test.go

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2012 Oliver Eilhard. All rights reserved.
2+
// Use of this source code is governed by a MIT-license.
3+
// See http://olivere.mit-license.org/license.txt for details.
4+
5+
package elastic
6+
7+
import (
8+
"encoding/json"
9+
"testing"
10+
)
11+
12+
func TestMultiGet(t *testing.T) {
13+
client := setupTestClientAndCreateIndex(t)
14+
15+
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and ElasticSearch."}
16+
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
17+
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
18+
19+
// Add some documents
20+
_, err := client.Index().Index(testIndexName).Type("tweet").Id("1").BodyJson(&tweet1).Do()
21+
if err != nil {
22+
t.Fatal(err)
23+
}
24+
25+
_, err = client.Index().Index(testIndexName).Type("tweet").Id("2").BodyJson(&tweet2).Do()
26+
if err != nil {
27+
t.Fatal(err)
28+
}
29+
30+
_, err = client.Index().Index(testIndexName).Type("tweet").Id("3").BodyJson(&tweet3).Do()
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
35+
_, err = client.Flush().Index(testIndexName).Do()
36+
if err != nil {
37+
t.Fatal(err)
38+
}
39+
40+
// Count documents
41+
count, err := client.Count(testIndexName).Do()
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
if count != 3 {
46+
t.Errorf("expected Count = %d; got %d", 3, count)
47+
}
48+
49+
// Get documents 1 and 3
50+
res, err := client.MultiGet().
51+
Add(NewMultiGetItem().Index(testIndexName).Type("tweet").Id("1")).
52+
Add(NewMultiGetItem().Index(testIndexName).Type("tweet").Id("3")).
53+
Do()
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
if res == nil {
58+
t.Fatal("expected result to be != nil; got nil")
59+
}
60+
if res.Docs == nil {
61+
t.Fatal("expected result docs to be != nil; got nil")
62+
}
63+
if len(res.Docs) != 2 {
64+
t.Fatalf("expected to have 2 docs; got %d", len(res.Docs))
65+
}
66+
67+
item := res.Docs[0]
68+
if item.Error != "" {
69+
t.Errorf("expected no error on item 0; got %q", item.Error)
70+
}
71+
if item.Source == nil {
72+
t.Errorf("expected Source != nil; got %v", item.Source)
73+
}
74+
var doc tweet
75+
if err := json.Unmarshal(*item.Source, &doc); err != nil {
76+
t.Fatalf("expected to unmarshal item Source; got %v", err)
77+
}
78+
if doc.Message != tweet1.Message {
79+
t.Errorf("expected Message of first tweet to be %q; got %q", tweet1.Message, doc.Message)
80+
}
81+
82+
item = res.Docs[1]
83+
if item.Error != "" {
84+
t.Errorf("expected no error on item 1; got %q", item.Error)
85+
}
86+
if item.Source == nil {
87+
t.Errorf("expected Source != nil; got %v", item.Source)
88+
}
89+
if err := json.Unmarshal(*item.Source, &doc); err != nil {
90+
t.Fatalf("expected to unmarshal item Source; got %v", err)
91+
}
92+
if doc.Message != tweet3.Message {
93+
t.Errorf("expected Message of second tweet to be %q; got %q", tweet3.Message, doc.Message)
94+
}
95+
}

0 commit comments

Comments
 (0)