github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/refcache/cacher_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 refcache
    13  
    14  import (
    15  	"context"
    16  	"fmt"
    17  	"testing"
    18  
    19  	"github.com/go-openapi/strfmt"
    20  	"github.com/sirupsen/logrus/hooks/test"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  	"github.com/weaviate/weaviate/entities/additional"
    24  	"github.com/weaviate/weaviate/entities/models"
    25  	"github.com/weaviate/weaviate/entities/multi"
    26  	"github.com/weaviate/weaviate/entities/search"
    27  )
    28  
    29  func TestCacher(t *testing.T) {
    30  	// some ids to be used in the tests, they carry no meaning outside each test
    31  	id1 := "132bdf92-ffec-4a52-9196-73ea7cbb5a5e"
    32  	id2 := "a60a26dc-791a-41fc-8dda-c0f21f90cc98"
    33  	id3 := "a60a26dc-791a-41fc-8dda-c0f21f90cc99"
    34  	id4 := "a60a26dc-791a-41fc-8dda-c0f21f90cc97"
    35  
    36  	t.Run("with empty results", func(t *testing.T) {
    37  		repo := newFakeRepo()
    38  		logger, _ := test.NewNullLogger()
    39  		cr := NewCacher(repo, logger, "")
    40  		err := cr.Build(context.Background(), nil, nil, additional.Properties{})
    41  		assert.Nil(t, err)
    42  	})
    43  
    44  	t.Run("with results with nil-schemas", func(t *testing.T) {
    45  		repo := newFakeRepo()
    46  		logger, _ := test.NewNullLogger()
    47  		cr := NewCacher(repo, logger, "")
    48  		input := []search.Result{
    49  			{
    50  				ID:        "foo",
    51  				ClassName: "BestClass",
    52  			},
    53  		}
    54  		err := cr.Build(context.Background(), input, nil, additional.Properties{})
    55  		assert.Nil(t, err)
    56  	})
    57  
    58  	t.Run("with results without refs in the schema", func(t *testing.T) {
    59  		repo := newFakeRepo()
    60  		logger, _ := test.NewNullLogger()
    61  		cr := NewCacher(repo, logger, "")
    62  		input := []search.Result{
    63  			{
    64  				ID:        "foo",
    65  				ClassName: "BestClass",
    66  				Schema: map[string]interface{}{
    67  					"foo": "bar",
    68  					"baz": &models.PhoneNumber{},
    69  				},
    70  			},
    71  		}
    72  		err := cr.Build(context.Background(), input, nil, additional.Properties{})
    73  		assert.Nil(t, err)
    74  	})
    75  
    76  	t.Run("with a single ref, but no selectprops", func(t *testing.T) {
    77  		repo := newFakeRepo()
    78  		logger, _ := test.NewNullLogger()
    79  		cr := NewCacher(repo, logger, "")
    80  		input := []search.Result{
    81  			{
    82  				ID:        "foo",
    83  				ClassName: "BestClass",
    84  				Schema: map[string]interface{}{
    85  					"refProp": models.MultipleRef{
    86  						&models.SingleRef{
    87  							Beacon: "weaviate://localhost/123",
    88  						},
    89  					},
    90  				},
    91  			},
    92  		}
    93  		err := cr.Build(context.Background(), input, nil, additional.Properties{})
    94  		require.Nil(t, err)
    95  		_, ok := cr.Get(multi.Identifier{ID: "123", ClassName: "SomeClass"})
    96  		assert.False(t, ok)
    97  	})
    98  
    99  	t.Run("with a single ref, and a matching select prop", func(t *testing.T) {
   100  		repo := newFakeRepo()
   101  		repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{
   102  			ClassName: "SomeClass",
   103  			ID:        strfmt.UUID(id1),
   104  			Schema: map[string]interface{}{
   105  				"bar": "some string",
   106  			},
   107  		}
   108  		logger, _ := test.NewNullLogger()
   109  		cr := NewCacher(repo, logger, "")
   110  		input := []search.Result{
   111  			{
   112  				ID:        "foo",
   113  				ClassName: "BestClass",
   114  				Schema: map[string]interface{}{
   115  					"refProp": models.MultipleRef{
   116  						&models.SingleRef{
   117  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   118  						},
   119  					},
   120  				},
   121  			},
   122  		}
   123  		selectProps := search.SelectProperties{
   124  			search.SelectProperty{
   125  				Name: "refProp",
   126  				Refs: []search.SelectClass{
   127  					{
   128  						ClassName: "SomeClass",
   129  						RefProperties: search.SelectProperties{
   130  							search.SelectProperty{
   131  								Name:        "bar",
   132  								IsPrimitive: true,
   133  							},
   134  						},
   135  					},
   136  				},
   137  			},
   138  		}
   139  
   140  		expected := search.Result{
   141  			ID:        strfmt.UUID(id1),
   142  			ClassName: "SomeClass",
   143  			Schema: map[string]interface{}{
   144  				"bar": "some string",
   145  			},
   146  		}
   147  
   148  		err := cr.Build(context.Background(), input, selectProps, additional.Properties{})
   149  		require.Nil(t, err)
   150  		res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"})
   151  		require.True(t, ok)
   152  		assert.Equal(t, expected, res)
   153  		assert.Equal(t, 1, repo.counter, "required the expected amount of lookups")
   154  	})
   155  
   156  	t.Run("with a nested lookup, partially resolved", func(t *testing.T) {
   157  		repo := newFakeRepo()
   158  		repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{
   159  			ClassName: "SomeClass",
   160  			ID:        strfmt.UUID(id1),
   161  			Schema: map[string]interface{}{
   162  				"primitive": "foobar",
   163  				"ignoredRef": models.MultipleRef{
   164  					&models.SingleRef{
   165  						Beacon: strfmt.URI("weaviate://localhost/ignoreMe"),
   166  					},
   167  				},
   168  				"nestedRef": models.MultipleRef{
   169  					&models.SingleRef{
   170  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   171  					},
   172  				},
   173  			},
   174  		}
   175  		repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{
   176  			ClassName: "SomeNestedClass",
   177  			ID:        strfmt.UUID(id2),
   178  			Schema: map[string]interface{}{
   179  				"name": "John Doe",
   180  			},
   181  		}
   182  		logger, _ := test.NewNullLogger()
   183  		cr := NewCacher(repo, logger, "")
   184  		input := []search.Result{
   185  			{
   186  				ID:        "foo",
   187  				ClassName: "BestClass",
   188  				Schema: map[string]interface{}{
   189  					"refProp": models.MultipleRef{
   190  						&models.SingleRef{
   191  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   192  						},
   193  					},
   194  				},
   195  			},
   196  		}
   197  		selectProps := search.SelectProperties{
   198  			search.SelectProperty{
   199  				Name: "refProp",
   200  				Refs: []search.SelectClass{
   201  					{
   202  						ClassName: "SomeClass",
   203  						RefProperties: search.SelectProperties{
   204  							search.SelectProperty{
   205  								Name:        "primitive",
   206  								IsPrimitive: true,
   207  							},
   208  							search.SelectProperty{
   209  								Name: "nestedRef",
   210  								Refs: []search.SelectClass{
   211  									{
   212  										ClassName: "SomeNestedClass",
   213  										RefProperties: []search.SelectProperty{
   214  											{
   215  												Name:        "name",
   216  												IsPrimitive: true,
   217  											},
   218  										},
   219  									},
   220  								},
   221  							},
   222  						},
   223  					},
   224  				},
   225  			},
   226  		}
   227  
   228  		expectedOuter := search.Result{
   229  			ID:        strfmt.UUID(id1),
   230  			ClassName: "SomeClass",
   231  			Schema: map[string]interface{}{
   232  				"primitive": "foobar",
   233  				"ignoredRef": models.MultipleRef{
   234  					&models.SingleRef{
   235  						Beacon: strfmt.URI("weaviate://localhost/ignoreMe"),
   236  					},
   237  				},
   238  				"nestedRef": models.MultipleRef{
   239  					&models.SingleRef{
   240  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   241  					},
   242  				},
   243  			},
   244  		}
   245  
   246  		expectedInner := search.Result{
   247  			ClassName: "SomeNestedClass",
   248  			ID:        strfmt.UUID(id2),
   249  			Schema: map[string]interface{}{
   250  				"name": "John Doe",
   251  			},
   252  		}
   253  
   254  		err := cr.Build(context.Background(), input, selectProps, additional.Properties{})
   255  		require.Nil(t, err)
   256  		res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"})
   257  		require.True(t, ok)
   258  		assert.Equal(t, expectedOuter, res)
   259  		res, ok = cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"})
   260  		require.True(t, ok)
   261  		assert.Equal(t, expectedInner, res)
   262  		assert.Equal(t, 2, repo.counter, "required the expected amount of lookups")
   263  	})
   264  
   265  	t.Run("with multiple items pointing to the same ref", func(t *testing.T) {
   266  		// this test asserts that we do not make unnecessary requests if an object
   267  		// is linked twice on the list. (This is very common if the reference is
   268  		// used for something like a product category, e.g. it would not be
   269  		// uncommon at all if all search results are of the same category)
   270  		repo := newFakeRepo()
   271  		repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{
   272  			ClassName: "SomeClass",
   273  			ID:        strfmt.UUID(id1),
   274  			Schema: map[string]interface{}{
   275  				"primitive": "foobar",
   276  				"nestedRef": models.MultipleRef{
   277  					&models.SingleRef{
   278  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   279  					},
   280  				},
   281  			},
   282  		}
   283  		repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{
   284  			ClassName: "SomeNestedClass",
   285  			ID:        strfmt.UUID(id2),
   286  			Schema: map[string]interface{}{
   287  				"name": "John Doe",
   288  			},
   289  		}
   290  		logger, _ := test.NewNullLogger()
   291  		cr := NewCacher(repo, logger, "")
   292  
   293  		// contains three items, all pointing to the same inner class
   294  		input := []search.Result{
   295  			{
   296  				ID:        "foo",
   297  				ClassName: "BestClass",
   298  				Schema: map[string]interface{}{
   299  					"refProp": models.MultipleRef{
   300  						&models.SingleRef{
   301  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   302  						},
   303  					},
   304  				},
   305  			},
   306  			{
   307  				ID:        "bar",
   308  				ClassName: "BestClass",
   309  				Schema: map[string]interface{}{
   310  					"refProp": models.MultipleRef{
   311  						&models.SingleRef{
   312  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   313  						},
   314  					},
   315  				},
   316  			},
   317  			{
   318  				ID:        "baz",
   319  				ClassName: "BestClass",
   320  				Schema: map[string]interface{}{
   321  					"refProp": models.MultipleRef{
   322  						&models.SingleRef{
   323  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   324  						},
   325  					},
   326  				},
   327  			},
   328  		}
   329  		selectProps := search.SelectProperties{
   330  			search.SelectProperty{
   331  				Name: "refProp",
   332  				Refs: []search.SelectClass{
   333  					{
   334  						ClassName: "SomeClass",
   335  						RefProperties: search.SelectProperties{
   336  							search.SelectProperty{
   337  								Name:        "primitive",
   338  								IsPrimitive: true,
   339  							},
   340  							search.SelectProperty{
   341  								Name: "nestedRef",
   342  								Refs: []search.SelectClass{
   343  									{
   344  										ClassName: "SomeNestedClass",
   345  										RefProperties: []search.SelectProperty{
   346  											{
   347  												Name:        "name",
   348  												IsPrimitive: true,
   349  											},
   350  										},
   351  									},
   352  								},
   353  							},
   354  						},
   355  					},
   356  				},
   357  			},
   358  		}
   359  
   360  		expectedOuter := search.Result{
   361  			ID:        strfmt.UUID(id1),
   362  			ClassName: "SomeClass",
   363  			Schema: map[string]interface{}{
   364  				"primitive": "foobar",
   365  				"nestedRef": models.MultipleRef{
   366  					&models.SingleRef{
   367  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   368  					},
   369  				},
   370  			},
   371  		}
   372  
   373  		expectedInner := search.Result{
   374  			ClassName: "SomeNestedClass",
   375  			ID:        strfmt.UUID(id2),
   376  			Schema: map[string]interface{}{
   377  				"name": "John Doe",
   378  			},
   379  		}
   380  
   381  		err := cr.Build(context.Background(), input, selectProps, additional.Properties{})
   382  		require.Nil(t, err)
   383  		res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"})
   384  		require.True(t, ok)
   385  		assert.Equal(t, expectedOuter, res)
   386  		res, ok = cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"})
   387  		require.True(t, ok)
   388  		assert.Equal(t, expectedInner, res)
   389  		assert.Equal(t, 2, repo.counter, "required the expected amount of lookup queries")
   390  		assert.Equal(t, 2, repo.counter, "required the expected amount of objects on the lookup queries")
   391  	})
   392  
   393  	t.Run("with a nested lookup, and nested refs in nested refs", func(t *testing.T) {
   394  		repo := newFakeRepo()
   395  		idNested2ID := "132bdf92-ffec-4a52-9196-73ea7cbb5a00"
   396  		idNestedInNestedID := "132bdf92-ffec-4a52-9196-73ea7cbb5a01"
   397  		repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{
   398  			ClassName: "SomeClass",
   399  			ID:        strfmt.UUID(id1),
   400  			Schema: map[string]interface{}{
   401  				"primitive": "foobar",
   402  				"nestedRef": models.MultipleRef{
   403  					&models.SingleRef{
   404  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   405  					},
   406  				},
   407  				"nestedRef2": models.MultipleRef{
   408  					&models.SingleRef{
   409  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNested2ID)),
   410  						Schema: map[string]interface{}{
   411  							"title": "nestedRef2Title",
   412  							"nestedRefInNestedRef": models.MultipleRef{
   413  								&models.SingleRef{
   414  									Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNestedInNestedID)),
   415  								},
   416  							},
   417  						},
   418  					},
   419  				},
   420  			},
   421  		}
   422  		repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{
   423  			ClassName: "SomeNestedClass",
   424  			ID:        strfmt.UUID(id2),
   425  			Schema: map[string]interface{}{
   426  				"name": "John Doe",
   427  			},
   428  		}
   429  		repo.lookup[multi.Identifier{ID: idNested2ID, ClassName: "SomeNestedClass2"}] = search.Result{
   430  			ClassName: "SomeNestedClass2",
   431  			ID:        strfmt.UUID(idNested2ID),
   432  			Schema: map[string]interface{}{
   433  				"title": "nestedRef2Title",
   434  				"nestedRefInNestedRef": models.MultipleRef{
   435  					&models.SingleRef{
   436  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNestedInNestedID)),
   437  					},
   438  				},
   439  			},
   440  		}
   441  		repo.lookup[multi.Identifier{ID: idNestedInNestedID, ClassName: "SomeNestedClassNested2"}] = search.Result{
   442  			ClassName: "SomeNestedClassNested2",
   443  			ID:        strfmt.UUID(idNestedInNestedID),
   444  			Schema: map[string]interface{}{
   445  				"titleNested": "Nested In Nested Title",
   446  			},
   447  		}
   448  		logger, _ := test.NewNullLogger()
   449  		cr := NewCacher(repo, logger, "")
   450  		input := []search.Result{
   451  			{
   452  				ID:        "foo",
   453  				ClassName: "BestClass",
   454  				Schema: map[string]interface{}{
   455  					"refProp": models.MultipleRef{
   456  						&models.SingleRef{
   457  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   458  						},
   459  					},
   460  				},
   461  			},
   462  		}
   463  		selectProps := search.SelectProperties{
   464  			search.SelectProperty{
   465  				Name: "refProp",
   466  				Refs: []search.SelectClass{
   467  					{
   468  						ClassName: "SomeClass",
   469  						RefProperties: search.SelectProperties{
   470  							search.SelectProperty{
   471  								Name:        "primitive",
   472  								IsPrimitive: true,
   473  							},
   474  							search.SelectProperty{
   475  								Name: "nestedRef",
   476  								Refs: []search.SelectClass{
   477  									{
   478  										ClassName: "SomeNestedClass",
   479  										RefProperties: []search.SelectProperty{
   480  											{
   481  												Name:        "name",
   482  												IsPrimitive: true,
   483  											},
   484  										},
   485  									},
   486  								},
   487  							},
   488  							search.SelectProperty{
   489  								Name: "nestedRef2",
   490  								Refs: []search.SelectClass{
   491  									{
   492  										ClassName: "SomeNestedClass2",
   493  										RefProperties: []search.SelectProperty{
   494  											{
   495  												Name:        "title",
   496  												IsPrimitive: true,
   497  											},
   498  											{
   499  												Name: "nestedRefInNestedRef",
   500  												Refs: []search.SelectClass{
   501  													{
   502  														ClassName: "SomeNestedClassNested2",
   503  														RefProperties: []search.SelectProperty{
   504  															{
   505  																Name:        "titleNested",
   506  																IsPrimitive: true,
   507  															},
   508  														},
   509  													},
   510  												},
   511  											},
   512  										},
   513  									},
   514  								},
   515  							},
   516  						},
   517  					},
   518  				},
   519  			},
   520  		}
   521  
   522  		expectedOuter := search.Result{
   523  			ID:        strfmt.UUID(id1),
   524  			ClassName: "SomeClass",
   525  			Schema: map[string]interface{}{
   526  				"primitive": "foobar",
   527  				"nestedRef": models.MultipleRef{
   528  					&models.SingleRef{
   529  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   530  					},
   531  				},
   532  				"nestedRef2": models.MultipleRef{
   533  					&models.SingleRef{
   534  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNested2ID)),
   535  						Schema: map[string]interface{}{
   536  							"title": "nestedRef2Title",
   537  							"nestedRefInNestedRef": models.MultipleRef{
   538  								&models.SingleRef{
   539  									Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNestedInNestedID)),
   540  								},
   541  							},
   542  						},
   543  					},
   544  				},
   545  			},
   546  		}
   547  
   548  		expectedInner := search.Result{
   549  			ClassName: "SomeNestedClass",
   550  			ID:        strfmt.UUID(id2),
   551  			Schema: map[string]interface{}{
   552  				"name": "John Doe",
   553  			},
   554  		}
   555  
   556  		expectedInner2 := search.Result{
   557  			ClassName: "SomeNestedClass2",
   558  			ID:        strfmt.UUID(idNested2ID),
   559  			Schema: map[string]interface{}{
   560  				"title": "nestedRef2Title",
   561  				"nestedRefInNestedRef": models.MultipleRef{
   562  					&models.SingleRef{
   563  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", idNestedInNestedID)),
   564  					},
   565  				},
   566  			},
   567  		}
   568  
   569  		expectedInnerInner := search.Result{
   570  			ClassName: "SomeNestedClassNested2",
   571  			ID:        strfmt.UUID(idNestedInNestedID),
   572  			Schema: map[string]interface{}{
   573  				"titleNested": "Nested In Nested Title",
   574  			},
   575  		}
   576  
   577  		err := cr.Build(context.Background(), input, selectProps, additional.Properties{})
   578  		require.Nil(t, err)
   579  		res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"})
   580  		require.True(t, ok)
   581  		assert.Equal(t, expectedOuter, res)
   582  		input2 := []search.Result{expectedInner, expectedInner2}
   583  		err = cr.Build(context.Background(), input2, nil, additional.Properties{})
   584  		require.Nil(t, err)
   585  		nested1, ok := cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"})
   586  		require.True(t, ok)
   587  		assert.Equal(t, expectedInner, nested1)
   588  		nested2, ok := cr.Get(multi.Identifier{ID: idNested2ID, ClassName: "SomeNestedClass2"})
   589  		require.True(t, ok)
   590  		assert.Equal(t, expectedInner2, nested2)
   591  		nestedSchema, ok := nested2.Schema.(map[string]interface{})
   592  		require.True(t, ok)
   593  		nestedRefInNestedRef, ok := nestedSchema["nestedRefInNestedRef"]
   594  		require.True(t, ok)
   595  		require.NotNil(t, nestedRefInNestedRef)
   596  		nestedRefInNestedMultiRef, ok := nestedRefInNestedRef.(models.MultipleRef)
   597  		require.True(t, ok)
   598  		require.NotNil(t, nestedRefInNestedMultiRef)
   599  		require.Nil(t, err)
   600  		res, ok = cr.Get(multi.Identifier{ID: idNestedInNestedID, ClassName: "SomeNestedClassNested2"})
   601  		require.True(t, ok)
   602  		assert.Equal(t, expectedInnerInner, res)
   603  		assert.Equal(t, 3, repo.counter, "required the expected amount of lookups")
   604  	})
   605  
   606  	t.Run("with group and with a additional group lookup", func(t *testing.T) {
   607  		repo := newFakeRepo()
   608  		repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{
   609  			ClassName: "SomeClass",
   610  			ID:        strfmt.UUID(id1),
   611  			Schema: map[string]interface{}{
   612  				"primitive": "foobar",
   613  			},
   614  			AdditionalProperties: models.AdditionalProperties{
   615  				"group": &additional.Group{
   616  					Hits: []map[string]interface{}{
   617  						{
   618  							"primitive": "foobar",
   619  							"ignoredRef": models.MultipleRef{
   620  								&models.SingleRef{
   621  									Beacon: strfmt.URI("weaviate://localhost/ignoreMe"),
   622  								},
   623  							},
   624  							"nestedRef": models.MultipleRef{
   625  								&models.SingleRef{
   626  									Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   627  								},
   628  							},
   629  						},
   630  					},
   631  				},
   632  			},
   633  		}
   634  		repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{
   635  			ClassName: "SomeNestedClass",
   636  			ID:        strfmt.UUID(id2),
   637  			Schema: map[string]interface{}{
   638  				"name": "John Doe",
   639  			},
   640  		}
   641  		logger, _ := test.NewNullLogger()
   642  		cr := NewCacherWithGroup(repo, logger, "")
   643  		input := []search.Result{
   644  			{
   645  				ID:        "foo",
   646  				ClassName: "BestClass",
   647  				Schema: map[string]interface{}{
   648  					"refProp": models.MultipleRef{
   649  						&models.SingleRef{
   650  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   651  						},
   652  					},
   653  				},
   654  				AdditionalProperties: models.AdditionalProperties{
   655  					"group": &additional.Group{
   656  						Hits: []map[string]interface{}{
   657  							{
   658  								"primitive": "foobar",
   659  								"ignoredRef": models.MultipleRef{
   660  									&models.SingleRef{
   661  										Beacon: strfmt.URI("weaviate://localhost/ignoreMe"),
   662  									},
   663  								},
   664  								"nestedRef": models.MultipleRef{
   665  									&models.SingleRef{
   666  										Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   667  									},
   668  								},
   669  							},
   670  						},
   671  					},
   672  				},
   673  			},
   674  		}
   675  		selectProps := search.SelectProperties{
   676  			search.SelectProperty{
   677  				Name: "_additional:group:hits:nestedRef",
   678  				Refs: []search.SelectClass{
   679  					{
   680  						ClassName: "SomeNestedClass",
   681  						RefProperties: []search.SelectProperty{
   682  							{
   683  								Name:        "name",
   684  								IsPrimitive: true,
   685  							},
   686  						},
   687  					},
   688  				},
   689  			},
   690  		}
   691  
   692  		expectedInner := search.Result{
   693  			ClassName: "SomeNestedClass",
   694  			ID:        strfmt.UUID(id2),
   695  			Schema: map[string]interface{}{
   696  				"name": "John Doe",
   697  			},
   698  		}
   699  
   700  		err := cr.Build(context.Background(), input, selectProps, additional.Properties{})
   701  		require.Nil(t, err)
   702  		res, ok := cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"})
   703  		require.True(t, ok)
   704  		assert.Equal(t, expectedInner, res)
   705  		assert.Equal(t, 1, repo.counter, "required the expected amount of lookups")
   706  	})
   707  
   708  	t.Run("with group and with 2 additional group lookups", func(t *testing.T) {
   709  		repo := newFakeRepo()
   710  		repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{
   711  			ClassName: "SomeClass",
   712  			ID:        strfmt.UUID(id1),
   713  			Schema: map[string]interface{}{
   714  				"primitive": "foobar",
   715  			},
   716  			AdditionalProperties: models.AdditionalProperties{
   717  				"group": &additional.Group{
   718  					Hits: []map[string]interface{}{
   719  						{
   720  							"primitive": "foobar",
   721  							"ignoredRef": models.MultipleRef{
   722  								&models.SingleRef{
   723  									Beacon: strfmt.URI("weaviate://localhost/ignoreMe"),
   724  								},
   725  							},
   726  							"nestedRef": models.MultipleRef{
   727  								&models.SingleRef{
   728  									Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   729  								},
   730  							},
   731  						},
   732  					},
   733  				},
   734  			},
   735  		}
   736  		repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{
   737  			ClassName: "SomeNestedClass",
   738  			ID:        strfmt.UUID(id2),
   739  			Schema: map[string]interface{}{
   740  				"name": "John Doe",
   741  			},
   742  		}
   743  		repo.lookup[multi.Identifier{ID: id3, ClassName: "OtherNestedClass"}] = search.Result{
   744  			ClassName: "OtherNestedClass",
   745  			ID:        strfmt.UUID(id3),
   746  			Schema: map[string]interface{}{
   747  				"name": "John Doe",
   748  			},
   749  		}
   750  		logger, _ := test.NewNullLogger()
   751  		cr := NewCacherWithGroup(repo, logger, "")
   752  		input := []search.Result{
   753  			{
   754  				ID:        "foo",
   755  				ClassName: "BestClass",
   756  				Schema: map[string]interface{}{
   757  					"refProp": models.MultipleRef{
   758  						&models.SingleRef{
   759  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   760  						},
   761  					},
   762  				},
   763  				AdditionalProperties: models.AdditionalProperties{
   764  					"group": &additional.Group{
   765  						Hits: []map[string]interface{}{
   766  							{
   767  								"primitive": "foobar",
   768  								"nestedRef": models.MultipleRef{
   769  									&models.SingleRef{
   770  										Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   771  									},
   772  								},
   773  								"otherNestedRef": models.MultipleRef{
   774  									&models.SingleRef{
   775  										Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id3)),
   776  									},
   777  								},
   778  							},
   779  						},
   780  					},
   781  				},
   782  			},
   783  		}
   784  		selectProps := search.SelectProperties{
   785  			search.SelectProperty{
   786  				Name: "_additional:group:hits:nestedRef",
   787  				Refs: []search.SelectClass{
   788  					{
   789  						ClassName: "SomeNestedClass",
   790  						RefProperties: []search.SelectProperty{
   791  							{
   792  								Name:        "name",
   793  								IsPrimitive: true,
   794  							},
   795  						},
   796  					},
   797  				},
   798  			},
   799  			search.SelectProperty{
   800  				Name: "_additional:group:hits:otherNestedRef",
   801  				Refs: []search.SelectClass{
   802  					{
   803  						ClassName: "OtherNestedClass",
   804  						RefProperties: []search.SelectProperty{
   805  							{
   806  								Name:        "name",
   807  								IsPrimitive: true,
   808  							},
   809  						},
   810  					},
   811  				},
   812  			},
   813  		}
   814  
   815  		expectedSomeNestedClass := search.Result{
   816  			ClassName: "SomeNestedClass",
   817  			ID:        strfmt.UUID(id2),
   818  			Schema: map[string]interface{}{
   819  				"name": "John Doe",
   820  			},
   821  		}
   822  
   823  		expectedOtherNestedClass := search.Result{
   824  			ClassName: "OtherNestedClass",
   825  			ID:        strfmt.UUID(id3),
   826  			Schema: map[string]interface{}{
   827  				"name": "John Doe",
   828  			},
   829  		}
   830  
   831  		err := cr.Build(context.Background(), input, selectProps, additional.Properties{})
   832  		require.Nil(t, err)
   833  		res, ok := cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"})
   834  		require.True(t, ok)
   835  		assert.Equal(t, expectedSomeNestedClass, res)
   836  		res, ok = cr.Get(multi.Identifier{ID: id3, ClassName: "OtherNestedClass"})
   837  		require.True(t, ok)
   838  		assert.Equal(t, expectedOtherNestedClass, res)
   839  		assert.Equal(t, 1, repo.counter, "required the expected amount of lookups")
   840  	})
   841  
   842  	t.Run("with group with a nested lookup and with 2 additional group lookups", func(t *testing.T) {
   843  		repo := newFakeRepo()
   844  		repo.lookup[multi.Identifier{ID: id1, ClassName: "SomeClass"}] = search.Result{
   845  			ClassName: "SomeClass",
   846  			ID:        strfmt.UUID(id1),
   847  			Schema: map[string]interface{}{
   848  				"primitive": "foobar",
   849  				"ignoredRef": models.MultipleRef{
   850  					&models.SingleRef{
   851  						Beacon: strfmt.URI("weaviate://localhost/ignoreMe"),
   852  					},
   853  				},
   854  				"nestedRef": models.MultipleRef{
   855  					&models.SingleRef{
   856  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   857  					},
   858  				},
   859  			},
   860  		}
   861  		repo.lookup[multi.Identifier{ID: id2, ClassName: "SomeNestedClass"}] = search.Result{
   862  			ClassName: "SomeNestedClass",
   863  			ID:        strfmt.UUID(id2),
   864  			Schema: map[string]interface{}{
   865  				"name": "John Doe",
   866  			},
   867  		}
   868  		repo.lookup[multi.Identifier{ID: id3, ClassName: "InnerNestedClass"}] = search.Result{
   869  			ClassName: "InnerNestedClass",
   870  			ID:        strfmt.UUID(id3),
   871  			Schema: map[string]interface{}{
   872  				"name": "John Doe",
   873  			},
   874  		}
   875  		repo.lookup[multi.Identifier{ID: id4, ClassName: "OtherNestedClass"}] = search.Result{
   876  			ClassName: "OtherNestedClass",
   877  			ID:        strfmt.UUID(id4),
   878  			Schema: map[string]interface{}{
   879  				"name": "John Doe",
   880  			},
   881  		}
   882  		logger, _ := test.NewNullLogger()
   883  		cr := NewCacherWithGroup(repo, logger, "")
   884  		input := []search.Result{
   885  			{
   886  				ID:        "foo",
   887  				ClassName: "BestClass",
   888  				Schema: map[string]interface{}{
   889  					"refProp": models.MultipleRef{
   890  						&models.SingleRef{
   891  							Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id1)),
   892  						},
   893  					},
   894  				},
   895  				AdditionalProperties: models.AdditionalProperties{
   896  					"group": &additional.Group{
   897  						Hits: []map[string]interface{}{
   898  							{
   899  								"primitive": "foobar",
   900  								"innerNestedRef": models.MultipleRef{
   901  									&models.SingleRef{
   902  										Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id3)),
   903  									},
   904  								},
   905  								"otherNestedRef": models.MultipleRef{
   906  									&models.SingleRef{
   907  										Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id4)),
   908  									},
   909  								},
   910  							},
   911  						},
   912  					},
   913  				},
   914  			},
   915  		}
   916  		selectProps := search.SelectProperties{
   917  			search.SelectProperty{
   918  				Name: "refProp",
   919  				Refs: []search.SelectClass{
   920  					{
   921  						ClassName: "SomeClass",
   922  						RefProperties: search.SelectProperties{
   923  							search.SelectProperty{
   924  								Name:        "primitive",
   925  								IsPrimitive: true,
   926  							},
   927  							search.SelectProperty{
   928  								Name: "nestedRef",
   929  								Refs: []search.SelectClass{
   930  									{
   931  										ClassName: "SomeNestedClass",
   932  										RefProperties: []search.SelectProperty{
   933  											{
   934  												Name:        "name",
   935  												IsPrimitive: true,
   936  											},
   937  										},
   938  									},
   939  								},
   940  							},
   941  						},
   942  					},
   943  				},
   944  			},
   945  			search.SelectProperty{
   946  				Name: "_additional:group:hits:innerNestedRef",
   947  				Refs: []search.SelectClass{
   948  					{
   949  						ClassName: "InnerNestedClass",
   950  						RefProperties: []search.SelectProperty{
   951  							{
   952  								Name:        "name",
   953  								IsPrimitive: true,
   954  							},
   955  						},
   956  					},
   957  				},
   958  			},
   959  			search.SelectProperty{
   960  				Name: "_additional:group:hits:otherNestedRef",
   961  				Refs: []search.SelectClass{
   962  					{
   963  						ClassName: "OtherNestedClass",
   964  						RefProperties: []search.SelectProperty{
   965  							{
   966  								Name:        "name",
   967  								IsPrimitive: true,
   968  							},
   969  						},
   970  					},
   971  				},
   972  			},
   973  		}
   974  
   975  		expectedOuter := search.Result{
   976  			ID:        strfmt.UUID(id1),
   977  			ClassName: "SomeClass",
   978  			Schema: map[string]interface{}{
   979  				"primitive": "foobar",
   980  				"ignoredRef": models.MultipleRef{
   981  					&models.SingleRef{
   982  						Beacon: strfmt.URI("weaviate://localhost/ignoreMe"),
   983  					},
   984  				},
   985  				"nestedRef": models.MultipleRef{
   986  					&models.SingleRef{
   987  						Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", id2)),
   988  					},
   989  				},
   990  			},
   991  		}
   992  
   993  		expectedInner := search.Result{
   994  			ClassName: "SomeNestedClass",
   995  			ID:        strfmt.UUID(id2),
   996  			Schema: map[string]interface{}{
   997  				"name": "John Doe",
   998  			},
   999  		}
  1000  
  1001  		expectedInnerNestedClass := search.Result{
  1002  			ClassName: "InnerNestedClass",
  1003  			ID:        strfmt.UUID(id3),
  1004  			Schema: map[string]interface{}{
  1005  				"name": "John Doe",
  1006  			},
  1007  		}
  1008  
  1009  		expectedOtherNestedClass := search.Result{
  1010  			ClassName: "OtherNestedClass",
  1011  			ID:        strfmt.UUID(id4),
  1012  			Schema: map[string]interface{}{
  1013  				"name": "John Doe",
  1014  			},
  1015  		}
  1016  
  1017  		err := cr.Build(context.Background(), input, selectProps, additional.Properties{})
  1018  		require.Nil(t, err)
  1019  		res, ok := cr.Get(multi.Identifier{ID: id1, ClassName: "SomeClass"})
  1020  		require.True(t, ok)
  1021  		assert.Equal(t, expectedOuter, res)
  1022  		res, ok = cr.Get(multi.Identifier{ID: id2, ClassName: "SomeNestedClass"})
  1023  		require.True(t, ok)
  1024  		assert.Equal(t, expectedInner, res)
  1025  		res, ok = cr.Get(multi.Identifier{ID: id3, ClassName: "InnerNestedClass"})
  1026  		require.True(t, ok)
  1027  		assert.Equal(t, expectedInnerNestedClass, res)
  1028  		res, ok = cr.Get(multi.Identifier{ID: id4, ClassName: "OtherNestedClass"})
  1029  		require.True(t, ok)
  1030  		assert.Equal(t, expectedOtherNestedClass, res)
  1031  		assert.Equal(t, 2, repo.counter, "required the expected amount of lookups")
  1032  	})
  1033  }
  1034  
  1035  type fakeRepo struct {
  1036  	lookup        map[multi.Identifier]search.Result
  1037  	counter       int // count request
  1038  	objectCounter int // count total objects on request(s)
  1039  }
  1040  
  1041  func newFakeRepo() *fakeRepo {
  1042  	return &fakeRepo{
  1043  		lookup: map[multi.Identifier]search.Result{},
  1044  	}
  1045  }
  1046  
  1047  func (f *fakeRepo) MultiGet(ctx context.Context, query []multi.Identifier, additional additional.Properties, tenant string) ([]search.Result, error) {
  1048  	f.counter++
  1049  	f.objectCounter += len(query)
  1050  	out := make([]search.Result, len(query))
  1051  	for i, q := range query {
  1052  		if res, ok := f.lookup[q]; ok {
  1053  			out[i] = res
  1054  		} else {
  1055  			out[i] = search.Result{}
  1056  		}
  1057  	}
  1058  
  1059  	return out, nil
  1060  }