// _ _ // __ _____ __ ___ ___ __ _| |_ ___ // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ // \ V V / __/ (_| |\ V /| | (_| | || __/ // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| // // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. // // CONTACT: hello@weaviate.io // package schema import ( "context" "fmt" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/weaviate/weaviate/adapters/repos/db/helpers" "github.com/weaviate/weaviate/adapters/repos/db/inverted/stopwords" "github.com/weaviate/weaviate/entities/models" "github.com/weaviate/weaviate/entities/schema" "github.com/weaviate/weaviate/usecases/config" "github.com/weaviate/weaviate/usecases/sharding" ) func TestAddClass(t *testing.T) { t.Run("with empty class name", func(t *testing.T) { err := newSchemaManager().AddClass(context.Background(), nil, &models.Class{}) require.EqualError(t, err, "'' is not a valid class name") }) t.Run("with permuted-casing class names", func(t *testing.T) { mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, &models.Class{Class: "NewClass"}) require.Nil(t, err) err = mgr.AddClass(context.Background(), nil, &models.Class{Class: "NewCLASS"}) require.NotNil(t, err) require.Equal(t, "class name \"NewCLASS\" already exists as a permutation of: \"NewClass\". "+ "class names must be unique when lowercased", err.Error()) }) t.Run("with default BM25 params", func(t *testing.T) { mgr := newSchemaManager() expectedBM25Config := &models.BM25Config{ K1: config.DefaultBM25k1, B: config.DefaultBM25b, } err := mgr.AddClass(context.Background(), nil, &models.Class{Class: "NewClass"}) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, "NewClass", mgr.schemaCache.ObjectSchema.Classes[0].Class) require.Equal(t, expectedBM25Config, mgr.schemaCache.ObjectSchema.Classes[0].InvertedIndexConfig.Bm25) }) t.Run("with customized BM25 params", func(t *testing.T) { mgr := newSchemaManager() expectedBM25Config := &models.BM25Config{ K1: 1.88, B: 0.44, } err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", InvertedIndexConfig: &models.InvertedIndexConfig{ Bm25: expectedBM25Config, }, }) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, "NewClass", mgr.schemaCache.ObjectSchema.Classes[0].Class) require.Equal(t, expectedBM25Config, mgr.schemaCache.ObjectSchema.Classes[0].InvertedIndexConfig.Bm25) }) t.Run("with default Stopwords config", func(t *testing.T) { mgr := newSchemaManager() expectedStopwordConfig := &models.StopwordConfig{ Preset: stopwords.EnglishPreset, } err := mgr.AddClass(context.Background(), nil, &models.Class{Class: "NewClass"}) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, "NewClass", mgr.schemaCache.ObjectSchema.Classes[0].Class) require.Equal(t, expectedStopwordConfig, mgr.schemaCache.ObjectSchema.Classes[0].InvertedIndexConfig.Stopwords) }) t.Run("with customized Stopwords config", func(t *testing.T) { mgr := newSchemaManager() expectedStopwordConfig := &models.StopwordConfig{ Preset: "none", Additions: []string{"monkey", "zebra", "octopus"}, Removals: []string{"are"}, } err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", InvertedIndexConfig: &models.InvertedIndexConfig{ Stopwords: expectedStopwordConfig, }, }) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, "NewClass", mgr.schemaCache.ObjectSchema.Classes[0].Class) require.Equal(t, expectedStopwordConfig, mgr.schemaCache.ObjectSchema.Classes[0].InvertedIndexConfig.Stopwords) }) t.Run("with tokenizations", func(t *testing.T) { type testCase struct { propName string dataType []string tokenization string expectedErrMsg string } propName := func(dataType schema.DataType, tokenization string) string { dtStr := strings.ReplaceAll(string(dataType), "[]", "Array") tStr := "empty" if tokenization != "" { tStr = tokenization } return fmt.Sprintf("%s_%s", dtStr, tStr) } runTestCases := func(t *testing.T, testCases []testCase, mgr *Manager) { for i, tc := range testCases { t.Run(tc.propName, func(t *testing.T) { err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: fmt.Sprintf("NewClass_%d", i), Properties: []*models.Property{ { Name: tc.propName, DataType: tc.dataType, Tokenization: tc.tokenization, }, }, }) if tc.expectedErrMsg == "" { require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) } else { require.EqualError(t, err, tc.expectedErrMsg) } }) } } t.Run("text/textArray and all tokenizations", func(t *testing.T) { testCases := []testCase{} for _, dataType := range []schema.DataType{ schema.DataTypeText, schema.DataTypeTextArray, } { for _, tokenization := range append(helpers.Tokenizations, "") { testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType.PropString(), tokenization: tokenization, expectedErrMsg: "", }) } tokenization := "non_existing" testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType.PropString(), tokenization: tokenization, expectedErrMsg: fmt.Sprintf("Tokenization '%s' is not allowed for data type '%s'", tokenization, dataType), }) } runTestCases(t, testCases, newSchemaManager()) }) t.Run("non text/textArray and all tokenizations", func(t *testing.T) { testCases := []testCase{} for _, dataType := range schema.PrimitiveDataTypes { switch dataType { case schema.DataTypeText, schema.DataTypeTextArray: continue default: tokenization := "" testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType.PropString(), tokenization: tokenization, expectedErrMsg: "", }) for _, tokenization := range append(helpers.Tokenizations, "non_existing") { testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType.PropString(), tokenization: tokenization, expectedErrMsg: fmt.Sprintf("Tokenization is not allowed for data type '%s'", dataType), }) } } } runTestCases(t, testCases, newSchemaManager()) }) t.Run("non text/textArray and all tokenizations", func(t *testing.T) { ctx := context.Background() mgr := newSchemaManager() _, err := mgr.addClass(ctx, &models.Class{Class: "SomeClass"}) require.Nil(t, err) _, err = mgr.addClass(ctx, &models.Class{Class: "SomeOtherClass"}) require.Nil(t, err) _, err = mgr.addClass(ctx, &models.Class{Class: "YetAnotherClass"}) require.Nil(t, err) testCases := []testCase{} for i, dataType := range [][]string{ {"SomeClass"}, {"SomeOtherClass", "YetAnotherClass"}, } { testCases = append(testCases, testCase{ propName: fmt.Sprintf("RefProp_%d_empty", i), dataType: dataType, tokenization: "", expectedErrMsg: "", }) for _, tokenization := range append(helpers.Tokenizations, "non_existing") { testCases = append(testCases, testCase{ propName: fmt.Sprintf("RefProp_%d_%s", i, tokenization), dataType: dataType, tokenization: tokenization, expectedErrMsg: "Tokenization is not allowed for reference data type", }) } } runTestCases(t, testCases, mgr) }) t.Run("[deprecated string] string/stringArray and all tokenizations", func(t *testing.T) { testCases := []testCase{} for _, dataType := range []schema.DataType{ schema.DataTypeString, schema.DataTypeStringArray, } { for _, tokenization := range []string{ models.PropertyTokenizationWord, models.PropertyTokenizationField, "", } { testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType.PropString(), tokenization: tokenization, expectedErrMsg: "", }) } for _, tokenization := range append(helpers.Tokenizations, "non_existing") { switch tokenization { case models.PropertyTokenizationWord, models.PropertyTokenizationField: continue default: testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType.PropString(), tokenization: tokenization, expectedErrMsg: fmt.Sprintf("Tokenization '%s' is not allowed for data type '%s'", tokenization, dataType), }) } } } runTestCases(t, testCases, newSchemaManager()) }) }) t.Run("with default vector distance metric", func(t *testing.T) { mgr := newSchemaManager() expected := fakeVectorConfig{raw: map[string]interface{}{"distance": "cosine"}} err := mgr.AddClass(context.Background(), nil, &models.Class{Class: "NewClass"}) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, "NewClass", mgr.schemaCache.ObjectSchema.Classes[0].Class) require.Equal(t, expected, mgr.schemaCache.ObjectSchema.Classes[0].VectorIndexConfig) }) t.Run("with default vector distance metric when class already has VectorIndexConfig", func(t *testing.T) { mgr := newSchemaManager() expected := fakeVectorConfig{raw: map[string]interface{}{ "distance": "cosine", "otherVectorIndexConfig": "1234", }} err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", VectorIndexConfig: map[string]interface{}{ "otherVectorIndexConfig": "1234", }, }) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, "NewClass", mgr.schemaCache.ObjectSchema.Classes[0].Class) require.Equal(t, expected, mgr.schemaCache.ObjectSchema.Classes[0].VectorIndexConfig) }) t.Run("with customized distance metric", func(t *testing.T) { mgr := newSchemaManager() expected := fakeVectorConfig{ raw: map[string]interface{}{"distance": "l2-squared"}, } err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", VectorIndexConfig: map[string]interface{}{ "distance": "l2-squared", }, }) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, "NewClass", mgr.schemaCache.ObjectSchema.Classes[0].Class) require.Equal(t, expected, mgr.schemaCache.ObjectSchema.Classes[0].VectorIndexConfig) }) t.Run("with two identical prop names", func(t *testing.T) { mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", Properties: []*models.Property{ { Name: "my_prop", DataType: []string{"text"}, }, { Name: "my_prop", DataType: []string{"int"}, }, }, }) require.NotNil(t, err) assert.Contains(t, err.Error(), "conflict for property") }) t.Run("trying to add an identical prop later", func(t *testing.T) { mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", Properties: []*models.Property{ { Name: "my_prop", DataType: []string{"text"}, }, { Name: "otherProp", DataType: []string{"text"}, }, }, }) require.Nil(t, err) attempts := []string{ "my_prop", // lowercase, same casing "my_Prop", // lowercase, different casing "otherProp", // mixed case, same casing "otherprop", // mixed case, all lower "OtHerProP", // mixed case, other casing } for _, propName := range attempts { t.Run(propName, func(t *testing.T) { err = mgr.AddClassProperty(context.Background(), nil, "NewClass", &models.Property{ Name: propName, DataType: []string{"int"}, }) require.NotNil(t, err) assert.Contains(t, err.Error(), "conflict for property") }) } }) // To prevent a regression on // https://github.com/weaviate/weaviate/issues/2530 t.Run("with two props that are identical when ignoring casing", func(t *testing.T) { mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", Properties: []*models.Property{ { Name: "my_prop", DataType: []string{"text"}, }, { Name: "mY_PrOP", DataType: []string{"int"}, }, }, }) require.NotNil(t, err) assert.Contains(t, err.Error(), "conflict for property") }) t.Run("with multi tenancy enabled", func(t *testing.T) { t.Run("valid multiTenancyConfig", func(t *testing.T) { class := &models.Class{ Class: "NewClass", Properties: []*models.Property{ { Name: "textProp", DataType: []string{"text"}, }, }, MultiTenancyConfig: &models.MultiTenancyConfig{ Enabled: true, }, } mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, class) require.Nil(t, err) require.NotNil(t, class.ShardingConfig) require.Zero(t, class.ShardingConfig.(sharding.Config).DesiredCount) }) t.Run("multiTenancyConfig and shardingConfig both provided", func(t *testing.T) { mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", Properties: []*models.Property{ { Name: "uuidProp", DataType: []string{"uuid"}, }, }, MultiTenancyConfig: &models.MultiTenancyConfig{ Enabled: true, }, ShardingConfig: map[string]interface{}{ "desiredCount": 2, }, }, ) require.NotNil(t, err) require.Equal(t, "cannot have both shardingConfig and multiTenancyConfig", err.Error()) }) t.Run("multiTenancyConfig and shardingConfig both provided but multi tenancy config is set to false", func(t *testing.T) { mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass1", Properties: []*models.Property{ { Name: "uuidProp", DataType: []string{"uuid"}, }, }, MultiTenancyConfig: &models.MultiTenancyConfig{ Enabled: false, }, ShardingConfig: map[string]interface{}{ "desiredCount": 2, }, }, ) require.Nil(t, err) }) t.Run("multiTenancyConfig and shardingConfig both provided but multi tenancy config is empty", func(t *testing.T) { mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", Properties: []*models.Property{ { Name: "uuidProp", DataType: []string{"uuid"}, }, }, MultiTenancyConfig: &models.MultiTenancyConfig{}, ShardingConfig: map[string]interface{}{ "desiredCount": 2, }, }, ) require.Nil(t, err) }) t.Run("multiTenancyConfig and shardingConfig both provided but multi tenancy is nil", func(t *testing.T) { mgr := newSchemaManager() err := mgr.AddClass(context.Background(), nil, &models.Class{ Class: "NewClass", Properties: []*models.Property{ { Name: "uuidProp", DataType: []string{"uuid"}, }, }, MultiTenancyConfig: nil, ShardingConfig: map[string]interface{}{ "desiredCount": 2, }, }, ) require.Nil(t, err) }) }) } func TestAddClass_DefaultsAndMigration(t *testing.T) { t.Run("set defaults and migrate string|stringArray datatype and tokenization", func(t *testing.T) { type testCase struct { propName string dataType schema.DataType tokenization string expectedDataType schema.DataType expectedTokenization string } propName := func(dataType schema.DataType, tokenization string) string { return strings.ReplaceAll(fmt.Sprintf("%s_%s", dataType, tokenization), "[]", "Array") } mgr := newSchemaManager() ctx := context.Background() className := "MigrationClass" testCases := []testCase{} for _, dataType := range []schema.DataType{ schema.DataTypeText, schema.DataTypeTextArray, } { for _, tokenization := range helpers.Tokenizations { testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType, tokenization: tokenization, expectedDataType: dataType, expectedTokenization: tokenization, }) } tokenization := "" testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType, tokenization: tokenization, expectedDataType: dataType, expectedTokenization: models.PropertyTokenizationWord, }) } for _, dataType := range []schema.DataType{ schema.DataTypeString, schema.DataTypeStringArray, } { for _, tokenization := range []string{ models.PropertyTokenizationWord, models.PropertyTokenizationField, "", } { var expectedDataType schema.DataType switch dataType { case schema.DataTypeStringArray: expectedDataType = schema.DataTypeTextArray default: expectedDataType = schema.DataTypeText } var expectedTokenization string switch tokenization { case models.PropertyTokenizationField: expectedTokenization = models.PropertyTokenizationField default: expectedTokenization = models.PropertyTokenizationWhitespace } testCases = append(testCases, testCase{ propName: propName(dataType, tokenization), dataType: dataType, tokenization: tokenization, expectedDataType: expectedDataType, expectedTokenization: expectedTokenization, }) } } t.Run("create class with all properties", func(t *testing.T) { properties := []*models.Property{} for _, tc := range testCases { properties = append(properties, &models.Property{ Name: "created_" + tc.propName, DataType: tc.dataType.PropString(), Tokenization: tc.tokenization, }) } err := mgr.AddClass(ctx, nil, &models.Class{ Class: className, Properties: properties, }) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, className, mgr.schemaCache.ObjectSchema.Classes[0].Class) }) t.Run("add properties to existing class", func(t *testing.T) { for _, tc := range testCases { t.Run("added_"+tc.propName, func(t *testing.T) { err := mgr.addClassProperty(ctx, className, &models.Property{ Name: "added_" + tc.propName, DataType: tc.dataType.PropString(), Tokenization: tc.tokenization, }) require.Nil(t, err) }) } }) t.Run("verify defaults and migration", func(t *testing.T) { class := mgr.schemaCache.ObjectSchema.Classes[0] for _, tc := range testCases { t.Run("created_"+tc.propName, func(t *testing.T) { createdProperty, err := schema.GetPropertyByName(class, "created_"+tc.propName) require.Nil(t, err) assert.Equal(t, tc.expectedDataType.PropString(), createdProperty.DataType) assert.Equal(t, tc.expectedTokenization, createdProperty.Tokenization) }) t.Run("added_"+tc.propName, func(t *testing.T) { addedProperty, err := schema.GetPropertyByName(class, "added_"+tc.propName) require.Nil(t, err) assert.Equal(t, tc.expectedDataType.PropString(), addedProperty.DataType) assert.Equal(t, tc.expectedTokenization, addedProperty.Tokenization) }) } }) }) t.Run("set defaults and migrate IndexInverted to IndexFilterable + IndexSearchable", func(t *testing.T) { vFalse := false vTrue := true allBoolPtrs := []*bool{nil, &vFalse, &vTrue} type testCase struct { propName string dataType schema.DataType indexInverted *bool indexFilterable *bool indexSearchable *bool expectedInverted *bool expectedFilterable *bool expectedSearchable *bool } boolPtrToStr := func(ptr *bool) string { if ptr == nil { return "nil" } return fmt.Sprintf("%v", *ptr) } propName := func(dt schema.DataType, inverted, filterable, searchable *bool) string { return fmt.Sprintf("%s_inverted_%s_filterable_%s_searchable_%s", dt.String(), boolPtrToStr(inverted), boolPtrToStr(filterable), boolPtrToStr(searchable)) } mgr := newSchemaManager() ctx := context.Background() className := "MigrationClass" testCases := []testCase{} for _, dataType := range []schema.DataType{schema.DataTypeText, schema.DataTypeInt} { for _, inverted := range allBoolPtrs { for _, filterable := range allBoolPtrs { for _, searchable := range allBoolPtrs { if inverted != nil { if filterable != nil || searchable != nil { // invalid combination, indexInverted can not be set // together with indexFilterable or indexSearchable continue } } if searchable != nil && *searchable { if dataType != schema.DataTypeText { // invalid combination, indexSearchable can not be enabled // for non text/text[] data type continue } } switch dataType { case schema.DataTypeText: if inverted != nil { testCases = append(testCases, testCase{ propName: propName(dataType, inverted, filterable, searchable), dataType: dataType, indexInverted: inverted, indexFilterable: filterable, indexSearchable: searchable, expectedInverted: nil, expectedFilterable: inverted, expectedSearchable: inverted, }) } else { expectedFilterable := filterable if filterable == nil { expectedFilterable = &vTrue } expectedSearchable := searchable if searchable == nil { expectedSearchable = &vTrue } testCases = append(testCases, testCase{ propName: propName(dataType, inverted, filterable, searchable), dataType: dataType, indexInverted: inverted, indexFilterable: filterable, indexSearchable: searchable, expectedInverted: nil, expectedFilterable: expectedFilterable, expectedSearchable: expectedSearchable, }) } default: if inverted != nil { testCases = append(testCases, testCase{ propName: propName(dataType, inverted, filterable, searchable), dataType: dataType, indexInverted: inverted, indexFilterable: filterable, indexSearchable: searchable, expectedInverted: nil, expectedFilterable: inverted, expectedSearchable: &vFalse, }) } else { expectedFilterable := filterable if filterable == nil { expectedFilterable = &vTrue } expectedSearchable := searchable if searchable == nil { expectedSearchable = &vFalse } testCases = append(testCases, testCase{ propName: propName(dataType, inverted, filterable, searchable), dataType: dataType, indexInverted: inverted, indexFilterable: filterable, indexSearchable: searchable, expectedInverted: nil, expectedFilterable: expectedFilterable, expectedSearchable: expectedSearchable, }) } } } } } } t.Run("create class with all properties", func(t *testing.T) { properties := []*models.Property{} for _, tc := range testCases { properties = append(properties, &models.Property{ Name: "created_" + tc.propName, DataType: tc.dataType.PropString(), IndexInverted: tc.indexInverted, IndexFilterable: tc.indexFilterable, IndexSearchable: tc.indexSearchable, }) } err := mgr.AddClass(ctx, nil, &models.Class{ Class: className, Properties: properties, }) require.Nil(t, err) require.NotNil(t, mgr.schemaCache.ObjectSchema) require.NotEmpty(t, mgr.schemaCache.ObjectSchema.Classes) require.Equal(t, className, mgr.schemaCache.ObjectSchema.Classes[0].Class) }) t.Run("add properties to existing class", func(t *testing.T) { for _, tc := range testCases { t.Run("added_"+tc.propName, func(t *testing.T) { err := mgr.addClassProperty(ctx, className, &models.Property{ Name: "added_" + tc.propName, DataType: tc.dataType.PropString(), IndexInverted: tc.indexInverted, IndexFilterable: tc.indexFilterable, IndexSearchable: tc.indexSearchable, }) require.Nil(t, err) }) } }) t.Run("verify migration", func(t *testing.T) { class := mgr.schemaCache.ObjectSchema.Classes[0] for _, tc := range testCases { t.Run("created_"+tc.propName, func(t *testing.T) { createdProperty, err := schema.GetPropertyByName(class, "created_"+tc.propName) require.Nil(t, err) assert.Equal(t, tc.expectedInverted, createdProperty.IndexInverted) assert.Equal(t, tc.expectedFilterable, createdProperty.IndexFilterable) assert.Equal(t, tc.expectedSearchable, createdProperty.IndexSearchable) }) t.Run("added_"+tc.propName, func(t *testing.T) { addedProperty, err := schema.GetPropertyByName(class, "added_"+tc.propName) require.Nil(t, err) assert.Equal(t, tc.expectedInverted, addedProperty.IndexInverted) assert.Equal(t, tc.expectedFilterable, addedProperty.IndexFilterable) assert.Equal(t, tc.expectedSearchable, addedProperty.IndexSearchable) }) } }) }) } func Test_Defaults_NestedProperties(t *testing.T) { for _, pdt := range schema.PrimitiveDataTypes { t.Run(pdt.String(), func(t *testing.T) { nestedProperties := []*models.NestedProperty{ { Name: "nested_" + pdt.String(), DataType: pdt.PropString(), }, } for _, ndt := range schema.NestedDataTypes { t.Run(ndt.String(), func(t *testing.T) { propPrimitives := &models.Property{ Name: "objectProp", DataType: ndt.PropString(), NestedProperties: nestedProperties, } propLvl2Primitives := &models.Property{ Name: "objectPropLvl2", DataType: ndt.PropString(), NestedProperties: []*models.NestedProperty{ { Name: "nested_object", DataType: ndt.PropString(), NestedProperties: nestedProperties, }, }, } setPropertyDefaults(propPrimitives) setPropertyDefaults(propLvl2Primitives) t.Run("primitive data types", func(t *testing.T) { for _, np := range []*models.NestedProperty{ propPrimitives.NestedProperties[0], propLvl2Primitives.NestedProperties[0].NestedProperties[0], } { switch pdt { case schema.DataTypeText, schema.DataTypeTextArray: require.NotNil(t, np.IndexFilterable) assert.True(t, *np.IndexFilterable) require.NotNil(t, np.IndexSearchable) assert.True(t, *np.IndexSearchable) assert.Equal(t, models.PropertyTokenizationWord, np.Tokenization) case schema.DataTypeBlob: require.NotNil(t, np.IndexFilterable) assert.False(t, *np.IndexFilterable) require.NotNil(t, np.IndexSearchable) assert.False(t, *np.IndexSearchable) assert.Equal(t, "", np.Tokenization) default: require.NotNil(t, np.IndexFilterable) assert.True(t, *np.IndexFilterable) require.NotNil(t, np.IndexSearchable) assert.False(t, *np.IndexSearchable) assert.Equal(t, "", np.Tokenization) } } }) t.Run("nested data types", func(t *testing.T) { for _, indexFilterable := range []*bool{ propPrimitives.IndexFilterable, propLvl2Primitives.IndexFilterable, propLvl2Primitives.NestedProperties[0].IndexFilterable, } { require.NotNil(t, indexFilterable) assert.True(t, *indexFilterable) } for _, indexSearchable := range []*bool{ propPrimitives.IndexSearchable, propLvl2Primitives.IndexSearchable, propLvl2Primitives.NestedProperties[0].IndexSearchable, } { require.NotNil(t, indexSearchable) assert.False(t, *indexSearchable) } for _, tokenization := range []string{ propPrimitives.Tokenization, propLvl2Primitives.Tokenization, propLvl2Primitives.NestedProperties[0].Tokenization, } { assert.Equal(t, "", tokenization) } }) }) } }) } }