Spaces:
Sleeping
Sleeping
| // _ _ | |
| // __ _____ __ ___ ___ __ _| |_ ___ | |
| // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
| // \ V V / __/ (_| |\ V /| | (_| | || __/ | |
| // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
| // | |
| // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
| // | |
| // CONTACT: [email protected] | |
| // | |
| package objects | |
| import ( | |
| "context" | |
| "strings" | |
| "testing" | |
| "github.com/go-openapi/strfmt" | |
| "github.com/google/uuid" | |
| "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/vectorindex/hnsw" | |
| "github.com/weaviate/weaviate/usecases/config" | |
| ) | |
| func Test_Add_Object_WithNoVectorizerModule(t *testing.T) { | |
| var ( | |
| vectorRepo *fakeVectorRepo | |
| modulesProvider *fakeModulesProvider | |
| manager *Manager | |
| ) | |
| sch := schema.Schema{ | |
| Objects: &models.Schema{ | |
| Classes: []*models.Class{ | |
| { | |
| Class: "Foo", | |
| Vectorizer: config.VectorizerModuleNone, | |
| VectorIndexConfig: hnsw.UserConfig{}, | |
| }, | |
| { | |
| Class: "FooSkipped", | |
| Vectorizer: config.VectorizerModuleNone, | |
| VectorIndexConfig: hnsw.UserConfig{ | |
| Skip: true, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| resetAutoSchema := func(autoSchemaEnabled bool) { | |
| vectorRepo = &fakeVectorRepo{} | |
| vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once() | |
| schemaManager := &fakeSchemaManager{ | |
| GetSchemaResponse: sch, | |
| } | |
| locks := &fakeLocks{} | |
| cfg := &config.WeaviateConfig{ | |
| Config: config.Config{ | |
| AutoSchema: config.AutoSchema{ | |
| Enabled: autoSchemaEnabled, | |
| DefaultString: schema.DataTypeText.String(), | |
| }, | |
| }, | |
| } | |
| authorizer := &fakeAuthorizer{} | |
| logger, _ := test.NewNullLogger() | |
| modulesProvider = getFakeModulesProvider() | |
| metrics := &fakeMetrics{} | |
| manager = NewManager(locks, schemaManager, cfg, logger, authorizer, | |
| vectorRepo, modulesProvider, metrics) | |
| } | |
| reset := func() { | |
| resetAutoSchema(false) | |
| } | |
| t.Run("without an id set", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| class := &models.Object{ | |
| Vector: []float32{0.1, 0.2, 0.3}, | |
| Class: "Foo", | |
| } | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| res, err := manager.AddObject(ctx, nil, class, nil) | |
| require.Nil(t, err) | |
| uuidDuringCreation := vectorRepo.Mock.Calls[0].Arguments.Get(0).(*models.Object).ID | |
| assert.Len(t, uuidDuringCreation, 36, "check that a uuid was assigned") | |
| assert.Equal(t, uuidDuringCreation, res.ID, "check that connector add ID and user response match") | |
| }) | |
| t.Run("with an explicit (correct) ID set", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| id := strfmt.UUID("5a1cd361-1e0d-42ae-bd52-ee09cb5f31cc") | |
| object := &models.Object{ | |
| Vector: []float32{0.1, 0.2, 0.3}, | |
| ID: id, | |
| Class: "Foo", | |
| } | |
| vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once() | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| res, err := manager.AddObject(ctx, nil, object, nil) | |
| require.Nil(t, err) | |
| uuidDuringCreation := vectorRepo.Mock.Calls[1].Arguments.Get(0).(*models.Object).ID | |
| assert.Equal(t, id, uuidDuringCreation, "check that a uuid is the user specified one") | |
| assert.Equal(t, res.ID, uuidDuringCreation, "check that connector add ID and user response match") | |
| }) | |
| t.Run("with an explicit (correct) uppercase id set", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| id := strfmt.UUID("4A334D0B-6347-40A0-A5AE-339677B20EDE") | |
| lowered := strfmt.UUID(strings.ToLower(id.String())) | |
| object := &models.Object{ | |
| ID: id, | |
| Class: "Foo", | |
| Vector: []float32{0.1, 0.2, 0.3}, | |
| } | |
| vectorRepo.On("Exists", "Foo", lowered).Return(false, nil).Once() | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| res, err := manager.AddObject(ctx, nil, object, nil) | |
| require.Nil(t, err) | |
| assert.Equal(t, res.ID, lowered, "check that id was lowered and added") | |
| }) | |
| t.Run("with an explicit (correct) ID set and a property that doesn't exist", func(t *testing.T) { | |
| resetAutoSchema(true) | |
| ctx := context.Background() | |
| id := strfmt.UUID("5aaad361-1e0d-42ae-bb52-ee09cb5f31cc") | |
| object := &models.Object{ | |
| Vector: []float32{0.1, 0.2, 0.3}, | |
| ID: id, | |
| Class: "Foo", | |
| Properties: map[string]interface{}{ | |
| "newProperty": "string value", | |
| }, | |
| } | |
| vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once() | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| res, err := manager.AddObject(ctx, nil, object, nil) | |
| require.Nil(t, err) | |
| uuidDuringCreation := vectorRepo.Mock.Calls[1].Arguments.Get(0).(*models.Object).ID | |
| assert.Equal(t, id, uuidDuringCreation, "check that a uuid is the user specified one") | |
| assert.Equal(t, res.ID, uuidDuringCreation, "check that connector add ID and user response match") | |
| }) | |
| t.Run("with a uuid that's already taken", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| id := strfmt.UUID("5a1cd361-1e0d-42ae-bd52-ee09cb5f31cc") | |
| class := &models.Object{ | |
| ID: id, | |
| Class: "Foo", | |
| } | |
| vectorRepo.On("Exists", "Foo", id).Return(true, nil).Once() | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| _, err := manager.AddObject(ctx, nil, class, nil) | |
| assert.Equal(t, NewErrInvalidUserInput("id '%s' already exists", id), err) | |
| }) | |
| t.Run("with a uuid that's malformed", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| id := strfmt.UUID("5a1cd361-1e0d-4fooooooo2ae-bd52-ee09cb5f31cc") | |
| class := &models.Object{ | |
| ID: id, | |
| Class: "Foo", | |
| } | |
| vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once() | |
| _, err := manager.AddObject(ctx, nil, class, nil) | |
| assert.Equal(t, NewErrInvalidUserInput("invalid object: invalid UUID length: %d", len(id)), err) | |
| }) | |
| t.Run("without a vector", func(t *testing.T) { | |
| // Note that this was an invalid case before v1.10 which added this | |
| // functionality, as part of | |
| // https://github.com/weaviate/weaviate/issues/1800 | |
| reset() | |
| ctx := context.Background() | |
| class := &models.Object{ | |
| Class: "Foo", | |
| } | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| _, err := manager.AddObject(ctx, nil, class, nil) | |
| assert.Nil(t, err) | |
| }) | |
| t.Run("without a vector, but indexing skipped", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| class := &models.Object{ | |
| Class: "FooSkipped", | |
| } | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| _, err := manager.AddObject(ctx, nil, class, nil) | |
| assert.Nil(t, err) | |
| }) | |
| } | |
| func Test_Add_Object_WithExternalVectorizerModule(t *testing.T) { | |
| var ( | |
| vectorRepo *fakeVectorRepo | |
| modulesProvider *fakeModulesProvider | |
| manager *Manager | |
| ) | |
| schema := schema.Schema{ | |
| Objects: &models.Schema{ | |
| Classes: []*models.Class{ | |
| { | |
| Class: "Foo", | |
| Vectorizer: config.VectorizerModuleText2VecContextionary, | |
| VectorIndexConfig: hnsw.UserConfig{}, | |
| }, | |
| }, | |
| }, | |
| } | |
| reset := func() { | |
| vectorRepo = &fakeVectorRepo{} | |
| vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once() | |
| schemaManager := &fakeSchemaManager{ | |
| GetSchemaResponse: schema, | |
| } | |
| locks := &fakeLocks{} | |
| cfg := &config.WeaviateConfig{} | |
| authorizer := &fakeAuthorizer{} | |
| logger, _ := test.NewNullLogger() | |
| metrics := &fakeMetrics{} | |
| modulesProvider = getFakeModulesProvider() | |
| modulesProvider.On("UsingRef2Vec", mock.Anything).Return(false) | |
| manager = NewManager(locks, schemaManager, cfg, logger, authorizer, | |
| vectorRepo, modulesProvider, metrics) | |
| } | |
| t.Run("without an id set", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| object := &models.Object{ | |
| Class: "Foo", | |
| } | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| res, err := manager.AddObject(ctx, nil, object, nil) | |
| require.Nil(t, err) | |
| uuidDuringCreation := vectorRepo.Mock.Calls[0].Arguments.Get(0).(*models.Object).ID | |
| assert.Len(t, uuidDuringCreation, 36, "check that a uuid was assigned") | |
| assert.Equal(t, uuidDuringCreation, res.ID, "check that connector add ID and user response match") | |
| }) | |
| t.Run("with an explicit (correct) ID set", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| id := strfmt.UUID("5a1cd361-1e0d-42ae-bd52-ee09cb5f31cc") | |
| object := &models.Object{ | |
| ID: id, | |
| Class: "Foo", | |
| } | |
| vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once() | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| res, err := manager.AddObject(ctx, nil, object, nil) | |
| uuidDuringCreation := vectorRepo.Mock.Calls[1].Arguments.Get(0).(*models.Object).ID | |
| assert.Nil(t, err) | |
| assert.Equal(t, id, uuidDuringCreation, "check that a uuid is the user specified one") | |
| assert.Equal(t, res.ID, uuidDuringCreation, "check that connector add ID and user response match") | |
| }) | |
| t.Run("with a uuid that's already taken", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| id := strfmt.UUID("5a1cd361-1e0d-42ae-bd52-ee09cb5f31cc") | |
| object := &models.Object{ | |
| ID: id, | |
| Class: "Foo", | |
| } | |
| vectorRepo.On("Exists", "Foo", id).Return(true, nil).Once() | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| _, err := manager.AddObject(ctx, nil, object, nil) | |
| assert.Equal(t, NewErrInvalidUserInput("id '%s' already exists", id), err) | |
| }) | |
| t.Run("with a uuid that's malformed", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| id := strfmt.UUID("5a1cd361-1e0d-4f00000002ae-bd52-ee09cb5f31cc") | |
| object := &models.Object{ | |
| ID: id, | |
| Class: "Foo", | |
| } | |
| vectorRepo.On("Exists", "Foo", id).Return(false, nil).Once() | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| _, err := manager.AddObject(ctx, nil, object, nil) | |
| assert.Equal(t, NewErrInvalidUserInput("invalid object: invalid UUID length: %d", len(id)), err) | |
| }) | |
| } | |
| func Test_Add_Object_OverrideVectorizer(t *testing.T) { | |
| var ( | |
| vectorRepo *fakeVectorRepo | |
| modulesProvider *fakeModulesProvider | |
| manager *Manager | |
| ) | |
| schema := schema.Schema{ | |
| Objects: &models.Schema{ | |
| Classes: []*models.Class{ | |
| { | |
| Class: "FooOverride", | |
| Vectorizer: config.VectorizerModuleText2VecContextionary, | |
| VectorIndexConfig: hnsw.UserConfig{}, | |
| }, | |
| }, | |
| }, | |
| } | |
| reset := func() { | |
| vectorRepo = &fakeVectorRepo{} | |
| vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once() | |
| schemaManager := &fakeSchemaManager{ | |
| GetSchemaResponse: schema, | |
| } | |
| locks := &fakeLocks{} | |
| cfg := &config.WeaviateConfig{} | |
| authorizer := &fakeAuthorizer{} | |
| logger, _ := test.NewNullLogger() | |
| modulesProvider = getFakeModulesProvider() | |
| metrics := &fakeMetrics{} | |
| manager = NewManager(locks, schemaManager, cfg, logger, | |
| authorizer, vectorRepo, modulesProvider, metrics) | |
| } | |
| t.Run("overriding the vector by explicitly specifying it", func(t *testing.T) { | |
| reset() | |
| ctx := context.Background() | |
| object := &models.Object{ | |
| Class: "FooOverride", | |
| Vector: []float32{9, 9, 9}, | |
| } | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(object.Vector, nil) | |
| _, err := manager.AddObject(ctx, nil, object, nil) | |
| require.Nil(t, err) | |
| vec := vectorRepo.Mock.Calls[0].Arguments.Get(1).([]float32) | |
| assert.Equal(t, []float32{9, 9, 9}, vec, "check that vector was overridden") | |
| }) | |
| } | |
| func Test_AddObjectEmptyProperties(t *testing.T) { | |
| var ( | |
| vectorRepo *fakeVectorRepo | |
| modulesProvider *fakeModulesProvider | |
| manager *Manager | |
| ) | |
| schema := schema.Schema{ | |
| Objects: &models.Schema{ | |
| Classes: []*models.Class{ | |
| { | |
| Class: "TestClass", | |
| VectorIndexConfig: hnsw.UserConfig{}, | |
| Properties: []*models.Property{ | |
| { | |
| Name: "strings", | |
| DataType: schema.DataTypeTextArray.PropString(), | |
| Tokenization: models.PropertyTokenizationWhitespace, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| reset := func() { | |
| vectorRepo = &fakeVectorRepo{} | |
| vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once() | |
| schemaManager := &fakeSchemaManager{ | |
| GetSchemaResponse: schema, | |
| } | |
| locks := &fakeLocks{} | |
| cfg := &config.WeaviateConfig{} | |
| authorizer := &fakeAuthorizer{} | |
| logger, _ := test.NewNullLogger() | |
| modulesProvider = getFakeModulesProvider() | |
| metrics := &fakeMetrics{} | |
| manager = NewManager(locks, schemaManager, cfg, logger, | |
| authorizer, vectorRepo, modulesProvider, metrics) | |
| } | |
| reset() | |
| ctx := context.Background() | |
| object := &models.Object{ | |
| Class: "TestClass", | |
| Vector: []float32{9, 9, 9}, | |
| } | |
| assert.Nil(t, object.Properties) | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| addedObject, err := manager.AddObject(ctx, nil, object, nil) | |
| assert.Nil(t, err) | |
| assert.NotNil(t, addedObject.Properties) | |
| } | |
| func Test_AddObjectWithUUIDProps(t *testing.T) { | |
| var ( | |
| vectorRepo *fakeVectorRepo | |
| modulesProvider *fakeModulesProvider | |
| manager *Manager | |
| ) | |
| schema := schema.Schema{ | |
| Objects: &models.Schema{ | |
| Classes: []*models.Class{ | |
| { | |
| Class: "TestClass", | |
| VectorIndexConfig: hnsw.UserConfig{}, | |
| Properties: []*models.Property{ | |
| { | |
| Name: "my_id", | |
| DataType: []string{"uuid"}, | |
| }, | |
| { | |
| Name: "my_idz", | |
| DataType: []string{"uuid[]"}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| reset := func() { | |
| vectorRepo = &fakeVectorRepo{} | |
| vectorRepo.On("PutObject", mock.Anything, mock.Anything).Return(nil).Once() | |
| schemaManager := &fakeSchemaManager{ | |
| GetSchemaResponse: schema, | |
| } | |
| locks := &fakeLocks{} | |
| cfg := &config.WeaviateConfig{} | |
| authorizer := &fakeAuthorizer{} | |
| logger, _ := test.NewNullLogger() | |
| modulesProvider = getFakeModulesProvider() | |
| metrics := &fakeMetrics{} | |
| manager = NewManager(locks, schemaManager, cfg, logger, | |
| authorizer, vectorRepo, modulesProvider, metrics) | |
| } | |
| reset() | |
| ctx := context.Background() | |
| object := &models.Object{ | |
| Class: "TestClass", | |
| Vector: []float32{9, 9, 9}, | |
| Properties: map[string]interface{}{ | |
| "my_id": "28bafa1e-7956-4c58-8a02-4499a9d15253", | |
| "my_idz": []any{"28bafa1e-7956-4c58-8a02-4499a9d15253"}, | |
| }, | |
| } | |
| modulesProvider.On("UpdateVector", mock.Anything, mock.AnythingOfType(FindObjectFn)). | |
| Return(nil, nil) | |
| addedObject, err := manager.AddObject(ctx, nil, object, nil) | |
| require.Nil(t, err) | |
| require.NotNil(t, addedObject.Properties) | |
| expectedID := uuid.MustParse("28bafa1e-7956-4c58-8a02-4499a9d15253") | |
| expectedIDz := []uuid.UUID{uuid.MustParse("28bafa1e-7956-4c58-8a02-4499a9d15253")} | |
| assert.Equal(t, expectedID, addedObject.Properties.(map[string]interface{})["my_id"]) | |
| assert.Equal(t, expectedIDz, addedObject.Properties.(map[string]interface{})["my_idz"]) | |
| } | |