github.com/weaviate/weaviate@v1.24.6/entities/moduletools/vectorizable_props_comparator_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 moduletools 13 14 import ( 15 "testing" 16 "time" 17 18 "github.com/google/uuid" 19 "github.com/stretchr/testify/assert" 20 "github.com/weaviate/weaviate/entities/models" 21 "github.com/weaviate/weaviate/entities/schema" 22 ) 23 24 func TestVectorizablePropsComparator(t *testing.T) { 25 propsSchema := createPropsSchema() 26 nextProps := createNextProps() 27 prevProps := createPrevProps() 28 prevVector := []float32{1, 2, 3} 29 // nil prevVectors 30 var prevVectors models.Vectors 31 32 t.Run("iterator", func(t *testing.T) { 33 comp := NewVectorizablePropsComparator(propsSchema, nextProps, prevProps, prevVector, prevVectors) 34 35 t.Run("returns props in asc order", func(t *testing.T) { 36 expectedNames := []string{"blob", "text", "texts"} 37 names := []string{} 38 39 it := comp.PropsIterator() 40 for propName, _, ok := it.Next(); ok; propName, _, ok = it.Next() { 41 names = append(names, propName) 42 } 43 44 assert.Equal(t, expectedNames, names) 45 }) 46 47 t.Run("returns values of next props", func(t *testing.T) { 48 expectedValues := []interface{}{ 49 nextProps["blob"], 50 nextProps["text"], 51 nextProps["texts"], 52 } 53 values := []interface{}{} 54 55 it := comp.PropsIterator() 56 for _, propValue, ok := it.Next(); ok; _, propValue, ok = it.Next() { 57 values = append(values, propValue) 58 } 59 60 assert.Equal(t, expectedValues, values) 61 }) 62 }) 63 64 t.Run("returns prev vector", func(t *testing.T) { 65 comp := NewVectorizablePropsComparator(propsSchema, nextProps, prevProps, prevVector, prevVectors) 66 67 vector := comp.PrevVector() 68 69 assert.Equal(t, prevVector, vector) 70 }) 71 72 t.Run("considers non-vectorizable props as not changed", func(t *testing.T) { 73 t.Run("all different", func(t *testing.T) { 74 comp := NewVectorizablePropsComparator(propsSchema, nextProps, prevProps, prevVector, prevVectors) 75 76 for _, propSchema := range propsSchema { 77 switch propSchema.Name { 78 case "blob", "text", "texts": 79 assert.True(t, comp.IsChanged(propSchema.Name)) 80 default: 81 assert.False(t, comp.IsChanged(propSchema.Name)) 82 } 83 } 84 }) 85 86 t.Run("next nils", func(t *testing.T) { 87 comp := NewVectorizablePropsComparator(propsSchema, map[string]interface{}{}, prevProps, prevVector, prevVectors) 88 89 for _, propSchema := range propsSchema { 90 switch propSchema.Name { 91 case "blob", "text", "texts": 92 assert.True(t, comp.IsChanged(propSchema.Name)) 93 default: 94 assert.False(t, comp.IsChanged(propSchema.Name)) 95 } 96 } 97 }) 98 99 t.Run("prev nils", func(t *testing.T) { 100 comp := NewVectorizablePropsComparator(propsSchema, nextProps, map[string]interface{}{}, prevVector, prevVectors) 101 102 for _, propSchema := range propsSchema { 103 switch propSchema.Name { 104 case "blob", "text", "texts": 105 assert.True(t, comp.IsChanged(propSchema.Name)) 106 default: 107 assert.False(t, comp.IsChanged(propSchema.Name)) 108 } 109 } 110 }) 111 }) 112 113 t.Run("considers vectorizable props not changed", func(t *testing.T) { 114 missingProps := map[string]interface{}{} 115 nilProps := map[string]interface{}{ 116 "blob": nil, 117 "text": nil, 118 "texts": nil, 119 } 120 emptyArrayProps := map[string]interface{}{ 121 "texts": []string{}, 122 } 123 emptyIfArrayProps := map[string]interface{}{ 124 "texts": []interface{}{}, 125 } 126 127 t.Run("nil -> nil", func(t *testing.T) { 128 comp := NewVectorizablePropsComparator(propsSchema, nilProps, nilProps, prevVector, prevVectors) 129 130 for _, propName := range []string{"blob", "text", "texts"} { 131 assert.False(t, comp.IsChanged(propName)) 132 } 133 }) 134 135 t.Run("missing -> nil", func(t *testing.T) { 136 comp := NewVectorizablePropsComparator(propsSchema, nilProps, missingProps, prevVector, prevVectors) 137 138 for _, propName := range []string{"blob", "text", "texts"} { 139 assert.False(t, comp.IsChanged(propName)) 140 } 141 }) 142 143 t.Run("nil -> missing", func(t *testing.T) { 144 comp := NewVectorizablePropsComparator(propsSchema, missingProps, nilProps, prevVector, prevVectors) 145 146 for _, propName := range []string{"blob", "text", "texts"} { 147 assert.False(t, comp.IsChanged(propName)) 148 } 149 }) 150 151 t.Run("missing -> missing", func(t *testing.T) { 152 comp := NewVectorizablePropsComparator(propsSchema, missingProps, missingProps, prevVector, prevVectors) 153 154 for _, propName := range []string{"blob", "text", "texts"} { 155 assert.False(t, comp.IsChanged(propName)) 156 } 157 }) 158 159 t.Run("missing -> empty array", func(t *testing.T) { 160 comp := NewVectorizablePropsComparator(propsSchema, emptyArrayProps, missingProps, prevVector, prevVectors) 161 162 for _, propName := range []string{"texts"} { 163 assert.False(t, comp.IsChanged(propName)) 164 } 165 }) 166 167 t.Run("nil -> empty array", func(t *testing.T) { 168 comp := NewVectorizablePropsComparator(propsSchema, emptyArrayProps, nilProps, prevVector, prevVectors) 169 170 for _, propName := range []string{"texts"} { 171 assert.False(t, comp.IsChanged(propName)) 172 } 173 }) 174 175 t.Run("empty array -> missing", func(t *testing.T) { 176 comp := NewVectorizablePropsComparator(propsSchema, missingProps, emptyArrayProps, prevVector, prevVectors) 177 178 for _, propName := range []string{"texts"} { 179 assert.False(t, comp.IsChanged(propName)) 180 } 181 }) 182 183 t.Run("empty array -> nil", func(t *testing.T) { 184 comp := NewVectorizablePropsComparator(propsSchema, nilProps, emptyArrayProps, prevVector, prevVectors) 185 186 for _, propName := range []string{"texts"} { 187 assert.False(t, comp.IsChanged(propName)) 188 } 189 }) 190 191 t.Run("empty interface array -> missing", func(t *testing.T) { 192 comp := NewVectorizablePropsComparator(propsSchema, missingProps, emptyIfArrayProps, prevVector, prevVectors) 193 194 for _, propName := range []string{"texts"} { 195 assert.False(t, comp.IsChanged(propName)) 196 } 197 }) 198 199 t.Run("empty interface array -> nil", func(t *testing.T) { 200 comp := NewVectorizablePropsComparator(propsSchema, nilProps, emptyIfArrayProps, prevVector, prevVectors) 201 202 for _, propName := range []string{"texts"} { 203 assert.False(t, comp.IsChanged(propName)) 204 } 205 }) 206 207 t.Run("empty interface array -> empty array", func(t *testing.T) { 208 comp := NewVectorizablePropsComparator(propsSchema, emptyArrayProps, emptyIfArrayProps, prevVector, prevVectors) 209 210 for _, propName := range []string{"texts"} { 211 assert.False(t, comp.IsChanged(propName)) 212 } 213 }) 214 }) 215 216 t.Run("considers vectorizable props changed", func(t *testing.T) { 217 textPropsABC := map[string]interface{}{ 218 "texts": []string{"aaa", "bbb", "ccc"}, 219 } 220 textPropsABCD := map[string]interface{}{ 221 "texts": []string{"aaa", "bbb", "ccc", "ddd"}, 222 } 223 textPropsCBA := map[string]interface{}{ 224 "texts": []string{"ccc", "bbb", "aaa"}, 225 } 226 227 t.Run("different array sizes (1)", func(t *testing.T) { 228 comp := NewVectorizablePropsComparator(propsSchema, textPropsABCD, textPropsABC, prevVector, prevVectors) 229 230 for _, propName := range []string{"texts"} { 231 assert.True(t, comp.IsChanged(propName)) 232 } 233 }) 234 235 t.Run("different array sizes (2)", func(t *testing.T) { 236 comp := NewVectorizablePropsComparator(propsSchema, textPropsABC, textPropsABCD, prevVector, prevVectors) 237 238 for _, propName := range []string{"texts"} { 239 assert.True(t, comp.IsChanged(propName)) 240 } 241 }) 242 243 t.Run("different array order", func(t *testing.T) { 244 comp := NewVectorizablePropsComparator(propsSchema, textPropsCBA, textPropsABC, prevVector, prevVectors) 245 246 for _, propName := range []string{"texts"} { 247 assert.True(t, comp.IsChanged(propName)) 248 } 249 }) 250 251 // none of following should happen 252 253 missingProps := map[string]interface{}{} 254 nilProps := map[string]interface{}{ 255 "texts": nil, 256 } 257 emptyArrayProps := map[string]interface{}{ 258 "texts": []string{}, 259 } 260 emptyIfArrayProps := map[string]interface{}{ 261 "texts": []interface{}{}, 262 } 263 arrayProps := map[string]interface{}{ 264 "texts": []string{"txt1", "txt2"}, 265 } 266 ifArrayProps := map[string]interface{}{ 267 "texts": []interface{}{"txt1", "txt2"}, 268 } 269 270 t.Run("interface array -> array", func(t *testing.T) { 271 comp := NewVectorizablePropsComparator(propsSchema, arrayProps, ifArrayProps, prevVector, prevVectors) 272 273 for _, propName := range []string{"texts"} { 274 assert.True(t, comp.IsChanged(propName)) 275 } 276 }) 277 278 t.Run("array -> interface array", func(t *testing.T) { 279 comp := NewVectorizablePropsComparator(propsSchema, ifArrayProps, arrayProps, prevVector, prevVectors) 280 281 for _, propName := range []string{"texts"} { 282 assert.True(t, comp.IsChanged(propName)) 283 } 284 }) 285 286 t.Run("missing -> empty interface array", func(t *testing.T) { 287 comp := NewVectorizablePropsComparator(propsSchema, emptyIfArrayProps, missingProps, prevVector, prevVectors) 288 289 for _, propName := range []string{"texts"} { 290 assert.True(t, comp.IsChanged(propName)) 291 } 292 }) 293 294 t.Run("nil -> empty interface array", func(t *testing.T) { 295 comp := NewVectorizablePropsComparator(propsSchema, emptyIfArrayProps, nilProps, prevVector, prevVectors) 296 297 for _, propName := range []string{"texts"} { 298 assert.True(t, comp.IsChanged(propName)) 299 } 300 }) 301 302 t.Run("empty array -> empty interface array", func(t *testing.T) { 303 comp := NewVectorizablePropsComparator(propsSchema, emptyIfArrayProps, emptyArrayProps, prevVector, prevVectors) 304 305 for _, propName := range []string{"texts"} { 306 assert.True(t, comp.IsChanged(propName)) 307 } 308 }) 309 }) 310 } 311 312 func TestVectorizablePropsComparatorDummy(t *testing.T) { 313 propsSchema := createPropsSchema() 314 nextProps := createNextProps() 315 316 t.Run("iterator", func(t *testing.T) { 317 comp := NewVectorizablePropsComparatorDummy(propsSchema, nextProps) 318 319 t.Run("returns props in asc order", func(t *testing.T) { 320 expectedNames := []string{"blob", "text", "texts"} 321 names := []string{} 322 323 it := comp.PropsIterator() 324 for propName, _, ok := it.Next(); ok; propName, _, ok = it.Next() { 325 names = append(names, propName) 326 } 327 328 assert.Equal(t, expectedNames, names) 329 }) 330 331 t.Run("returns values of next props", func(t *testing.T) { 332 expectedValues := []interface{}{ 333 nextProps["blob"], 334 nextProps["text"], 335 nextProps["texts"], 336 } 337 values := []interface{}{} 338 339 it := comp.PropsIterator() 340 for _, propValue, ok := it.Next(); ok; _, propValue, ok = it.Next() { 341 values = append(values, propValue) 342 } 343 344 assert.Equal(t, expectedValues, values) 345 }) 346 }) 347 348 t.Run("returns no prev vector", func(t *testing.T) { 349 comp := NewVectorizablePropsComparatorDummy(propsSchema, nextProps) 350 351 vector := comp.PrevVector() 352 353 assert.Nil(t, vector) 354 }) 355 356 t.Run("considers non-vectorizable props as not changed", func(t *testing.T) { 357 comp := NewVectorizablePropsComparatorDummy(propsSchema, nextProps) 358 359 for _, propSchema := range propsSchema { 360 switch propSchema.Name { 361 case "blob", "text", "texts": 362 assert.True(t, comp.IsChanged(propSchema.Name)) 363 default: 364 assert.False(t, comp.IsChanged(propSchema.Name)) 365 } 366 } 367 }) 368 369 t.Run("considers vectorizable props not changed", func(t *testing.T) { 370 t.Run("nil", func(t *testing.T) { 371 comp := NewVectorizablePropsComparatorDummy(propsSchema, map[string]interface{}{ 372 "blob": nil, 373 "text": nil, 374 "texts": nil, 375 }) 376 377 for _, propName := range []string{"blob", "text", "texts"} { 378 assert.False(t, comp.IsChanged(propName)) 379 } 380 }) 381 382 t.Run("missing", func(t *testing.T) { 383 comp := NewVectorizablePropsComparatorDummy(propsSchema, map[string]interface{}{}) 384 385 for _, propName := range []string{"blob", "text", "texts"} { 386 assert.False(t, comp.IsChanged(propName)) 387 } 388 }) 389 390 t.Run("empty array", func(t *testing.T) { 391 comp := NewVectorizablePropsComparatorDummy(propsSchema, map[string]interface{}{ 392 "texts": []string{}, 393 }) 394 395 for _, propName := range []string{"texts"} { 396 assert.False(t, comp.IsChanged(propName)) 397 } 398 }) 399 }) 400 } 401 402 func createPropsSchema() []*models.Property { 403 return []*models.Property{ 404 { 405 Name: "text", 406 DataType: schema.DataTypeText.PropString(), 407 }, 408 { 409 Name: "texts", 410 DataType: schema.DataTypeTextArray.PropString(), 411 }, 412 { 413 Name: "int", 414 DataType: schema.DataTypeInt.PropString(), 415 }, 416 { 417 Name: "ints", 418 DataType: schema.DataTypeIntArray.PropString(), 419 }, 420 { 421 Name: "number", 422 DataType: schema.DataTypeNumber.PropString(), 423 }, 424 { 425 Name: "numbers", 426 DataType: schema.DataTypeNumberArray.PropString(), 427 }, 428 { 429 Name: "boolean", 430 DataType: schema.DataTypeBoolean.PropString(), 431 }, 432 { 433 Name: "booleans", 434 DataType: schema.DataTypeBooleanArray.PropString(), 435 }, 436 { 437 Name: "date", 438 DataType: schema.DataTypeDate.PropString(), 439 }, 440 { 441 Name: "dates", 442 DataType: schema.DataTypeDateArray.PropString(), 443 }, 444 { 445 Name: "uuid", 446 DataType: schema.DataTypeUUID.PropString(), 447 }, 448 { 449 Name: "uuids", 450 DataType: schema.DataTypeUUIDArray.PropString(), 451 }, 452 { 453 Name: "phone", 454 DataType: schema.DataTypePhoneNumber.PropString(), 455 }, 456 { 457 Name: "geo", 458 DataType: schema.DataTypeGeoCoordinates.PropString(), 459 }, 460 { 461 Name: "blob", 462 DataType: schema.DataTypeBlob.PropString(), 463 }, 464 { 465 Name: "object", 466 DataType: schema.DataTypeObject.PropString(), 467 NestedProperties: []*models.NestedProperty{ 468 { 469 Name: "n_text", 470 DataType: schema.DataTypeText.PropString(), 471 }, 472 }, 473 }, 474 { 475 Name: "objects", 476 DataType: schema.DataTypeObjectArray.PropString(), 477 NestedProperties: []*models.NestedProperty{ 478 { 479 Name: "n_text", 480 DataType: schema.DataTypeText.PropString(), 481 }, 482 }, 483 }, 484 } 485 } 486 487 const ( 488 image = "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACrElEQVR4nOyUS0wUeRDGv37N9Dx6GJiF3ckOWUhgQ7LsssthN+xlA9k9bDBelJM3n2AkwYOCXhC9GGLiTeOBxKPxYsLBdwwXHzFRTAg4ikh05DGODE73DP36P0z3BEQT9CAXEyvppCtV9ctX1dUlc86xmSZuKu2rBcqAE/58qRsEIH0KKDIy0UHMgbuOvu1ZGbyhCUTvnKSl3vvMGf/H89cC3ldmLJeizvlTwHQX5xUio3mmRkc8lfZGxOJ8Upcjf2ii9B04a7yghPceEuXvX/lAfbnrlijq7WbpMTh3oYZbaKzyqgd0NgJmZ0I6o44mBZKIxP8Go4l7WuJsm99WoThVbVtpEMqgKHEIAXNtDAU385/D9K2eo4iR0bhSdx0AL7kclDAwaw4OG0co+me117EPLLoMKw4DZYDCKWS3hAU323C7ODa8THKdebYESZBQI9bsD0mRK23R9oOGCxACMAb/RWLEV+ADdZdzL6EMJCiaprRnZuiBy6HmaA6UlkepBFSklOT/ztJYR79B5J8UQBLKNUFG3wMLLmAQIG8DBcJQJ1iYzZkqFRmQ54DOAMphaw7SqdeATIIvSkCWAz+GAVUD1PUK3zjJ7OzbJ79aBFBlCsMqQlrQICwWAFcFZwEf6PeXphAaguBVABWAnBNECrWgdnzRm62/h//+fOZARfi3cUECLMbwvGBDegjImSi0pURxoGX7iaOtXUOxfJUhZcIQbwrIGIAaCKE+UQ+TVE80/dDf40v01sZ7XOrIo1PHj3RfCpq9owFeueMw/71vOD3xcu6X1ZypzHxTa8+xSW3LLr7voswHr9VYN56eHHSpHVjNET4+X4tGuvFO5ly3bTQUOpt3no5FQsb6uLFiRS4/GukLVkwn/qrdPZKMNU9+8At9u4dfbO8CAAD///eAU+2FY+BGAAAAAElFTkSuQmCC" 489 image2 = "iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAACpF6WWAAAC1UlEQVR4nNyUS0h8dRTHv/f+7tz/nTv6d2YaXQgq+KSHaQQRLiQyGmnRRmgRSWRtiswWEkUhGNFGKouiRbRrEURgIWS4SBBFy7JclEzqZGaJr3nd9+8Vd6YmEbGFburAvZtzzufc8z1frialxHWHeu3E/wX0SsPONYsoD2bGqPXEkmC77Zd28v0mbj2+JLxPRgGuXwRVOVtPU3d8VWJtWol03Ct49s7LmIxm2ij9sU/I+XeC4uga879+AIBShXrOuy/R4PW5gG51l3Ifg9INiH/xmhBcWrlvUTycAWXZbuZPz7uF918Nc1r4KhY/TYegwC8AIEjq7dDVKlQRYCnK7aYIif2qQjsBILkEnEBCiCNIax43k30g2vpDUWCiDLUDT7GdU3AOKIoKkzkgupSOsBoyzuprVOQeA0hUgfRU1fyoK9r3criIRQEuAME5iJ+HoQaofqnNpCz8VQAIxAILc3Tx0eXc8ocl4d3yC83CEy5qSK3ReaPjKaO4NtQj3dnbKSBFZR1DMKhC/AMtMSBPASHLA+BZLt4rfvPI90iBwQccF/ApoDMsxTegEyPREvw2/GQeaDGBuA7USA6tMkGWoblAkacUOLABi0ncZjhgeQ2gOrTDEEoAoVaENDhYmwC/AbgM2CwAdTpws06Hwiq7lqERfWAhs7/SX7aDApR8B/R3ArIbChYFhBGeu6IPF5A/c4hWDbIViBAViZpG2KIBBD0LVUsNdE5OprveGIMas2wqsZ33QbMKtD9qYR4nvKn7hke3xt9ufPPBkafNXNwhBybYpop9V0U8loJCEk7SHHrh7ubxF8uihlf8+zksZZvfWkzPPvs5kfe8Mih7npnKfLe913u25oedvTt6RyZ+uuu5fvn8Z5r8YOXhL4+sndazNcpFHs+cfNVvB8f1t9YPzhlarX0+7zPb3Dz6YtCMJE87UvcvnM9fCL1q/Pf/p1eKPwMAAP//lJuHtr5ZxxcAAAAASUVORK5CYII=" 490 ) 491 492 func createPrevProps() map[string]interface{} { 493 return map[string]interface{}{ 494 "text": "prev text", 495 "texts": []string{"prev texts_1", "prev texts_2"}, 496 "int": float64(-1), 497 "ints": []float64{-2, -3}, 498 "number": float64(-1.1), 499 "numbers": []float64{-2.2, -3.3}, 500 "boolean": false, 501 "booleans": []bool{true, true}, 502 "date": time.Unix(1707332399, 0), 503 "dates": []time.Time{time.Unix(1707335999, 0), time.Unix(1707339599, 0)}, 504 "uuid": uuid.MustParse("018d85e5-eec6-71bb-9a0f-f7cb545784c3"), 505 "uuids": []uuid.UUID{ 506 uuid.MustParse("018d85e6-f7c9-74c4-b960-f1fd74bb2d3c"), 507 uuid.MustParse("018d85e7-1b69-7e86-a487-bab1dec5d2f4"), 508 }, 509 "phone": &models.PhoneNumber{ 510 DefaultCountry: "PL", 511 Input: "123456789", 512 }, 513 "geo": &models.GeoCoordinates{ 514 Latitude: ptrFloat32(51.107882), 515 Longitude: ptrFloat32(17.038537), 516 }, 517 "blob": image, 518 "object": map[string]interface{}{ 519 "n_text": "prev n_text", 520 }, 521 "objects": []map[string]interface{}{ 522 {"n_text": "prev n_text_0"}, 523 {"n_text": "prev n_text_1"}, 524 }, 525 } 526 } 527 528 func createNextProps() map[string]interface{} { 529 return map[string]interface{}{ 530 "text": "text", 531 "texts": []string{"texts_1", "texts_2"}, 532 "int": float64(1), 533 "ints": []float64{2, 3}, 534 "number": float64(1.1), 535 "numbers": []float64{2.2, 3.3}, 536 "boolean": true, 537 "booleans": []bool{false, false}, 538 "date": time.Unix(1707332400, 0), 539 "dates": []time.Time{time.Unix(1707336000, 0), time.Unix(1707339600, 0)}, 540 "uuid": uuid.MustParse("a2578e23-f665-4f04-97f1-5e56f5d4cea4"), 541 "uuids": []uuid.UUID{ 542 uuid.MustParse("037a19a2-aeba-40cf-9a6b-60e36f10b030"), 543 uuid.MustParse("6e92f90c-99c9-4579-9711-17015c361e07"), 544 }, 545 "phone": &models.PhoneNumber{ 546 DefaultCountry: "PL", 547 Input: "100000000", 548 }, 549 "geo": &models.GeoCoordinates{ 550 Latitude: ptrFloat32(51.107883), 551 Longitude: ptrFloat32(17.038538), 552 }, 553 "blob": image2, 554 "object": map[string]interface{}{ 555 "n_text": "n_text", 556 }, 557 "objects": []map[string]interface{}{ 558 {"n_text": "n_text_0"}, 559 {"n_text": "n_text_1"}, 560 }, 561 } 562 } 563 564 func ptrFloat32(f float32) *float32 { 565 return &f 566 }