github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/refcache/resolver_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 "time" 19 20 "github.com/go-openapi/strfmt" 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 TestResolver(t *testing.T) { 30 id1 := "df5d4e49-0c56-4b87-ade1-3d46cc9b425f" 31 id2 := "3a08d808-8eb5-49ee-86b2-68b6035e8b69" 32 33 t.Run("with nil input", func(t *testing.T) { 34 r := NewResolver(newFakeCacher()) 35 res, err := r.Do(context.Background(), nil, nil, additional.Properties{}) 36 require.Nil(t, err) 37 assert.Nil(t, res) 38 }) 39 40 t.Run("with nil-schemas", func(t *testing.T) { 41 r := NewResolver(newFakeCacher()) 42 input := []search.Result{ 43 { 44 ID: "foo", 45 ClassName: "BestClass", 46 }, 47 } 48 49 expected := input 50 res, err := r.Do(context.Background(), input, nil, additional.Properties{}) 51 require.Nil(t, err) 52 assert.Equal(t, expected, res) 53 }) 54 55 t.Run("with single ref but no select props", func(t *testing.T) { 56 r := NewResolver(newFakeCacher()) 57 input := []search.Result{ 58 { 59 ID: "foo", 60 ClassName: "BestClass", 61 Schema: map[string]interface{}{ 62 "refProp": models.MultipleRef{ 63 &models.SingleRef{ 64 Beacon: "weaviate://localhost/123", 65 }, 66 }, 67 }, 68 }, 69 } 70 71 expected := input 72 res, err := r.Do(context.Background(), input, nil, additional.Properties{}) 73 require.Nil(t, err) 74 assert.Equal(t, expected, res) 75 }) 76 77 t.Run("with single ref with vector and matching select prop", func(t *testing.T) { 78 getInput := func() []search.Result { 79 return []search.Result{ 80 { 81 ID: "foo", 82 ClassName: "BestClass", 83 Schema: map[string]interface{}{ 84 "refProp": models.MultipleRef{ 85 &models.SingleRef{ 86 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 87 }, 88 }, 89 }, 90 }, 91 } 92 } 93 getResolver := func() *Resolver { 94 cacher := newFakeCacher() 95 r := NewResolver(cacher) 96 cacher.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 97 ClassName: "SomeClass", 98 ID: strfmt.UUID(id1), 99 Schema: map[string]interface{}{ 100 "bar": "some string", 101 }, 102 Vector: []float32{0.1, 0.2}, 103 } 104 return r 105 } 106 getSelectProps := func(withVector bool) search.SelectProperties { 107 return search.SelectProperties{ 108 search.SelectProperty{ 109 Name: "refProp", 110 Refs: []search.SelectClass{ 111 { 112 ClassName: "SomeClass", 113 RefProperties: search.SelectProperties{ 114 search.SelectProperty{ 115 Name: "bar", 116 IsPrimitive: true, 117 }, 118 }, 119 AdditionalProperties: additional.Properties{ 120 Vector: withVector, 121 }, 122 }, 123 }, 124 }, 125 } 126 } 127 getExpectedResult := func(withVector bool) []search.Result { 128 fields := map[string]interface{}{ 129 "bar": "some string", 130 } 131 if withVector { 132 fields["vector"] = []float32{0.1, 0.2} 133 } 134 return []search.Result{ 135 { 136 ID: "foo", 137 ClassName: "BestClass", 138 Schema: map[string]interface{}{ 139 "refProp": []interface{}{ 140 search.LocalRef{ 141 Class: "SomeClass", 142 Fields: fields, 143 }, 144 }, 145 }, 146 }, 147 } 148 } 149 // ask for vector in ref property 150 res, err := getResolver().Do(context.Background(), getInput(), getSelectProps(true), additional.Properties{}) 151 require.Nil(t, err) 152 assert.Equal(t, getExpectedResult(true), res) 153 // don't ask for vector in ref property 154 res, err = getResolver().Do(context.Background(), getInput(), getSelectProps(false), additional.Properties{}) 155 require.Nil(t, err) 156 assert.Equal(t, getExpectedResult(false), res) 157 }) 158 159 t.Run("with single ref with creation/update timestamps and matching select prop", func(t *testing.T) { 160 now := time.Now().UnixMilli() 161 getInput := func() []search.Result { 162 return []search.Result{ 163 { 164 ID: "foo", 165 ClassName: "BestClass", 166 Schema: map[string]interface{}{ 167 "refProp": models.MultipleRef{ 168 &models.SingleRef{ 169 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 170 }, 171 }, 172 }, 173 }, 174 } 175 } 176 getResolver := func() *Resolver { 177 cacher := newFakeCacher() 178 r := NewResolver(cacher) 179 cacher.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 180 ClassName: "SomeClass", 181 ID: strfmt.UUID(id1), 182 Schema: map[string]interface{}{ 183 "bar": "some string", 184 }, 185 Created: now, 186 Updated: now, 187 } 188 return r 189 } 190 selectProps := search.SelectProperties{ 191 search.SelectProperty{ 192 Name: "refProp", 193 Refs: []search.SelectClass{ 194 { 195 ClassName: "SomeClass", 196 RefProperties: search.SelectProperties{ 197 search.SelectProperty{ 198 Name: "bar", 199 IsPrimitive: true, 200 }, 201 }, 202 AdditionalProperties: additional.Properties{ 203 CreationTimeUnix: true, 204 LastUpdateTimeUnix: true, 205 }, 206 }, 207 }, 208 }, 209 } 210 expected := []search.Result{ 211 { 212 ID: "foo", 213 ClassName: "BestClass", 214 Schema: map[string]interface{}{ 215 "refProp": []interface{}{ 216 search.LocalRef{ 217 Class: "SomeClass", 218 Fields: map[string]interface{}{ 219 "bar": "some string", 220 "creationTimeUnix": now, 221 "lastUpdateTimeUnix": now, 222 }, 223 }, 224 }, 225 }, 226 }, 227 } 228 res, err := getResolver().Do(context.Background(), getInput(), selectProps, additional.Properties{}) 229 require.Nil(t, err) 230 assert.Equal(t, expected, res) 231 }) 232 233 t.Run("with single ref and matching select prop", func(t *testing.T) { 234 cacher := newFakeCacher() 235 r := NewResolver(cacher) 236 cacher.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 237 ClassName: "SomeClass", 238 ID: strfmt.UUID(id1), 239 Schema: map[string]interface{}{ 240 "bar": "some string", 241 }, 242 } 243 input := []search.Result{ 244 { 245 ID: "foo", 246 ClassName: "BestClass", 247 Schema: map[string]interface{}{ 248 "refProp": models.MultipleRef{ 249 &models.SingleRef{ 250 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 251 }, 252 }, 253 }, 254 }, 255 } 256 selectProps := search.SelectProperties{ 257 search.SelectProperty{ 258 Name: "refProp", 259 Refs: []search.SelectClass{ 260 { 261 ClassName: "SomeClass", 262 RefProperties: search.SelectProperties{ 263 search.SelectProperty{ 264 Name: "bar", 265 IsPrimitive: true, 266 }, 267 }, 268 }, 269 }, 270 }, 271 } 272 273 expected := []search.Result{ 274 { 275 ID: "foo", 276 ClassName: "BestClass", 277 Schema: map[string]interface{}{ 278 "refProp": []interface{}{ 279 search.LocalRef{ 280 Class: "SomeClass", 281 Fields: map[string]interface{}{ 282 "bar": "some string", 283 }, 284 }, 285 }, 286 }, 287 }, 288 } 289 res, err := r.Do(context.Background(), input, selectProps, additional.Properties{}) 290 require.Nil(t, err) 291 assert.Equal(t, expected, res) 292 }) 293 294 t.Run("with a nested lookup", func(t *testing.T) { 295 cacher := newFakeCacher() 296 r := NewResolver(cacher) 297 cacher.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 298 ClassName: "SomeClass", 299 ID: strfmt.UUID(id1), 300 Schema: map[string]interface{}{ 301 "primitive": "foobar", 302 "ignoredRef": models.MultipleRef{ 303 &models.SingleRef{ 304 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 305 }, 306 }, 307 "nestedRef": models.MultipleRef{ 308 &models.SingleRef{ 309 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)), 310 }, 311 }, 312 }, 313 } 314 cacher.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{ 315 ClassName: "SomeNestedClass", 316 ID: strfmt.UUID(id2), 317 Schema: map[string]interface{}{ 318 "name": "John Doe", 319 }, 320 } 321 input := []search.Result{ 322 { 323 ID: "foo", 324 ClassName: "BestClass", 325 Schema: map[string]interface{}{ 326 "refProp": models.MultipleRef{ 327 &models.SingleRef{ 328 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 329 }, 330 }, 331 }, 332 }, 333 } 334 selectProps := search.SelectProperties{ 335 search.SelectProperty{ 336 Name: "refProp", 337 Refs: []search.SelectClass{ 338 { 339 ClassName: "SomeClass", 340 RefProperties: search.SelectProperties{ 341 search.SelectProperty{ 342 Name: "primitive", 343 IsPrimitive: true, 344 }, 345 search.SelectProperty{ 346 Name: "nestedRef", 347 Refs: []search.SelectClass{ 348 { 349 ClassName: "SomeNestedClass", 350 RefProperties: []search.SelectProperty{ 351 { 352 Name: "name", 353 IsPrimitive: true, 354 }, 355 }, 356 }, 357 }, 358 }, 359 }, 360 }, 361 }, 362 }, 363 } 364 365 expected := []search.Result{ 366 { 367 ID: "foo", 368 ClassName: "BestClass", 369 Schema: map[string]interface{}{ 370 "refProp": []interface{}{ 371 search.LocalRef{ 372 Class: "SomeClass", 373 Fields: map[string]interface{}{ 374 "primitive": "foobar", 375 "ignoredRef": models.MultipleRef{ 376 &models.SingleRef{ 377 Beacon: strfmt.URI("weaviate://localhost/ignoreMe"), 378 }, 379 }, 380 "nestedRef": []interface{}{ 381 search.LocalRef{ 382 Class: "SomeNestedClass", 383 Fields: map[string]interface{}{ 384 "name": "John Doe", 385 }, 386 }, 387 }, 388 }, 389 }, 390 }, 391 }, 392 }, 393 } 394 res, err := r.Do(context.Background(), input, selectProps, additional.Properties{}) 395 require.Nil(t, err) 396 assert.Equal(t, expected, res) 397 }) 398 399 t.Run("with single ref with vector and matching select prop and group", func(t *testing.T) { 400 getInput := func() []search.Result { 401 return []search.Result{ 402 { 403 ID: "foo", 404 ClassName: "BestClass", 405 Schema: map[string]interface{}{ 406 "refProp": models.MultipleRef{ 407 &models.SingleRef{ 408 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)), 409 }, 410 }, 411 }, 412 AdditionalProperties: models.AdditionalProperties{ 413 "group": &additional.Group{ 414 Hits: []map[string]interface{}{ 415 { 416 "nestedRef": models.MultipleRef{ 417 &models.SingleRef{ 418 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/SomeNestedClass/%s", id2)), 419 }, 420 }, 421 }, 422 }, 423 }, 424 }, 425 }, 426 } 427 } 428 getResolver := func() *Resolver { 429 cacher := newFakeCacher() 430 r := NewResolverWithGroup(cacher) 431 cacher.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{ 432 ClassName: "SomeClass", 433 ID: strfmt.UUID(id1), 434 Schema: map[string]interface{}{ 435 "bar": "some string", 436 }, 437 Vector: []float32{0.1, 0.2}, 438 } 439 cacher.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{ 440 ClassName: "SomeNestedClass", 441 ID: strfmt.UUID(id2), 442 Schema: map[string]interface{}{ 443 "name": "John Doe", 444 }, 445 } 446 return r 447 } 448 getSelectProps := func(withVector bool) search.SelectProperties { 449 return search.SelectProperties{ 450 search.SelectProperty{ 451 Name: "refProp", 452 Refs: []search.SelectClass{ 453 { 454 ClassName: "SomeClass", 455 RefProperties: search.SelectProperties{ 456 search.SelectProperty{ 457 Name: "bar", 458 IsPrimitive: true, 459 }, 460 }, 461 AdditionalProperties: additional.Properties{ 462 Vector: withVector, 463 }, 464 }, 465 }, 466 }, 467 search.SelectProperty{ 468 Name: "_additional:group:hits:nestedRef", 469 Refs: []search.SelectClass{ 470 { 471 ClassName: "SomeNestedClass", 472 RefProperties: []search.SelectProperty{ 473 { 474 Name: "name", 475 IsPrimitive: true, 476 }, 477 }, 478 }, 479 }, 480 }, 481 } 482 } 483 getExpectedResult := func(withVector bool) []search.Result { 484 fields := map[string]interface{}{ 485 "bar": "some string", 486 } 487 if withVector { 488 fields["vector"] = []float32{0.1, 0.2} 489 } 490 return []search.Result{ 491 { 492 ID: "foo", 493 ClassName: "BestClass", 494 Schema: map[string]interface{}{ 495 "refProp": []interface{}{ 496 search.LocalRef{ 497 Class: "SomeClass", 498 Fields: fields, 499 }, 500 }, 501 }, 502 AdditionalProperties: models.AdditionalProperties{ 503 "group": &additional.Group{ 504 Hits: []map[string]interface{}{ 505 { 506 "nestedRef": []interface{}{ 507 search.LocalRef{ 508 Class: "SomeNestedClass", 509 Fields: map[string]interface{}{ 510 "name": "John Doe", 511 }, 512 }, 513 }, 514 }, 515 }, 516 }, 517 }, 518 }, 519 } 520 } 521 // ask for vector in ref property 522 res, err := getResolver().Do(context.Background(), getInput(), getSelectProps(true), additional.Properties{}) 523 require.Nil(t, err) 524 assert.Equal(t, getExpectedResult(true), res) 525 // don't ask for vector in ref property 526 res, err = getResolver().Do(context.Background(), getInput(), getSelectProps(false), additional.Properties{}) 527 require.Nil(t, err) 528 assert.Equal(t, getExpectedResult(false), res) 529 }) 530 } 531 532 func newFakeCacher() *fakeCacher { 533 return &fakeCacher{ 534 lookup: map[multi.Identifier]search.Result{}, 535 } 536 } 537 538 type fakeCacher struct { 539 lookup map[multi.Identifier]search.Result 540 } 541 542 func (f *fakeCacher) Build(ctx context.Context, objects []search.Result, properties search.SelectProperties, 543 additional additional.Properties, 544 ) error { 545 return nil 546 } 547 548 func (f *fakeCacher) Get(si multi.Identifier) (search.Result, bool) { 549 res, ok := f.lookup[si] 550 return res, ok 551 }