github.com/weaviate/weaviate@v1.24.6/entities/storobj/storage_object_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 storobj 13 14 import ( 15 "fmt" 16 "testing" 17 "time" 18 19 "github.com/go-openapi/strfmt" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 "github.com/weaviate/weaviate/entities/additional" 23 "github.com/weaviate/weaviate/entities/models" 24 "github.com/weaviate/weaviate/entities/schema" 25 ) 26 27 func TestStorageObjectMarshalling(t *testing.T) { 28 before := FromObject( 29 &models.Object{ 30 Class: "MyFavoriteClass", 31 CreationTimeUnix: 123456, 32 LastUpdateTimeUnix: 56789, 33 ID: strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168247"), 34 Additional: models.AdditionalProperties{ 35 "classification": &additional.Classification{ 36 BasedOn: []string{"some", "fields"}, 37 }, 38 "interpretation": map[string]interface{}{ 39 "Source": []interface{}{ 40 map[string]interface{}{ 41 "concept": "foo", 42 "occurrence": float64(7), 43 "weight": float64(3), 44 }, 45 }, 46 }, 47 }, 48 Properties: map[string]interface{}{ 49 "name": "MyName", 50 "foo": float64(17), 51 }, 52 }, 53 []float32{1, 2, 0.7}, 54 models.Vectors{ 55 "vector1": {1, 2, 3}, 56 "vector2": {4, 5, 6}, 57 }, 58 ) 59 before.DocID = 7 60 61 asBinary, err := before.MarshalBinary() 62 require.Nil(t, err) 63 64 after, err := FromBinary(asBinary) 65 require.Nil(t, err) 66 67 t.Run("compare", func(t *testing.T) { 68 assert.Equal(t, before, after) 69 }) 70 71 t.Run("extract only doc id and compare", func(t *testing.T) { 72 id, err := DocIDFromBinary(asBinary) 73 require.Nil(t, err) 74 assert.Equal(t, uint64(7), id) 75 }) 76 77 t.Run("extract single text prop", func(t *testing.T) { 78 prop, ok, err := ParseAndExtractTextProp(asBinary, "name") 79 require.Nil(t, err) 80 require.True(t, ok) 81 require.NotEmpty(t, prop) 82 assert.Equal(t, "MyName", prop[0]) 83 }) 84 85 t.Run("extract non-existing text prop", func(t *testing.T) { 86 prop, ok, err := ParseAndExtractTextProp(asBinary, "IDoNotExist") 87 require.Nil(t, err) 88 require.True(t, ok) 89 require.Empty(t, prop) 90 }) 91 } 92 93 func TestFilteringNilProperty(t *testing.T) { 94 object := FromObject( 95 &models.Object{ 96 Class: "MyFavoriteClass", 97 ID: "73f2eb5f-5abf-447a-81ca-74b1dd168247", 98 Properties: map[string]interface{}{ 99 "IWillBeRemoved": nil, 100 "IWillStay": float64(17), 101 }, 102 }, 103 []float32{1, 2, 0.7}, 104 nil, 105 ) 106 props := object.Properties() 107 propsTyped, ok := props.(map[string]interface{}) 108 require.True(t, ok) 109 assert.Equal(t, propsTyped["IWillStay"], float64(17)) 110 111 elem, ok := propsTyped["IWillBeRemoved"] 112 require.False(t, ok) 113 require.Nil(t, elem) 114 } 115 116 func TestStorageObjectUnmarshallingSpecificProps(t *testing.T) { 117 before := FromObject( 118 &models.Object{ 119 Class: "MyFavoriteClass", 120 CreationTimeUnix: 123456, 121 LastUpdateTimeUnix: 56789, 122 ID: strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168247"), 123 Additional: models.AdditionalProperties{ 124 "classification": &additional.Classification{ 125 BasedOn: []string{"some", "fields"}, 126 }, 127 "interpretation": map[string]interface{}{ 128 "Source": []interface{}{ 129 map[string]interface{}{ 130 "concept": "foo", 131 "occurrence": float64(7), 132 "weight": float64(3), 133 }, 134 }, 135 }, 136 }, 137 Properties: map[string]interface{}{ 138 "name": "MyName", 139 "foo": float64(17), 140 }, 141 }, 142 []float32{1, 2, 0.7}, 143 models.Vectors{ 144 "vector1": {1, 2, 3}, 145 "vector2": {4, 5, 6}, 146 "vector3": {7, 8, 9}, 147 }, 148 ) 149 before.DocID = 7 150 151 asBinary, err := before.MarshalBinary() 152 require.Nil(t, err) 153 154 t.Run("without any optional", func(t *testing.T) { 155 after, err := FromBinaryOptional(asBinary, additional.Properties{}) 156 require.Nil(t, err) 157 158 t.Run("compare", func(t *testing.T) { 159 // modify before to match expectations of after 160 before.Object.Additional = nil 161 before.Vector = nil 162 before.VectorLen = 3 163 before.Vectors = nil 164 assert.Equal(t, before, after) 165 166 assert.Equal(t, before.DocID, after.DocID) 167 168 // The vector length should always be returned (for usage metrics 169 // purposes) even if the vector itself is skipped 170 assert.Equal(t, after.VectorLen, 3) 171 }) 172 }) 173 } 174 175 func TestNewStorageObject(t *testing.T) { 176 t.Run("objects", func(t *testing.T) { 177 so := New(12) 178 179 t.Run("check index id", func(t *testing.T) { 180 assert.Equal(t, uint64(12), so.DocID) 181 }) 182 183 t.Run("is invalid without required params", func(t *testing.T) { 184 assert.False(t, so.Valid()) 185 }) 186 187 t.Run("reassign index id", func(t *testing.T) { 188 so.DocID = 13 189 assert.Equal(t, uint64(13), so.DocID) 190 }) 191 192 t.Run("assign class", func(t *testing.T) { 193 so.SetClass("MyClass") 194 assert.Equal(t, schema.ClassName("MyClass"), so.Class()) 195 }) 196 197 t.Run("assign uuid", func(t *testing.T) { 198 id := strfmt.UUID("bf706904-8618-463f-899c-4a2aafd48d56") 199 so.SetID(id) 200 assert.Equal(t, id, so.ID()) 201 }) 202 203 t.Run("assign uuid", func(t *testing.T) { 204 schema := map[string]interface{}{ 205 "foo": "bar", 206 } 207 so.SetProperties(schema) 208 assert.Equal(t, schema, so.Properties()) 209 }) 210 211 t.Run("must now be valid", func(t *testing.T) { 212 assert.True(t, so.Valid()) 213 }) 214 215 t.Run("make sure it's identical with an object created from an existing object", 216 func(t *testing.T) { 217 alt := FromObject(&models.Object{ 218 Class: "MyClass", 219 ID: "bf706904-8618-463f-899c-4a2aafd48d56", 220 Properties: map[string]interface{}{ 221 "foo": "bar", 222 }, 223 }, nil, nil) 224 alt.DocID = 13 225 226 assert.Equal(t, so, alt) 227 }) 228 }) 229 230 t.Run("objects", func(t *testing.T) { 231 so := New(12) 232 233 t.Run("check index id", func(t *testing.T) { 234 assert.Equal(t, uint64(12), so.DocID) 235 }) 236 237 t.Run("is invalid without required params", func(t *testing.T) { 238 assert.False(t, so.Valid()) 239 }) 240 241 t.Run("reassign index id", func(t *testing.T) { 242 so.DocID = 13 243 assert.Equal(t, uint64(13), so.DocID) 244 }) 245 246 t.Run("assign class", func(t *testing.T) { 247 so.SetClass("MyClass") 248 assert.Equal(t, schema.ClassName("MyClass"), so.Class()) 249 }) 250 251 t.Run("assign uuid", func(t *testing.T) { 252 id := strfmt.UUID("bf706904-8618-463f-899c-4a2aafd48d56") 253 so.SetID(id) 254 assert.Equal(t, id, so.ID()) 255 }) 256 257 t.Run("assign uuid", func(t *testing.T) { 258 schema := map[string]interface{}{ 259 "foo": "bar", 260 } 261 so.SetProperties(schema) 262 assert.Equal(t, schema, so.Properties()) 263 }) 264 265 t.Run("must now be valid", func(t *testing.T) { 266 assert.True(t, so.Valid()) 267 }) 268 269 t.Run("make sure it's identical with an object created from an existing action", 270 func(t *testing.T) { 271 alt := FromObject(&models.Object{ 272 Class: "MyClass", 273 ID: "bf706904-8618-463f-899c-4a2aafd48d56", 274 Properties: map[string]interface{}{ 275 "foo": "bar", 276 }, 277 }, nil, nil) 278 alt.DocID = 13 279 280 assert.Equal(t, so, alt) 281 }) 282 }) 283 } 284 285 func TestStorageArrayObjectMarshalling(t *testing.T) { 286 before := FromObject( 287 &models.Object{ 288 Class: "MyFavoriteClass", 289 CreationTimeUnix: 123456, 290 LastUpdateTimeUnix: 56789, 291 ID: strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168247"), 292 Additional: models.AdditionalProperties{ 293 "classification": &additional.Classification{ 294 BasedOn: []string{"some", "fields"}, 295 }, 296 "interpretation": map[string]interface{}{ 297 "Source": []interface{}{ 298 map[string]interface{}{ 299 "concept": "foo", 300 "occurrence": float64(7), 301 "weight": float64(3), 302 }, 303 }, 304 }, 305 }, 306 Properties: map[string]interface{}{ 307 "textArray": []string{"c", "d"}, 308 "numberArray": []float64{1.1, 2.1}, 309 "foo": float64(17), 310 }, 311 }, 312 []float32{1, 2, 0.7}, 313 models.Vectors{ 314 "vector1": {1, 2, 3}, 315 "vector2": {4, 5, 6}, 316 "vector3": {7, 8, 9}, 317 }, 318 ) 319 before.DocID = 7 320 321 asBinary, err := before.MarshalBinary() 322 require.Nil(t, err) 323 324 after, err := FromBinary(asBinary) 325 require.Nil(t, err) 326 327 t.Run("compare", func(t *testing.T) { 328 assert.Equal(t, before, after) 329 }) 330 331 t.Run("extract only doc id and compare", func(t *testing.T) { 332 id, err := DocIDFromBinary(asBinary) 333 require.Nil(t, err) 334 assert.Equal(t, uint64(7), id) 335 }) 336 337 t.Run("extract text array prop", func(t *testing.T) { 338 prop, ok, err := ParseAndExtractTextProp(asBinary, "textArray") 339 require.Nil(t, err) 340 require.True(t, ok) 341 assert.Equal(t, []string{"c", "d"}, prop) 342 }) 343 344 t.Run("extract number array prop", func(t *testing.T) { 345 prop, ok, err := ParseAndExtractNumberArrayProp(asBinary, "numberArray") 346 require.Nil(t, err) 347 require.True(t, ok) 348 assert.Equal(t, []float64{1.1, 2.1}, prop) 349 }) 350 } 351 352 func TestExtractionOfSingleProperties(t *testing.T) { 353 expected := map[string]interface{}{ 354 "numberArray": []interface{}{1.1, 2.1}, 355 "intArray": []interface{}{1., 2., 5000.}, 356 "textArrayUTF": []interface{}{"語", "b"}, 357 "textArray": []interface{}{"hello", ",", "I", "am", "a", "veeery", "long", "Array", "with some text."}, 358 "foo": float64(17), 359 "text": "single string", 360 "bool": true, 361 "time": "2011-11-23T01:52:23.000004234Z", 362 "boolArray": []interface{}{true, false, true}, 363 "beacon": []interface{}{map[string]interface{}{"beacon": "weaviate://localhost/SomeClass/3453/73f4eb5f-5abf-447a-81ca-74b1dd168247"}}, 364 "ref": []interface{}{map[string]interface{}{"beacon": "weaviate://localhost/SomeClass/3453/73f4eb5f-5abf-447a-81ca-74b1dd168247"}}, 365 } 366 properties := map[string]interface{}{ 367 "numberArray": []float64{1.1, 2.1}, 368 "intArray": []int32{1, 2, 5000}, 369 "textArrayUTF": []string{"語", "b"}, 370 "textArray": []string{"hello", ",", "I", "am", "a", "veeery", "long", "Array", "with some text."}, 371 "foo": float64(17), 372 "text": "single string", 373 "bool": true, 374 "time": time.Date(2011, 11, 23, 1, 52, 23, 4234, time.UTC), 375 "boolArray": []bool{true, false, true}, 376 "beacon": []map[string]interface{}{{"beacon": "weaviate://localhost/SomeClass/3453/73f4eb5f-5abf-447a-81ca-74b1dd168247"}}, 377 "ref": []models.SingleRef{{Beacon: "weaviate://localhost/SomeClass/3453/73f4eb5f-5abf-447a-81ca-74b1dd168247", Class: "OtherClass", Href: "/v1/f81bfe5e-16ba-4615-a516-46c2ae2e5a80"}}, 378 } 379 before := FromObject( 380 &models.Object{ 381 Class: "MyFavoriteClass", 382 CreationTimeUnix: 123456, 383 LastUpdateTimeUnix: 56789, 384 ID: strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168247"), 385 Properties: properties, 386 }, 387 []float32{1, 2, 0.7}, 388 nil, 389 ) 390 391 before.DocID = 7 392 byteObject, err := before.MarshalBinary() 393 require.Nil(t, err) 394 395 var propertyNames []string 396 var propStrings [][]string 397 for key := range properties { 398 propertyNames = append(propertyNames, key) 399 propStrings = append(propStrings, []string{key}) 400 } 401 402 extractedProperties := map[string]interface{}{} 403 404 // test with reused property map 405 for i := 0; i < 2; i++ { 406 require.Nil(t, UnmarshalPropertiesFromObject(byteObject, &extractedProperties, propertyNames, propStrings)) 407 for key := range expected { 408 require.Equal(t, expected[key], extractedProperties[key]) 409 } 410 411 } 412 } 413 414 func TestStorageObjectMarshallingWithGroup(t *testing.T) { 415 before := FromObject( 416 &models.Object{ 417 Class: "MyFavoriteClass", 418 CreationTimeUnix: 123456, 419 LastUpdateTimeUnix: 56789, 420 ID: strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168247"), 421 Additional: models.AdditionalProperties{ 422 "classification": &additional.Classification{ 423 BasedOn: []string{"some", "fields"}, 424 }, 425 "interpretation": map[string]interface{}{ 426 "Source": []interface{}{ 427 map[string]interface{}{ 428 "concept": "foo", 429 "occurrence": float64(7), 430 "weight": float64(3), 431 }, 432 }, 433 }, 434 "group": &additional.Group{ 435 ID: 100, 436 GroupedBy: &additional.GroupedBy{ 437 Value: "group-by-some-property", 438 Path: []string{"property-path"}, 439 }, 440 MaxDistance: 0.1, 441 MinDistance: 0.2, 442 Count: 200, 443 Hits: []map[string]interface{}{ 444 { 445 "property1": "value1", 446 "_additional": &additional.GroupHitAdditional{ 447 ID: "2c76ca18-2073-4c48-aa52-7f444d2f5b80", 448 Distance: 0.24, 449 }, 450 }, 451 { 452 "property1": "value2", 453 }, 454 }, 455 }, 456 }, 457 Properties: map[string]interface{}{ 458 "name": "MyName", 459 "foo": float64(17), 460 }, 461 }, 462 []float32{1, 2, 0.7}, 463 models.Vectors{ 464 "vector1": {1, 2, 3}, 465 "vector2": {4, 5, 6}, 466 "vector3": {7, 8, 9}, 467 }, 468 ) 469 before.DocID = 7 470 471 asBinary, err := before.MarshalBinary() 472 require.Nil(t, err) 473 474 after, err := FromBinary(asBinary) 475 require.Nil(t, err) 476 477 t.Run("compare", func(t *testing.T) { 478 assert.Equal(t, before, after) 479 }) 480 481 t.Run("extract only doc id and compare", func(t *testing.T) { 482 id, err := DocIDFromBinary(asBinary) 483 require.Nil(t, err) 484 assert.Equal(t, uint64(7), id) 485 }) 486 487 t.Run("extract single text prop", func(t *testing.T) { 488 prop, ok, err := ParseAndExtractTextProp(asBinary, "name") 489 require.Nil(t, err) 490 require.True(t, ok) 491 require.NotEmpty(t, prop) 492 assert.Equal(t, "MyName", prop[0]) 493 }) 494 495 t.Run("extract non-existing text prop", func(t *testing.T) { 496 prop, ok, err := ParseAndExtractTextProp(asBinary, "IDoNotExist") 497 require.Nil(t, err) 498 require.True(t, ok) 499 require.Empty(t, prop) 500 }) 501 502 t.Run("extract group additional property", func(t *testing.T) { 503 require.NotNil(t, after.AdditionalProperties()) 504 require.NotNil(t, after.AdditionalProperties()["group"]) 505 group, ok := after.AdditionalProperties()["group"].(*additional.Group) 506 require.True(t, ok) 507 assert.Equal(t, 100, group.ID) 508 assert.NotNil(t, group.GroupedBy) 509 assert.Equal(t, "group-by-some-property", group.GroupedBy.Value) 510 assert.Equal(t, []string{"property-path"}, group.GroupedBy.Path) 511 assert.Equal(t, 200, group.Count) 512 assert.Equal(t, float32(0.1), group.MaxDistance) 513 assert.Equal(t, float32(0.2), group.MinDistance) 514 require.Len(t, group.Hits, 2) 515 require.NotNil(t, group.Hits[0]["_additional"]) 516 groupHitAdditional, ok := group.Hits[0]["_additional"].(*additional.GroupHitAdditional) 517 require.True(t, ok) 518 assert.Equal(t, strfmt.UUID("2c76ca18-2073-4c48-aa52-7f444d2f5b80"), groupHitAdditional.ID) 519 assert.Equal(t, float32(0.24), groupHitAdditional.Distance) 520 assert.Equal(t, "value1", group.Hits[0]["property1"]) 521 require.Nil(t, group.Hits[1]["_additional"]) 522 assert.Equal(t, "value2", group.Hits[1]["property1"]) 523 }) 524 } 525 526 func TestStorageMaxVectorDimensionsObjectMarshalling(t *testing.T) { 527 generateVector := func(dims uint16) []float32 { 528 vector := make([]float32, dims) 529 for i := range vector { 530 vector[i] = 0.1 531 } 532 return vector 533 } 534 // 65535 is max uint16 number 535 edgeVectorLengths := []uint16{0, 1, 768, 50000, 65535} 536 for _, vectorLength := range edgeVectorLengths { 537 t.Run(fmt.Sprintf("%v vector dimensions", vectorLength), func(t *testing.T) { 538 t.Run("marshal binary", func(t *testing.T) { 539 vector := generateVector(vectorLength) 540 before := FromObject( 541 &models.Object{ 542 Class: "MyFavoriteClass", 543 CreationTimeUnix: 123456, 544 ID: strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168247"), 545 Properties: map[string]interface{}{ 546 "name": "myName", 547 }, 548 }, 549 vector, 550 nil, 551 ) 552 before.DocID = 7 553 554 asBinary, err := before.MarshalBinary() 555 require.Nil(t, err) 556 557 after, err := FromBinary(asBinary) 558 require.Nil(t, err) 559 560 t.Run("compare", func(t *testing.T) { 561 assert.Equal(t, before, after) 562 }) 563 564 t.Run("try to extract a property", func(t *testing.T) { 565 prop, ok, err := ParseAndExtractTextProp(asBinary, "name") 566 require.Nil(t, err) 567 require.True(t, ok) 568 assert.Equal(t, []string{"myName"}, prop) 569 }) 570 }) 571 572 t.Run("marshal optional binary", func(t *testing.T) { 573 vector := generateVector(vectorLength) 574 before := FromObject( 575 &models.Object{ 576 Class: "MyFavoriteClass", 577 CreationTimeUnix: 123456, 578 ID: strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168247"), 579 Properties: map[string]interface{}{ 580 "name": "myName", 581 }, 582 }, 583 vector, 584 nil, 585 ) 586 before.DocID = 7 587 588 asBinary, err := before.MarshalBinary() 589 require.Nil(t, err) 590 591 t.Run("get without additional properties", func(t *testing.T) { 592 after, err := FromBinaryOptional(asBinary, additional.Properties{}) 593 require.Nil(t, err) 594 // modify before to match expectations of after 595 before.Object.Additional = nil 596 before.Vector = nil 597 before.VectorLen = int(vectorLength) 598 assert.Equal(t, before, after) 599 600 assert.Equal(t, before.DocID, after.DocID) 601 602 // The vector length should always be returned (for usage metrics 603 // purposes) even if the vector itself is skipped 604 assert.Equal(t, after.VectorLen, int(vectorLength)) 605 }) 606 607 t.Run("get with additional property vector", func(t *testing.T) { 608 after, err := FromBinaryOptional(asBinary, additional.Properties{Vector: true}) 609 require.Nil(t, err) 610 // modify before to match expectations of after 611 before.Object.Additional = nil 612 before.Vector = vector 613 before.VectorLen = int(vectorLength) 614 assert.Equal(t, before, after) 615 616 assert.Equal(t, before.DocID, after.DocID) 617 618 // The vector length should always be returned (for usage metrics 619 // purposes) even if the vector itself is skipped 620 assert.Equal(t, after.VectorLen, int(vectorLength)) 621 assert.Equal(t, vector, after.Vector) 622 }) 623 }) 624 }) 625 } 626 }