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