github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/crud_references_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  // +build integrationTest
    14  
    15  package db
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"log"
    21  	"testing"
    22  
    23  	"github.com/go-openapi/strfmt"
    24  	"github.com/sirupsen/logrus"
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  	"github.com/weaviate/weaviate/entities/additional"
    28  	"github.com/weaviate/weaviate/entities/models"
    29  	"github.com/weaviate/weaviate/entities/schema"
    30  	"github.com/weaviate/weaviate/entities/schema/crossref"
    31  	"github.com/weaviate/weaviate/entities/search"
    32  	enthnsw "github.com/weaviate/weaviate/entities/vectorindex/hnsw"
    33  )
    34  
    35  func TestNestedReferences(t *testing.T) {
    36  	dirName := t.TempDir()
    37  
    38  	refSchema := schema.Schema{
    39  		Objects: &models.Schema{
    40  			Classes: []*models.Class{
    41  				{
    42  					Class:               "Planet",
    43  					VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
    44  					InvertedIndexConfig: invertedConfig(),
    45  					Properties: []*models.Property{
    46  						{
    47  							Name:         "name",
    48  							DataType:     schema.DataTypeText.PropString(),
    49  							Tokenization: models.PropertyTokenizationWhitespace,
    50  						},
    51  					},
    52  				},
    53  				{
    54  					Class:               "Continent",
    55  					VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
    56  					InvertedIndexConfig: invertedConfig(),
    57  					Properties: []*models.Property{
    58  						{
    59  							Name:         "name",
    60  							DataType:     schema.DataTypeText.PropString(),
    61  							Tokenization: models.PropertyTokenizationWhitespace,
    62  						},
    63  						{
    64  							Name:     "onPlanet",
    65  							DataType: []string{"Planet"},
    66  						},
    67  					},
    68  				},
    69  				{
    70  					Class:               "Country",
    71  					VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
    72  					InvertedIndexConfig: invertedConfig(),
    73  					Properties: []*models.Property{
    74  						{
    75  							Name:         "name",
    76  							DataType:     schema.DataTypeText.PropString(),
    77  							Tokenization: models.PropertyTokenizationWhitespace,
    78  						},
    79  						{
    80  							Name:     "onContinent",
    81  							DataType: []string{"Continent"},
    82  						},
    83  					},
    84  				},
    85  				{
    86  					Class:               "City",
    87  					VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
    88  					InvertedIndexConfig: invertedConfig(),
    89  					Properties: []*models.Property{
    90  						{
    91  							Name:         "name",
    92  							DataType:     schema.DataTypeText.PropString(),
    93  							Tokenization: models.PropertyTokenizationWhitespace,
    94  						},
    95  						{
    96  							Name:     "inCountry",
    97  							DataType: []string{"Country"},
    98  						},
    99  					},
   100  				},
   101  				{
   102  					Class:               "Place",
   103  					VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
   104  					InvertedIndexConfig: invertedConfig(),
   105  					Properties: []*models.Property{
   106  						{
   107  							Name:         "name",
   108  							DataType:     schema.DataTypeText.PropString(),
   109  							Tokenization: models.PropertyTokenizationWhitespace,
   110  						},
   111  						{
   112  							Name:     "inCity",
   113  							DataType: []string{"City"},
   114  						},
   115  					},
   116  				},
   117  			},
   118  		},
   119  	}
   120  	logger := logrus.New()
   121  	schemaGetter := &fakeSchemaGetter{
   122  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   123  		shardState: singleShardState(),
   124  	}
   125  	repo, err := New(logger, Config{
   126  		MemtablesFlushDirtyAfter:  60,
   127  		RootPath:                  dirName,
   128  		MaxImportGoroutinesFactor: 1,
   129  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, &fakeReplicationClient{}, nil)
   130  	require.Nil(t, err)
   131  	repo.SetSchemaGetter(schemaGetter)
   132  	require.Nil(t, repo.WaitForStartup(testCtx()))
   133  	defer repo.Shutdown(context.Background())
   134  	migrator := NewMigrator(repo, logger)
   135  
   136  	t.Run("adding all classes to the schema", func(t *testing.T) {
   137  		for _, class := range refSchema.Objects.Classes {
   138  			t.Run(fmt.Sprintf("add %s", class.Class), func(t *testing.T) {
   139  				err := migrator.AddClass(context.Background(), class, schemaGetter.shardState)
   140  				require.Nil(t, err)
   141  			})
   142  		}
   143  	})
   144  
   145  	// update schema getter so it's in sync with class
   146  	schemaGetter.schema = refSchema
   147  
   148  	t.Run("importing some thing objects with references", func(t *testing.T) {
   149  		objects := []models.Object{
   150  			{
   151  				Class: "Planet",
   152  				Properties: map[string]interface{}{
   153  					"name": "Earth",
   154  				},
   155  				ID:               "32c69af9-cbbe-4ec9-bf6c-365cd6c22fdf",
   156  				CreationTimeUnix: 1566464889,
   157  			},
   158  			{
   159  				Class: "Continent",
   160  				Properties: map[string]interface{}{
   161  					"name": "North America",
   162  					"onPlanet": models.MultipleRef{
   163  						&models.SingleRef{
   164  							Beacon: "weaviate://localhost/32c69af9-cbbe-4ec9-bf6c-365cd6c22fdf",
   165  						},
   166  					},
   167  				},
   168  				ID:               "4aad8154-e7f3-45b8-81a6-725171419e55",
   169  				CreationTimeUnix: 1566464892,
   170  			},
   171  			{
   172  				Class: "Country",
   173  				Properties: map[string]interface{}{
   174  					"name": "USA",
   175  					"onContinent": models.MultipleRef{
   176  						&models.SingleRef{
   177  							Beacon: "weaviate://localhost/4aad8154-e7f3-45b8-81a6-725171419e55",
   178  						},
   179  					},
   180  				},
   181  				ID:               "18c80a16-346a-477d-849d-9d92e5040ac9",
   182  				CreationTimeUnix: 1566464896,
   183  			},
   184  			{
   185  				Class: "City",
   186  				Properties: map[string]interface{}{
   187  					"name": "San Francisco",
   188  					"inCountry": models.MultipleRef{
   189  						&models.SingleRef{
   190  							Beacon: "weaviate://localhost/18c80a16-346a-477d-849d-9d92e5040ac9",
   191  						},
   192  					},
   193  				},
   194  				ID:               "2297e094-6218-43d4-85b1-3d20af752f23",
   195  				CreationTimeUnix: 1566464899,
   196  			},
   197  			{
   198  				Class: "Place",
   199  				Properties: map[string]interface{}{
   200  					"name": "Tim Apple's Fruit Bar",
   201  					"inCity": models.MultipleRef{
   202  						&models.SingleRef{
   203  							Beacon: "weaviate://localhost/2297e094-6218-43d4-85b1-3d20af752f23",
   204  						},
   205  					},
   206  				},
   207  				ID:               "4ef47fb0-3cf5-44fc-b378-9e217dff13ac",
   208  				CreationTimeUnix: 1566464904,
   209  			},
   210  		}
   211  
   212  		for _, thing := range objects {
   213  			t.Run(fmt.Sprintf("add %s", thing.ID), func(t *testing.T) {
   214  				err := repo.PutObject(context.Background(), &thing, []float32{1, 2, 3, 4, 5, 6, 7}, nil, nil)
   215  				require.Nil(t, err)
   216  			})
   217  		}
   218  	})
   219  
   220  	t.Run("fully resolving the place", func(t *testing.T) {
   221  		expectedSchema := map[string]interface{}{
   222  			"inCity": []interface{}{
   223  				search.LocalRef{
   224  					Class: "City",
   225  					Fields: map[string]interface{}{
   226  						"inCountry": []interface{}{
   227  							search.LocalRef{
   228  								Class: "Country",
   229  								Fields: map[string]interface{}{
   230  									"onContinent": []interface{}{
   231  										search.LocalRef{
   232  											Class: "Continent",
   233  											Fields: map[string]interface{}{
   234  												"onPlanet": []interface{}{
   235  													search.LocalRef{
   236  														Class: "Planet",
   237  														Fields: map[string]interface{}{
   238  															"name": "Earth",
   239  															"id":   strfmt.UUID("32c69af9-cbbe-4ec9-bf6c-365cd6c22fdf"),
   240  														},
   241  													},
   242  												},
   243  												"name": "North America",
   244  												"id":   strfmt.UUID("4aad8154-e7f3-45b8-81a6-725171419e55"),
   245  											},
   246  										},
   247  									},
   248  									"name": "USA",
   249  									"id":   strfmt.UUID("18c80a16-346a-477d-849d-9d92e5040ac9"),
   250  								},
   251  							},
   252  						},
   253  						"name": "San Francisco",
   254  						"id":   strfmt.UUID("2297e094-6218-43d4-85b1-3d20af752f23"),
   255  					},
   256  				},
   257  			},
   258  			"name": "Tim Apple's Fruit Bar",
   259  			"id":   strfmt.UUID("4ef47fb0-3cf5-44fc-b378-9e217dff13ac"),
   260  		}
   261  
   262  		res, err := repo.ObjectByID(context.Background(), "4ef47fb0-3cf5-44fc-b378-9e217dff13ac", fullyNestedSelectProperties(), additional.Properties{}, "")
   263  		require.Nil(t, err)
   264  		assert.Equal(t, expectedSchema, res.Schema)
   265  	})
   266  
   267  	t.Run("fully resolving the place with vectors", func(t *testing.T) {
   268  		expectedSchema := map[string]interface{}{
   269  			"inCity": []interface{}{
   270  				search.LocalRef{
   271  					Class: "City",
   272  					Fields: map[string]interface{}{
   273  						"inCountry": []interface{}{
   274  							search.LocalRef{
   275  								Class: "Country",
   276  								Fields: map[string]interface{}{
   277  									"onContinent": []interface{}{
   278  										search.LocalRef{
   279  											Class: "Continent",
   280  											Fields: map[string]interface{}{
   281  												"onPlanet": []interface{}{
   282  													search.LocalRef{
   283  														Class: "Planet",
   284  														Fields: map[string]interface{}{
   285  															"name":   "Earth",
   286  															"id":     strfmt.UUID("32c69af9-cbbe-4ec9-bf6c-365cd6c22fdf"),
   287  															"vector": []float32{1, 2, 3, 4, 5, 6, 7},
   288  														},
   289  													},
   290  												},
   291  												"name":   "North America",
   292  												"id":     strfmt.UUID("4aad8154-e7f3-45b8-81a6-725171419e55"),
   293  												"vector": []float32{1, 2, 3, 4, 5, 6, 7},
   294  											},
   295  										},
   296  									},
   297  									"name":   "USA",
   298  									"id":     strfmt.UUID("18c80a16-346a-477d-849d-9d92e5040ac9"),
   299  									"vector": []float32{1, 2, 3, 4, 5, 6, 7},
   300  								},
   301  							},
   302  						},
   303  						"name":   "San Francisco",
   304  						"id":     strfmt.UUID("2297e094-6218-43d4-85b1-3d20af752f23"),
   305  						"vector": []float32{1, 2, 3, 4, 5, 6, 7},
   306  					},
   307  				},
   308  			},
   309  			"name": "Tim Apple's Fruit Bar",
   310  			"id":   strfmt.UUID("4ef47fb0-3cf5-44fc-b378-9e217dff13ac"),
   311  		}
   312  
   313  		res, err := repo.ObjectByID(context.Background(), "4ef47fb0-3cf5-44fc-b378-9e217dff13ac", fullyNestedSelectPropertiesWithVector(), additional.Properties{}, "")
   314  		require.Nil(t, err)
   315  		assert.Equal(t, expectedSchema, res.Schema)
   316  	})
   317  
   318  	t.Run("partially resolving the place", func(t *testing.T) {
   319  		expectedSchema := map[string]interface{}{
   320  			"inCity": []interface{}{
   321  				search.LocalRef{
   322  					Class: "City",
   323  					Fields: map[string]interface{}{
   324  						"name": "San Francisco",
   325  						"id":   strfmt.UUID("2297e094-6218-43d4-85b1-3d20af752f23"),
   326  						// why is inCountry present here? We didn't specify it our select
   327  						// properties. Note it is "inCountry" with a lowercase letter
   328  						// (meaning unresolved) whereas "inCountry" would mean it was
   329  						// resolved. In GraphQL this property would simply be hidden (as
   330  						// the GQL is unaware of unresolved properties)
   331  						// However, for caching and other queries it is helpful that this
   332  						// info is still present, the important thing is that we're
   333  						// avoiding the costly resolving of it, if we don't need it.
   334  						"inCountry": models.MultipleRef{
   335  							&models.SingleRef{
   336  								Beacon: "weaviate://localhost/18c80a16-346a-477d-849d-9d92e5040ac9",
   337  							},
   338  						},
   339  					},
   340  				},
   341  			},
   342  			"name": "Tim Apple's Fruit Bar",
   343  			"id":   strfmt.UUID("4ef47fb0-3cf5-44fc-b378-9e217dff13ac"),
   344  		}
   345  
   346  		res, err := repo.ObjectByID(context.Background(), "4ef47fb0-3cf5-44fc-b378-9e217dff13ac", partiallyNestedSelectProperties(), additional.Properties{}, "")
   347  		require.Nil(t, err)
   348  		assert.Equal(t, expectedSchema, res.Schema)
   349  	})
   350  
   351  	t.Run("resolving without any refs", func(t *testing.T) {
   352  		res, err := repo.ObjectByID(context.Background(), "4ef47fb0-3cf5-44fc-b378-9e217dff13ac", search.SelectProperties{}, additional.Properties{}, "")
   353  
   354  		expectedSchema := map[string]interface{}{
   355  			"id": strfmt.UUID("4ef47fb0-3cf5-44fc-b378-9e217dff13ac"),
   356  			"inCity": models.MultipleRef{
   357  				&models.SingleRef{
   358  					Beacon: "weaviate://localhost/2297e094-6218-43d4-85b1-3d20af752f23",
   359  				},
   360  			},
   361  			"name": "Tim Apple's Fruit Bar",
   362  		}
   363  
   364  		require.Nil(t, err)
   365  
   366  		assert.Equal(t, expectedSchema, res.Schema, "does not contain any resolved refs")
   367  	})
   368  
   369  	t.Run("adding a new place to verify idnexing is constantly happening in the background", func(t *testing.T) {
   370  		newPlace := models.Object{
   371  			Class: "Place",
   372  			Properties: map[string]interface{}{
   373  				"name": "John Oliver's Avocados",
   374  				"inCity": models.MultipleRef{
   375  					&models.SingleRef{
   376  						Beacon: "weaviate://localhost/2297e094-6218-43d4-85b1-3d20af752f23",
   377  					},
   378  				},
   379  			},
   380  			ID:               "0f02d525-902d-4dc0-8052-647cb420c1a6",
   381  			CreationTimeUnix: 1566464912,
   382  		}
   383  
   384  		err := repo.PutObject(context.Background(), &newPlace, []float32{1, 2, 3, 4, 5, 6, 7}, nil, nil)
   385  		require.Nil(t, err)
   386  	})
   387  }
   388  
   389  func fullyNestedSelectProperties() search.SelectProperties {
   390  	return search.SelectProperties{
   391  		search.SelectProperty{
   392  			Name:        "inCity",
   393  			IsPrimitive: false,
   394  			Refs: []search.SelectClass{
   395  				{
   396  					ClassName: "City",
   397  					RefProperties: search.SelectProperties{
   398  						search.SelectProperty{
   399  							Name:        "inCountry",
   400  							IsPrimitive: false,
   401  							Refs: []search.SelectClass{
   402  								{
   403  									ClassName: "Country",
   404  									RefProperties: search.SelectProperties{
   405  										search.SelectProperty{
   406  											Name:        "onContinent",
   407  											IsPrimitive: false,
   408  											Refs: []search.SelectClass{
   409  												{
   410  													ClassName: "Continent",
   411  													RefProperties: search.SelectProperties{
   412  														search.SelectProperty{
   413  															Name:        "onPlanet",
   414  															IsPrimitive: false,
   415  															Refs: []search.SelectClass{
   416  																{
   417  																	ClassName:     "Planet",
   418  																	RefProperties: nil,
   419  																},
   420  															},
   421  														},
   422  													},
   423  												},
   424  											},
   425  										},
   426  									},
   427  								},
   428  							},
   429  						},
   430  					},
   431  				},
   432  			},
   433  		},
   434  	}
   435  }
   436  
   437  func fullyNestedSelectPropertiesWithVector() search.SelectProperties {
   438  	return search.SelectProperties{
   439  		search.SelectProperty{
   440  			Name:        "inCity",
   441  			IsPrimitive: false,
   442  			Refs: []search.SelectClass{
   443  				{
   444  					ClassName: "City",
   445  					RefProperties: search.SelectProperties{
   446  						search.SelectProperty{
   447  							Name:        "inCountry",
   448  							IsPrimitive: false,
   449  							Refs: []search.SelectClass{
   450  								{
   451  									ClassName: "Country",
   452  									RefProperties: search.SelectProperties{
   453  										search.SelectProperty{
   454  											Name:        "onContinent",
   455  											IsPrimitive: false,
   456  											Refs: []search.SelectClass{
   457  												{
   458  													ClassName: "Continent",
   459  													RefProperties: search.SelectProperties{
   460  														search.SelectProperty{
   461  															Name:        "onPlanet",
   462  															IsPrimitive: false,
   463  															Refs: []search.SelectClass{
   464  																{
   465  																	ClassName:     "Planet",
   466  																	RefProperties: nil,
   467  																	AdditionalProperties: additional.Properties{
   468  																		Vector: true,
   469  																	},
   470  																},
   471  															},
   472  														},
   473  													},
   474  													AdditionalProperties: additional.Properties{
   475  														Vector: true,
   476  													},
   477  												},
   478  											},
   479  										},
   480  									},
   481  									AdditionalProperties: additional.Properties{
   482  										Vector: true,
   483  									},
   484  								},
   485  							},
   486  						},
   487  					},
   488  					AdditionalProperties: additional.Properties{
   489  						Vector: true,
   490  					},
   491  				},
   492  			},
   493  		},
   494  	}
   495  }
   496  
   497  func partiallyNestedSelectProperties() search.SelectProperties {
   498  	return search.SelectProperties{
   499  		search.SelectProperty{
   500  			Name:        "inCity",
   501  			IsPrimitive: false,
   502  			Refs: []search.SelectClass{
   503  				{
   504  					ClassName:     "City",
   505  					RefProperties: search.SelectProperties{},
   506  				},
   507  			},
   508  		},
   509  	}
   510  }
   511  
   512  func GetDimensionsFromRepo(repo *DB, className string) int {
   513  	if !repo.config.TrackVectorDimensions {
   514  		log.Printf("Vector dimensions tracking is disabled, returning 0")
   515  		return 0
   516  	}
   517  	index := repo.GetIndex(schema.ClassName(className))
   518  	sum := 0
   519  	index.ForEachShard(func(name string, shard ShardLike) error {
   520  		sum += shard.Dimensions()
   521  		return nil
   522  	})
   523  	return sum
   524  }
   525  
   526  func GetQuantizedDimensionsFromRepo(repo *DB, className string, segments int) int {
   527  	if !repo.config.TrackVectorDimensions {
   528  		log.Printf("Vector dimensions tracking is disabled, returning 0")
   529  		return 0
   530  	}
   531  	index := repo.GetIndex(schema.ClassName(className))
   532  	sum := 0
   533  	index.ForEachShard(func(name string, shard ShardLike) error {
   534  		sum += shard.QuantizedDimensions(segments)
   535  		return nil
   536  	})
   537  	return sum
   538  }
   539  
   540  func Test_AddingReferenceOneByOne(t *testing.T) {
   541  	dirName := t.TempDir()
   542  
   543  	sch := schema.Schema{
   544  		Objects: &models.Schema{
   545  			Classes: []*models.Class{
   546  				{
   547  					Class:               "AddingReferencesTestTarget",
   548  					VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
   549  					InvertedIndexConfig: invertedConfig(),
   550  					Properties: []*models.Property{
   551  						{
   552  							Name:         "name",
   553  							DataType:     schema.DataTypeText.PropString(),
   554  							Tokenization: models.PropertyTokenizationWhitespace,
   555  						},
   556  					},
   557  				},
   558  				{
   559  					Class:               "AddingReferencesTestSource",
   560  					VectorIndexConfig:   enthnsw.NewDefaultUserConfig(),
   561  					InvertedIndexConfig: invertedConfig(),
   562  					Properties: []*models.Property{
   563  						{
   564  							Name:         "name",
   565  							DataType:     schema.DataTypeText.PropString(),
   566  							Tokenization: models.PropertyTokenizationWhitespace,
   567  						},
   568  						{
   569  							Name:     "toTarget",
   570  							DataType: []string{"AddingReferencesTestTarget"},
   571  						},
   572  					},
   573  				},
   574  			},
   575  		},
   576  	}
   577  	logger := logrus.New()
   578  	schemaGetter := &fakeSchemaGetter{
   579  		schema:     schema.Schema{Objects: &models.Schema{Classes: nil}},
   580  		shardState: singleShardState(),
   581  	}
   582  	repo, err := New(logger, Config{
   583  		MemtablesFlushDirtyAfter:  60,
   584  		RootPath:                  dirName,
   585  		MaxImportGoroutinesFactor: 1,
   586  		TrackVectorDimensions:     true,
   587  	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, &fakeReplicationClient{}, nil)
   588  	require.Nil(t, err)
   589  	repo.SetSchemaGetter(schemaGetter)
   590  	require.Nil(t, repo.WaitForStartup(testCtx()))
   591  	defer repo.Shutdown(context.Background())
   592  	migrator := NewMigrator(repo, logger)
   593  
   594  	t.Run("add required classes", func(t *testing.T) {
   595  		for _, class := range sch.Objects.Classes {
   596  			t.Run(fmt.Sprintf("add %s", class.Class), func(t *testing.T) {
   597  				err := migrator.AddClass(context.Background(), class, schemaGetter.shardState)
   598  				require.Nil(t, err)
   599  			})
   600  		}
   601  	})
   602  
   603  	schemaGetter.schema = sch
   604  	targetID := strfmt.UUID("a4a92239-e748-4e55-bbbd-f606926619a7")
   605  	target2ID := strfmt.UUID("325084e7-4faa-43a5-b2b1-56e207be169a")
   606  	sourceID := strfmt.UUID("0826c61b-85c1-44ac-aebb-cfd07ace6a57")
   607  
   608  	t.Run("add objects", func(t *testing.T) {
   609  		err := repo.PutObject(context.Background(), &models.Object{
   610  			ID:    sourceID,
   611  			Class: "AddingReferencesTestSource",
   612  			Properties: map[string]interface{}{
   613  				"name": "source item",
   614  			},
   615  		}, []float32{0.5}, nil, nil)
   616  		require.Nil(t, err)
   617  
   618  		err = repo.PutObject(context.Background(), &models.Object{
   619  			ID:    targetID,
   620  			Class: "AddingReferencesTestTarget",
   621  			Properties: map[string]interface{}{
   622  				"name": "target item",
   623  			},
   624  		}, []float32{0.5}, nil, nil)
   625  		require.Nil(t, err)
   626  
   627  		err = repo.PutObject(context.Background(), &models.Object{
   628  			ID:    target2ID,
   629  			Class: "AddingReferencesTestTarget",
   630  			Properties: map[string]interface{}{
   631  				"name": "another target item",
   632  			},
   633  		}, []float32{0.5}, nil, nil)
   634  		require.Nil(t, err)
   635  	})
   636  
   637  	t.Run("add reference between them", func(t *testing.T) {
   638  		// Get dimensions before adding reference
   639  		sourceShardDimension := GetDimensionsFromRepo(repo, "AddingReferencesTestSource")
   640  		targetShardDimension := GetDimensionsFromRepo(repo, "AddingReferencesTestTarget")
   641  
   642  		source := crossref.NewSource("AddingReferencesTestSource", "toTarget", sourceID)
   643  		target := crossref.New("localhost", "", targetID)
   644  
   645  		err := repo.AddReference(context.Background(), source, target, nil, "")
   646  		assert.Nil(t, err)
   647  
   648  		// Check dimensions after adding reference
   649  		sourceDimensionAfter := GetDimensionsFromRepo(repo, "AddingReferencesTestSource")
   650  		targetDimensionAfter := GetDimensionsFromRepo(repo, "AddingReferencesTestTarget")
   651  
   652  		require.Equalf(t, sourceShardDimension, sourceDimensionAfter, "dimensions of source should not change")
   653  		require.Equalf(t, targetShardDimension, targetDimensionAfter, "dimensions of target should not change")
   654  	})
   655  
   656  	t.Run("check reference was added", func(t *testing.T) {
   657  		source, err := repo.ObjectByID(context.Background(), sourceID, nil, additional.Properties{}, "")
   658  		require.Nil(t, err)
   659  		require.NotNil(t, source)
   660  		require.NotNil(t, source.Object())
   661  		require.NotNil(t, source.Object().Properties)
   662  
   663  		refs := source.Object().Properties.(map[string]interface{})["toTarget"]
   664  		refsSlice, ok := refs.(models.MultipleRef)
   665  		require.True(t, ok,
   666  			fmt.Sprintf("toTarget must be models.MultipleRef, but got %#v", refs))
   667  
   668  		foundBeacons := []string{}
   669  		for _, ref := range refsSlice {
   670  			foundBeacons = append(foundBeacons, ref.Beacon.String())
   671  		}
   672  		expectedBeacons := []string{
   673  			fmt.Sprintf("weaviate://localhost/%s", targetID),
   674  		}
   675  
   676  		assert.ElementsMatch(t, foundBeacons, expectedBeacons)
   677  	})
   678  
   679  	t.Run("reference a second target", func(t *testing.T) {
   680  		source := crossref.NewSource("AddingReferencesTestSource", "toTarget", sourceID)
   681  		target := crossref.New("localhost", "", target2ID)
   682  
   683  		err := repo.AddReference(context.Background(), source, target, nil, "")
   684  		assert.Nil(t, err)
   685  	})
   686  
   687  	t.Run("check both references are now present", func(t *testing.T) {
   688  		source, err := repo.ObjectByID(context.Background(), sourceID, nil, additional.Properties{}, "")
   689  		require.Nil(t, err)
   690  		require.NotNil(t, source)
   691  		require.NotNil(t, source.Object())
   692  		require.NotNil(t, source.Object().Properties)
   693  
   694  		refs := source.Object().Properties.(map[string]interface{})["toTarget"]
   695  		refsSlice, ok := refs.(models.MultipleRef)
   696  		require.True(t, ok,
   697  			fmt.Sprintf("toTarget must be models.MultipleRef, but got %#v", refs))
   698  
   699  		foundBeacons := []string{}
   700  		for _, ref := range refsSlice {
   701  			foundBeacons = append(foundBeacons, ref.Beacon.String())
   702  		}
   703  		expectedBeacons := []string{
   704  			fmt.Sprintf("weaviate://localhost/%s", targetID),
   705  			fmt.Sprintf("weaviate://localhost/%s", target2ID),
   706  		}
   707  
   708  		assert.ElementsMatch(t, foundBeacons, expectedBeacons)
   709  	})
   710  }