1//  Copyright (c) 2014 Couchbase, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// 		http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package bleve
16
17import (
18	"fmt"
19	"reflect"
20	"testing"
21	"time"
22
23	"golang.org/x/net/context"
24
25	"github.com/blevesearch/bleve/document"
26	"github.com/blevesearch/bleve/index"
27	"github.com/blevesearch/bleve/index/store"
28	"github.com/blevesearch/bleve/mapping"
29	"github.com/blevesearch/bleve/numeric"
30	"github.com/blevesearch/bleve/search"
31)
32
33func TestIndexAliasSingle(t *testing.T) {
34	expectedError := fmt.Errorf("expected")
35	ei1 := &stubIndex{
36		err: expectedError,
37	}
38
39	alias := NewIndexAlias(ei1)
40
41	err := alias.Index("a", "a")
42	if err != expectedError {
43		t.Errorf("expected %v, got %v", expectedError, err)
44	}
45
46	err = alias.Delete("a")
47	if err != expectedError {
48		t.Errorf("expected %v, got %v", expectedError, err)
49	}
50
51	batch := alias.NewBatch()
52	err = alias.Batch(batch)
53	if err != expectedError {
54		t.Errorf("expected %v, got %v", expectedError, err)
55	}
56
57	_, err = alias.Document("a")
58	if err != expectedError {
59		t.Errorf("expected %v, got %v", expectedError, err)
60	}
61
62	_, err = alias.Fields()
63	if err != expectedError {
64		t.Errorf("expected %v, got %v", expectedError, err)
65	}
66
67	_, err = alias.GetInternal([]byte("a"))
68	if err != expectedError {
69		t.Errorf("expected %v, got %v", expectedError, err)
70	}
71
72	err = alias.SetInternal([]byte("a"), []byte("a"))
73	if err != expectedError {
74		t.Errorf("expected %v, got %v", expectedError, err)
75	}
76
77	err = alias.DeleteInternal([]byte("a"))
78	if err != expectedError {
79		t.Errorf("expected %v, got %v", expectedError, err)
80	}
81
82	mapping := alias.Mapping()
83	if mapping != nil {
84		t.Errorf("expected nil, got %v", mapping)
85	}
86
87	indexStat := alias.Stats()
88	if indexStat != nil {
89		t.Errorf("expected nil, got %v", indexStat)
90	}
91
92	// now a few things that should work
93	sr := NewSearchRequest(NewTermQuery("test"))
94	_, err = alias.Search(sr)
95	if err != expectedError {
96		t.Errorf("expected %v, got %v", expectedError, err)
97	}
98
99	count, err := alias.DocCount()
100	if err != nil {
101		t.Errorf("expected no error, got %v", err)
102	}
103	if count != 0 {
104		t.Errorf("expected count 0, got %d", count)
105	}
106
107	// now change the def using add/remove
108	expectedError2 := fmt.Errorf("expected2")
109	ei2 := &stubIndex{
110		err: expectedError2,
111	}
112
113	alias.Add(ei2)
114	alias.Remove(ei1)
115
116	err = alias.Index("a", "a")
117	if err != expectedError2 {
118		t.Errorf("expected %v, got %v", expectedError2, err)
119	}
120
121	err = alias.Delete("a")
122	if err != expectedError2 {
123		t.Errorf("expected %v, got %v", expectedError2, err)
124	}
125
126	err = alias.Batch(batch)
127	if err != expectedError2 {
128		t.Errorf("expected %v, got %v", expectedError2, err)
129	}
130
131	_, err = alias.Document("a")
132	if err != expectedError2 {
133		t.Errorf("expected %v, got %v", expectedError2, err)
134	}
135
136	_, err = alias.Fields()
137	if err != expectedError2 {
138		t.Errorf("expected %v, got %v", expectedError2, err)
139	}
140
141	_, err = alias.GetInternal([]byte("a"))
142	if err != expectedError2 {
143		t.Errorf("expected %v, got %v", expectedError2, err)
144	}
145
146	err = alias.SetInternal([]byte("a"), []byte("a"))
147	if err != expectedError2 {
148		t.Errorf("expected %v, got %v", expectedError2, err)
149	}
150
151	err = alias.DeleteInternal([]byte("a"))
152	if err != expectedError2 {
153		t.Errorf("expected %v, got %v", expectedError2, err)
154	}
155
156	mapping = alias.Mapping()
157	if mapping != nil {
158		t.Errorf("expected nil, got %v", mapping)
159	}
160
161	indexStat = alias.Stats()
162	if indexStat != nil {
163		t.Errorf("expected nil, got %v", indexStat)
164	}
165
166	// now a few things that should work
167	_, err = alias.Search(sr)
168	if err != expectedError2 {
169		t.Errorf("expected %v, got %v", expectedError2, err)
170	}
171
172	count, err = alias.DocCount()
173	if err != nil {
174		t.Errorf("expected no error, got %v", err)
175	}
176	if count != 0 {
177		t.Errorf("expected count 0, got %d", count)
178	}
179
180	// now change the def using swap
181	expectedError3 := fmt.Errorf("expected3")
182	ei3 := &stubIndex{
183		err: expectedError3,
184	}
185
186	alias.Swap([]Index{ei3}, []Index{ei2})
187
188	err = alias.Index("a", "a")
189	if err != expectedError3 {
190		t.Errorf("expected %v, got %v", expectedError3, err)
191	}
192
193	err = alias.Delete("a")
194	if err != expectedError3 {
195		t.Errorf("expected %v, got %v", expectedError3, err)
196	}
197
198	err = alias.Batch(batch)
199	if err != expectedError3 {
200		t.Errorf("expected %v, got %v", expectedError3, err)
201	}
202
203	_, err = alias.Document("a")
204	if err != expectedError3 {
205		t.Errorf("expected %v, got %v", expectedError3, err)
206	}
207
208	_, err = alias.Fields()
209	if err != expectedError3 {
210		t.Errorf("expected %v, got %v", expectedError3, err)
211	}
212
213	_, err = alias.GetInternal([]byte("a"))
214	if err != expectedError3 {
215		t.Errorf("expected %v, got %v", expectedError3, err)
216	}
217
218	err = alias.SetInternal([]byte("a"), []byte("a"))
219	if err != expectedError3 {
220		t.Errorf("expected %v, got %v", expectedError3, err)
221	}
222
223	err = alias.DeleteInternal([]byte("a"))
224	if err != expectedError3 {
225		t.Errorf("expected %v, got %v", expectedError3, err)
226	}
227
228	mapping = alias.Mapping()
229	if mapping != nil {
230		t.Errorf("expected nil, got %v", mapping)
231	}
232
233	indexStat = alias.Stats()
234	if indexStat != nil {
235		t.Errorf("expected nil, got %v", indexStat)
236	}
237
238	// now a few things that should work
239	_, err = alias.Search(sr)
240	if err != expectedError3 {
241		t.Errorf("expected %v, got %v", expectedError3, err)
242	}
243
244	count, err = alias.DocCount()
245	if err != nil {
246		t.Errorf("expected no error, got %v", err)
247	}
248	if count != 0 {
249		t.Errorf("expected count 0, got %d", count)
250	}
251}
252
253func TestIndexAliasClosed(t *testing.T) {
254	alias := NewIndexAlias()
255	err := alias.Close()
256	if err != nil {
257		t.Fatal(err)
258	}
259
260	err = alias.Index("a", "a")
261	if err != ErrorIndexClosed {
262		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
263	}
264
265	err = alias.Delete("a")
266	if err != ErrorIndexClosed {
267		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
268	}
269
270	batch := alias.NewBatch()
271	err = alias.Batch(batch)
272	if err != ErrorIndexClosed {
273		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
274	}
275
276	_, err = alias.Document("a")
277	if err != ErrorIndexClosed {
278		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
279	}
280
281	_, err = alias.Fields()
282	if err != ErrorIndexClosed {
283		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
284	}
285
286	_, err = alias.GetInternal([]byte("a"))
287	if err != ErrorIndexClosed {
288		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
289	}
290
291	err = alias.SetInternal([]byte("a"), []byte("a"))
292	if err != ErrorIndexClosed {
293		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
294	}
295
296	err = alias.DeleteInternal([]byte("a"))
297	if err != ErrorIndexClosed {
298		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
299	}
300
301	mapping := alias.Mapping()
302	if mapping != nil {
303		t.Errorf("expected nil, got %v", mapping)
304	}
305
306	indexStat := alias.Stats()
307	if indexStat != nil {
308		t.Errorf("expected nil, got %v", indexStat)
309	}
310
311	// now a few things that should work
312	sr := NewSearchRequest(NewTermQuery("test"))
313	_, err = alias.Search(sr)
314	if err != ErrorIndexClosed {
315		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
316	}
317
318	_, err = alias.DocCount()
319	if err != ErrorIndexClosed {
320		t.Errorf("expected %v, got %v", ErrorIndexClosed, err)
321	}
322}
323
324func TestIndexAliasEmpty(t *testing.T) {
325	alias := NewIndexAlias()
326
327	err := alias.Index("a", "a")
328	if err != ErrorAliasEmpty {
329		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
330	}
331
332	err = alias.Delete("a")
333	if err != ErrorAliasEmpty {
334		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
335	}
336
337	batch := alias.NewBatch()
338	err = alias.Batch(batch)
339	if err != ErrorAliasEmpty {
340		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
341	}
342
343	_, err = alias.Document("a")
344	if err != ErrorAliasEmpty {
345		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
346	}
347
348	_, err = alias.Fields()
349	if err != ErrorAliasEmpty {
350		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
351	}
352
353	_, err = alias.GetInternal([]byte("a"))
354	if err != ErrorAliasEmpty {
355		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
356	}
357
358	err = alias.SetInternal([]byte("a"), []byte("a"))
359	if err != ErrorAliasEmpty {
360		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
361	}
362
363	err = alias.DeleteInternal([]byte("a"))
364	if err != ErrorAliasEmpty {
365		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
366	}
367
368	mapping := alias.Mapping()
369	if mapping != nil {
370		t.Errorf("expected nil, got %v", mapping)
371	}
372
373	indexStat := alias.Stats()
374	if indexStat != nil {
375		t.Errorf("expected nil, got %v", indexStat)
376	}
377
378	// now a few things that should work
379	sr := NewSearchRequest(NewTermQuery("test"))
380	_, err = alias.Search(sr)
381	if err != ErrorAliasEmpty {
382		t.Errorf("expected %v, got %v", ErrorAliasEmpty, err)
383	}
384
385	count, err := alias.DocCount()
386	if err != nil {
387		t.Errorf("error getting alias doc count: %v", err)
388	}
389	if count != 0 {
390		t.Errorf("expected %d, got %d", 0, count)
391	}
392}
393
394func TestIndexAliasMulti(t *testing.T) {
395	score1, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(1.0), 0)
396	score2, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(2.0), 0)
397	ei1Count := uint64(7)
398	ei1 := &stubIndex{
399		err:            nil,
400		docCountResult: &ei1Count,
401		searchResult: &SearchResult{
402			Status: &SearchStatus{
403				Total:      1,
404				Successful: 1,
405				Errors:     make(map[string]error),
406			},
407			Total: 1,
408			Hits: search.DocumentMatchCollection{
409				{
410					ID:    "a",
411					Score: 1.0,
412					Sort:  []string{string(score1)},
413				},
414			},
415			MaxScore: 1.0,
416		}}
417	ei2Count := uint64(8)
418	ei2 := &stubIndex{
419		err:            nil,
420		docCountResult: &ei2Count,
421		searchResult: &SearchResult{
422			Status: &SearchStatus{
423				Total:      1,
424				Successful: 1,
425				Errors:     make(map[string]error),
426			},
427			Total: 1,
428			Hits: search.DocumentMatchCollection{
429				{
430					ID:    "b",
431					Score: 2.0,
432					Sort:  []string{string(score2)},
433				},
434			},
435			MaxScore: 2.0,
436		}}
437
438	alias := NewIndexAlias(ei1, ei2)
439
440	err := alias.Index("a", "a")
441	if err != ErrorAliasMulti {
442		t.Errorf("expected %v, got %v", ErrorAliasMulti, err)
443	}
444
445	err = alias.Delete("a")
446	if err != ErrorAliasMulti {
447		t.Errorf("expected %v, got %v", ErrorAliasMulti, err)
448	}
449
450	batch := alias.NewBatch()
451	err = alias.Batch(batch)
452	if err != ErrorAliasMulti {
453		t.Errorf("expected %v, got %v", ErrorAliasMulti, err)
454	}
455
456	_, err = alias.Document("a")
457	if err != ErrorAliasMulti {
458		t.Errorf("expected %v, got %v", ErrorAliasMulti, err)
459	}
460
461	_, err = alias.Fields()
462	if err != ErrorAliasMulti {
463		t.Errorf("expected %v, got %v", ErrorAliasMulti, err)
464	}
465
466	_, err = alias.GetInternal([]byte("a"))
467	if err != ErrorAliasMulti {
468		t.Errorf("expected %v, got %v", ErrorAliasMulti, err)
469	}
470
471	err = alias.SetInternal([]byte("a"), []byte("a"))
472	if err != ErrorAliasMulti {
473		t.Errorf("expected %v, got %v", ErrorAliasMulti, err)
474	}
475
476	err = alias.DeleteInternal([]byte("a"))
477	if err != ErrorAliasMulti {
478		t.Errorf("expected %v, got %v", ErrorAliasMulti, err)
479	}
480
481	mapping := alias.Mapping()
482	if mapping != nil {
483		t.Errorf("expected nil, got %v", mapping)
484	}
485
486	indexStat := alias.Stats()
487	if indexStat != nil {
488		t.Errorf("expected nil, got %v", indexStat)
489	}
490
491	// now a few things that should work
492	sr := NewSearchRequest(NewTermQuery("test"))
493	expected := &SearchResult{
494		Status: &SearchStatus{
495			Total:      2,
496			Successful: 2,
497			Errors:     make(map[string]error),
498		},
499		Request: sr,
500		Total:   2,
501		Hits: search.DocumentMatchCollection{
502			{
503				ID:    "b",
504				Score: 2.0,
505				Sort:  []string{string(score2)},
506			},
507			{
508				ID:    "a",
509				Score: 1.0,
510				Sort:  []string{string(score1)},
511			},
512		},
513		MaxScore: 2.0,
514	}
515	results, err := alias.Search(sr)
516	if err != nil {
517		t.Error(err)
518	}
519	// cheat and ensure that Took field matches since it invovles time
520	expected.Took = results.Took
521	if !reflect.DeepEqual(results, expected) {
522		t.Errorf("expected %#v, got %#v", expected, results)
523	}
524
525	count, err := alias.DocCount()
526	if err != nil {
527		t.Errorf("error getting alias doc count: %v", err)
528	}
529	if count != (*ei1.docCountResult + *ei2.docCountResult) {
530		t.Errorf("expected %d, got %d", (*ei1.docCountResult + *ei2.docCountResult), count)
531	}
532}
533
534// TestMultiSearchNoError
535func TestMultiSearchNoError(t *testing.T) {
536	score1, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(1.0), 0)
537	score2, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(2.0), 0)
538	ei1 := &stubIndex{err: nil, searchResult: &SearchResult{
539		Status: &SearchStatus{
540			Total:      1,
541			Successful: 1,
542			Errors:     make(map[string]error),
543		},
544		Total: 1,
545		Hits: search.DocumentMatchCollection{
546			{
547				Index: "1",
548				ID:    "a",
549				Score: 1.0,
550				Sort:  []string{string(score1)},
551			},
552		},
553		MaxScore: 1.0,
554	}}
555	ei2 := &stubIndex{err: nil, searchResult: &SearchResult{
556		Status: &SearchStatus{
557			Total:      1,
558			Successful: 1,
559			Errors:     make(map[string]error),
560		},
561		Total: 1,
562		Hits: search.DocumentMatchCollection{
563			{
564				Index: "2",
565				ID:    "b",
566				Score: 2.0,
567				Sort:  []string{string(score2)},
568			},
569		},
570		MaxScore: 2.0,
571	}}
572
573	sr := NewSearchRequest(NewTermQuery("test"))
574	expected := &SearchResult{
575		Status: &SearchStatus{
576			Total:      2,
577			Successful: 2,
578			Errors:     make(map[string]error),
579		},
580		Request: sr,
581		Total:   2,
582		Hits: search.DocumentMatchCollection{
583			{
584				Index: "2",
585				ID:    "b",
586				Score: 2.0,
587				Sort:  []string{string(score2)},
588			},
589			{
590				Index: "1",
591				ID:    "a",
592				Score: 1.0,
593				Sort:  []string{string(score1)},
594			},
595		},
596		MaxScore: 2.0,
597	}
598
599	results, err := MultiSearch(context.Background(), sr, ei1, ei2)
600	if err != nil {
601		t.Error(err)
602	}
603	// cheat and ensure that Took field matches since it invovles time
604	expected.Took = results.Took
605	if !reflect.DeepEqual(results, expected) {
606		t.Errorf("expected %#v, got %#v", expected, results)
607	}
608}
609
610// TestMultiSearchSomeError
611func TestMultiSearchSomeError(t *testing.T) {
612	ei1 := &stubIndex{name: "ei1", err: nil, searchResult: &SearchResult{
613		Status: &SearchStatus{
614			Total:      1,
615			Successful: 1,
616			Errors:     make(map[string]error),
617		},
618		Total: 1,
619		Hits: search.DocumentMatchCollection{
620			{
621				ID:    "a",
622				Score: 1.0,
623			},
624		},
625		Took:     1 * time.Second,
626		MaxScore: 1.0,
627	}}
628	ei2 := &stubIndex{name: "ei2", err: fmt.Errorf("deliberate error")}
629	sr := NewSearchRequest(NewTermQuery("test"))
630	res, err := MultiSearch(context.Background(), sr, ei1, ei2)
631	if err != nil {
632		t.Errorf("expected no error, got %v", err)
633	}
634	if res.Status.Total != 2 {
635		t.Errorf("expected 2 indexes to be queried, got %d", res.Status.Total)
636	}
637	if res.Status.Failed != 1 {
638		t.Errorf("expected 1 index to fail, got %d", res.Status.Failed)
639	}
640	if res.Status.Successful != 1 {
641		t.Errorf("expected 1 index to be successful, got %d", res.Status.Successful)
642	}
643	if len(res.Status.Errors) != 1 {
644		t.Fatalf("expected 1 status error message, got %d", len(res.Status.Errors))
645	}
646	if res.Status.Errors["ei2"].Error() != "deliberate error" {
647		t.Errorf("expected ei2 index error message 'deliberate error', got '%s'", res.Status.Errors["ei2"])
648	}
649}
650
651// TestMultiSearchAllError
652// reproduces https://github.com/blevesearch/bleve/issues/126
653func TestMultiSearchAllError(t *testing.T) {
654	ei1 := &stubIndex{name: "ei1", err: fmt.Errorf("deliberate error")}
655	ei2 := &stubIndex{name: "ei2", err: fmt.Errorf("deliberate error")}
656	sr := NewSearchRequest(NewTermQuery("test"))
657	res, err := MultiSearch(context.Background(), sr, ei1, ei2)
658	if err != nil {
659		t.Errorf("expected no error, got %v", err)
660	}
661	if res.Status.Total != 2 {
662		t.Errorf("expected 2 indexes to be queried, got %d", res.Status.Total)
663	}
664	if res.Status.Failed != 2 {
665		t.Errorf("expected 2 indexes to fail, got %d", res.Status.Failed)
666	}
667	if res.Status.Successful != 0 {
668		t.Errorf("expected 0 indexes to be successful, got %d", res.Status.Successful)
669	}
670	if len(res.Status.Errors) != 2 {
671		t.Fatalf("expected 2 status error messages, got %d", len(res.Status.Errors))
672	}
673	if res.Status.Errors["ei1"].Error() != "deliberate error" {
674		t.Errorf("expected ei1 index error message 'deliberate error', got '%s'", res.Status.Errors["ei1"])
675	}
676	if res.Status.Errors["ei2"].Error() != "deliberate error" {
677		t.Errorf("expected ei2 index error message 'deliberate error', got '%s'", res.Status.Errors["ei2"])
678	}
679}
680
681func TestMultiSearchSecondPage(t *testing.T) {
682	checkRequest := func(sr *SearchRequest) error {
683		if sr.From != 0 {
684			return fmt.Errorf("child request from should be 0")
685		}
686		if sr.Size != 20 {
687			return fmt.Errorf("child request size should be 20")
688		}
689		return nil
690	}
691
692	ei1 := &stubIndex{
693		searchResult: &SearchResult{
694			Status: &SearchStatus{
695				Total:      1,
696				Successful: 1,
697				Errors:     make(map[string]error),
698			},
699		},
700		checkRequest: checkRequest,
701	}
702	ei2 := &stubIndex{
703		searchResult: &SearchResult{
704			Status: &SearchStatus{
705				Total:      1,
706				Successful: 1,
707				Errors:     make(map[string]error),
708			},
709		},
710		checkRequest: checkRequest,
711	}
712	sr := NewSearchRequestOptions(NewTermQuery("test"), 10, 10, false)
713	_, err := MultiSearch(context.Background(), sr, ei1, ei2)
714	if err != nil {
715		t.Errorf("unexpected error %v", err)
716	}
717
718}
719
720// TestMultiSearchTimeout tests simple timeout cases
721// 1. all searches finish successfully before timeout
722// 2. no searchers finish before the timeout
723// 3. no searches finish before cancellation
724func TestMultiSearchTimeout(t *testing.T) {
725	score1, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(1.0), 0)
726	score2, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(2.0), 0)
727	var ctx context.Context
728	ei1 := &stubIndex{
729		name: "ei1",
730		checkRequest: func(req *SearchRequest) error {
731			select {
732			case <-ctx.Done():
733				return ctx.Err()
734			case <-time.After(50 * time.Millisecond):
735				return nil
736			}
737		},
738		err: nil,
739		searchResult: &SearchResult{
740			Status: &SearchStatus{
741				Total:      1,
742				Successful: 1,
743				Errors:     make(map[string]error),
744			},
745			Total: 1,
746			Hits: []*search.DocumentMatch{
747				{
748					Index: "1",
749					ID:    "a",
750					Score: 1.0,
751					Sort:  []string{string(score1)},
752				},
753			},
754			MaxScore: 1.0,
755		}}
756	ei2 := &stubIndex{
757		name: "ei2",
758		checkRequest: func(req *SearchRequest) error {
759			select {
760			case <-ctx.Done():
761				return ctx.Err()
762			case <-time.After(50 * time.Millisecond):
763				return nil
764			}
765		},
766		err: nil,
767		searchResult: &SearchResult{
768			Status: &SearchStatus{
769				Total:      1,
770				Successful: 1,
771				Errors:     make(map[string]error),
772			},
773			Total: 1,
774			Hits: []*search.DocumentMatch{
775				{
776					Index: "2",
777					ID:    "b",
778					Score: 2.0,
779					Sort:  []string{string(score2)},
780				},
781			},
782			MaxScore: 2.0,
783		}}
784
785	// first run with absurdly long time out, should succeed
786	ctx, _ = context.WithTimeout(context.Background(), 10*time.Second)
787	query := NewTermQuery("test")
788	sr := NewSearchRequest(query)
789	res, err := MultiSearch(ctx, sr, ei1, ei2)
790	if err != nil {
791		t.Errorf("expected no error, got %v", err)
792	}
793	if res.Status.Total != 2 {
794		t.Errorf("expected 2 total, got %d", res.Status.Failed)
795	}
796	if res.Status.Successful != 2 {
797		t.Errorf("expected 0 success, got %d", res.Status.Successful)
798	}
799	if res.Status.Failed != 0 {
800		t.Errorf("expected 2 failed, got %d", res.Status.Failed)
801	}
802	if len(res.Status.Errors) != 0 {
803		t.Errorf("expected 0 errors, got %v", res.Status.Errors)
804	}
805
806	// now run a search again with an absurdly low timeout (should timeout)
807	ctx, _ = context.WithTimeout(context.Background(), 1*time.Microsecond)
808	res, err = MultiSearch(ctx, sr, ei1, ei2)
809	if err != nil {
810		t.Errorf("expected no error, got %v", err)
811	}
812	if res.Status.Total != 2 {
813		t.Errorf("expected 2 failed, got %d", res.Status.Failed)
814	}
815	if res.Status.Successful != 0 {
816		t.Errorf("expected 0 success, got %d", res.Status.Successful)
817	}
818	if res.Status.Failed != 2 {
819		t.Errorf("expected 2 failed, got %d", res.Status.Failed)
820	}
821	if len(res.Status.Errors) != 2 {
822		t.Errorf("expected 2 errors, got %v", res.Status.Errors)
823	} else {
824		if res.Status.Errors["ei1"].Error() != context.DeadlineExceeded.Error() {
825			t.Errorf("expected err for 'ei1' to be '%s' got '%s'", context.DeadlineExceeded.Error(), res.Status.Errors["ei1"])
826		}
827		if res.Status.Errors["ei2"].Error() != context.DeadlineExceeded.Error() {
828			t.Errorf("expected err for 'ei2' to be '%s' got '%s'", context.DeadlineExceeded.Error(), res.Status.Errors["ei2"])
829		}
830	}
831
832	// now run a search again with a normal timeout, but cancel it first
833	var cancel context.CancelFunc
834	ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
835	cancel()
836	res, err = MultiSearch(ctx, sr, ei1, ei2)
837	if err != nil {
838		t.Errorf("expected no error, got %v", err)
839	}
840	if res.Status.Total != 2 {
841		t.Errorf("expected 2 failed, got %d", res.Status.Failed)
842	}
843	if res.Status.Successful != 0 {
844		t.Errorf("expected 0 success, got %d", res.Status.Successful)
845	}
846	if res.Status.Failed != 2 {
847		t.Errorf("expected 2 failed, got %d", res.Status.Failed)
848	}
849	if len(res.Status.Errors) != 2 {
850		t.Errorf("expected 2 errors, got %v", res.Status.Errors)
851	} else {
852		if res.Status.Errors["ei1"].Error() != context.Canceled.Error() {
853			t.Errorf("expected err for 'ei1' to be '%s' got '%s'", context.Canceled.Error(), res.Status.Errors["ei1"])
854		}
855		if res.Status.Errors["ei2"].Error() != context.Canceled.Error() {
856			t.Errorf("expected err for 'ei2' to be '%s' got '%s'", context.Canceled.Error(), res.Status.Errors["ei2"])
857		}
858	}
859}
860
861// TestMultiSearchTimeoutPartial tests the case where some indexes exceed
862// the timeout, while others complete successfully
863func TestMultiSearchTimeoutPartial(t *testing.T) {
864	score1, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(1.0), 0)
865	score2, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(2.0), 0)
866	score3, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(3.0), 0)
867	var ctx context.Context
868	ei1 := &stubIndex{
869		name: "ei1",
870		err:  nil,
871		searchResult: &SearchResult{
872			Status: &SearchStatus{
873				Total:      1,
874				Successful: 1,
875				Errors:     make(map[string]error),
876			},
877			Total: 1,
878			Hits: []*search.DocumentMatch{
879				{
880					Index: "1",
881					ID:    "a",
882					Score: 1.0,
883					Sort:  []string{string(score1)},
884				},
885			},
886			MaxScore: 1.0,
887		}}
888	ei2 := &stubIndex{
889		name: "ei2",
890		err:  nil,
891		searchResult: &SearchResult{
892			Status: &SearchStatus{
893				Total:      1,
894				Successful: 1,
895				Errors:     make(map[string]error),
896			},
897			Total: 1,
898			Hits: []*search.DocumentMatch{
899				{
900					Index: "2",
901					ID:    "b",
902					Score: 2.0,
903					Sort:  []string{string(score2)},
904				},
905			},
906			MaxScore: 2.0,
907		}}
908
909	ei3 := &stubIndex{
910		name: "ei3",
911		checkRequest: func(req *SearchRequest) error {
912			select {
913			case <-ctx.Done():
914				return ctx.Err()
915			case <-time.After(50 * time.Millisecond):
916				return nil
917			}
918		},
919		err: nil,
920		searchResult: &SearchResult{
921			Status: &SearchStatus{
922				Total:      1,
923				Successful: 1,
924				Errors:     make(map[string]error),
925			},
926			Total: 1,
927			Hits: []*search.DocumentMatch{
928				{
929					Index: "3",
930					ID:    "c",
931					Score: 3.0,
932					Sort:  []string{string(score3)},
933				},
934			},
935			MaxScore: 3.0,
936		}}
937
938	// ei3 is set to take >50ms, so run search with timeout less than
939	// this, this should return partial results
940	ctx, _ = context.WithTimeout(context.Background(), 25*time.Millisecond)
941	query := NewTermQuery("test")
942	sr := NewSearchRequest(query)
943	expected := &SearchResult{
944		Status: &SearchStatus{
945			Total:      3,
946			Successful: 2,
947			Failed:     1,
948			Errors: map[string]error{
949				"ei3": context.DeadlineExceeded,
950			},
951		},
952		Request: sr,
953		Total:   2,
954		Hits: search.DocumentMatchCollection{
955			{
956				Index: "2",
957				ID:    "b",
958				Score: 2.0,
959				Sort:  []string{string(score2)},
960			},
961			{
962				Index: "1",
963				ID:    "a",
964				Score: 1.0,
965				Sort:  []string{string(score1)},
966			},
967		},
968		MaxScore: 2.0,
969	}
970
971	res, err := MultiSearch(ctx, sr, ei1, ei2, ei3)
972	if err != nil {
973		t.Fatalf("expected no err, got %v", err)
974	}
975	expected.Took = res.Took
976	if !reflect.DeepEqual(res, expected) {
977		t.Errorf("expected %#v, got %#v", expected, res)
978	}
979}
980
981func TestIndexAliasMultipleLayer(t *testing.T) {
982	score1, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(1.0), 0)
983	score2, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(2.0), 0)
984	score3, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(3.0), 0)
985	score4, _ := numeric.NewPrefixCodedInt64(numeric.Float64ToInt64(4.0), 0)
986	var ctx context.Context
987	ei1 := &stubIndex{
988		name: "ei1",
989		err:  nil,
990		searchResult: &SearchResult{
991			Status: &SearchStatus{
992				Total:      1,
993				Successful: 1,
994				Errors:     make(map[string]error),
995			},
996			Total: 1,
997			Hits: []*search.DocumentMatch{
998				{
999					Index: "1",
1000					ID:    "a",
1001					Score: 1.0,
1002					Sort:  []string{string(score1)},
1003				},
1004			},
1005			MaxScore: 1.0,
1006		}}
1007	ei2 := &stubIndex{
1008		name: "ei2",
1009		checkRequest: func(req *SearchRequest) error {
1010			select {
1011			case <-ctx.Done():
1012				return ctx.Err()
1013			case <-time.After(50 * time.Millisecond):
1014				return nil
1015			}
1016		},
1017		err: nil,
1018		searchResult: &SearchResult{
1019			Status: &SearchStatus{
1020				Total:      1,
1021				Successful: 1,
1022				Errors:     make(map[string]error),
1023			},
1024			Total: 1,
1025			Hits: []*search.DocumentMatch{
1026				{
1027					Index: "2",
1028					ID:    "b",
1029					Score: 2.0,
1030					Sort:  []string{string(score2)},
1031				},
1032			},
1033			MaxScore: 2.0,
1034		}}
1035
1036	ei3 := &stubIndex{
1037		name: "ei3",
1038		checkRequest: func(req *SearchRequest) error {
1039			select {
1040			case <-ctx.Done():
1041				return ctx.Err()
1042			case <-time.After(50 * time.Millisecond):
1043				return nil
1044			}
1045		},
1046		err: nil,
1047		searchResult: &SearchResult{
1048			Status: &SearchStatus{
1049				Total:      1,
1050				Successful: 1,
1051				Errors:     make(map[string]error),
1052			},
1053			Total: 1,
1054			Hits: []*search.DocumentMatch{
1055				{
1056					Index: "3",
1057					ID:    "c",
1058					Score: 3.0,
1059					Sort:  []string{string(score3)},
1060				},
1061			},
1062			MaxScore: 3.0,
1063		}}
1064
1065	ei4 := &stubIndex{
1066		name: "ei4",
1067		err:  nil,
1068		searchResult: &SearchResult{
1069			Status: &SearchStatus{
1070				Total:      1,
1071				Successful: 1,
1072				Errors:     make(map[string]error),
1073			},
1074			Total: 1,
1075			Hits: []*search.DocumentMatch{
1076				{
1077					Index: "4",
1078					ID:    "d",
1079					Score: 4.0,
1080					Sort:  []string{string(score4)},
1081				},
1082			},
1083			MaxScore: 4.0,
1084		}}
1085
1086	alias1 := NewIndexAlias(ei1, ei2)
1087	alias2 := NewIndexAlias(ei3, ei4)
1088	aliasTop := NewIndexAlias(alias1, alias2)
1089
1090	// ei2 and ei3 have 50ms delay
1091	// search across aliasTop should still get results from ei1 and ei4
1092	// total should still be 4
1093
1094	ctx, _ = context.WithTimeout(context.Background(), 25*time.Millisecond)
1095	query := NewTermQuery("test")
1096	sr := NewSearchRequest(query)
1097	expected := &SearchResult{
1098		Status: &SearchStatus{
1099			Total:      4,
1100			Successful: 2,
1101			Failed:     2,
1102			Errors: map[string]error{
1103				"ei2": context.DeadlineExceeded,
1104				"ei3": context.DeadlineExceeded,
1105			},
1106		},
1107		Request: sr,
1108		Total:   2,
1109		Hits: search.DocumentMatchCollection{
1110			{
1111				Index: "4",
1112				ID:    "d",
1113				Score: 4.0,
1114				Sort:  []string{string(score4)},
1115			},
1116			{
1117				Index: "1",
1118				ID:    "a",
1119				Score: 1.0,
1120				Sort:  []string{string(score1)},
1121			},
1122		},
1123		MaxScore: 4.0,
1124	}
1125
1126	res, err := aliasTop.SearchInContext(ctx, sr)
1127	if err != nil {
1128		t.Fatalf("expected no err, got %v", err)
1129	}
1130	expected.Took = res.Took
1131	if !reflect.DeepEqual(res, expected) {
1132		t.Errorf("expected %#v, got %#v", expected, res)
1133	}
1134}
1135
1136// TestMultiSearchNoError
1137func TestMultiSearchCustomSort(t *testing.T) {
1138	ei1 := &stubIndex{err: nil, searchResult: &SearchResult{
1139		Status: &SearchStatus{
1140			Total:      1,
1141			Successful: 1,
1142			Errors:     make(map[string]error),
1143		},
1144		Total: 2,
1145		Hits: search.DocumentMatchCollection{
1146			{
1147				Index: "1",
1148				ID:    "a",
1149				Score: 1.0,
1150				Sort:  []string{"albert"},
1151			},
1152			{
1153				Index: "1",
1154				ID:    "b",
1155				Score: 2.0,
1156				Sort:  []string{"crown"},
1157			},
1158		},
1159		MaxScore: 2.0,
1160	}}
1161	ei2 := &stubIndex{err: nil, searchResult: &SearchResult{
1162		Status: &SearchStatus{
1163			Total:      1,
1164			Successful: 1,
1165			Errors:     make(map[string]error),
1166		},
1167		Total: 2,
1168		Hits: search.DocumentMatchCollection{
1169			{
1170				Index: "2",
1171				ID:    "c",
1172				Score: 2.5,
1173				Sort:  []string{"frank"},
1174			},
1175			{
1176				Index: "2",
1177				ID:    "d",
1178				Score: 3.0,
1179				Sort:  []string{"zombie"},
1180			},
1181		},
1182		MaxScore: 3.0,
1183	}}
1184
1185	sr := NewSearchRequest(NewTermQuery("test"))
1186	sr.SortBy([]string{"name"})
1187	expected := &SearchResult{
1188		Status: &SearchStatus{
1189			Total:      2,
1190			Successful: 2,
1191			Errors:     make(map[string]error),
1192		},
1193		Request: sr,
1194		Total:   4,
1195		Hits: search.DocumentMatchCollection{
1196			{
1197				Index: "1",
1198				ID:    "a",
1199				Score: 1.0,
1200				Sort:  []string{"albert"},
1201			},
1202			{
1203				Index: "1",
1204				ID:    "b",
1205				Score: 2.0,
1206				Sort:  []string{"crown"},
1207			},
1208			{
1209				Index: "2",
1210				ID:    "c",
1211				Score: 2.5,
1212				Sort:  []string{"frank"},
1213			},
1214			{
1215				Index: "2",
1216				ID:    "d",
1217				Score: 3.0,
1218				Sort:  []string{"zombie"},
1219			},
1220		},
1221		MaxScore: 3.0,
1222	}
1223
1224	results, err := MultiSearch(context.Background(), sr, ei1, ei2)
1225	if err != nil {
1226		t.Error(err)
1227	}
1228	// cheat and ensure that Took field matches since it invovles time
1229	expected.Took = results.Took
1230	if !reflect.DeepEqual(results, expected) {
1231		t.Errorf("expected %v, got %v", expected, results)
1232	}
1233}
1234
1235// stubIndex is an Index impl for which all operations
1236// return the configured error value, unless the
1237// corresponding operation result value has been
1238// set, in which case that is returned instead
1239type stubIndex struct {
1240	name           string
1241	err            error
1242	searchResult   *SearchResult
1243	documentResult *document.Document
1244	docCountResult *uint64
1245	checkRequest   func(*SearchRequest) error
1246}
1247
1248func (i *stubIndex) Index(id string, data interface{}) error {
1249	return i.err
1250}
1251
1252func (i *stubIndex) Delete(id string) error {
1253	return i.err
1254}
1255
1256func (i *stubIndex) Batch(b *Batch) error {
1257	return i.err
1258}
1259
1260func (i *stubIndex) Document(id string) (*document.Document, error) {
1261	if i.documentResult != nil {
1262		return i.documentResult, nil
1263	}
1264	return nil, i.err
1265}
1266
1267func (i *stubIndex) DocCount() (uint64, error) {
1268	if i.docCountResult != nil {
1269		return *i.docCountResult, nil
1270	}
1271	return 0, i.err
1272}
1273
1274func (i *stubIndex) Search(req *SearchRequest) (*SearchResult, error) {
1275	return i.SearchInContext(context.Background(), req)
1276}
1277
1278func (i *stubIndex) SearchInContext(ctx context.Context, req *SearchRequest) (*SearchResult, error) {
1279	if i.checkRequest != nil {
1280		err := i.checkRequest(req)
1281		if err != nil {
1282			return nil, err
1283		}
1284	}
1285	if i.searchResult != nil {
1286		return i.searchResult, nil
1287	}
1288	return nil, i.err
1289}
1290
1291func (i *stubIndex) Fields() ([]string, error) {
1292	return nil, i.err
1293}
1294
1295func (i *stubIndex) FieldDict(field string) (index.FieldDict, error) {
1296	return nil, i.err
1297}
1298
1299func (i *stubIndex) FieldDictRange(field string, startTerm []byte, endTerm []byte) (index.FieldDict, error) {
1300	return nil, i.err
1301}
1302
1303func (i *stubIndex) FieldDictPrefix(field string, termPrefix []byte) (index.FieldDict, error) {
1304	return nil, i.err
1305}
1306
1307func (i *stubIndex) Close() error {
1308	return i.err
1309}
1310
1311func (i *stubIndex) Mapping() mapping.IndexMapping {
1312	return nil
1313}
1314
1315func (i *stubIndex) Stats() *IndexStat {
1316	return nil
1317}
1318
1319func (i *stubIndex) StatsMap() map[string]interface{} {
1320	return nil
1321}
1322
1323func (i *stubIndex) GetInternal(key []byte) ([]byte, error) {
1324	return nil, i.err
1325}
1326
1327func (i *stubIndex) SetInternal(key, val []byte) error {
1328	return i.err
1329}
1330
1331func (i *stubIndex) DeleteInternal(key []byte) error {
1332	return i.err
1333}
1334
1335func (i *stubIndex) Advanced() (index.Index, store.KVStore, error) {
1336	return nil, nil, nil
1337}
1338
1339func (i *stubIndex) NewBatch() *Batch {
1340	return &Batch{}
1341}
1342
1343func (i *stubIndex) Name() string {
1344	return i.name
1345}
1346
1347func (i *stubIndex) SetName(name string) {
1348	i.name = name
1349}
1350