github.com/weaviate/weaviate@v1.24.6/usecases/objects/auto_schema_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 objects 13 14 import ( 15 "context" 16 "encoding/json" 17 "fmt" 18 "reflect" 19 "testing" 20 21 "github.com/go-openapi/strfmt" 22 "github.com/sirupsen/logrus/hooks/test" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/mock" 25 "github.com/stretchr/testify/require" 26 "github.com/weaviate/weaviate/entities/models" 27 "github.com/weaviate/weaviate/entities/schema" 28 "github.com/weaviate/weaviate/entities/schema/test_utils" 29 "github.com/weaviate/weaviate/entities/search" 30 "github.com/weaviate/weaviate/usecases/config" 31 "github.com/weaviate/weaviate/usecases/objects/validation" 32 ) 33 34 func Test_autoSchemaManager_determineType(t *testing.T) { 35 type fields struct { 36 config config.AutoSchema 37 } 38 type args struct { 39 value interface{} 40 } 41 42 autoSchemaEnabledFields := fields{ 43 config: config.AutoSchema{ 44 Enabled: true, 45 }, 46 } 47 48 tests := []struct { 49 name string 50 fields fields 51 args args 52 want []schema.DataType 53 errMsgs []string 54 }{ 55 { 56 name: "fail determining type of nested array (1)", 57 fields: fields{ 58 config: config.AutoSchema{ 59 Enabled: true, 60 DefaultString: schema.DataTypeText.String(), 61 }, 62 }, 63 args: args{ 64 value: []interface{}{[]interface{}{"panic"}}, 65 }, 66 errMsgs: []string{"unrecognized data type"}, 67 }, 68 { 69 name: "fail determining type of nested array (2)", 70 fields: fields{ 71 config: config.AutoSchema{ 72 Enabled: true, 73 DefaultString: schema.DataTypeText.String(), 74 }, 75 }, 76 args: args{ 77 value: []interface{}{[]string{}}, 78 }, 79 errMsgs: []string{"unrecognized data type"}, 80 }, 81 { 82 name: "fail determining type of mixed elements in array", 83 fields: fields{ 84 config: config.AutoSchema{ 85 Enabled: true, 86 DefaultString: schema.DataTypeText.String(), 87 }, 88 }, 89 args: args{ 90 value: []interface{}{"something", false}, 91 }, 92 errMsgs: []string{"mismatched data type", "'text' expected, got 'boolean'"}, 93 }, 94 { 95 name: "fail determining type of mixed refs and objects (1)", 96 fields: fields{ 97 config: config.AutoSchema{ 98 Enabled: true, 99 }, 100 }, 101 args: args{ 102 value: []interface{}{ 103 map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"}, 104 map[string]interface{}{"propOfObject": "something"}, 105 }, 106 }, 107 errMsgs: []string{"mismatched data type", "reference expected, got 'object'"}, 108 }, 109 { 110 name: "fail determining type of mixed refs and objects (2)", 111 fields: fields{ 112 config: config.AutoSchema{ 113 Enabled: true, 114 }, 115 }, 116 args: args{ 117 value: []interface{}{ 118 map[string]interface{}{"propOfObject": "something"}, 119 map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"}, 120 }, 121 }, 122 errMsgs: []string{"mismatched data type", "'object' expected, got reference"}, 123 }, 124 { 125 name: "determine text", 126 fields: fields{ 127 config: config.AutoSchema{ 128 Enabled: true, 129 DefaultString: schema.DataTypeText.String(), 130 }, 131 }, 132 args: args{ 133 value: "string", 134 }, 135 want: []schema.DataType{schema.DataTypeText}, 136 }, 137 { 138 name: "determine text (implicit)", 139 fields: fields{ 140 config: config.AutoSchema{ 141 Enabled: true, 142 }, 143 }, 144 args: args{ 145 value: "string", 146 }, 147 want: []schema.DataType{schema.DataTypeText}, 148 }, 149 { 150 name: "determine date", 151 fields: fields{ 152 config: config.AutoSchema{ 153 Enabled: true, 154 DefaultDate: "date", 155 }, 156 }, 157 args: args{ 158 value: "2002-10-02T15:00:00Z", 159 }, 160 want: []schema.DataType{schema.DataTypeDate}, 161 }, 162 { 163 name: "determine uuid (1)", 164 fields: fields{ 165 config: config.AutoSchema{ 166 Enabled: true, 167 }, 168 }, 169 args: args{ 170 value: "5b2cbe85-c38a-41f7-9e8c-7406ff6d15aa", 171 }, 172 want: []schema.DataType{schema.DataTypeUUID}, 173 }, 174 { 175 name: "determine uuid (2)", 176 fields: fields{ 177 config: config.AutoSchema{ 178 Enabled: true, 179 }, 180 }, 181 args: args{ 182 value: "5b2cbe85c38a41f79e8c7406ff6d15aa", 183 }, 184 want: []schema.DataType{schema.DataTypeUUID}, 185 }, 186 { 187 name: "determine int", 188 fields: fields{ 189 config: config.AutoSchema{ 190 Enabled: true, 191 DefaultNumber: "int", 192 }, 193 }, 194 args: args{ 195 value: json.Number("1"), 196 }, 197 want: []schema.DataType{schema.DataTypeInt}, 198 }, 199 { 200 name: "determine number", 201 fields: fields{ 202 config: config.AutoSchema{ 203 Enabled: true, 204 DefaultNumber: "number", 205 }, 206 }, 207 args: args{ 208 value: json.Number("1"), 209 }, 210 want: []schema.DataType{schema.DataTypeNumber}, 211 }, 212 { 213 name: "determine boolean", 214 fields: fields{ 215 config: config.AutoSchema{ 216 Enabled: true, 217 DefaultNumber: "number", 218 }, 219 }, 220 args: args{ 221 value: true, 222 }, 223 want: []schema.DataType{schema.DataTypeBoolean}, 224 }, 225 { 226 name: "determine geoCoordinates", 227 fields: fields{ 228 config: config.AutoSchema{ 229 Enabled: true, 230 }, 231 }, 232 args: args{ 233 value: map[string]interface{}{ 234 "latitude": json.Number("1.1"), 235 "longitude": json.Number("1.1"), 236 }, 237 }, 238 want: []schema.DataType{schema.DataTypeGeoCoordinates}, 239 }, 240 { 241 name: "determine phoneNumber", 242 fields: fields{ 243 config: config.AutoSchema{ 244 Enabled: true, 245 }, 246 }, 247 args: args{ 248 value: map[string]interface{}{ 249 "input": "020 1234567", 250 }, 251 }, 252 want: []schema.DataType{schema.DataTypePhoneNumber}, 253 }, 254 { 255 name: "determine phoneNumber (2)", 256 fields: fields{ 257 config: config.AutoSchema{ 258 Enabled: true, 259 }, 260 }, 261 args: args{ 262 value: map[string]interface{}{ 263 "input": "020 1234567", 264 "defaultCountry": "nl", 265 }, 266 }, 267 want: []schema.DataType{schema.DataTypePhoneNumber}, 268 }, 269 { 270 name: "determine cross reference", 271 fields: fields{ 272 config: config.AutoSchema{ 273 Enabled: true, 274 }, 275 }, 276 args: args{ 277 value: []interface{}{ 278 map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"}, 279 }, 280 }, 281 want: []schema.DataType{schema.DataType("Publication")}, 282 }, 283 { 284 name: "determine cross references", 285 fields: fields{ 286 config: config.AutoSchema{ 287 Enabled: true, 288 }, 289 }, 290 args: args{ 291 value: []interface{}{ 292 map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"}, 293 map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07391"}, 294 }, 295 }, 296 want: []schema.DataType{schema.DataType("Publication"), schema.DataType("Article")}, 297 }, 298 { 299 name: "determine text array", 300 fields: fields{ 301 config: config.AutoSchema{ 302 Enabled: true, 303 DefaultString: schema.DataTypeText.String(), 304 }, 305 }, 306 args: args{ 307 value: []interface{}{"a", "b"}, 308 }, 309 want: []schema.DataType{schema.DataTypeTextArray}, 310 }, 311 { 312 name: "determine text array (implicit)", 313 fields: fields{ 314 config: config.AutoSchema{ 315 Enabled: true, 316 }, 317 }, 318 args: args{ 319 value: []interface{}{"a", "b"}, 320 }, 321 want: []schema.DataType{schema.DataTypeTextArray}, 322 }, 323 { 324 name: "determine int array", 325 fields: fields{ 326 config: config.AutoSchema{ 327 Enabled: true, 328 DefaultNumber: "int", 329 }, 330 }, 331 args: args{ 332 value: []interface{}{json.Number("11"), json.Number("12")}, 333 }, 334 want: []schema.DataType{schema.DataTypeIntArray}, 335 }, 336 { 337 name: "determine number array", 338 fields: fields{ 339 config: config.AutoSchema{ 340 Enabled: true, 341 DefaultNumber: "number", 342 }, 343 }, 344 args: args{ 345 value: []interface{}{json.Number("1.1"), json.Number("1.2")}, 346 }, 347 want: []schema.DataType{schema.DataTypeNumberArray}, 348 }, 349 { 350 name: "determine boolean array", 351 fields: fields{ 352 config: config.AutoSchema{ 353 Enabled: true, 354 }, 355 }, 356 args: args{ 357 value: []interface{}{true, false}, 358 }, 359 want: []schema.DataType{schema.DataTypeBooleanArray}, 360 }, 361 { 362 name: "determine date array", 363 fields: fields{ 364 config: config.AutoSchema{ 365 Enabled: true, 366 DefaultDate: "date", 367 }, 368 }, 369 args: args{ 370 value: []interface{}{"2002-10-02T15:00:00Z", "2002-10-02T15:01:00Z"}, 371 }, 372 want: []schema.DataType{schema.DataTypeDateArray}, 373 }, 374 { 375 name: "determine uuid array (1)", 376 fields: fields{ 377 config: config.AutoSchema{ 378 Enabled: true, 379 }, 380 }, 381 args: args{ 382 value: []interface{}{ 383 "5b2cbe85-c38a-41f7-9e8c-7406ff6d15aa", 384 "57a8564d-089b-4cd9-be39-56681605e0da", 385 }, 386 }, 387 want: []schema.DataType{schema.DataTypeUUIDArray}, 388 }, 389 { 390 name: "determine uuid array (2)", 391 fields: fields{ 392 config: config.AutoSchema{ 393 Enabled: true, 394 }, 395 }, 396 args: args{ 397 value: []interface{}{ 398 "5b2cbe85c38a41f79e8c7406ff6d15aa", 399 "57a8564d089b4cd9be3956681605e0da", 400 }, 401 }, 402 want: []schema.DataType{schema.DataTypeUUIDArray}, 403 }, 404 { 405 name: "[deprecated string] determine string", 406 fields: fields{ 407 config: config.AutoSchema{ 408 Enabled: true, 409 DefaultString: schema.DataTypeString.String(), 410 }, 411 }, 412 args: args{ 413 value: "string", 414 }, 415 want: []schema.DataType{schema.DataTypeString}, 416 }, 417 { 418 name: "[deprecated string] determine string array", 419 fields: fields{ 420 config: config.AutoSchema{ 421 Enabled: true, 422 DefaultString: schema.DataTypeString.String(), 423 }, 424 }, 425 args: args{ 426 value: []interface{}{"a", "b"}, 427 }, 428 want: []schema.DataType{schema.DataTypeStringArray}, 429 }, 430 { 431 name: "determine object", 432 fields: autoSchemaEnabledFields, 433 args: args{ 434 value: map[string]interface{}{ 435 "some_number": 1.23, 436 "some_bool": false, 437 }, 438 }, 439 want: []schema.DataType{schema.DataTypeObject}, 440 }, 441 { 442 name: "determine object array", 443 fields: autoSchemaEnabledFields, 444 args: args{ 445 value: []interface{}{ 446 map[string]interface{}{ 447 "some_number": 1.23, 448 "some_bool": false, 449 }, 450 }, 451 }, 452 want: []schema.DataType{schema.DataTypeObjectArray}, 453 }, 454 { 455 name: "determine object, not geoCoordinates (too few props 1)", 456 fields: autoSchemaEnabledFields, 457 args: args{ 458 value: map[string]interface{}{ 459 "latitude": json.Number("1.1"), 460 }, 461 }, 462 want: []schema.DataType{schema.DataTypeObject}, 463 }, 464 { 465 name: "determine object, not geoCoordinates (too few props 2)", 466 fields: autoSchemaEnabledFields, 467 args: args{ 468 value: map[string]interface{}{ 469 "longitude": json.Number("1.1"), 470 }, 471 }, 472 want: []schema.DataType{schema.DataTypeObject}, 473 }, 474 { 475 name: "determine object, not geoCoordinates (too many props)", 476 fields: autoSchemaEnabledFields, 477 args: args{ 478 value: map[string]interface{}{ 479 "latitude": json.Number("1.1"), 480 "longitude": json.Number("1.1"), 481 "unrelevant": "some text", 482 }, 483 }, 484 want: []schema.DataType{schema.DataTypeObject}, 485 }, 486 { 487 name: "determine object, not phoneNumber (too few props)", 488 fields: autoSchemaEnabledFields, 489 args: args{ 490 value: map[string]interface{}{ 491 "defaultCountry": "nl", 492 }, 493 }, 494 want: []schema.DataType{schema.DataTypeObject}, 495 }, 496 { 497 name: "determine object, not phoneNumber (too many props)", 498 fields: autoSchemaEnabledFields, 499 args: args{ 500 value: map[string]interface{}{ 501 "input": "020 1234567", 502 "defaultCountry": "nl", 503 "internationalFormatted": "+31 20 1234567", 504 "countryCode": 31, 505 "national": 201234567, 506 "nationalFormatted": "020 1234567", 507 "valid": true, 508 }, 509 }, 510 want: []schema.DataType{schema.DataTypeObject}, 511 }, 512 } 513 for _, tt := range tests { 514 vectorRepo := &fakeVectorRepo{} 515 vectorRepo.On("ObjectByID", strfmt.UUID("df48b9f6-ba48-470c-bf6a-57657cb07390"), mock.Anything, mock.Anything, mock.Anything). 516 Return(&search.Result{ClassName: "Publication"}, nil).Once() 517 vectorRepo.On("ObjectByID", strfmt.UUID("df48b9f6-ba48-470c-bf6a-57657cb07391"), mock.Anything, mock.Anything, mock.Anything). 518 Return(&search.Result{ClassName: "Article"}, nil).Once() 519 m := &autoSchemaManager{ 520 schemaManager: &fakeSchemaManager{}, 521 vectorRepo: vectorRepo, 522 config: tt.fields.config, 523 } 524 t.Run(tt.name, func(t *testing.T) { 525 got, err := m.determineType(tt.args.value, false) 526 if len(tt.errMsgs) == 0 { 527 require.NoError(t, err) 528 if !reflect.DeepEqual(got, tt.want) { 529 t.Errorf("autoSchemaManager.determineType() = %v, want %v", got, tt.want) 530 } 531 } else { 532 for _, errMsg := range tt.errMsgs { 533 require.ErrorContains(t, err, errMsg) 534 } 535 assert.Nil(t, got) 536 } 537 }) 538 } 539 } 540 541 func Test_autoSchemaManager_autoSchema_emptyRequest(t *testing.T) { 542 // given 543 vectorRepo := &fakeVectorRepo{} 544 vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything). 545 Return(&search.Result{ClassName: "Publication"}, nil).Once() 546 schemaManager := &fakeSchemaManager{} 547 logger, _ := test.NewNullLogger() 548 autoSchemaManager := &autoSchemaManager{ 549 schemaManager: schemaManager, 550 vectorRepo: vectorRepo, 551 config: config.AutoSchema{ 552 Enabled: true, 553 DefaultString: schema.DataTypeText.String(), 554 DefaultNumber: "number", 555 DefaultDate: "date", 556 }, 557 logger: logger, 558 } 559 560 var obj *models.Object 561 562 err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true) 563 assert.EqualError(t, fmt.Errorf(validation.ErrorMissingObject), err.Error()) 564 } 565 566 func Test_autoSchemaManager_autoSchema_create(t *testing.T) { 567 // given 568 vectorRepo := &fakeVectorRepo{} 569 vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything). 570 Return(&search.Result{ClassName: "Publication"}, nil).Once() 571 schemaManager := &fakeSchemaManager{} 572 logger, _ := test.NewNullLogger() 573 autoSchemaManager := &autoSchemaManager{ 574 schemaManager: schemaManager, 575 vectorRepo: vectorRepo, 576 config: config.AutoSchema{ 577 Enabled: true, 578 DefaultString: schema.DataTypeText.String(), 579 DefaultNumber: "number", 580 DefaultDate: "date", 581 }, 582 logger: logger, 583 } 584 obj := &models.Object{ 585 Class: "Publication", 586 Properties: map[string]interface{}{ 587 "name": "Jodie Sparrow", 588 "age": json.Number("30"), 589 "publicationDate": "2002-10-02T15:00:00Z", 590 "textArray": []interface{}{"a", "b"}, 591 "numberArray": []interface{}{json.Number("30")}, 592 }, 593 } 594 // when 595 schemaBefore := schemaManager.GetSchemaResponse 596 err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true) 597 schemaAfter := schemaManager.GetSchemaResponse 598 599 // then 600 require.Nil(t, schemaBefore.Objects) 601 require.Nil(t, err) 602 require.NotNil(t, schemaAfter.Objects) 603 assert.Equal(t, 1, len(schemaAfter.Objects.Classes)) 604 assert.Equal(t, "Publication", (schemaAfter.Objects.Classes)[0].Class) 605 assert.Equal(t, 5, len((schemaAfter.Objects.Classes)[0].Properties)) 606 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "name")) 607 assert.Equal(t, "name", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").Name) 608 assert.Equal(t, "text", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").DataType[0]) 609 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "age")) 610 assert.Equal(t, "age", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").Name) 611 assert.Equal(t, "number", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").DataType[0]) 612 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate")) 613 assert.Equal(t, "publicationDate", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").Name) 614 assert.Equal(t, "date", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").DataType[0]) 615 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray")) 616 assert.Equal(t, "textArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").Name) 617 assert.Equal(t, "text[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").DataType[0]) 618 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray")) 619 assert.Equal(t, "numberArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").Name) 620 assert.Equal(t, "number[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").DataType[0]) 621 } 622 623 func Test_autoSchemaManager_autoSchema_update(t *testing.T) { 624 // given 625 vectorRepo := &fakeVectorRepo{} 626 vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything). 627 Return(&search.Result{ClassName: "Publication"}, nil).Once() 628 logger, _ := test.NewNullLogger() 629 schemaManager := &fakeSchemaManager{ 630 GetSchemaResponse: schema.Schema{ 631 Objects: &models.Schema{ 632 Classes: []*models.Class{ 633 { 634 Class: "Publication", 635 Properties: []*models.Property{ 636 { 637 Name: "age", 638 DataType: []string{"int"}, 639 }, 640 }, 641 }, 642 }, 643 }, 644 }, 645 } 646 autoSchemaManager := &autoSchemaManager{ 647 schemaManager: schemaManager, 648 vectorRepo: vectorRepo, 649 config: config.AutoSchema{ 650 Enabled: true, 651 DefaultString: schema.DataTypeText.String(), 652 DefaultNumber: "int", 653 DefaultDate: "date", 654 }, 655 logger: logger, 656 } 657 obj := &models.Object{ 658 Class: "Publication", 659 Properties: map[string]interface{}{ 660 "name": "Jodie Sparrow", 661 "age": json.Number("30"), 662 "publicationDate": "2002-10-02T15:00:00Z", 663 "textArray": []interface{}{"a", "b"}, 664 "numberArray": []interface{}{json.Number("30")}, 665 }, 666 } 667 // when 668 // then 669 schemaBefore := schemaManager.GetSchemaResponse 670 require.NotNil(t, schemaBefore.Objects) 671 assert.Equal(t, 1, len(schemaBefore.Objects.Classes)) 672 assert.Equal(t, "Publication", (schemaBefore.Objects.Classes)[0].Class) 673 assert.Equal(t, 1, len((schemaBefore.Objects.Classes)[0].Properties)) 674 assert.Equal(t, "age", (schemaBefore.Objects.Classes)[0].Properties[0].Name) 675 assert.Equal(t, "int", (schemaBefore.Objects.Classes)[0].Properties[0].DataType[0]) 676 677 err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true) 678 require.Nil(t, err) 679 680 schemaAfter := schemaManager.GetSchemaResponse 681 require.NotNil(t, schemaAfter.Objects) 682 assert.Equal(t, 1, len(schemaAfter.Objects.Classes)) 683 assert.Equal(t, "Publication", (schemaAfter.Objects.Classes)[0].Class) 684 assert.Equal(t, 5, len((schemaAfter.Objects.Classes)[0].Properties)) 685 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "age")) 686 assert.Equal(t, "age", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").Name) 687 assert.Equal(t, "int", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").DataType[0]) 688 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "name")) 689 assert.Equal(t, "name", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").Name) 690 assert.Equal(t, "text", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").DataType[0]) 691 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate")) 692 assert.Equal(t, "publicationDate", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").Name) 693 assert.Equal(t, "date", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").DataType[0]) 694 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray")) 695 assert.Equal(t, "textArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").Name) 696 assert.Equal(t, "text[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").DataType[0]) 697 require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray")) 698 assert.Equal(t, "numberArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").Name) 699 assert.Equal(t, "int[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").DataType[0]) 700 } 701 702 func Test_autoSchemaManager_getProperties(t *testing.T) { 703 type testCase struct { 704 name string 705 valProperties map[string]interface{} 706 expectedProperties []*models.Property 707 } 708 709 testCases := []testCase{ 710 { 711 name: "mixed 1", 712 valProperties: map[string]interface{}{ 713 "name": "someName", 714 "objectProperty": map[string]interface{}{ 715 "nested_int": json.Number("123"), 716 "nested_text": "some text", 717 "nested_objects": []interface{}{ 718 map[string]interface{}{ 719 "nested_bool_lvl2": false, 720 "nested_numbers_lvl2": []interface{}{ 721 json.Number("11.11"), 722 }, 723 }, 724 }, 725 }, 726 }, 727 expectedProperties: []*models.Property{ 728 { 729 Name: "name", 730 DataType: schema.DataTypeText.PropString(), 731 }, 732 { 733 Name: "objectProperty", 734 DataType: schema.DataTypeObject.PropString(), 735 NestedProperties: []*models.NestedProperty{ 736 { 737 Name: "nested_int", 738 DataType: schema.DataTypeNumber.PropString(), 739 }, 740 { 741 Name: "nested_text", 742 DataType: schema.DataTypeText.PropString(), 743 }, 744 { 745 Name: "nested_objects", 746 DataType: schema.DataTypeObjectArray.PropString(), 747 NestedProperties: []*models.NestedProperty{ 748 { 749 Name: "nested_bool_lvl2", 750 DataType: schema.DataTypeBoolean.PropString(), 751 }, 752 { 753 Name: "nested_numbers_lvl2", 754 DataType: schema.DataTypeNumberArray.PropString(), 755 }, 756 }, 757 }, 758 }, 759 }, 760 }, 761 }, 762 { 763 name: "mixed 2", 764 valProperties: map[string]interface{}{ 765 "name": "someName", 766 "objectProperty": map[string]interface{}{ 767 "nested_number": json.Number("123"), 768 "nested_text": "some text", 769 "nested_objects": []interface{}{ 770 map[string]interface{}{ 771 "nested_date_lvl2": "2022-01-01T00:00:00+02:00", 772 "nested_numbers_lvl2": []interface{}{ 773 json.Number("11.11"), 774 }, 775 }, 776 }, 777 }, 778 }, 779 expectedProperties: []*models.Property{ 780 { 781 Name: "name", 782 DataType: schema.DataTypeText.PropString(), 783 }, 784 { 785 Name: "objectProperty", 786 DataType: schema.DataTypeObject.PropString(), 787 NestedProperties: []*models.NestedProperty{ 788 { 789 Name: "nested_number", 790 DataType: schema.DataTypeNumber.PropString(), 791 }, 792 { 793 Name: "nested_text", 794 DataType: schema.DataTypeText.PropString(), 795 }, 796 { 797 Name: "nested_objects", 798 DataType: schema.DataTypeObjectArray.PropString(), 799 NestedProperties: []*models.NestedProperty{ 800 { 801 Name: "nested_date_lvl2", 802 DataType: schema.DataTypeDate.PropString(), 803 }, 804 { 805 Name: "nested_numbers_lvl2", 806 DataType: schema.DataTypeNumberArray.PropString(), 807 }, 808 }, 809 }, 810 }, 811 }, 812 }, 813 }, 814 { 815 name: "ref", 816 valProperties: map[string]interface{}{ 817 "name": "someName", 818 "objectProperty": map[string]interface{}{ 819 "nested_ref_wannabe": []interface{}{ 820 map[string]interface{}{ 821 "beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825", 822 }, 823 }, 824 "nested_objects": []interface{}{ 825 map[string]interface{}{ 826 "nested_ref_wannabe_lvl2": []interface{}{ 827 map[string]interface{}{ 828 "beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825", 829 }, 830 }, 831 }, 832 }, 833 }, 834 "ref": []interface{}{ 835 map[string]interface{}{ 836 "beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825", 837 }, 838 }, 839 }, 840 expectedProperties: []*models.Property{ 841 { 842 Name: "name", 843 DataType: schema.DataTypeText.PropString(), 844 }, 845 { 846 Name: "objectProperty", 847 DataType: schema.DataTypeObject.PropString(), 848 NestedProperties: []*models.NestedProperty{ 849 { 850 Name: "nested_ref_wannabe", 851 DataType: schema.DataTypeObjectArray.PropString(), 852 NestedProperties: []*models.NestedProperty{ 853 { 854 Name: "beacon", 855 DataType: schema.DataTypeText.PropString(), 856 }, 857 }, 858 }, 859 { 860 Name: "nested_objects", 861 DataType: schema.DataTypeObjectArray.PropString(), 862 NestedProperties: []*models.NestedProperty{ 863 { 864 Name: "nested_ref_wannabe_lvl2", 865 DataType: schema.DataTypeObjectArray.PropString(), 866 NestedProperties: []*models.NestedProperty{ 867 { 868 Name: "beacon", 869 DataType: schema.DataTypeText.PropString(), 870 }, 871 }, 872 }, 873 }, 874 }, 875 }, 876 }, 877 { 878 Name: "ref", 879 DataType: []string{"Soup"}, 880 }, 881 }, 882 }, 883 { 884 name: "phone", 885 valProperties: map[string]interface{}{ 886 "name": "someName", 887 "objectProperty": map[string]interface{}{ 888 "nested_phone_wannabe": map[string]interface{}{ 889 "input": "020 1234567", 890 "defaultCountry": "nl", 891 }, 892 "nested_phone_wannabes": []interface{}{ 893 map[string]interface{}{ 894 "input": "020 1234567", 895 "defaultCountry": "nl", 896 }, 897 }, 898 "nested_objects": []interface{}{ 899 map[string]interface{}{ 900 "nested_phone_wannabe_lvl2": map[string]interface{}{ 901 "input": "020 1234567", 902 "defaultCountry": "nl", 903 }, 904 "nested_phone_wannabes_lvl2": []interface{}{ 905 map[string]interface{}{ 906 "input": "020 1234567", 907 "defaultCountry": "nl", 908 }, 909 }, 910 }, 911 }, 912 }, 913 "phone": map[string]interface{}{ 914 "input": "020 1234567", 915 "defaultCountry": "nl", 916 }, 917 "phone_wannabes": []interface{}{ 918 map[string]interface{}{ 919 "input": "020 1234567", 920 "defaultCountry": "nl", 921 }, 922 }, 923 }, 924 expectedProperties: []*models.Property{ 925 { 926 Name: "name", 927 DataType: schema.DataTypeText.PropString(), 928 }, 929 { 930 Name: "objectProperty", 931 DataType: schema.DataTypeObject.PropString(), 932 NestedProperties: []*models.NestedProperty{ 933 { 934 Name: "nested_phone_wannabe", 935 DataType: schema.DataTypeObject.PropString(), 936 NestedProperties: []*models.NestedProperty{ 937 { 938 Name: "input", 939 DataType: schema.DataTypeText.PropString(), 940 }, 941 { 942 Name: "defaultCountry", 943 DataType: schema.DataTypeText.PropString(), 944 }, 945 }, 946 }, 947 { 948 Name: "nested_phone_wannabes", 949 DataType: schema.DataTypeObjectArray.PropString(), 950 NestedProperties: []*models.NestedProperty{ 951 { 952 Name: "input", 953 DataType: schema.DataTypeText.PropString(), 954 }, 955 { 956 Name: "defaultCountry", 957 DataType: schema.DataTypeText.PropString(), 958 }, 959 }, 960 }, 961 { 962 Name: "nested_objects", 963 DataType: schema.DataTypeObjectArray.PropString(), 964 NestedProperties: []*models.NestedProperty{ 965 { 966 Name: "nested_phone_wannabe_lvl2", 967 DataType: schema.DataTypeObject.PropString(), 968 NestedProperties: []*models.NestedProperty{ 969 { 970 Name: "input", 971 DataType: schema.DataTypeText.PropString(), 972 }, 973 { 974 Name: "defaultCountry", 975 DataType: schema.DataTypeText.PropString(), 976 }, 977 }, 978 }, 979 { 980 Name: "nested_phone_wannabes_lvl2", 981 DataType: schema.DataTypeObjectArray.PropString(), 982 NestedProperties: []*models.NestedProperty{ 983 { 984 Name: "input", 985 DataType: schema.DataTypeText.PropString(), 986 }, 987 { 988 Name: "defaultCountry", 989 DataType: schema.DataTypeText.PropString(), 990 }, 991 }, 992 }, 993 }, 994 }, 995 }, 996 }, 997 { 998 Name: "phone", 999 DataType: schema.DataTypePhoneNumber.PropString(), 1000 }, 1001 { 1002 Name: "phone_wannabes", 1003 DataType: schema.DataTypeObjectArray.PropString(), 1004 NestedProperties: []*models.NestedProperty{ 1005 { 1006 Name: "input", 1007 DataType: schema.DataTypeText.PropString(), 1008 }, 1009 { 1010 Name: "defaultCountry", 1011 DataType: schema.DataTypeText.PropString(), 1012 }, 1013 }, 1014 }, 1015 }, 1016 }, 1017 { 1018 name: "geo", 1019 valProperties: map[string]interface{}{ 1020 "name": "someName", 1021 "objectProperty": map[string]interface{}{ 1022 "nested_geo_wannabe": map[string]interface{}{ 1023 "latitude": json.Number("1.1"), 1024 "longitude": json.Number("2.2"), 1025 }, 1026 "nested_geo_wannabes": []interface{}{ 1027 map[string]interface{}{ 1028 "latitude": json.Number("1.1"), 1029 "longitude": json.Number("2.2"), 1030 }, 1031 }, 1032 "nested_objects": []interface{}{ 1033 map[string]interface{}{ 1034 "nested_geo_wannabe_lvl2": map[string]interface{}{ 1035 "latitude": json.Number("1.1"), 1036 "longitude": json.Number("2.2"), 1037 }, 1038 "nested_geo_wannabes_lvl2": []interface{}{ 1039 map[string]interface{}{ 1040 "latitude": json.Number("1.1"), 1041 "longitude": json.Number("2.2"), 1042 }, 1043 }, 1044 }, 1045 }, 1046 }, 1047 "geo": map[string]interface{}{ 1048 "latitude": json.Number("1.1"), 1049 "longitude": json.Number("2.2"), 1050 }, 1051 "geo_wannabes": []interface{}{ 1052 map[string]interface{}{ 1053 "latitude": json.Number("1.1"), 1054 "longitude": json.Number("2.2"), 1055 }, 1056 }, 1057 }, 1058 expectedProperties: []*models.Property{ 1059 { 1060 Name: "name", 1061 DataType: schema.DataTypeText.PropString(), 1062 }, 1063 { 1064 Name: "objectProperty", 1065 DataType: schema.DataTypeObject.PropString(), 1066 NestedProperties: []*models.NestedProperty{ 1067 { 1068 Name: "nested_geo_wannabe", 1069 DataType: schema.DataTypeObject.PropString(), 1070 NestedProperties: []*models.NestedProperty{ 1071 { 1072 Name: "latitude", 1073 DataType: schema.DataTypeNumber.PropString(), 1074 }, 1075 { 1076 Name: "longitude", 1077 DataType: schema.DataTypeNumber.PropString(), 1078 }, 1079 }, 1080 }, 1081 { 1082 Name: "nested_geo_wannabes", 1083 DataType: schema.DataTypeObjectArray.PropString(), 1084 NestedProperties: []*models.NestedProperty{ 1085 { 1086 Name: "latitude", 1087 DataType: schema.DataTypeNumber.PropString(), 1088 }, 1089 { 1090 Name: "longitude", 1091 DataType: schema.DataTypeNumber.PropString(), 1092 }, 1093 }, 1094 }, 1095 { 1096 Name: "nested_objects", 1097 DataType: schema.DataTypeObjectArray.PropString(), 1098 NestedProperties: []*models.NestedProperty{ 1099 { 1100 Name: "nested_geo_wannabe_lvl2", 1101 DataType: schema.DataTypeObject.PropString(), 1102 NestedProperties: []*models.NestedProperty{ 1103 { 1104 Name: "latitude", 1105 DataType: schema.DataTypeNumber.PropString(), 1106 }, 1107 { 1108 Name: "longitude", 1109 DataType: schema.DataTypeNumber.PropString(), 1110 }, 1111 }, 1112 }, 1113 { 1114 Name: "nested_geo_wannabes_lvl2", 1115 DataType: schema.DataTypeObjectArray.PropString(), 1116 NestedProperties: []*models.NestedProperty{ 1117 { 1118 Name: "latitude", 1119 DataType: schema.DataTypeNumber.PropString(), 1120 }, 1121 { 1122 Name: "longitude", 1123 DataType: schema.DataTypeNumber.PropString(), 1124 }, 1125 }, 1126 }, 1127 }, 1128 }, 1129 }, 1130 }, 1131 { 1132 Name: "geo", 1133 DataType: schema.DataTypeGeoCoordinates.PropString(), 1134 }, 1135 { 1136 Name: "geo_wannabes", 1137 DataType: schema.DataTypeObjectArray.PropString(), 1138 NestedProperties: []*models.NestedProperty{ 1139 { 1140 Name: "latitude", 1141 DataType: schema.DataTypeNumber.PropString(), 1142 }, 1143 { 1144 Name: "longitude", 1145 DataType: schema.DataTypeNumber.PropString(), 1146 }, 1147 }, 1148 }, 1149 }, 1150 }, 1151 } 1152 1153 manager := &autoSchemaManager{ 1154 schemaManager: &fakeSchemaManager{}, 1155 vectorRepo: &fakeVectorRepo{}, 1156 config: config.AutoSchema{ 1157 Enabled: true, 1158 DefaultNumber: schema.DataTypeNumber.String(), 1159 DefaultString: schema.DataTypeText.String(), 1160 DefaultDate: schema.DataTypeDate.String(), 1161 }, 1162 } 1163 1164 for i, tc := range testCases { 1165 t.Run(fmt.Sprintf("testCase_%d", i), func(t *testing.T) { 1166 properties, _ := manager.getProperties(&models.Object{ 1167 Class: "ClassWithObjectProps", 1168 Properties: tc.valProperties, 1169 }) 1170 1171 assertPropsMatch(t, tc.expectedProperties, properties) 1172 }) 1173 } 1174 } 1175 1176 func Test_autoSchemaManager_perform_withNested(t *testing.T) { 1177 logger, _ := test.NewNullLogger() 1178 className := "ClassWithObjectProps" 1179 1180 class := &models.Class{ 1181 Class: className, 1182 Properties: []*models.Property{ 1183 { 1184 Name: "name", 1185 DataType: schema.DataTypeText.PropString(), 1186 }, 1187 { 1188 Name: "objectProperty", 1189 DataType: schema.DataTypeObject.PropString(), 1190 NestedProperties: []*models.NestedProperty{ 1191 { 1192 Name: "nested_int", 1193 DataType: schema.DataTypeNumber.PropString(), 1194 }, 1195 { 1196 Name: "nested_text", 1197 DataType: schema.DataTypeText.PropString(), 1198 }, 1199 { 1200 Name: "nested_objects", 1201 DataType: schema.DataTypeObjectArray.PropString(), 1202 NestedProperties: []*models.NestedProperty{ 1203 { 1204 Name: "nested_bool_lvl2", 1205 DataType: schema.DataTypeBoolean.PropString(), 1206 }, 1207 { 1208 Name: "nested_numbers_lvl2", 1209 DataType: schema.DataTypeNumberArray.PropString(), 1210 }, 1211 }, 1212 }, 1213 }, 1214 }, 1215 }, 1216 } 1217 object := &models.Object{ 1218 Class: className, 1219 Properties: map[string]interface{}{ 1220 "name": "someName", 1221 "objectProperty": map[string]interface{}{ 1222 "nested_number": json.Number("123"), 1223 "nested_text": "some text", 1224 "nested_objects": []interface{}{ 1225 map[string]interface{}{ 1226 "nested_date_lvl2": "2022-01-01T00:00:00+02:00", 1227 "nested_numbers_lvl2": []interface{}{ 1228 json.Number("11.11"), 1229 }, 1230 "nested_phone_wannabe_lvl2": map[string]interface{}{ 1231 "input": "020 1234567", 1232 "defaultCountry": "nl", 1233 }, 1234 "nested_phone_wannabes_lvl2": []interface{}{ 1235 map[string]interface{}{ 1236 "input": "020 1234567", 1237 "defaultCountry": "nl", 1238 }, 1239 }, 1240 }, 1241 }, 1242 "nested_phone_wannabe": map[string]interface{}{ 1243 "input": "020 1234567", 1244 "defaultCountry": "nl", 1245 }, 1246 "nested_phone_wannabes": []interface{}{ 1247 map[string]interface{}{ 1248 "input": "020 1234567", 1249 "defaultCountry": "nl", 1250 }, 1251 }, 1252 }, 1253 "phone": map[string]interface{}{ 1254 "input": "020 1234567", 1255 "defaultCountry": "nl", 1256 }, 1257 "phone_wannabes": []interface{}{ 1258 map[string]interface{}{ 1259 "input": "020 1234567", 1260 "defaultCountry": "nl", 1261 }, 1262 }, 1263 "objectPropertyGeo": map[string]interface{}{ 1264 "nested_objects": []interface{}{ 1265 map[string]interface{}{ 1266 "nested_geo_wannabe_lvl2": map[string]interface{}{ 1267 "latitude": json.Number("1.1"), 1268 "longitude": json.Number("2.2"), 1269 }, 1270 "nested_geo_wannabes_lvl2": []interface{}{ 1271 map[string]interface{}{ 1272 "latitude": json.Number("1.1"), 1273 "longitude": json.Number("2.2"), 1274 }, 1275 }, 1276 }, 1277 }, 1278 "nested_geo_wannabe": map[string]interface{}{ 1279 "latitude": json.Number("1.1"), 1280 "longitude": json.Number("2.2"), 1281 }, 1282 "nested_geo_wannabes": []interface{}{ 1283 map[string]interface{}{ 1284 "latitude": json.Number("1.1"), 1285 "longitude": json.Number("2.2"), 1286 }, 1287 }, 1288 }, 1289 "geo": map[string]interface{}{ 1290 "latitude": json.Number("1.1"), 1291 "longitude": json.Number("2.2"), 1292 }, 1293 "geo_wannabes": []interface{}{ 1294 map[string]interface{}{ 1295 "latitude": json.Number("1.1"), 1296 "longitude": json.Number("2.2"), 1297 }, 1298 }, 1299 }, 1300 } 1301 expectedClass := &models.Class{ 1302 Class: className, 1303 Properties: []*models.Property{ 1304 { 1305 Name: "name", 1306 DataType: schema.DataTypeText.PropString(), 1307 }, 1308 { 1309 Name: "objectProperty", 1310 DataType: schema.DataTypeObject.PropString(), 1311 NestedProperties: []*models.NestedProperty{ 1312 { 1313 Name: "nested_int", 1314 DataType: schema.DataTypeNumber.PropString(), 1315 }, 1316 { 1317 Name: "nested_number", 1318 DataType: schema.DataTypeNumber.PropString(), 1319 }, 1320 { 1321 Name: "nested_text", 1322 DataType: schema.DataTypeText.PropString(), 1323 }, 1324 { 1325 Name: "nested_phone_wannabe", 1326 DataType: schema.DataTypeObject.PropString(), 1327 NestedProperties: []*models.NestedProperty{ 1328 { 1329 Name: "input", 1330 DataType: schema.DataTypeText.PropString(), 1331 }, 1332 { 1333 Name: "defaultCountry", 1334 DataType: schema.DataTypeText.PropString(), 1335 }, 1336 }, 1337 }, 1338 { 1339 Name: "nested_phone_wannabes", 1340 DataType: schema.DataTypeObjectArray.PropString(), 1341 NestedProperties: []*models.NestedProperty{ 1342 { 1343 Name: "input", 1344 DataType: schema.DataTypeText.PropString(), 1345 }, 1346 { 1347 Name: "defaultCountry", 1348 DataType: schema.DataTypeText.PropString(), 1349 }, 1350 }, 1351 }, 1352 { 1353 Name: "nested_objects", 1354 DataType: schema.DataTypeObjectArray.PropString(), 1355 NestedProperties: []*models.NestedProperty{ 1356 { 1357 Name: "nested_bool_lvl2", 1358 DataType: schema.DataTypeBoolean.PropString(), 1359 }, 1360 { 1361 Name: "nested_date_lvl2", 1362 DataType: schema.DataTypeDate.PropString(), 1363 }, 1364 { 1365 Name: "nested_numbers_lvl2", 1366 DataType: schema.DataTypeNumberArray.PropString(), 1367 }, 1368 { 1369 Name: "nested_phone_wannabe_lvl2", 1370 DataType: schema.DataTypeObject.PropString(), 1371 NestedProperties: []*models.NestedProperty{ 1372 { 1373 Name: "input", 1374 DataType: schema.DataTypeText.PropString(), 1375 }, 1376 { 1377 Name: "defaultCountry", 1378 DataType: schema.DataTypeText.PropString(), 1379 }, 1380 }, 1381 }, 1382 { 1383 Name: "nested_phone_wannabes_lvl2", 1384 DataType: schema.DataTypeObjectArray.PropString(), 1385 NestedProperties: []*models.NestedProperty{ 1386 { 1387 Name: "input", 1388 DataType: schema.DataTypeText.PropString(), 1389 }, 1390 { 1391 Name: "defaultCountry", 1392 DataType: schema.DataTypeText.PropString(), 1393 }, 1394 }, 1395 }, 1396 }, 1397 }, 1398 }, 1399 }, 1400 { 1401 Name: "phone", 1402 DataType: schema.DataTypePhoneNumber.PropString(), 1403 }, 1404 { 1405 Name: "phone_wannabes", 1406 DataType: schema.DataTypeObjectArray.PropString(), 1407 NestedProperties: []*models.NestedProperty{ 1408 { 1409 Name: "input", 1410 DataType: schema.DataTypeText.PropString(), 1411 }, 1412 { 1413 Name: "defaultCountry", 1414 DataType: schema.DataTypeText.PropString(), 1415 }, 1416 }, 1417 }, 1418 { 1419 Name: "objectPropertyGeo", 1420 DataType: schema.DataTypeObject.PropString(), 1421 NestedProperties: []*models.NestedProperty{ 1422 { 1423 Name: "nested_geo_wannabe", 1424 DataType: schema.DataTypeObject.PropString(), 1425 NestedProperties: []*models.NestedProperty{ 1426 { 1427 Name: "latitude", 1428 DataType: schema.DataTypeNumber.PropString(), 1429 }, 1430 { 1431 Name: "longitude", 1432 DataType: schema.DataTypeNumber.PropString(), 1433 }, 1434 }, 1435 }, 1436 { 1437 Name: "nested_geo_wannabes", 1438 DataType: schema.DataTypeObjectArray.PropString(), 1439 NestedProperties: []*models.NestedProperty{ 1440 { 1441 Name: "latitude", 1442 DataType: schema.DataTypeNumber.PropString(), 1443 }, 1444 { 1445 Name: "longitude", 1446 DataType: schema.DataTypeNumber.PropString(), 1447 }, 1448 }, 1449 }, 1450 { 1451 Name: "nested_objects", 1452 DataType: schema.DataTypeObjectArray.PropString(), 1453 NestedProperties: []*models.NestedProperty{ 1454 { 1455 Name: "nested_geo_wannabe_lvl2", 1456 DataType: schema.DataTypeObject.PropString(), 1457 NestedProperties: []*models.NestedProperty{ 1458 { 1459 Name: "latitude", 1460 DataType: schema.DataTypeNumber.PropString(), 1461 }, 1462 { 1463 Name: "longitude", 1464 DataType: schema.DataTypeNumber.PropString(), 1465 }, 1466 }, 1467 }, 1468 { 1469 Name: "nested_geo_wannabes_lvl2", 1470 DataType: schema.DataTypeObjectArray.PropString(), 1471 NestedProperties: []*models.NestedProperty{ 1472 { 1473 Name: "latitude", 1474 DataType: schema.DataTypeNumber.PropString(), 1475 }, 1476 { 1477 Name: "longitude", 1478 DataType: schema.DataTypeNumber.PropString(), 1479 }, 1480 }, 1481 }, 1482 }, 1483 }, 1484 }, 1485 }, 1486 { 1487 Name: "geo", 1488 DataType: schema.DataTypeGeoCoordinates.PropString(), 1489 }, 1490 { 1491 Name: "geo_wannabes", 1492 DataType: schema.DataTypeObjectArray.PropString(), 1493 NestedProperties: []*models.NestedProperty{ 1494 { 1495 Name: "latitude", 1496 DataType: schema.DataTypeNumber.PropString(), 1497 }, 1498 { 1499 Name: "longitude", 1500 DataType: schema.DataTypeNumber.PropString(), 1501 }, 1502 }, 1503 }, 1504 }, 1505 } 1506 1507 schemaManager := &fakeSchemaManager{ 1508 GetSchemaResponse: schema.Schema{ 1509 Objects: &models.Schema{ 1510 Classes: []*models.Class{class}, 1511 }, 1512 }, 1513 } 1514 manager := &autoSchemaManager{ 1515 schemaManager: schemaManager, 1516 vectorRepo: &fakeVectorRepo{}, 1517 config: config.AutoSchema{ 1518 Enabled: true, 1519 DefaultNumber: schema.DataTypeNumber.String(), 1520 DefaultString: schema.DataTypeText.String(), 1521 DefaultDate: schema.DataTypeDate.String(), 1522 }, 1523 logger: logger, 1524 } 1525 1526 err := manager.autoSchema(context.Background(), &models.Principal{}, object, true) 1527 require.NoError(t, err) 1528 1529 schemaAfter := schemaManager.GetSchemaResponse 1530 require.NotNil(t, schemaAfter.Objects) 1531 require.Len(t, schemaAfter.Objects.Classes, 1) 1532 require.Equal(t, className, schemaAfter.Objects.Classes[0].Class) 1533 1534 assertPropsMatch(t, expectedClass.Properties, schemaAfter.Objects.Classes[0].Properties) 1535 } 1536 1537 func getProperty(properties []*models.Property, name string) *models.Property { 1538 for _, prop := range properties { 1539 if prop.Name == name { 1540 return prop 1541 } 1542 } 1543 return nil 1544 } 1545 1546 func assertPropsMatch(t *testing.T, propsA, propsB []*models.Property) { 1547 require.Len(t, propsB, len(propsA), "props: different length") 1548 1549 pMap := map[string]int{} 1550 for index, p := range propsA { 1551 pMap[p.Name] = index 1552 } 1553 1554 for _, pB := range propsB { 1555 require.Contains(t, pMap, pB.Name) 1556 pA := propsA[pMap[pB.Name]] 1557 1558 assert.Equal(t, pA.DataType, pB.DataType) 1559 test_utils.AssertNestedPropsMatch(t, pA.NestedProperties, pB.NestedProperties) 1560 } 1561 }