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