github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/refcache/cacher_test.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package refcache 13 14 import ( 15 "context" 16 "fmt" 17 "testing" 18 19 "github.com/go-openapi/strfmt" 20 "github.com/sirupsen/logrus/hooks/test" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 "github.com/weaviate/weaviate/entities/additional" 24 "github.com/weaviate/weaviate/entities/models" 25 "github.com/weaviate/weaviate/entities/multi" 26 "github.com/weaviate/weaviate/entities/search" 27 ) 28 29 func TestCacher(t *testing.T) { 30 // some ids to be used in the tests, they carry no meaning outside each test 31 id1 := "132bdf92-ffec-4a52-9196-73ea7cbb5a5e" 32 id2 := "a60a26dc-791a-41fc-8dda-c0f21f90cc98" 33 id3 := "a60a26dc-791a-41fc-8dda-c0f21f90cc99" 34 id4 := "a60a26dc-791a-41fc-8dda-c0f21f90cc97" 35 36 t.Run("with empty results", func(t *testing.T) { 37 repo := newFakeRepo() 38 logger, _ := test.NewNullLogger() 39 cr := NewCacher(repo, logger, "") 40 err := cr.Build(context.Background(), nil, nil, additional.Properties{}) 41 assert.Nil(t, err) 42 }) 43 44 t.Run("with results with nil-schemas", func(t *testing.T) { 45 repo := newFakeRepo() 46 logger, _ := test.NewNullLogger() 47 cr := NewCacher(repo, logger, "") 48 input := []search.Result{ 49 { 50 ID: "foo", 51 ClassName: "BestClass", 52 }, 53 } 54 err := cr.Build(context.Background(), input, nil, additional.Properties{}) 55 assert.Nil(t, err) 56 }) 57 58 t.Run("with results without refs in the schema", func(t *testing.T) { 59 repo := newFakeRepo() 60 logger, _ := test.NewNullLogger() 61 cr := NewCacher(repo, logger, "") 62 input := []search.Result{ 63 { 64 ID: "foo", 65 ClassName: "BestClass", 66 Schema: map[string]interface{}{ 67 "foo": "bar", 68 "baz": &models.PhoneNumber{}, 69 }, 70 }, 71 } 72 err := cr.Build(context.Background(), input, nil, additional.Properties{}) 73 assert.Nil(t, err) 74 }) 75 76 t.Run("with a single ref, but no selectprops", func(t *testing.T) { 77 repo := newFakeRepo() 78 logger, _ := test.NewNullLogger() 79 cr := NewCacher(repo, logger, "") 80 input := []search.Result{ 81 { 82 ID: "foo", 83 ClassName: "BestClass", 84 Schema: map[string]interface{}{ 85 "refProp": models.MultipleRef{ 86 &models.SingleRef{ 87 Beacon: "weaviate://localhost/123", 88 }, 89 }, 90 }, 91 }, 92 } 93 err := cr.Build(context.Background(), input, nil, additional.Properties{}) 94 require.Nil(t, err) 95 _, ok := cr.Get(multi.Identifier{ID: "123", ClassName: "SomeClass"}) 96 assert.False(t, ok) 97 }) 98 99 t.Run("with a single ref, and a matching select prop", func(t *testing.T) { 100 repo := newFakeRepo() 101 repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 102 ClassName: "SomeClass", 103 ID: strfmt.UUID(id1), 104 Schema: map[string]interface{}{ 105 "bar": "some string", 106 }, 107 } 108 logger, _ := test.NewNullLogger() 109 cr := NewCacher(repo, logger, "") 110 input := []search.Result{ 111 { 112 ID: "foo", 113 ClassName: "BestClass", 114 Schema: map[string]interface{}{ 115 "refProp": models.MultipleRef{ 116 &models.SingleRef{ 117 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 118 }, 119 }, 120 }, 121 }, 122 } 123 selectProps := search.SelectProperties{ 124 search.SelectProperty{ 125 Name: "refProp", 126 Refs: []search.SelectClass{ 127 { 128 ClassName: "SomeClass", 129 RefProperties: search.SelectProperties{ 130 search.SelectProperty{ 131 Name: "bar", 132 IsPrimitive: true, 133 }, 134 }, 135 }, 136 }, 137 }, 138 } 139 140 expected := search.Result{ 141 ID: strfmt.UUID(id1), 142 ClassName: "SomeClass", 143 Schema: map[string]interface{}{ 144 "bar": "some string", 145 }, 146 } 147 148 err := cr.Build(context.Background(), input, selectProps, additional.Properties{}) 149 require.Nil(t, err) 150 res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"}) 151 require.True(t, ok) 152 assert.Equal(t, expected, res) 153 assert.Equal(t, 1, repo.counter, "required the expected amount of lookups") 154 }) 155 156 t.Run("with a nested lookup, partially resolved", func(t *testing.T) { 157 repo := newFakeRepo() 158 repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 159 ClassName: "SomeClass", 160 ID: strfmt.UUID(id1), 161 Schema: map[string]interface{}{ 162 "primitive": "foobar", 163 "ignoredRef": models.MultipleRef{ 164 &models.SingleRef{ 165 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 166 }, 167 }, 168 "nestedRef": models.MultipleRef{ 169 &models.SingleRef{ 170 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 171 }, 172 }, 173 }, 174 } 175 repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{ 176 ClassName: "SomeNestedClass", 177 ID: strfmt.UUID(id2), 178 Schema: map[string]interface{}{ 179 "name": "John Doe", 180 }, 181 } 182 logger, _ := test.NewNullLogger() 183 cr := NewCacher(repo, logger, "") 184 input := []search.Result{ 185 { 186 ID: "foo", 187 ClassName: "BestClass", 188 Schema: map[string]interface{}{ 189 "refProp": models.MultipleRef{ 190 &models.SingleRef{ 191 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 192 }, 193 }, 194 }, 195 }, 196 } 197 selectProps := search.SelectProperties{ 198 search.SelectProperty{ 199 Name: "refProp", 200 Refs: []search.SelectClass{ 201 { 202 ClassName: "SomeClass", 203 RefProperties: search.SelectProperties{ 204 search.SelectProperty{ 205 Name: "primitive", 206 IsPrimitive: true, 207 }, 208 search.SelectProperty{ 209 Name: "nestedRef", 210 Refs: []search.SelectClass{ 211 { 212 ClassName: "SomeNestedClass", 213 RefProperties: []search.SelectProperty{ 214 { 215 Name: "name", 216 IsPrimitive: true, 217 }, 218 }, 219 }, 220 }, 221 }, 222 }, 223 }, 224 }, 225 }, 226 } 227 228 expectedOuter := search.Result{ 229 ID: strfmt.UUID(id1), 230 ClassName: "SomeClass", 231 Schema: map[string]interface{}{ 232 "primitive": "foobar", 233 "ignoredRef": models.MultipleRef{ 234 &models.SingleRef{ 235 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 236 }, 237 }, 238 "nestedRef": models.MultipleRef{ 239 &models.SingleRef{ 240 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 241 }, 242 }, 243 }, 244 } 245 246 expectedInner := search.Result{ 247 ClassName: "SomeNestedClass", 248 ID: strfmt.UUID(id2), 249 Schema: map[string]interface{}{ 250 "name": "John Doe", 251 }, 252 } 253 254 err := cr.Build(context.Background(), input, selectProps, additional.Properties{}) 255 require.Nil(t, err) 256 res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"}) 257 require.True(t, ok) 258 assert.Equal(t, expectedOuter, res) 259 res, ok = cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}) 260 require.True(t, ok) 261 assert.Equal(t, expectedInner, res) 262 assert.Equal(t, 2, repo.counter, "required the expected amount of lookups") 263 }) 264 265 t.Run("with multiple items pointing to the same ref", func(t *testing.T) { 266 // this test asserts that we do not make unnecessary requests if an object 267 // is linked twice on the list. (This is very common if the reference is 268 // used for something like a product category, e.g. it would not be 269 // uncommon at all if all search results are of the same category) 270 repo := newFakeRepo() 271 repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 272 ClassName: "SomeClass", 273 ID: strfmt.UUID(id1), 274 Schema: map[string]interface{}{ 275 "primitive": "foobar", 276 "nestedRef": models.MultipleRef{ 277 &models.SingleRef{ 278 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 279 }, 280 }, 281 }, 282 } 283 repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{ 284 ClassName: "SomeNestedClass", 285 ID: strfmt.UUID(id2), 286 Schema: map[string]interface{}{ 287 "name": "John Doe", 288 }, 289 } 290 logger, _ := test.NewNullLogger() 291 cr := NewCacher(repo, logger, "") 292 293 // contains three items, all pointing to the same inner class 294 input := []search.Result{ 295 { 296 ID: "foo", 297 ClassName: "BestClass", 298 Schema: map[string]interface{}{ 299 "refProp": models.MultipleRef{ 300 &models.SingleRef{ 301 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 302 }, 303 }, 304 }, 305 }, 306 { 307 ID: "bar", 308 ClassName: "BestClass", 309 Schema: map[string]interface{}{ 310 "refProp": models.MultipleRef{ 311 &models.SingleRef{ 312 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 313 }, 314 }, 315 }, 316 }, 317 { 318 ID: "baz", 319 ClassName: "BestClass", 320 Schema: map[string]interface{}{ 321 "refProp": models.MultipleRef{ 322 &models.SingleRef{ 323 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 324 }, 325 }, 326 }, 327 }, 328 } 329 selectProps := search.SelectProperties{ 330 search.SelectProperty{ 331 Name: "refProp", 332 Refs: []search.SelectClass{ 333 { 334 ClassName: "SomeClass", 335 RefProperties: search.SelectProperties{ 336 search.SelectProperty{ 337 Name: "primitive", 338 IsPrimitive: true, 339 }, 340 search.SelectProperty{ 341 Name: "nestedRef", 342 Refs: []search.SelectClass{ 343 { 344 ClassName: "SomeNestedClass", 345 RefProperties: []search.SelectProperty{ 346 { 347 Name: "name", 348 IsPrimitive: true, 349 }, 350 }, 351 }, 352 }, 353 }, 354 }, 355 }, 356 }, 357 }, 358 } 359 360 expectedOuter := search.Result{ 361 ID: strfmt.UUID(id1), 362 ClassName: "SomeClass", 363 Schema: map[string]interface{}{ 364 "primitive": "foobar", 365 "nestedRef": models.MultipleRef{ 366 &models.SingleRef{ 367 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 368 }, 369 }, 370 }, 371 } 372 373 expectedInner := search.Result{ 374 ClassName: "SomeNestedClass", 375 ID: strfmt.UUID(id2), 376 Schema: map[string]interface{}{ 377 "name": "John Doe", 378 }, 379 } 380 381 err := cr.Build(context.Background(), input, selectProps, additional.Properties{}) 382 require.Nil(t, err) 383 res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"}) 384 require.True(t, ok) 385 assert.Equal(t, expectedOuter, res) 386 res, ok = cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}) 387 require.True(t, ok) 388 assert.Equal(t, expectedInner, res) 389 assert.Equal(t, 2, repo.counter, "required the expected amount of lookup queries") 390 assert.Equal(t, 2, repo.counter, "required the expected amount of objects on the lookup queries") 391 }) 392 393 t.Run("with a nested lookup, and nested refs in nested refs", func(t *testing.T) { 394 repo := newFakeRepo() 395 idNested2ID := "132bdf92-ffec-4a52-9196-73ea7cbb5a00" 396 idNestedInNestedID := "132bdf92-ffec-4a52-9196-73ea7cbb5a01" 397 repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 398 ClassName: "SomeClass", 399 ID: strfmt.UUID(id1), 400 Schema: map[string]interface{}{ 401 "primitive": "foobar", 402 "nestedRef": models.MultipleRef{ 403 &models.SingleRef{ 404 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 405 }, 406 }, 407 "nestedRef2": models.MultipleRef{ 408 &models.SingleRef{ 409 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNested2ID)), 410 Schema: map[string]interface{}{ 411 "title": "nestedRef2Title", 412 "nestedRefInNestedRef": models.MultipleRef{ 413 &models.SingleRef{ 414 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNestedInNestedID)), 415 }, 416 }, 417 }, 418 }, 419 }, 420 }, 421 } 422 repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{ 423 ClassName: "SomeNestedClass", 424 ID: strfmt.UUID(id2), 425 Schema: map[string]interface{}{ 426 "name": "John Doe", 427 }, 428 } 429 repo.lookup[multi.Identifier{ID: idNested2ID, ClassName: "SomeNestedClass2"}] = search.Result{ 430 ClassName: "SomeNestedClass2", 431 ID: strfmt.UUID(idNested2ID), 432 Schema: map[string]interface{}{ 433 "title": "nestedRef2Title", 434 "nestedRefInNestedRef": models.MultipleRef{ 435 &models.SingleRef{ 436 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNestedInNestedID)), 437 }, 438 }, 439 }, 440 } 441 repo.lookup[multi.Identifier{ID: idNestedInNestedID, ClassName: "SomeNestedClassNested2"}] = search.Result{ 442 ClassName: "SomeNestedClassNested2", 443 ID: strfmt.UUID(idNestedInNestedID), 444 Schema: map[string]interface{}{ 445 "titleNested": "Nested In Nested Title", 446 }, 447 } 448 logger, _ := test.NewNullLogger() 449 cr := NewCacher(repo, logger, "") 450 input := []search.Result{ 451 { 452 ID: "foo", 453 ClassName: "BestClass", 454 Schema: map[string]interface{}{ 455 "refProp": models.MultipleRef{ 456 &models.SingleRef{ 457 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 458 }, 459 }, 460 }, 461 }, 462 } 463 selectProps := search.SelectProperties{ 464 search.SelectProperty{ 465 Name: "refProp", 466 Refs: []search.SelectClass{ 467 { 468 ClassName: "SomeClass", 469 RefProperties: search.SelectProperties{ 470 search.SelectProperty{ 471 Name: "primitive", 472 IsPrimitive: true, 473 }, 474 search.SelectProperty{ 475 Name: "nestedRef", 476 Refs: []search.SelectClass{ 477 { 478 ClassName: "SomeNestedClass", 479 RefProperties: []search.SelectProperty{ 480 { 481 Name: "name", 482 IsPrimitive: true, 483 }, 484 }, 485 }, 486 }, 487 }, 488 search.SelectProperty{ 489 Name: "nestedRef2", 490 Refs: []search.SelectClass{ 491 { 492 ClassName: "SomeNestedClass2", 493 RefProperties: []search.SelectProperty{ 494 { 495 Name: "title", 496 IsPrimitive: true, 497 }, 498 { 499 Name: "nestedRefInNestedRef", 500 Refs: []search.SelectClass{ 501 { 502 ClassName: "SomeNestedClassNested2", 503 RefProperties: []search.SelectProperty{ 504 { 505 Name: "titleNested", 506 IsPrimitive: true, 507 }, 508 }, 509 }, 510 }, 511 }, 512 }, 513 }, 514 }, 515 }, 516 }, 517 }, 518 }, 519 }, 520 } 521 522 expectedOuter := search.Result{ 523 ID: strfmt.UUID(id1), 524 ClassName: "SomeClass", 525 Schema: map[string]interface{}{ 526 "primitive": "foobar", 527 "nestedRef": models.MultipleRef{ 528 &models.SingleRef{ 529 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 530 }, 531 }, 532 "nestedRef2": models.MultipleRef{ 533 &models.SingleRef{ 534 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNested2ID)), 535 Schema: map[string]interface{}{ 536 "title": "nestedRef2Title", 537 "nestedRefInNestedRef": models.MultipleRef{ 538 &models.SingleRef{ 539 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNestedInNestedID)), 540 }, 541 }, 542 }, 543 }, 544 }, 545 }, 546 } 547 548 expectedInner := search.Result{ 549 ClassName: "SomeNestedClass", 550 ID: strfmt.UUID(id2), 551 Schema: map[string]interface{}{ 552 "name": "John Doe", 553 }, 554 } 555 556 expectedInner2 := search.Result{ 557 ClassName: "SomeNestedClass2", 558 ID: strfmt.UUID(idNested2ID), 559 Schema: map[string]interface{}{ 560 "title": "nestedRef2Title", 561 "nestedRefInNestedRef": models.MultipleRef{ 562 &models.SingleRef{ 563 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNestedInNestedID)), 564 }, 565 }, 566 }, 567 } 568 569 expectedInnerInner := search.Result{ 570 ClassName: "SomeNestedClassNested2", 571 ID: strfmt.UUID(idNestedInNestedID), 572 Schema: map[string]interface{}{ 573 "titleNested": "Nested In Nested Title", 574 }, 575 } 576 577 err := cr.Build(context.Background(), input, selectProps, additional.Properties{}) 578 require.Nil(t, err) 579 res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"}) 580 require.True(t, ok) 581 assert.Equal(t, expectedOuter, res) 582 input2 := []search.Result{expectedInner, expectedInner2} 583 err = cr.Build(context.Background(), input2, nil, additional.Properties{}) 584 require.Nil(t, err) 585 nested1, ok := cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}) 586 require.True(t, ok) 587 assert.Equal(t, expectedInner, nested1) 588 nested2, ok := cr.Get(multi.Identifier{ID: idNested2ID, ClassName: "SomeNestedClass2"}) 589 require.True(t, ok) 590 assert.Equal(t, expectedInner2, nested2) 591 nestedSchema, ok := nested2.Schema.(map[string]interface{}) 592 require.True(t, ok) 593 nestedRefInNestedRef, ok := nestedSchema["nestedRefInNestedRef"] 594 require.True(t, ok) 595 require.NotNil(t, nestedRefInNestedRef) 596 nestedRefInNestedMultiRef, ok := nestedRefInNestedRef.(models.MultipleRef) 597 require.True(t, ok) 598 require.NotNil(t, nestedRefInNestedMultiRef) 599 require.Nil(t, err) 600 res, ok = cr.Get(multi.Identifier{ID: idNestedInNestedID, ClassName: "SomeNestedClassNested2"}) 601 require.True(t, ok) 602 assert.Equal(t, expectedInnerInner, res) 603 assert.Equal(t, 3, repo.counter, "required the expected amount of lookups") 604 }) 605 606 t.Run("with group and with a additional group lookup", func(t *testing.T) { 607 repo := newFakeRepo() 608 repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 609 ClassName: "SomeClass", 610 ID: strfmt.UUID(id1), 611 Schema: map[string]interface{}{ 612 "primitive": "foobar", 613 }, 614 AdditionalProperties: models.AdditionalProperties{ 615 "group": &additional.Group{ 616 Hits: []map[string]interface{}{ 617 { 618 "primitive": "foobar", 619 "ignoredRef": models.MultipleRef{ 620 &models.SingleRef{ 621 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 622 }, 623 }, 624 "nestedRef": models.MultipleRef{ 625 &models.SingleRef{ 626 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 627 }, 628 }, 629 }, 630 }, 631 }, 632 }, 633 } 634 repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{ 635 ClassName: "SomeNestedClass", 636 ID: strfmt.UUID(id2), 637 Schema: map[string]interface{}{ 638 "name": "John Doe", 639 }, 640 } 641 logger, _ := test.NewNullLogger() 642 cr := NewCacherWithGroup(repo, logger, "") 643 input := []search.Result{ 644 { 645 ID: "foo", 646 ClassName: "BestClass", 647 Schema: map[string]interface{}{ 648 "refProp": models.MultipleRef{ 649 &models.SingleRef{ 650 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 651 }, 652 }, 653 }, 654 AdditionalProperties: models.AdditionalProperties{ 655 "group": &additional.Group{ 656 Hits: []map[string]interface{}{ 657 { 658 "primitive": "foobar", 659 "ignoredRef": models.MultipleRef{ 660 &models.SingleRef{ 661 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 662 }, 663 }, 664 "nestedRef": models.MultipleRef{ 665 &models.SingleRef{ 666 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 667 }, 668 }, 669 }, 670 }, 671 }, 672 }, 673 }, 674 } 675 selectProps := search.SelectProperties{ 676 search.SelectProperty{ 677 Name: "_additional:group:hits:nestedRef", 678 Refs: []search.SelectClass{ 679 { 680 ClassName: "SomeNestedClass", 681 RefProperties: []search.SelectProperty{ 682 { 683 Name: "name", 684 IsPrimitive: true, 685 }, 686 }, 687 }, 688 }, 689 }, 690 } 691 692 expectedInner := search.Result{ 693 ClassName: "SomeNestedClass", 694 ID: strfmt.UUID(id2), 695 Schema: map[string]interface{}{ 696 "name": "John Doe", 697 }, 698 } 699 700 err := cr.Build(context.Background(), input, selectProps, additional.Properties{}) 701 require.Nil(t, err) 702 res, ok := cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}) 703 require.True(t, ok) 704 assert.Equal(t, expectedInner, res) 705 assert.Equal(t, 1, repo.counter, "required the expected amount of lookups") 706 }) 707 708 t.Run("with group and with 2 additional group lookups", func(t *testing.T) { 709 repo := newFakeRepo() 710 repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 711 ClassName: "SomeClass", 712 ID: strfmt.UUID(id1), 713 Schema: map[string]interface{}{ 714 "primitive": "foobar", 715 }, 716 AdditionalProperties: models.AdditionalProperties{ 717 "group": &additional.Group{ 718 Hits: []map[string]interface{}{ 719 { 720 "primitive": "foobar", 721 "ignoredRef": models.MultipleRef{ 722 &models.SingleRef{ 723 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 724 }, 725 }, 726 "nestedRef": models.MultipleRef{ 727 &models.SingleRef{ 728 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 729 }, 730 }, 731 }, 732 }, 733 }, 734 }, 735 } 736 repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{ 737 ClassName: "SomeNestedClass", 738 ID: strfmt.UUID(id2), 739 Schema: map[string]interface{}{ 740 "name": "John Doe", 741 }, 742 } 743 repo.lookup[multi.Identifier{ID: id3, ClassName: "OtherNestedClass"}] = search.Result{ 744 ClassName: "OtherNestedClass", 745 ID: strfmt.UUID(id3), 746 Schema: map[string]interface{}{ 747 "name": "John Doe", 748 }, 749 } 750 logger, _ := test.NewNullLogger() 751 cr := NewCacherWithGroup(repo, logger, "") 752 input := []search.Result{ 753 { 754 ID: "foo", 755 ClassName: "BestClass", 756 Schema: map[string]interface{}{ 757 "refProp": models.MultipleRef{ 758 &models.SingleRef{ 759 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 760 }, 761 }, 762 }, 763 AdditionalProperties: models.AdditionalProperties{ 764 "group": &additional.Group{ 765 Hits: []map[string]interface{}{ 766 { 767 "primitive": "foobar", 768 "nestedRef": models.MultipleRef{ 769 &models.SingleRef{ 770 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 771 }, 772 }, 773 "otherNestedRef": models.MultipleRef{ 774 &models.SingleRef{ 775 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id3)), 776 }, 777 }, 778 }, 779 }, 780 }, 781 }, 782 }, 783 } 784 selectProps := search.SelectProperties{ 785 search.SelectProperty{ 786 Name: "_additional:group:hits:nestedRef", 787 Refs: []search.SelectClass{ 788 { 789 ClassName: "SomeNestedClass", 790 RefProperties: []search.SelectProperty{ 791 { 792 Name: "name", 793 IsPrimitive: true, 794 }, 795 }, 796 }, 797 }, 798 }, 799 search.SelectProperty{ 800 Name: "_additional:group:hits:otherNestedRef", 801 Refs: []search.SelectClass{ 802 { 803 ClassName: "OtherNestedClass", 804 RefProperties: []search.SelectProperty{ 805 { 806 Name: "name", 807 IsPrimitive: true, 808 }, 809 }, 810 }, 811 }, 812 }, 813 } 814 815 expectedSomeNestedClass := search.Result{ 816 ClassName: "SomeNestedClass", 817 ID: strfmt.UUID(id2), 818 Schema: map[string]interface{}{ 819 "name": "John Doe", 820 }, 821 } 822 823 expectedOtherNestedClass := search.Result{ 824 ClassName: "OtherNestedClass", 825 ID: strfmt.UUID(id3), 826 Schema: map[string]interface{}{ 827 "name": "John Doe", 828 }, 829 } 830 831 err := cr.Build(context.Background(), input, selectProps, additional.Properties{}) 832 require.Nil(t, err) 833 res, ok := cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}) 834 require.True(t, ok) 835 assert.Equal(t, expectedSomeNestedClass, res) 836 res, ok = cr.Get(multi.Identifier{ID: id3, ClassName: "OtherNestedClass"}) 837 require.True(t, ok) 838 assert.Equal(t, expectedOtherNestedClass, res) 839 assert.Equal(t, 1, repo.counter, "required the expected amount of lookups") 840 }) 841 842 t.Run("with group with a nested lookup and with 2 additional group lookups", func(t *testing.T) { 843 repo := newFakeRepo() 844 repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 845 ClassName: "SomeClass", 846 ID: strfmt.UUID(id1), 847 Schema: map[string]interface{}{ 848 "primitive": "foobar", 849 "ignoredRef": models.MultipleRef{ 850 &models.SingleRef{ 851 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 852 }, 853 }, 854 "nestedRef": models.MultipleRef{ 855 &models.SingleRef{ 856 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 857 }, 858 }, 859 }, 860 } 861 repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{ 862 ClassName: "SomeNestedClass", 863 ID: strfmt.UUID(id2), 864 Schema: map[string]interface{}{ 865 "name": "John Doe", 866 }, 867 } 868 repo.lookup[multi.Identifier{ID: id3, ClassName: "InnerNestedClass"}] = search.Result{ 869 ClassName: "InnerNestedClass", 870 ID: strfmt.UUID(id3), 871 Schema: map[string]interface{}{ 872 "name": "John Doe", 873 }, 874 } 875 repo.lookup[multi.Identifier{ID: id4, ClassName: "OtherNestedClass"}] = search.Result{ 876 ClassName: "OtherNestedClass", 877 ID: strfmt.UUID(id4), 878 Schema: map[string]interface{}{ 879 "name": "John Doe", 880 }, 881 } 882 logger, _ := test.NewNullLogger() 883 cr := NewCacherWithGroup(repo, logger, "") 884 input := []search.Result{ 885 { 886 ID: "foo", 887 ClassName: "BestClass", 888 Schema: map[string]interface{}{ 889 "refProp": models.MultipleRef{ 890 &models.SingleRef{ 891 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 892 }, 893 }, 894 }, 895 AdditionalProperties: models.AdditionalProperties{ 896 "group": &additional.Group{ 897 Hits: []map[string]interface{}{ 898 { 899 "primitive": "foobar", 900 "innerNestedRef": models.MultipleRef{ 901 &models.SingleRef{ 902 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id3)), 903 }, 904 }, 905 "otherNestedRef": models.MultipleRef{ 906 &models.SingleRef{ 907 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id4)), 908 }, 909 }, 910 }, 911 }, 912 }, 913 }, 914 }, 915 } 916 selectProps := search.SelectProperties{ 917 search.SelectProperty{ 918 Name: "refProp", 919 Refs: []search.SelectClass{ 920 { 921 ClassName: "SomeClass", 922 RefProperties: search.SelectProperties{ 923 search.SelectProperty{ 924 Name: "primitive", 925 IsPrimitive: true, 926 }, 927 search.SelectProperty{ 928 Name: "nestedRef", 929 Refs: []search.SelectClass{ 930 { 931 ClassName: "SomeNestedClass", 932 RefProperties: []search.SelectProperty{ 933 { 934 Name: "name", 935 IsPrimitive: true, 936 }, 937 }, 938 }, 939 }, 940 }, 941 }, 942 }, 943 }, 944 }, 945 search.SelectProperty{ 946 Name: "_additional:group:hits:innerNestedRef", 947 Refs: []search.SelectClass{ 948 { 949 ClassName: "InnerNestedClass", 950 RefProperties: []search.SelectProperty{ 951 { 952 Name: "name", 953 IsPrimitive: true, 954 }, 955 }, 956 }, 957 }, 958 }, 959 search.SelectProperty{ 960 Name: "_additional:group:hits:otherNestedRef", 961 Refs: []search.SelectClass{ 962 { 963 ClassName: "OtherNestedClass", 964 RefProperties: []search.SelectProperty{ 965 { 966 Name: "name", 967 IsPrimitive: true, 968 }, 969 }, 970 }, 971 }, 972 }, 973 } 974 975 expectedOuter := search.Result{ 976 ID: strfmt.UUID(id1), 977 ClassName: "SomeClass", 978 Schema: map[string]interface{}{ 979 "primitive": "foobar", 980 "ignoredRef": models.MultipleRef{ 981 &models.SingleRef{ 982 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 983 }, 984 }, 985 "nestedRef": models.MultipleRef{ 986 &models.SingleRef{ 987 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 988 }, 989 }, 990 }, 991 } 992 993 expectedInner := search.Result{ 994 ClassName: "SomeNestedClass", 995 ID: strfmt.UUID(id2), 996 Schema: map[string]interface{}{ 997 "name": "John Doe", 998 }, 999 } 1000 1001 expectedInnerNestedClass := search.Result{ 1002 ClassName: "InnerNestedClass", 1003 ID: strfmt.UUID(id3), 1004 Schema: map[string]interface{}{ 1005 "name": "John Doe", 1006 }, 1007 } 1008 1009 expectedOtherNestedClass := search.Result{ 1010 ClassName: "OtherNestedClass", 1011 ID: strfmt.UUID(id4), 1012 Schema: map[string]interface{}{ 1013 "name": "John Doe", 1014 }, 1015 } 1016 1017 err := cr.Build(context.Background(), input, selectProps, additional.Properties{}) 1018 require.Nil(t, err) 1019 res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"}) 1020 require.True(t, ok) 1021 assert.Equal(t, expectedOuter, res) 1022 res, ok = cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}) 1023 require.True(t, ok) 1024 assert.Equal(t, expectedInner, res) 1025 res, ok = cr.Get(multi.Identifier{ID: id3, ClassName: "InnerNestedClass"}) 1026 require.True(t, ok) 1027 assert.Equal(t, expectedInnerNestedClass, res) 1028 res, ok = cr.Get(multi.Identifier{ID: id4, ClassName: "OtherNestedClass"}) 1029 require.True(t, ok) 1030 assert.Equal(t, expectedOtherNestedClass, res) 1031 assert.Equal(t, 2, repo.counter, "required the expected amount of lookups") 1032 }) 1033 } 1034 1035 type fakeRepo struct { 1036 lookup map[multi.Identifier]search.Result 1037 counter int // count request 1038 objectCounter int // count total objects on request(s) 1039 } 1040 1041 func newFakeRepo() *fakeRepo { 1042 return &fakeRepo{ 1043 lookup: map[multi.Identifier]search.Result{}, 1044 } 1045 } 1046 1047 func (f *fakeRepo) MultiGet(ctx context.Context, query []multi.Identifier, additional additional.Properties, tenant string) ([]search.Result, error) { 1048 f.counter++ 1049 f.objectCounter += len(query) 1050 out := make([]search.Result, len(query)) 1051 for i, q := range query { 1052 if res, ok := f.lookup[q]; ok { 1053 out[i] = res 1054 } else { 1055 out[i] = search.Result{} 1056 } 1057 } 1058 1059 return out, nil 1060 }