github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/crud_null_objects_integration_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 //go:build integrationTest 13 14 package db 15 16 import ( 17 "context" 18 "testing" 19 20 "github.com/weaviate/weaviate/entities/dto" 21 "github.com/weaviate/weaviate/entities/filters" 22 enthnsw "github.com/weaviate/weaviate/entities/vectorindex/hnsw" 23 24 "github.com/go-openapi/strfmt" 25 "github.com/stretchr/testify/assert" 26 27 "github.com/google/uuid" 28 "github.com/sirupsen/logrus/hooks/test" 29 "github.com/stretchr/testify/require" 30 "github.com/weaviate/weaviate/entities/additional" 31 "github.com/weaviate/weaviate/entities/models" 32 "github.com/weaviate/weaviate/entities/schema" 33 "github.com/weaviate/weaviate/usecases/objects" 34 ) 35 36 // Cannot filter for null state without enabling in the InvertedIndexConfig 37 func TestFilterNullStateError(t *testing.T) { 38 class := createClassWithEverything(false, false) 39 migrator, repo, schemaGetter := createRepo(t) 40 defer repo.Shutdown(context.Background()) 41 err := migrator.AddClass(context.Background(), class, schemaGetter.shardState) 42 require.Nil(t, err) 43 // update schema getter so it's in sync with class 44 schemaGetter.schema = schema.Schema{ 45 Objects: &models.Schema{ 46 Classes: []*models.Class{class}, 47 }, 48 } 49 50 nilFilter := &filters.LocalFilter{ 51 Root: &filters.Clause{ 52 Operator: filters.OperatorIsNull, 53 On: &filters.Path{ 54 Class: schema.ClassName(carClass.Class), 55 Property: schema.PropertyName(class.Properties[0].Name), 56 }, 57 Value: &filters.Value{ 58 Value: true, 59 Type: schema.DataTypeBoolean, 60 }, 61 }, 62 } 63 64 params := dto.GetParams{ 65 SearchVector: []float32{0.1, 0.1, 0.1, 1.1, 0.1}, 66 ClassName: class.Class, 67 Pagination: &filters.Pagination{Limit: 5}, 68 Filters: nilFilter, 69 } 70 _, err = repo.Search(context.Background(), params) 71 require.NotNil(t, err) 72 } 73 74 func TestNullArrayClass(t *testing.T) { 75 arrayClass := createClassWithEverything(true, false) 76 77 names := []string{"elements", "batches"} 78 for _, name := range names { 79 t.Run("add nil object via "+name, func(t *testing.T) { 80 migrator, repo, schemaGetter := createRepo(t) 81 defer repo.Shutdown(context.Background()) 82 err := migrator.AddClass(context.Background(), arrayClass, schemaGetter.shardState) 83 require.Nil(t, err) 84 // update schema getter so it's in sync with class 85 schemaGetter.schema = schema.Schema{ 86 Objects: &models.Schema{ 87 Classes: []*models.Class{arrayClass}, 88 }, 89 } 90 91 ObjectUuid1 := uuid.New() 92 arrayObjNil := &models.Object{ 93 ID: strfmt.UUID(ObjectUuid1.String()), 94 Class: "EverythingClass", 95 Properties: map[string]interface{}{ 96 "strings": nil, 97 "ints": nil, 98 "datesAsStrings": nil, 99 "numbers": nil, 100 "booleans": nil, 101 "texts": nil, 102 "number": nil, 103 "boolean": nil, 104 "int": nil, 105 "string": nil, 106 "text": nil, 107 "phoneNumber": nil, 108 "phoneNumbers": nil, 109 }, 110 } 111 112 ObjectUuid2 := uuid.New() 113 arrayObjEmpty := &models.Object{ 114 ID: strfmt.UUID(ObjectUuid2.String()), 115 Class: "EverythingClass", 116 Properties: map[string]interface{}{}, 117 } 118 119 if name == names[0] { 120 assert.Nil(t, repo.PutObject(context.Background(), arrayObjNil, []float32{1}, nil, nil)) 121 assert.Nil(t, repo.PutObject(context.Background(), arrayObjEmpty, []float32{1}, nil, nil)) 122 123 } else { 124 batch := make([]objects.BatchObject, 2) 125 batch[0] = objects.BatchObject{Object: arrayObjNil, UUID: arrayObjNil.ID} 126 batch[1] = objects.BatchObject{Object: arrayObjEmpty, UUID: arrayObjEmpty.ID} 127 _, err := repo.BatchPutObjects(context.Background(), batch, nil) 128 assert.Nil(t, err) 129 } 130 131 item1, err := repo.ObjectByID(context.Background(), arrayObjNil.ID, nil, additional.Properties{}, "") 132 assert.Nil(t, err) 133 item2, err := repo.ObjectByID(context.Background(), arrayObjEmpty.ID, nil, additional.Properties{}, "") 134 assert.Nil(t, err) 135 136 item1Schema := item1.Schema.(map[string]interface{}) 137 item2Schema := item2.Schema.(map[string]interface{}) 138 delete(item1Schema, "id") 139 delete(item2Schema, "id") 140 assert.Equal(t, item1Schema, item2Schema) 141 }) 142 } 143 } 144 145 func createRepo(t *testing.T) (*Migrator, *DB, *fakeSchemaGetter) { 146 schemaGetter := &fakeSchemaGetter{ 147 schema: schema.Schema{Objects: &models.Schema{Classes: nil}}, 148 shardState: singleShardState(), 149 } 150 logger, _ := test.NewNullLogger() 151 dirName := t.TempDir() 152 repo, err := New(logger, Config{ 153 MemtablesFlushDirtyAfter: 60, 154 RootPath: dirName, 155 QueryMaximumResults: 10, 156 MaxImportGoroutinesFactor: 1, 157 }, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, &fakeReplicationClient{}, nil) 158 require.Nil(t, err) 159 repo.SetSchemaGetter(schemaGetter) 160 require.Nil(t, repo.WaitForStartup(testCtx())) 161 return NewMigrator(repo, logger), repo, schemaGetter 162 } 163 164 func createClassWithEverything(IndexNullState bool, IndexPropertyLength bool) *models.Class { 165 return &models.Class{ 166 VectorIndexConfig: enthnsw.NewDefaultUserConfig(), 167 InvertedIndexConfig: &models.InvertedIndexConfig{ 168 CleanupIntervalSeconds: 60, 169 Stopwords: &models.StopwordConfig{ 170 Preset: "none", 171 }, 172 IndexNullState: IndexNullState, 173 IndexPropertyLength: IndexPropertyLength, 174 }, 175 Class: "EverythingClass", 176 Properties: []*models.Property{ 177 { 178 Name: "strings", 179 DataType: schema.DataTypeTextArray.PropString(), 180 Tokenization: models.PropertyTokenizationWhitespace, 181 }, 182 { 183 Name: "texts", 184 DataType: []string{"text[]"}, 185 Tokenization: models.PropertyTokenizationWord, 186 }, 187 { 188 Name: "numbers", 189 DataType: []string{"number[]"}, 190 }, 191 { 192 Name: "ints", 193 DataType: []string{"int[]"}, 194 }, 195 { 196 Name: "booleans", 197 DataType: []string{"boolean[]"}, 198 }, 199 { 200 Name: "datesAsStrings", 201 DataType: []string{"date[]"}, 202 }, 203 { 204 Name: "number", 205 DataType: []string{"number"}, 206 }, 207 { 208 Name: "bool", 209 DataType: []string{"boolean"}, 210 }, 211 { 212 Name: "int", 213 DataType: []string{"int"}, 214 }, 215 { 216 Name: "string", 217 DataType: schema.DataTypeText.PropString(), 218 Tokenization: models.PropertyTokenizationWhitespace, 219 }, 220 { 221 Name: "text", 222 DataType: []string{"text"}, 223 }, 224 { 225 Name: "phoneNumber", 226 DataType: []string{"phoneNumber"}, 227 }, 228 { 229 Name: "phoneNumbers", 230 DataType: []string{"phoneNumber[]"}, 231 }, 232 }, 233 } 234 }