Spaces:
Running
Running
| // _ _ | |
| // __ _____ __ ___ ___ __ _| |_ ___ | |
| // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
| // \ V V / __/ (_| |\ V /| | (_| | || __/ | |
| // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
| // | |
| // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
| // | |
| // CONTACT: [email protected] | |
| // | |
| package objects | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "reflect" | |
| "testing" | |
| "github.com/go-openapi/strfmt" | |
| "github.com/sirupsen/logrus/hooks/test" | |
| "github.com/stretchr/testify/assert" | |
| "github.com/stretchr/testify/mock" | |
| "github.com/stretchr/testify/require" | |
| "github.com/weaviate/weaviate/entities/models" | |
| "github.com/weaviate/weaviate/entities/schema" | |
| "github.com/weaviate/weaviate/entities/schema/test_utils" | |
| "github.com/weaviate/weaviate/entities/search" | |
| "github.com/weaviate/weaviate/usecases/config" | |
| "github.com/weaviate/weaviate/usecases/objects/validation" | |
| ) | |
| func Test_autoSchemaManager_determineType(t *testing.T) { | |
| type fields struct { | |
| config config.AutoSchema | |
| } | |
| type args struct { | |
| value interface{} | |
| } | |
| autoSchemaEnabledFields := fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| } | |
| tests := []struct { | |
| name string | |
| fields fields | |
| args args | |
| want []schema.DataType | |
| errMsgs []string | |
| }{ | |
| { | |
| name: "fail determining type of nested array (1)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeText.String(), | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{[]interface{}{"panic"}}, | |
| }, | |
| errMsgs: []string{"unrecognized data type"}, | |
| }, | |
| { | |
| name: "fail determining type of nested array (2)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeText.String(), | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{[]string{}}, | |
| }, | |
| errMsgs: []string{"unrecognized data type"}, | |
| }, | |
| { | |
| name: "fail determining type of mixed elements in array", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeText.String(), | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{"something", false}, | |
| }, | |
| errMsgs: []string{"mismatched data type", "'text' expected, got 'boolean'"}, | |
| }, | |
| { | |
| name: "fail determining type of mixed refs and objects (1)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{ | |
| map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"}, | |
| map[string]interface{}{"propOfObject": "something"}, | |
| }, | |
| }, | |
| errMsgs: []string{"mismatched data type", "reference expected, got 'object'"}, | |
| }, | |
| { | |
| name: "fail determining type of mixed refs and objects (2)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{ | |
| map[string]interface{}{"propOfObject": "something"}, | |
| map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"}, | |
| }, | |
| }, | |
| errMsgs: []string{"mismatched data type", "'object' expected, got reference"}, | |
| }, | |
| { | |
| name: "determine text", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeText.String(), | |
| }, | |
| }, | |
| args: args{ | |
| value: "string", | |
| }, | |
| want: []schema.DataType{schema.DataTypeText}, | |
| }, | |
| { | |
| name: "determine text (implicit)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: "string", | |
| }, | |
| want: []schema.DataType{schema.DataTypeText}, | |
| }, | |
| { | |
| name: "determine date", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultDate: "date", | |
| }, | |
| }, | |
| args: args{ | |
| value: "2002-10-02T15:00:00Z", | |
| }, | |
| want: []schema.DataType{schema.DataTypeDate}, | |
| }, | |
| { | |
| name: "determine uuid (1)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: "5b2cbe85-c38a-41f7-9e8c-7406ff6d15aa", | |
| }, | |
| want: []schema.DataType{schema.DataTypeUUID}, | |
| }, | |
| { | |
| name: "determine uuid (2)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: "5b2cbe85c38a41f79e8c7406ff6d15aa", | |
| }, | |
| want: []schema.DataType{schema.DataTypeUUID}, | |
| }, | |
| { | |
| name: "determine int", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultNumber: "int", | |
| }, | |
| }, | |
| args: args{ | |
| value: json.Number("1"), | |
| }, | |
| want: []schema.DataType{schema.DataTypeInt}, | |
| }, | |
| { | |
| name: "determine number", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultNumber: "number", | |
| }, | |
| }, | |
| args: args{ | |
| value: json.Number("1"), | |
| }, | |
| want: []schema.DataType{schema.DataTypeNumber}, | |
| }, | |
| { | |
| name: "determine boolean", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultNumber: "number", | |
| }, | |
| }, | |
| args: args{ | |
| value: true, | |
| }, | |
| want: []schema.DataType{schema.DataTypeBoolean}, | |
| }, | |
| { | |
| name: "determine geoCoordinates", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("1.1"), | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeGeoCoordinates}, | |
| }, | |
| { | |
| name: "determine phoneNumber", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "input": "020 1234567", | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypePhoneNumber}, | |
| }, | |
| { | |
| name: "determine phoneNumber (2)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypePhoneNumber}, | |
| }, | |
| { | |
| name: "determine cross reference", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{ | |
| map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"}, | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataType("Publication")}, | |
| }, | |
| { | |
| name: "determine cross references", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{ | |
| map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07390"}, | |
| map[string]interface{}{"beacon": "weaviate://localhost/df48b9f6-ba48-470c-bf6a-57657cb07391"}, | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataType("Publication"), schema.DataType("Article")}, | |
| }, | |
| { | |
| name: "determine text array", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeText.String(), | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{"a", "b"}, | |
| }, | |
| want: []schema.DataType{schema.DataTypeTextArray}, | |
| }, | |
| { | |
| name: "determine text array (implicit)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{"a", "b"}, | |
| }, | |
| want: []schema.DataType{schema.DataTypeTextArray}, | |
| }, | |
| { | |
| name: "determine int array", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultNumber: "int", | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{json.Number("11"), json.Number("12")}, | |
| }, | |
| want: []schema.DataType{schema.DataTypeIntArray}, | |
| }, | |
| { | |
| name: "determine number array", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultNumber: "number", | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{json.Number("1.1"), json.Number("1.2")}, | |
| }, | |
| want: []schema.DataType{schema.DataTypeNumberArray}, | |
| }, | |
| { | |
| name: "determine boolean array", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{true, false}, | |
| }, | |
| want: []schema.DataType{schema.DataTypeBooleanArray}, | |
| }, | |
| { | |
| name: "determine date array", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultDate: "date", | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{"2002-10-02T15:00:00Z", "2002-10-02T15:01:00Z"}, | |
| }, | |
| want: []schema.DataType{schema.DataTypeDateArray}, | |
| }, | |
| { | |
| name: "determine uuid array (1)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{ | |
| "5b2cbe85-c38a-41f7-9e8c-7406ff6d15aa", | |
| "57a8564d-089b-4cd9-be39-56681605e0da", | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeUUIDArray}, | |
| }, | |
| { | |
| name: "determine uuid array (2)", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{ | |
| "5b2cbe85c38a41f79e8c7406ff6d15aa", | |
| "57a8564d089b4cd9be3956681605e0da", | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeUUIDArray}, | |
| }, | |
| { | |
| name: "[deprecated string] determine string", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeString.String(), | |
| }, | |
| }, | |
| args: args{ | |
| value: "string", | |
| }, | |
| want: []schema.DataType{schema.DataTypeString}, | |
| }, | |
| { | |
| name: "[deprecated string] determine string array", | |
| fields: fields{ | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeString.String(), | |
| }, | |
| }, | |
| args: args{ | |
| value: []interface{}{"a", "b"}, | |
| }, | |
| want: []schema.DataType{schema.DataTypeStringArray}, | |
| }, | |
| { | |
| name: "determine object", | |
| fields: autoSchemaEnabledFields, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "some_number": 1.23, | |
| "some_bool": false, | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeObject}, | |
| }, | |
| { | |
| name: "determine object array", | |
| fields: autoSchemaEnabledFields, | |
| args: args{ | |
| value: []interface{}{ | |
| map[string]interface{}{ | |
| "some_number": 1.23, | |
| "some_bool": false, | |
| }, | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeObjectArray}, | |
| }, | |
| { | |
| name: "determine object, not geoCoordinates (too few props 1)", | |
| fields: autoSchemaEnabledFields, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeObject}, | |
| }, | |
| { | |
| name: "determine object, not geoCoordinates (too few props 2)", | |
| fields: autoSchemaEnabledFields, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "longitude": json.Number("1.1"), | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeObject}, | |
| }, | |
| { | |
| name: "determine object, not geoCoordinates (too many props)", | |
| fields: autoSchemaEnabledFields, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("1.1"), | |
| "unrelevant": "some text", | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeObject}, | |
| }, | |
| { | |
| name: "determine object, not phoneNumber (too few props)", | |
| fields: autoSchemaEnabledFields, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "defaultCountry": "nl", | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeObject}, | |
| }, | |
| { | |
| name: "determine object, not phoneNumber (too many props)", | |
| fields: autoSchemaEnabledFields, | |
| args: args{ | |
| value: map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| "internationalFormatted": "+31 20 1234567", | |
| "countryCode": 31, | |
| "national": 201234567, | |
| "nationalFormatted": "020 1234567", | |
| "valid": true, | |
| }, | |
| }, | |
| want: []schema.DataType{schema.DataTypeObject}, | |
| }, | |
| } | |
| for _, tt := range tests { | |
| vectorRepo := &fakeVectorRepo{} | |
| vectorRepo.On("ObjectByID", strfmt.UUID("df48b9f6-ba48-470c-bf6a-57657cb07390"), mock.Anything, mock.Anything, mock.Anything). | |
| Return(&search.Result{ClassName: "Publication"}, nil).Once() | |
| vectorRepo.On("ObjectByID", strfmt.UUID("df48b9f6-ba48-470c-bf6a-57657cb07391"), mock.Anything, mock.Anything, mock.Anything). | |
| Return(&search.Result{ClassName: "Article"}, nil).Once() | |
| m := &autoSchemaManager{ | |
| schemaManager: &fakeSchemaManager{}, | |
| vectorRepo: vectorRepo, | |
| config: tt.fields.config, | |
| } | |
| t.Run(tt.name, func(t *testing.T) { | |
| got, err := m.determineType(tt.args.value, false) | |
| if len(tt.errMsgs) == 0 { | |
| require.NoError(t, err) | |
| if !reflect.DeepEqual(got, tt.want) { | |
| t.Errorf("autoSchemaManager.determineType() = %v, want %v", got, tt.want) | |
| } | |
| } else { | |
| for _, errMsg := range tt.errMsgs { | |
| require.ErrorContains(t, err, errMsg) | |
| } | |
| assert.Nil(t, got) | |
| } | |
| }) | |
| } | |
| } | |
| func Test_autoSchemaManager_autoSchema_emptyRequest(t *testing.T) { | |
| // given | |
| vectorRepo := &fakeVectorRepo{} | |
| vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything). | |
| Return(&search.Result{ClassName: "Publication"}, nil).Once() | |
| schemaManager := &fakeSchemaManager{} | |
| logger, _ := test.NewNullLogger() | |
| autoSchemaManager := &autoSchemaManager{ | |
| schemaManager: schemaManager, | |
| vectorRepo: vectorRepo, | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeText.String(), | |
| DefaultNumber: "number", | |
| DefaultDate: "date", | |
| }, | |
| logger: logger, | |
| } | |
| var obj *models.Object | |
| err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true) | |
| assert.EqualError(t, fmt.Errorf(validation.ErrorMissingObject), err.Error()) | |
| } | |
| func Test_autoSchemaManager_autoSchema_create(t *testing.T) { | |
| // given | |
| vectorRepo := &fakeVectorRepo{} | |
| vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything). | |
| Return(&search.Result{ClassName: "Publication"}, nil).Once() | |
| schemaManager := &fakeSchemaManager{} | |
| logger, _ := test.NewNullLogger() | |
| autoSchemaManager := &autoSchemaManager{ | |
| schemaManager: schemaManager, | |
| vectorRepo: vectorRepo, | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeText.String(), | |
| DefaultNumber: "number", | |
| DefaultDate: "date", | |
| }, | |
| logger: logger, | |
| } | |
| obj := &models.Object{ | |
| Class: "Publication", | |
| Properties: map[string]interface{}{ | |
| "name": "Jodie Sparrow", | |
| "age": json.Number("30"), | |
| "publicationDate": "2002-10-02T15:00:00Z", | |
| "textArray": []interface{}{"a", "b"}, | |
| "numberArray": []interface{}{json.Number("30")}, | |
| }, | |
| } | |
| // when | |
| schemaBefore := schemaManager.GetSchemaResponse | |
| err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true) | |
| schemaAfter := schemaManager.GetSchemaResponse | |
| // then | |
| require.Nil(t, schemaBefore.Objects) | |
| require.Nil(t, err) | |
| require.NotNil(t, schemaAfter.Objects) | |
| assert.Equal(t, 1, len(schemaAfter.Objects.Classes)) | |
| assert.Equal(t, "Publication", (schemaAfter.Objects.Classes)[0].Class) | |
| assert.Equal(t, 5, len((schemaAfter.Objects.Classes)[0].Properties)) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "name")) | |
| assert.Equal(t, "name", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").Name) | |
| assert.Equal(t, "text", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").DataType[0]) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "age")) | |
| assert.Equal(t, "age", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").Name) | |
| assert.Equal(t, "number", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").DataType[0]) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate")) | |
| assert.Equal(t, "publicationDate", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").Name) | |
| assert.Equal(t, "date", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").DataType[0]) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray")) | |
| assert.Equal(t, "textArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").Name) | |
| assert.Equal(t, "text[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").DataType[0]) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray")) | |
| assert.Equal(t, "numberArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").Name) | |
| assert.Equal(t, "number[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").DataType[0]) | |
| } | |
| func Test_autoSchemaManager_autoSchema_update(t *testing.T) { | |
| // given | |
| vectorRepo := &fakeVectorRepo{} | |
| vectorRepo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything, mock.Anything). | |
| Return(&search.Result{ClassName: "Publication"}, nil).Once() | |
| logger, _ := test.NewNullLogger() | |
| schemaManager := &fakeSchemaManager{ | |
| GetSchemaResponse: schema.Schema{ | |
| Objects: &models.Schema{ | |
| Classes: []*models.Class{ | |
| { | |
| Class: "Publication", | |
| Properties: []*models.Property{ | |
| { | |
| Name: "age", | |
| DataType: []string{"int"}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| autoSchemaManager := &autoSchemaManager{ | |
| schemaManager: schemaManager, | |
| vectorRepo: vectorRepo, | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultString: schema.DataTypeText.String(), | |
| DefaultNumber: "int", | |
| DefaultDate: "date", | |
| }, | |
| logger: logger, | |
| } | |
| obj := &models.Object{ | |
| Class: "Publication", | |
| Properties: map[string]interface{}{ | |
| "name": "Jodie Sparrow", | |
| "age": json.Number("30"), | |
| "publicationDate": "2002-10-02T15:00:00Z", | |
| "textArray": []interface{}{"a", "b"}, | |
| "numberArray": []interface{}{json.Number("30")}, | |
| }, | |
| } | |
| // when | |
| // then | |
| schemaBefore := schemaManager.GetSchemaResponse | |
| require.NotNil(t, schemaBefore.Objects) | |
| assert.Equal(t, 1, len(schemaBefore.Objects.Classes)) | |
| assert.Equal(t, "Publication", (schemaBefore.Objects.Classes)[0].Class) | |
| assert.Equal(t, 1, len((schemaBefore.Objects.Classes)[0].Properties)) | |
| assert.Equal(t, "age", (schemaBefore.Objects.Classes)[0].Properties[0].Name) | |
| assert.Equal(t, "int", (schemaBefore.Objects.Classes)[0].Properties[0].DataType[0]) | |
| err := autoSchemaManager.autoSchema(context.Background(), &models.Principal{}, obj, true) | |
| require.Nil(t, err) | |
| schemaAfter := schemaManager.GetSchemaResponse | |
| require.NotNil(t, schemaAfter.Objects) | |
| assert.Equal(t, 1, len(schemaAfter.Objects.Classes)) | |
| assert.Equal(t, "Publication", (schemaAfter.Objects.Classes)[0].Class) | |
| assert.Equal(t, 5, len((schemaAfter.Objects.Classes)[0].Properties)) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "age")) | |
| assert.Equal(t, "age", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").Name) | |
| assert.Equal(t, "int", getProperty((schemaAfter.Objects.Classes)[0].Properties, "age").DataType[0]) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "name")) | |
| assert.Equal(t, "name", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").Name) | |
| assert.Equal(t, "text", getProperty((schemaAfter.Objects.Classes)[0].Properties, "name").DataType[0]) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate")) | |
| assert.Equal(t, "publicationDate", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").Name) | |
| assert.Equal(t, "date", getProperty((schemaAfter.Objects.Classes)[0].Properties, "publicationDate").DataType[0]) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray")) | |
| assert.Equal(t, "textArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").Name) | |
| assert.Equal(t, "text[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "textArray").DataType[0]) | |
| require.NotNil(t, getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray")) | |
| assert.Equal(t, "numberArray", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").Name) | |
| assert.Equal(t, "int[]", getProperty((schemaAfter.Objects.Classes)[0].Properties, "numberArray").DataType[0]) | |
| } | |
| func Test_autoSchemaManager_getProperties(t *testing.T) { | |
| type testCase struct { | |
| name string | |
| valProperties map[string]interface{} | |
| expectedProperties []*models.Property | |
| } | |
| testCases := []testCase{ | |
| { | |
| name: "mixed 1", | |
| valProperties: map[string]interface{}{ | |
| "name": "someName", | |
| "objectProperty": map[string]interface{}{ | |
| "nested_int": json.Number("123"), | |
| "nested_text": "some text", | |
| "nested_objects": []interface{}{ | |
| map[string]interface{}{ | |
| "nested_bool_lvl2": false, | |
| "nested_numbers_lvl2": []interface{}{ | |
| json.Number("11.11"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| expectedProperties: []*models.Property{ | |
| { | |
| Name: "name", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "objectProperty", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_int", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "nested_text", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "nested_objects", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_bool_lvl2", | |
| DataType: schema.DataTypeBoolean.PropString(), | |
| }, | |
| { | |
| Name: "nested_numbers_lvl2", | |
| DataType: schema.DataTypeNumberArray.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| name: "mixed 2", | |
| valProperties: map[string]interface{}{ | |
| "name": "someName", | |
| "objectProperty": map[string]interface{}{ | |
| "nested_number": json.Number("123"), | |
| "nested_text": "some text", | |
| "nested_objects": []interface{}{ | |
| map[string]interface{}{ | |
| "nested_date_lvl2": "2022-01-01T00:00:00+02:00", | |
| "nested_numbers_lvl2": []interface{}{ | |
| json.Number("11.11"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| expectedProperties: []*models.Property{ | |
| { | |
| Name: "name", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "objectProperty", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_number", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "nested_text", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "nested_objects", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_date_lvl2", | |
| DataType: schema.DataTypeDate.PropString(), | |
| }, | |
| { | |
| Name: "nested_numbers_lvl2", | |
| DataType: schema.DataTypeNumberArray.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| name: "ref", | |
| valProperties: map[string]interface{}{ | |
| "name": "someName", | |
| "objectProperty": map[string]interface{}{ | |
| "nested_ref_wannabe": []interface{}{ | |
| map[string]interface{}{ | |
| "beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825", | |
| }, | |
| }, | |
| "nested_objects": []interface{}{ | |
| map[string]interface{}{ | |
| "nested_ref_wannabe_lvl2": []interface{}{ | |
| map[string]interface{}{ | |
| "beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825", | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "ref": []interface{}{ | |
| map[string]interface{}{ | |
| "beacon": "weaviate://localhost/Soup/8c156d37-81aa-4ce9-a811-621e2702b825", | |
| }, | |
| }, | |
| }, | |
| expectedProperties: []*models.Property{ | |
| { | |
| Name: "name", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "objectProperty", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_ref_wannabe", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "beacon", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_objects", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_ref_wannabe_lvl2", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "beacon", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "ref", | |
| DataType: []string{"Soup"}, | |
| }, | |
| }, | |
| }, | |
| { | |
| name: "phone", | |
| valProperties: map[string]interface{}{ | |
| "name": "someName", | |
| "objectProperty": map[string]interface{}{ | |
| "nested_phone_wannabe": map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| "nested_phone_wannabes": []interface{}{ | |
| map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| }, | |
| "nested_objects": []interface{}{ | |
| map[string]interface{}{ | |
| "nested_phone_wannabe_lvl2": map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| "nested_phone_wannabes_lvl2": []interface{}{ | |
| map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "phone": map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| "phone_wannabes": []interface{}{ | |
| map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| }, | |
| }, | |
| expectedProperties: []*models.Property{ | |
| { | |
| Name: "name", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "objectProperty", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_phone_wannabe", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_phone_wannabes", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_objects", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_phone_wannabe_lvl2", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_phone_wannabes_lvl2", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "phone", | |
| DataType: schema.DataTypePhoneNumber.PropString(), | |
| }, | |
| { | |
| Name: "phone_wannabes", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| name: "geo", | |
| valProperties: map[string]interface{}{ | |
| "name": "someName", | |
| "objectProperty": map[string]interface{}{ | |
| "nested_geo_wannabe": map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| "nested_geo_wannabes": []interface{}{ | |
| map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| }, | |
| "nested_objects": []interface{}{ | |
| map[string]interface{}{ | |
| "nested_geo_wannabe_lvl2": map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| "nested_geo_wannabes_lvl2": []interface{}{ | |
| map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| "geo": map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| "geo_wannabes": []interface{}{ | |
| map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| }, | |
| }, | |
| expectedProperties: []*models.Property{ | |
| { | |
| Name: "name", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "objectProperty", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_geo_wannabe", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_geo_wannabes", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_objects", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_geo_wannabe_lvl2", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_geo_wannabes_lvl2", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "geo", | |
| DataType: schema.DataTypeGeoCoordinates.PropString(), | |
| }, | |
| { | |
| Name: "geo_wannabes", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| manager := &autoSchemaManager{ | |
| schemaManager: &fakeSchemaManager{}, | |
| vectorRepo: &fakeVectorRepo{}, | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultNumber: schema.DataTypeNumber.String(), | |
| DefaultString: schema.DataTypeText.String(), | |
| DefaultDate: schema.DataTypeDate.String(), | |
| }, | |
| } | |
| for i, tc := range testCases { | |
| t.Run(fmt.Sprintf("testCase_%d", i), func(t *testing.T) { | |
| properties, _ := manager.getProperties(&models.Object{ | |
| Class: "ClassWithObjectProps", | |
| Properties: tc.valProperties, | |
| }) | |
| assertPropsMatch(t, tc.expectedProperties, properties) | |
| }) | |
| } | |
| } | |
| func Test_autoSchemaManager_perform_withNested(t *testing.T) { | |
| logger, _ := test.NewNullLogger() | |
| className := "ClassWithObjectProps" | |
| class := &models.Class{ | |
| Class: className, | |
| Properties: []*models.Property{ | |
| { | |
| Name: "name", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "objectProperty", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_int", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "nested_text", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "nested_objects", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_bool_lvl2", | |
| DataType: schema.DataTypeBoolean.PropString(), | |
| }, | |
| { | |
| Name: "nested_numbers_lvl2", | |
| DataType: schema.DataTypeNumberArray.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| object := &models.Object{ | |
| Class: className, | |
| Properties: map[string]interface{}{ | |
| "name": "someName", | |
| "objectProperty": map[string]interface{}{ | |
| "nested_number": json.Number("123"), | |
| "nested_text": "some text", | |
| "nested_objects": []interface{}{ | |
| map[string]interface{}{ | |
| "nested_date_lvl2": "2022-01-01T00:00:00+02:00", | |
| "nested_numbers_lvl2": []interface{}{ | |
| json.Number("11.11"), | |
| }, | |
| "nested_phone_wannabe_lvl2": map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| "nested_phone_wannabes_lvl2": []interface{}{ | |
| map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| }, | |
| }, | |
| }, | |
| "nested_phone_wannabe": map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| "nested_phone_wannabes": []interface{}{ | |
| map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| }, | |
| }, | |
| "phone": map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| "phone_wannabes": []interface{}{ | |
| map[string]interface{}{ | |
| "input": "020 1234567", | |
| "defaultCountry": "nl", | |
| }, | |
| }, | |
| "objectPropertyGeo": map[string]interface{}{ | |
| "nested_objects": []interface{}{ | |
| map[string]interface{}{ | |
| "nested_geo_wannabe_lvl2": map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| "nested_geo_wannabes_lvl2": []interface{}{ | |
| map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| }, | |
| }, | |
| }, | |
| "nested_geo_wannabe": map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| "nested_geo_wannabes": []interface{}{ | |
| map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| }, | |
| }, | |
| "geo": map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| "geo_wannabes": []interface{}{ | |
| map[string]interface{}{ | |
| "latitude": json.Number("1.1"), | |
| "longitude": json.Number("2.2"), | |
| }, | |
| }, | |
| }, | |
| } | |
| expectedClass := &models.Class{ | |
| Class: className, | |
| Properties: []*models.Property{ | |
| { | |
| Name: "name", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "objectProperty", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_int", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "nested_number", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "nested_text", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "nested_phone_wannabe", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_phone_wannabes", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_objects", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_bool_lvl2", | |
| DataType: schema.DataTypeBoolean.PropString(), | |
| }, | |
| { | |
| Name: "nested_date_lvl2", | |
| DataType: schema.DataTypeDate.PropString(), | |
| }, | |
| { | |
| Name: "nested_numbers_lvl2", | |
| DataType: schema.DataTypeNumberArray.PropString(), | |
| }, | |
| { | |
| Name: "nested_phone_wannabe_lvl2", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_phone_wannabes_lvl2", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "phone", | |
| DataType: schema.DataTypePhoneNumber.PropString(), | |
| }, | |
| { | |
| Name: "phone_wannabes", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "input", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| { | |
| Name: "defaultCountry", | |
| DataType: schema.DataTypeText.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "objectPropertyGeo", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_geo_wannabe", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_geo_wannabes", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_objects", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "nested_geo_wannabe_lvl2", | |
| DataType: schema.DataTypeObject.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "nested_geo_wannabes_lvl2", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "geo", | |
| DataType: schema.DataTypeGeoCoordinates.PropString(), | |
| }, | |
| { | |
| Name: "geo_wannabes", | |
| DataType: schema.DataTypeObjectArray.PropString(), | |
| NestedProperties: []*models.NestedProperty{ | |
| { | |
| Name: "latitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| { | |
| Name: "longitude", | |
| DataType: schema.DataTypeNumber.PropString(), | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| schemaManager := &fakeSchemaManager{ | |
| GetSchemaResponse: schema.Schema{ | |
| Objects: &models.Schema{ | |
| Classes: []*models.Class{class}, | |
| }, | |
| }, | |
| } | |
| manager := &autoSchemaManager{ | |
| schemaManager: schemaManager, | |
| vectorRepo: &fakeVectorRepo{}, | |
| config: config.AutoSchema{ | |
| Enabled: true, | |
| DefaultNumber: schema.DataTypeNumber.String(), | |
| DefaultString: schema.DataTypeText.String(), | |
| DefaultDate: schema.DataTypeDate.String(), | |
| }, | |
| logger: logger, | |
| } | |
| err := manager.autoSchema(context.Background(), &models.Principal{}, object, true) | |
| require.NoError(t, err) | |
| schemaAfter := schemaManager.GetSchemaResponse | |
| require.NotNil(t, schemaAfter.Objects) | |
| require.Len(t, schemaAfter.Objects.Classes, 1) | |
| require.Equal(t, className, schemaAfter.Objects.Classes[0].Class) | |
| assertPropsMatch(t, expectedClass.Properties, schemaAfter.Objects.Classes[0].Properties) | |
| } | |
| func getProperty(properties []*models.Property, name string) *models.Property { | |
| for _, prop := range properties { | |
| if prop.Name == name { | |
| return prop | |
| } | |
| } | |
| return nil | |
| } | |
| func assertPropsMatch(t *testing.T, propsA, propsB []*models.Property) { | |
| require.Len(t, propsB, len(propsA), "props: different length") | |
| pMap := map[string]int{} | |
| for index, p := range propsA { | |
| pMap[p.Name] = index | |
| } | |
| for _, pB := range propsB { | |
| require.Contains(t, pMap, pB.Name) | |
| pA := propsA[pMap[pB.Name]] | |
| assert.Equal(t, pA.DataType, pB.DataType) | |
| test_utils.AssertNestedPropsMatch(t, pA.NestedProperties, pB.NestedProperties) | |
| } | |
| } | |