github.com/weaviate/weaviate@v1.24.6/usecases/schema/manager_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  package schema
    13  
    14  import (
    15  	"context"
    16  	"testing"
    17  
    18  	"github.com/sirupsen/logrus/hooks/test"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	"github.com/weaviate/weaviate/entities/models"
    22  	"github.com/weaviate/weaviate/entities/schema"
    23  	"github.com/weaviate/weaviate/usecases/cluster"
    24  	"github.com/weaviate/weaviate/usecases/config"
    25  	"github.com/weaviate/weaviate/usecases/scaler"
    26  	"github.com/weaviate/weaviate/usecases/schema/migrate"
    27  	"github.com/weaviate/weaviate/usecases/sharding"
    28  )
    29  
    30  // TODO: These tests don't match the overall testing style in Weaviate.
    31  // Refactor!
    32  type NilMigrator struct{}
    33  
    34  func (n *NilMigrator) AddClass(ctx context.Context, class *models.Class,
    35  	shardingState *sharding.State,
    36  ) error {
    37  	return nil
    38  }
    39  
    40  func (n *NilMigrator) DropClass(ctx context.Context, className string) error {
    41  	return nil
    42  }
    43  
    44  func (n *NilMigrator) UpdateClass(ctx context.Context, className string, newClassName *string) error {
    45  	return nil
    46  }
    47  
    48  func (n *NilMigrator) GetShardsQueueSize(ctx context.Context, className, tenant string) (map[string]int64, error) {
    49  	return nil, nil
    50  }
    51  
    52  func (n *NilMigrator) GetShardsStatus(ctx context.Context, className, tenant string) (map[string]string, error) {
    53  	return nil, nil
    54  }
    55  
    56  func (n *NilMigrator) UpdateShardStatus(ctx context.Context, className, shardName, targetStatus string) error {
    57  	return nil
    58  }
    59  
    60  func (n *NilMigrator) AddProperty(ctx context.Context, className string, prop *models.Property) error {
    61  	return nil
    62  }
    63  
    64  func (n *NilMigrator) NewTenants(ctx context.Context, class *models.Class, creates []*migrate.CreateTenantPayload) (commit func(success bool), err error) {
    65  	return func(bool) {}, nil
    66  }
    67  
    68  func (n *NilMigrator) UpdateTenants(ctx context.Context, class *models.Class, updates []*migrate.UpdateTenantPayload) (commit func(success bool), err error) {
    69  	return func(bool) {}, nil
    70  }
    71  
    72  func (n *NilMigrator) DeleteTenants(ctx context.Context, class *models.Class, tenants []string) (commit func(success bool), err error) {
    73  	return func(bool) {}, nil
    74  }
    75  
    76  func (n *NilMigrator) UpdateProperty(ctx context.Context, className string, propName string, newName *string) error {
    77  	return nil
    78  }
    79  
    80  func (n *NilMigrator) UpdatePropertyAddDataType(ctx context.Context, className string, propName string, newDataType string) error {
    81  	return nil
    82  }
    83  
    84  func (n *NilMigrator) DropProperty(ctx context.Context, className string, propName string) error {
    85  	return nil
    86  }
    87  
    88  func (n *NilMigrator) ValidateVectorIndexConfigUpdate(ctx context.Context,
    89  	old, updated schema.VectorIndexConfig,
    90  ) error {
    91  	return nil
    92  }
    93  
    94  func (n *NilMigrator) UpdateVectorIndexConfig(ctx context.Context, className string,
    95  	updated schema.VectorIndexConfig,
    96  ) error {
    97  	return nil
    98  }
    99  
   100  func (n *NilMigrator) ValidateVectorIndexConfigsUpdate(ctx context.Context,
   101  	old, updated map[string]schema.VectorIndexConfig,
   102  ) error {
   103  	return nil
   104  }
   105  
   106  func (n *NilMigrator) UpdateVectorIndexConfigs(ctx context.Context, className string,
   107  	updated map[string]schema.VectorIndexConfig,
   108  ) error {
   109  	return nil
   110  }
   111  
   112  func (n *NilMigrator) ValidateInvertedIndexConfigUpdate(ctx context.Context, old, updated *models.InvertedIndexConfig) error {
   113  	return nil
   114  }
   115  
   116  func (n *NilMigrator) UpdateInvertedIndexConfig(ctx context.Context, className string, updated *models.InvertedIndexConfig) error {
   117  	return nil
   118  }
   119  
   120  func (n *NilMigrator) RecalculateVectorDimensions(ctx context.Context) error {
   121  	return nil
   122  }
   123  
   124  func (n *NilMigrator) InvertedReindex(ctx context.Context, taskNames ...string) error {
   125  	return nil
   126  }
   127  
   128  func (n *NilMigrator) AdjustFilterablePropSettings(ctx context.Context) error {
   129  	return nil
   130  }
   131  
   132  func (n *NilMigrator) RecountProperties(ctx context.Context) error {
   133  	return nil
   134  }
   135  
   136  var schemaTests = []struct {
   137  	name string
   138  	fn   func(*testing.T, *Manager)
   139  }{
   140  	{name: "AddObjectClass", fn: testAddObjectClass},
   141  	{name: "AddObjectClassWithExplicitVectorizer", fn: testAddObjectClassExplicitVectorizer},
   142  	{name: "AddObjectClassWithImplicitVectorizer", fn: testAddObjectClassImplicitVectorizer},
   143  	{name: "AddObjectClassWithWrongVectorizer", fn: testAddObjectClassWrongVectorizer},
   144  	{name: "AddObjectClassWithWrongIndexType", fn: testAddObjectClassWrongIndexType},
   145  	{name: "RemoveObjectClass", fn: testRemoveObjectClass},
   146  	{name: "CantAddSameClassTwice", fn: testCantAddSameClassTwice},
   147  	{name: "CantAddSameClassTwiceDifferentKind", fn: testCantAddSameClassTwiceDifferentKinds},
   148  	{name: "AddPropertyDuringCreation", fn: testAddPropertyDuringCreation},
   149  	{name: "AddInvalidPropertyDuringCreation", fn: testAddInvalidPropertyDuringCreation},
   150  	{name: "AddInvalidPropertyWithEmptyDataTypeDuringCreation", fn: testAddInvalidPropertyWithEmptyDataTypeDuringCreation},
   151  	{name: "DropProperty", fn: testDropProperty},
   152  }
   153  
   154  func testAddObjectClass(t *testing.T, lsm *Manager) {
   155  	t.Parallel()
   156  
   157  	objectClassesNames := testGetClassNames(lsm)
   158  	assert.NotContains(t, objectClassesNames, "Car")
   159  
   160  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   161  		Class: "Car",
   162  		Properties: []*models.Property{{
   163  			DataType:     schema.DataTypeText.PropString(),
   164  			Tokenization: models.PropertyTokenizationWhitespace,
   165  			Name:         "dummy",
   166  		}},
   167  		VectorIndexConfig: map[string]interface{}{
   168  			"dummy": "this should be parsed",
   169  		},
   170  	})
   171  
   172  	assert.Nil(t, err)
   173  
   174  	objectClassesNames = testGetClassNames(lsm)
   175  	assert.Contains(t, objectClassesNames, "Car")
   176  
   177  	objectClasses := testGetClasses(lsm)
   178  	require.Len(t, objectClasses, 1)
   179  	assert.Equal(t, config.VectorizerModuleNone, objectClasses[0].Vectorizer)
   180  	assert.Equal(t, fakeVectorConfig{
   181  		raw: map[string]interface{}{
   182  			"distance": "cosine",
   183  			"dummy":    "this should be parsed",
   184  		},
   185  	}, objectClasses[0].VectorIndexConfig)
   186  	assert.Equal(t, int64(60), objectClasses[0].InvertedIndexConfig.CleanupIntervalSeconds,
   187  		"the default was set")
   188  }
   189  
   190  func testAddObjectClassExplicitVectorizer(t *testing.T, lsm *Manager) {
   191  	t.Parallel()
   192  
   193  	objectClassesNames := testGetClassNames(lsm)
   194  	assert.NotContains(t, objectClassesNames, "Car")
   195  
   196  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   197  		Vectorizer:      config.VectorizerModuleText2VecContextionary,
   198  		VectorIndexType: "hnsw",
   199  		Class:           "Car",
   200  		Properties: []*models.Property{{
   201  			DataType:     schema.DataTypeText.PropString(),
   202  			Tokenization: models.PropertyTokenizationWhitespace,
   203  			Name:         "dummy",
   204  		}},
   205  	})
   206  
   207  	assert.Nil(t, err)
   208  
   209  	objectClassesNames = testGetClassNames(lsm)
   210  	assert.Contains(t, objectClassesNames, "Car")
   211  
   212  	objectClasses := testGetClasses(lsm)
   213  	require.Len(t, objectClasses, 1)
   214  	assert.Equal(t, config.VectorizerModuleText2VecContextionary, objectClasses[0].Vectorizer)
   215  	assert.Equal(t, "hnsw", objectClasses[0].VectorIndexType)
   216  }
   217  
   218  func testAddObjectClassImplicitVectorizer(t *testing.T, lsm *Manager) {
   219  	t.Parallel()
   220  	lsm.config.DefaultVectorizerModule = config.VectorizerModuleText2VecContextionary
   221  
   222  	objectClassesNames := testGetClassNames(lsm)
   223  	assert.NotContains(t, objectClassesNames, "Car")
   224  
   225  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   226  		Class: "Car",
   227  		Properties: []*models.Property{{
   228  			DataType:     schema.DataTypeText.PropString(),
   229  			Tokenization: models.PropertyTokenizationWhitespace,
   230  			Name:         "dummy",
   231  		}},
   232  	})
   233  
   234  	assert.Nil(t, err)
   235  
   236  	objectClassesNames = testGetClassNames(lsm)
   237  	assert.Contains(t, objectClassesNames, "Car")
   238  
   239  	objectClasses := testGetClasses(lsm)
   240  	require.Len(t, objectClasses, 1)
   241  	assert.Equal(t, config.VectorizerModuleText2VecContextionary, objectClasses[0].Vectorizer)
   242  	assert.Equal(t, "hnsw", objectClasses[0].VectorIndexType)
   243  }
   244  
   245  func testAddObjectClassWrongVectorizer(t *testing.T, lsm *Manager) {
   246  	t.Parallel()
   247  
   248  	objectClassesNames := testGetClassNames(lsm)
   249  	assert.NotContains(t, objectClassesNames, "Car")
   250  
   251  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   252  		Class:      "Car",
   253  		Vectorizer: "vectorizer-5000000",
   254  		Properties: []*models.Property{{
   255  			DataType:     schema.DataTypeText.PropString(),
   256  			Tokenization: models.PropertyTokenizationWhitespace,
   257  			Name:         "dummy",
   258  		}},
   259  	})
   260  
   261  	require.NotNil(t, err)
   262  	assert.Equal(t, "vectorizer: invalid vectorizer \"vectorizer-5000000\"",
   263  		err.Error())
   264  }
   265  
   266  func testAddObjectClassWrongIndexType(t *testing.T, lsm *Manager) {
   267  	t.Parallel()
   268  
   269  	objectClassesNames := testGetClassNames(lsm)
   270  	assert.NotContains(t, objectClassesNames, "Car")
   271  
   272  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   273  		Class:           "Car",
   274  		VectorIndexType: "vector-index-2-million",
   275  		Properties: []*models.Property{{
   276  			DataType:     schema.DataTypeText.PropString(),
   277  			Tokenization: models.PropertyTokenizationWhitespace,
   278  			Name:         "dummy",
   279  		}},
   280  	})
   281  
   282  	require.NotNil(t, err)
   283  	assert.Equal(t, "unrecognized or unsupported vectorIndexType "+
   284  		"\"vector-index-2-million\"", err.Error())
   285  }
   286  
   287  func testRemoveObjectClass(t *testing.T, lsm *Manager) {
   288  	t.Parallel()
   289  
   290  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   291  		Class:      "Car",
   292  		Vectorizer: "text2vec-contextionary",
   293  		ModuleConfig: map[string]interface{}{
   294  			"text2vec-contextionary": map[string]interface{}{
   295  				"vectorizeClassName": true,
   296  			},
   297  		},
   298  	})
   299  
   300  	assert.Nil(t, err)
   301  
   302  	objectClasses := testGetClassNames(lsm)
   303  	assert.Contains(t, objectClasses, "Car")
   304  
   305  	// Now delete the class
   306  	err = lsm.DeleteClass(context.Background(), nil, "Car")
   307  	assert.Nil(t, err)
   308  
   309  	objectClasses = testGetClassNames(lsm)
   310  	assert.NotContains(t, objectClasses, "Car")
   311  }
   312  
   313  func testCantAddSameClassTwice(t *testing.T, lsm *Manager) {
   314  	t.Parallel()
   315  
   316  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   317  		Class:      "Car",
   318  		Vectorizer: "text2vec-contextionary",
   319  		ModuleConfig: map[string]interface{}{
   320  			"text2vec-contextionary": map[string]interface{}{
   321  				"vectorizeClassName": true,
   322  			},
   323  		},
   324  	})
   325  
   326  	assert.Nil(t, err)
   327  
   328  	// Add it again
   329  	err = lsm.AddClass(context.Background(), nil, &models.Class{
   330  		Class:      "Car",
   331  		Vectorizer: "text2vec-contextionary",
   332  		ModuleConfig: map[string]interface{}{
   333  			"text2vec-contextionary": map[string]interface{}{
   334  				"vectorizeClassName": true,
   335  			},
   336  		},
   337  	})
   338  
   339  	assert.NotNil(t, err)
   340  }
   341  
   342  func testCantAddSameClassTwiceDifferentKinds(t *testing.T, lsm *Manager) {
   343  	t.Parallel()
   344  
   345  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   346  		Class:      "Car",
   347  		Vectorizer: "text2vec-contextionary",
   348  		ModuleConfig: map[string]interface{}{
   349  			"text2vec-contextionary": map[string]interface{}{
   350  				"vectorizeClassName": true,
   351  			},
   352  		},
   353  	})
   354  
   355  	assert.Nil(t, err)
   356  
   357  	// Add it again, but with a different kind.
   358  	err = lsm.AddClass(context.Background(), nil, &models.Class{
   359  		ModuleConfig: map[string]interface{}{
   360  			"text2vec-contextionary": map[string]interface{}{
   361  				"vectorizeClassName": true,
   362  			},
   363  		},
   364  		Class:      "Car",
   365  		Vectorizer: "text2vec-contextionary",
   366  	})
   367  
   368  	assert.NotNil(t, err)
   369  }
   370  
   371  // TODO: parts of this test contain text2vec-contextionary logic, but parts are
   372  // also general logic
   373  func testAddPropertyDuringCreation(t *testing.T, lsm *Manager) {
   374  	t.Parallel()
   375  
   376  	vFalse := false
   377  	vTrue := true
   378  
   379  	var properties []*models.Property = []*models.Property{
   380  		{
   381  			Name:         "color",
   382  			DataType:     schema.DataTypeText.PropString(),
   383  			Tokenization: models.PropertyTokenizationWhitespace,
   384  			ModuleConfig: map[string]interface{}{
   385  				"text2vec-contextionary": map[string]interface{}{
   386  					"vectorizePropertyName": true,
   387  				},
   388  			},
   389  		},
   390  		{
   391  			Name:            "colorRaw1",
   392  			DataType:        schema.DataTypeText.PropString(),
   393  			Tokenization:    models.PropertyTokenizationWhitespace,
   394  			IndexFilterable: &vFalse,
   395  			IndexSearchable: &vFalse,
   396  			ModuleConfig: map[string]interface{}{
   397  				"text2vec-contextionary": map[string]interface{}{
   398  					"skip": true,
   399  				},
   400  			},
   401  		},
   402  		{
   403  			Name:            "colorRaw2",
   404  			DataType:        schema.DataTypeText.PropString(),
   405  			Tokenization:    models.PropertyTokenizationWhitespace,
   406  			IndexFilterable: &vTrue,
   407  			IndexSearchable: &vFalse,
   408  			ModuleConfig: map[string]interface{}{
   409  				"text2vec-contextionary": map[string]interface{}{
   410  					"skip": true,
   411  				},
   412  			},
   413  		},
   414  		{
   415  			Name:            "colorRaw3",
   416  			DataType:        schema.DataTypeText.PropString(),
   417  			Tokenization:    models.PropertyTokenizationWhitespace,
   418  			IndexFilterable: &vFalse,
   419  			IndexSearchable: &vTrue,
   420  			ModuleConfig: map[string]interface{}{
   421  				"text2vec-contextionary": map[string]interface{}{
   422  					"skip": true,
   423  				},
   424  			},
   425  		},
   426  		{
   427  			Name:            "colorRaw4",
   428  			DataType:        schema.DataTypeText.PropString(),
   429  			Tokenization:    models.PropertyTokenizationWhitespace,
   430  			IndexFilterable: &vTrue,
   431  			IndexSearchable: &vTrue,
   432  			ModuleConfig: map[string]interface{}{
   433  				"text2vec-contextionary": map[string]interface{}{
   434  					"skip": true,
   435  				},
   436  			},
   437  		},
   438  		{
   439  			Name:         "content",
   440  			DataType:     schema.DataTypeText.PropString(),
   441  			Tokenization: models.PropertyTokenizationWhitespace,
   442  			ModuleConfig: map[string]interface{}{
   443  				"text2vec-contextionary": map[string]interface{}{
   444  					"vectorizePropertyName": false,
   445  				},
   446  			},
   447  		},
   448  		{
   449  			Name:         "allDefault",
   450  			DataType:     schema.DataTypeText.PropString(),
   451  			Tokenization: models.PropertyTokenizationWhitespace,
   452  		},
   453  	}
   454  
   455  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   456  		Class:      "Car",
   457  		Properties: properties,
   458  	})
   459  	assert.Nil(t, err)
   460  
   461  	objectClasses := testGetClasses(lsm)
   462  	require.Len(t, objectClasses, 1)
   463  	require.Len(t, objectClasses[0].Properties, 7)
   464  	assert.Equal(t, objectClasses[0].Properties[0].Name, "color")
   465  	assert.Equal(t, objectClasses[0].Properties[0].DataType, schema.DataTypeText.PropString())
   466  
   467  	assert.True(t, lsm.IndexedInverted("Car", "color"), "color should be indexed")
   468  	assert.False(t, lsm.IndexedInverted("Car", "colorRaw1"), "colorRaw1 should not be indexed")
   469  	assert.True(t, lsm.IndexedInverted("Car", "colorRaw2"), "colorRaw2 should be indexed")
   470  	assert.True(t, lsm.IndexedInverted("Car", "colorRaw3"), "colorRaw3 should be indexed")
   471  	assert.True(t, lsm.IndexedInverted("Car", "colorRaw4"), "colorRaw4 should be indexed")
   472  	assert.True(t, lsm.IndexedInverted("Car", "allDefault"), "allDefault should be indexed")
   473  }
   474  
   475  func testAddInvalidPropertyDuringCreation(t *testing.T, lsm *Manager) {
   476  	t.Parallel()
   477  
   478  	var properties []*models.Property = []*models.Property{
   479  		{Name: "color", DataType: []string{"blurp"}},
   480  	}
   481  
   482  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   483  		Class:      "Car",
   484  		Properties: properties,
   485  	})
   486  	assert.NotNil(t, err)
   487  }
   488  
   489  func testAddInvalidPropertyWithEmptyDataTypeDuringCreation(t *testing.T, lsm *Manager) {
   490  	t.Parallel()
   491  
   492  	var properties []*models.Property = []*models.Property{
   493  		{Name: "color", DataType: []string{""}},
   494  	}
   495  
   496  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   497  		Class:      "Car",
   498  		Properties: properties,
   499  	})
   500  	assert.NotNil(t, err)
   501  }
   502  
   503  func testDropProperty(t *testing.T, lsm *Manager) {
   504  	// TODO: https://github.com/weaviate/weaviate/issues/973
   505  	// Remove skip
   506  
   507  	t.Skip()
   508  
   509  	t.Parallel()
   510  
   511  	var properties []*models.Property = []*models.Property{
   512  		{Name: "color", DataType: schema.DataTypeText.PropString(), Tokenization: models.PropertyTokenizationWhitespace},
   513  	}
   514  
   515  	err := lsm.AddClass(context.Background(), nil, &models.Class{
   516  		Class:      "Car",
   517  		Properties: properties,
   518  	})
   519  	assert.Nil(t, err)
   520  
   521  	objectClasses := testGetClasses(lsm)
   522  	require.Len(t, objectClasses, 1)
   523  	assert.Len(t, objectClasses[0].Properties, 1)
   524  
   525  	// Now drop the property
   526  	lsm.DeleteClassProperty(context.Background(), nil, "Car", "color")
   527  
   528  	objectClasses = testGetClasses(lsm)
   529  	require.Len(t, objectClasses, 1)
   530  	assert.Len(t, objectClasses[0].Properties, 0)
   531  }
   532  
   533  // This grant parent test setups up the temporary directory needed for the tests.
   534  func TestSchema(t *testing.T) {
   535  	// We need this test here to make sure that we wait until all child tests
   536  	// (that can be run in parallel) have finished, before cleaning up the temp directory.
   537  	t.Run("group", func(t *testing.T) {
   538  		for _, testCase := range schemaTests {
   539  			// Create a test case, and inject the etcd schema manager in there
   540  			// to reduce boilerplate in each separate test.
   541  			t.Run(testCase.name, func(t *testing.T) {
   542  				sm := newSchemaManager()
   543  				sm.StartServing(context.Background()) // will also mark tx manager as ready
   544  				testCase.fn(t, sm)
   545  			})
   546  		}
   547  	})
   548  }
   549  
   550  // New Local Schema *Manager
   551  func newSchemaManager() *Manager {
   552  	logger, _ := test.NewNullLogger()
   553  	vectorizerValidator := &fakeVectorizerValidator{
   554  		valid: []string{"text2vec-contextionary", "model1", "model2"},
   555  	}
   556  	dummyConfig := config.Config{
   557  		DefaultVectorizerModule:     config.VectorizerModuleNone,
   558  		DefaultVectorDistanceMetric: "cosine",
   559  	}
   560  	sm, err := NewManager(&NilMigrator{}, newFakeRepo(), logger, &fakeAuthorizer{},
   561  		dummyConfig, dummyParseVectorConfig, // only option for now
   562  		vectorizerValidator, dummyValidateInvertedConfig,
   563  		&fakeModuleConfig{}, &fakeClusterState{hosts: []string{"node1"}},
   564  		&fakeTxClient{}, &fakeTxPersistence{}, &fakeScaleOutManager{},
   565  	)
   566  	if err != nil {
   567  		panic(err.Error())
   568  	}
   569  
   570  	sm.StartServing(context.Background()) // will also mark tx manager as ready
   571  
   572  	return sm
   573  }
   574  
   575  func testGetClasses(l *Manager) []*models.Class {
   576  	var classes []*models.Class
   577  	schema, _ := l.GetSchema(nil)
   578  
   579  	classes = append(classes, schema.SemanticSchemaFor().Classes...)
   580  
   581  	return classes
   582  }
   583  
   584  func testGetClassNames(l *Manager) []string {
   585  	var names []string
   586  	schema, _ := l.GetSchema(nil)
   587  
   588  	// Extract all names
   589  	for _, class := range schema.SemanticSchemaFor().Classes {
   590  		names = append(names, class.Class)
   591  	}
   592  
   593  	return names
   594  }
   595  
   596  func Test_ParseVectorConfigOnDiskLoad(t *testing.T) {
   597  	logger, _ := test.NewNullLogger()
   598  
   599  	repo := newFakeRepo()
   600  	repo.schema = State{
   601  		ObjectSchema: &models.Schema{
   602  			Classes: []*models.Class{{
   603  				Class:             "Foo",
   604  				VectorIndexConfig: "parse me, i should be in some sort of an object",
   605  				VectorIndexType:   "hnsw", // will always be set when loading from disk
   606  			}},
   607  		},
   608  	}
   609  	sm, err := NewManager(&NilMigrator{}, repo, logger, &fakeAuthorizer{},
   610  		config.Config{DefaultVectorizerModule: config.VectorizerModuleNone},
   611  		dummyParseVectorConfig, // only option for now
   612  		&fakeVectorizerValidator{}, dummyValidateInvertedConfig,
   613  		&fakeModuleConfig{}, &fakeClusterState{hosts: []string{"node1"}},
   614  		&fakeTxClient{}, &fakeTxPersistence{}, &fakeScaleOutManager{},
   615  	)
   616  	require.Nil(t, err)
   617  
   618  	classes := sm.GetSchemaSkipAuth().Objects.Classes
   619  	assert.Equal(t, fakeVectorConfig{
   620  		raw: "parse me, i should be in some sort of an object",
   621  	}, classes[0].VectorIndexConfig)
   622  }
   623  
   624  func Test_ExtendSchemaWithExistingPropName(t *testing.T) {
   625  	logger, _ := test.NewNullLogger()
   626  
   627  	repo := newFakeRepo()
   628  	repo.schema = State{
   629  		ObjectSchema: &models.Schema{
   630  			Classes: []*models.Class{{
   631  				Class:             "Foo",
   632  				VectorIndexConfig: "parse me, i should be in some sort of an object",
   633  				VectorIndexType:   "hnsw", // will always be set when loading from disk
   634  				Properties: []*models.Property{{
   635  					Name:         "my_prop",
   636  					DataType:     schema.DataTypeText.PropString(),
   637  					Tokenization: models.PropertyTokenizationWhitespace,
   638  				}},
   639  			}},
   640  		},
   641  	}
   642  	sm, err := NewManager(&NilMigrator{}, repo, logger, &fakeAuthorizer{},
   643  		config.Config{DefaultVectorizerModule: config.VectorizerModuleNone},
   644  		dummyParseVectorConfig, // only option for now
   645  		&fakeVectorizerValidator{}, dummyValidateInvertedConfig,
   646  		&fakeModuleConfig{}, &fakeClusterState{hosts: []string{"node1"}},
   647  		&fakeTxClient{}, &fakeTxPersistence{}, &fakeScaleOutManager{},
   648  	)
   649  	require.Nil(t, err)
   650  
   651  	// exactly identical name
   652  	err = sm.AddClassProperty(context.Background(), nil, "Foo", &models.Property{
   653  		Name:     "my_prop",
   654  		DataType: []string{"int"},
   655  	})
   656  
   657  	require.NotNil(t, err)
   658  	assert.Contains(t, err.Error(), "conflict for property")
   659  
   660  	// identical if case insensitive
   661  	err = sm.AddClassProperty(context.Background(), nil, "Foo", &models.Property{
   662  		Name:     "mY_pROp",
   663  		DataType: []string{"int"},
   664  	})
   665  
   666  	require.NotNil(t, err)
   667  	assert.Contains(t, err.Error(), "conflict for property")
   668  }
   669  
   670  type fakeScaleOutManager struct{}
   671  
   672  func (f *fakeScaleOutManager) Scale(ctx context.Context,
   673  	className string, updated sharding.Config, _, _ int64,
   674  ) (*sharding.State, error) {
   675  	return nil, nil
   676  }
   677  
   678  func (f *fakeScaleOutManager) SetSchemaManager(sm scaler.SchemaManager) {
   679  }
   680  
   681  // does nothing as these do not involve crashes
   682  type fakeTxPersistence struct{}
   683  
   684  func (f *fakeTxPersistence) StoreTx(ctx context.Context,
   685  	tx *cluster.Transaction,
   686  ) error {
   687  	return nil
   688  }
   689  
   690  func (f *fakeTxPersistence) DeleteTx(ctx context.Context,
   691  	txID string,
   692  ) error {
   693  	return nil
   694  }
   695  
   696  func (f *fakeTxPersistence) IterateAll(ctx context.Context,
   697  	cb func(tx *cluster.Transaction),
   698  ) error {
   699  	return nil
   700  }
   701  
   702  type fakeBroadcaster struct {
   703  	openErr       error
   704  	commitErr     error
   705  	abortErr      error
   706  	abortCalledId string
   707  }
   708  
   709  func (f *fakeBroadcaster) BroadcastTransaction(ctx context.Context,
   710  	tx *cluster.Transaction,
   711  ) error {
   712  	return f.openErr
   713  }
   714  
   715  func (f *fakeBroadcaster) BroadcastAbortTransaction(ctx context.Context,
   716  	tx *cluster.Transaction,
   717  ) error {
   718  	f.abortCalledId = tx.ID
   719  	return f.abortErr
   720  }
   721  
   722  func (f *fakeBroadcaster) BroadcastCommitTransaction(ctx context.Context,
   723  	tx *cluster.Transaction,
   724  ) error {
   725  	return f.commitErr
   726  }