KevinStephenson
Adding in weaviate code
b110593
raw
history blame
30.8 kB
// _ _
// __ _____ __ ___ ___ __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
// \ V V / __/ (_| |\ V /| | (_| | || __/
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
//
// CONTACT: [email protected]
//
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)
}
})
})
}
})
}
}