github.com/weaviate/weaviate@v1.24.6/adapters/handlers/grpc/v1/prepare_reply_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 v1
    13  
    14  import (
    15  	"encoding/binary"
    16  	"encoding/json"
    17  	"math"
    18  	"math/big"
    19  	"strings"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/pkg/errors"
    24  
    25  	pb "github.com/weaviate/weaviate/grpc/generated/protocol/v1"
    26  
    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/search"
    31  	"github.com/weaviate/weaviate/entities/searchparams"
    32  
    33  	"github.com/go-openapi/strfmt"
    34  	"github.com/stretchr/testify/require"
    35  	"github.com/weaviate/weaviate/entities/dto"
    36  	"github.com/weaviate/weaviate/usecases/modulecomponents/additional/generate"
    37  	addModels "github.com/weaviate/weaviate/usecases/modulecomponents/additional/models"
    38  	"google.golang.org/protobuf/encoding/protojson"
    39  	"google.golang.org/protobuf/types/known/structpb"
    40  )
    41  
    42  const (
    43  	UUID1 = strfmt.UUID("a4de3ca0-6975-464f-b23b-adddd83630d7")
    44  	UUID2 = strfmt.UUID("7e10ec81-a26d-4ac7-8264-3e3e05397ddc")
    45  )
    46  
    47  func newStruct(t *testing.T, values map[string]interface{}) *structpb.Struct {
    48  	b, err := json.Marshal(values)
    49  	require.Nil(t, err)
    50  	s := &structpb.Struct{}
    51  	err = protojson.Unmarshal(b, s)
    52  	require.Nil(t, err)
    53  	return s
    54  }
    55  
    56  func ignoreError[T any](val T, err error) T {
    57  	return val
    58  }
    59  
    60  func byteVector(vec []float32) []byte {
    61  	vector := make([]byte, len(vec)*4)
    62  
    63  	for i := 0; i < len(vec); i++ {
    64  		binary.LittleEndian.PutUint32(vector[i*4:i*4+4], math.Float32bits(vec[i]))
    65  	}
    66  
    67  	return vector
    68  }
    69  
    70  func idByte(id string) []byte {
    71  	hexInteger, _ := new(big.Int).SetString(strings.Replace(id, "-", "", -1), 16)
    72  	return hexInteger.Bytes()
    73  }
    74  
    75  func TestGRPCReply(t *testing.T) {
    76  	allAdditional := dto.GetParams{AdditionalProperties: additional.Properties{
    77  		Vector:             true,
    78  		Certainty:          true,
    79  		ID:                 true,
    80  		Distance:           true,
    81  		CreationTimeUnix:   true,
    82  		LastUpdateTimeUnix: true,
    83  		ExplainScore:       true,
    84  		Score:              true,
    85  		IsConsistent:       true,
    86  	}}
    87  	truePointer := true
    88  
    89  	someFloat64 := float64(0.1)
    90  	refClass1 := "RefClass1"
    91  	refClass2 := "RefClass2"
    92  	className := "className"
    93  	objClass := "objClass"
    94  	NamedVecClass := "NamedVecs"
    95  	scheme := schema.Schema{
    96  		Objects: &models.Schema{
    97  			Classes: []*models.Class{
    98  				{
    99  					Class: className,
   100  					Properties: []*models.Property{
   101  						{Name: "word", DataType: schema.DataTypeText.PropString()},
   102  						{Name: "other", DataType: []string{"int"}},
   103  						{Name: "age", DataType: []string{"int"}},
   104  						{Name: "nums", DataType: schema.DataTypeIntArray.PropString()},
   105  						{Name: "ref", DataType: []string{refClass1}},
   106  						{Name: "multiRef", DataType: []string{refClass1, refClass2}},
   107  						{
   108  							Name:     "nested",
   109  							DataType: schema.DataTypeObject.PropString(),
   110  							NestedProperties: []*models.NestedProperty{
   111  								{Name: "text", DataType: schema.DataTypeText.PropString()},
   112  								{Name: "text2", DataType: schema.DataTypeText.PropString()},
   113  							},
   114  						},
   115  					},
   116  				},
   117  				{
   118  					Class: refClass1,
   119  					Properties: []*models.Property{
   120  						{Name: "something", DataType: schema.DataTypeText.PropString()},
   121  						{Name: "nums", DataType: schema.DataTypeIntArray.PropString()},
   122  						{Name: "ref2", DataType: []string{refClass2}},
   123  					},
   124  				},
   125  				{
   126  					Class: refClass2,
   127  					Properties: []*models.Property{
   128  						{Name: "else", DataType: schema.DataTypeText.PropString()},
   129  						{Name: "ref3", DataType: []string{refClass2}},
   130  					},
   131  				},
   132  				{
   133  					Class: NamedVecClass,
   134  					Properties: []*models.Property{
   135  						{Name: "name", DataType: schema.DataTypeText.PropString()},
   136  					},
   137  					VectorConfig: map[string]models.VectorConfig{
   138  						"custom": {
   139  							VectorIndexType: "hnsw",
   140  							Vectorizer:      map[string]interface{}{"none": map[string]interface{}{}},
   141  						},
   142  						"first": {
   143  							VectorIndexType: "flat",
   144  							Vectorizer:      map[string]interface{}{"text2vec-contextionary": map[string]interface{}{}},
   145  						},
   146  					},
   147  				},
   148  				{
   149  					Class: objClass,
   150  					Properties: []*models.Property{
   151  						{
   152  							Name:     "something",
   153  							DataType: schema.DataTypeObject.PropString(),
   154  							NestedProperties: []*models.NestedProperty{
   155  								{
   156  									Name:     "name",
   157  									DataType: schema.DataTypeText.PropString(),
   158  								},
   159  								{
   160  									Name:     "names",
   161  									DataType: schema.DataTypeTextArray.PropString(),
   162  								},
   163  								{
   164  									Name:     "else",
   165  									DataType: schema.DataTypeObject.PropString(),
   166  									NestedProperties: []*models.NestedProperty{
   167  										{
   168  											Name:     "name",
   169  											DataType: schema.DataTypeText.PropString(),
   170  										},
   171  										{
   172  											Name:     "names",
   173  											DataType: schema.DataTypeTextArray.PropString(),
   174  										},
   175  									},
   176  								},
   177  								{
   178  									Name:     "objs",
   179  									DataType: schema.DataTypeObjectArray.PropString(),
   180  									NestedProperties: []*models.NestedProperty{{
   181  										Name:     "name",
   182  										DataType: schema.DataTypeText.PropString(),
   183  									}},
   184  								},
   185  							},
   186  						},
   187  					},
   188  				},
   189  			},
   190  		},
   191  	}
   192  
   193  	tests := []struct {
   194  		name               string
   195  		res                []any
   196  		searchParams       dto.GetParams // only a few things are needed to control what is returned
   197  		outSearch          []*pb.SearchResult
   198  		outGenerative      string
   199  		outGroup           []*pb.GroupByResult
   200  		usesWeaviateStruct bool
   201  		hasError           bool
   202  	}{
   203  		{
   204  			name: "vector only",
   205  			res: []interface{}{
   206  				map[string]interface{}{
   207  					"_additional": map[string]interface{}{"vector": []float32{1}},
   208  				},
   209  				map[string]interface{}{
   210  					"_additional": map[string]interface{}{"vector": []float32{2}},
   211  				},
   212  			},
   213  			searchParams: dto.GetParams{AdditionalProperties: additional.Properties{Vector: true}},
   214  			outSearch: []*pb.SearchResult{
   215  				{Metadata: &pb.MetadataResult{Vector: []float32{1}, VectorBytes: byteVector([]float32{1})}, Properties: &pb.PropertiesResult{}},
   216  				{Metadata: &pb.MetadataResult{Vector: []float32{2}, VectorBytes: byteVector([]float32{2})}, Properties: &pb.PropertiesResult{}},
   217  			},
   218  			usesWeaviateStruct: true,
   219  		},
   220  		{
   221  			name: "named vector only",
   222  			res: []interface{}{
   223  				map[string]interface{}{
   224  					"_additional": map[string]interface{}{"vectors": map[string][]float32{"custom": {1}, "first": {2}}},
   225  				},
   226  			},
   227  			searchParams: dto.GetParams{AdditionalProperties: additional.Properties{Vectors: []string{"custom", "first"}}},
   228  			outSearch: []*pb.SearchResult{
   229  				{Metadata: &pb.MetadataResult{Vectors: []*pb.Vectors{
   230  					{Name: "custom", VectorBytes: byteVector([]float32{1})},
   231  					{Name: "first", VectorBytes: byteVector([]float32{2})},
   232  				}}, Properties: &pb.PropertiesResult{}},
   233  			},
   234  			usesWeaviateStruct: true,
   235  		},
   236  		{
   237  			name: "all additional",
   238  			res: []interface{}{
   239  				map[string]interface{}{
   240  					"id": UUID1,
   241  					"_additional": map[string]interface{}{
   242  						"vector":             []float32{1},
   243  						"certainty":          0.4,
   244  						"distance":           float32(0.01),
   245  						"creationTimeUnix":   int64(123),
   246  						"lastUpdateTimeUnix": int64(345),
   247  						"explainScore":       "other text",
   248  						"score":              float32(0.25),
   249  						"isConsistent":       true,
   250  					},
   251  				},
   252  				map[string]interface{}{
   253  					"id": UUID2,
   254  					"_additional": map[string]interface{}{
   255  						"vector":             []float32{2},
   256  						"certainty":          0.5,
   257  						"distance":           float32(0.1),
   258  						"creationTimeUnix":   int64(456),
   259  						"lastUpdateTimeUnix": int64(789),
   260  						"explainScore":       "some text",
   261  						"score":              float32(0.45),
   262  						"isConsistent":       true,
   263  					},
   264  				},
   265  			},
   266  			searchParams: allAdditional,
   267  			outSearch: []*pb.SearchResult{
   268  				{
   269  					Metadata: &pb.MetadataResult{
   270  						Vector:                    []float32{1},
   271  						Id:                        string(UUID1),
   272  						Certainty:                 0.4,
   273  						CertaintyPresent:          true,
   274  						Distance:                  0.01,
   275  						DistancePresent:           true,
   276  						CreationTimeUnix:          123,
   277  						CreationTimeUnixPresent:   true,
   278  						LastUpdateTimeUnix:        345,
   279  						LastUpdateTimeUnixPresent: true,
   280  						ExplainScore:              "other text",
   281  						ExplainScorePresent:       true,
   282  						Score:                     0.25,
   283  						ScorePresent:              true,
   284  						IsConsistent:              &truePointer,
   285  						IsConsistentPresent:       true,
   286  						VectorBytes:               byteVector([]float32{1}),
   287  						IdAsBytes:                 idByte(string(UUID1)),
   288  					},
   289  					Properties: &pb.PropertiesResult{},
   290  				},
   291  				{
   292  					Metadata: &pb.MetadataResult{
   293  						Vector:                    []float32{2},
   294  						Id:                        string(UUID2),
   295  						Certainty:                 0.5,
   296  						CertaintyPresent:          true,
   297  						Distance:                  0.1,
   298  						DistancePresent:           true,
   299  						CreationTimeUnix:          456,
   300  						CreationTimeUnixPresent:   true,
   301  						LastUpdateTimeUnix:        789,
   302  						LastUpdateTimeUnixPresent: true,
   303  						ExplainScore:              "some text",
   304  						ExplainScorePresent:       true,
   305  						Score:                     0.45,
   306  						ScorePresent:              true,
   307  						IsConsistent:              &truePointer,
   308  						IsConsistentPresent:       true,
   309  						VectorBytes:               byteVector([]float32{2}),
   310  						IdAsBytes:                 idByte(string(UUID2)),
   311  					},
   312  					Properties: &pb.PropertiesResult{},
   313  				},
   314  			},
   315  		},
   316  		{
   317  			name: "primitive properties deprecated",
   318  			res: []interface{}{
   319  				map[string]interface{}{
   320  					"word": "word",
   321  					"age":  21,
   322  				},
   323  				map[string]interface{}{
   324  					"word": "other",
   325  					"age":  26,
   326  				},
   327  			},
   328  			searchParams: dto.GetParams{
   329  				ClassName:  className,
   330  				Properties: search.SelectProperties{{Name: "word", IsPrimitive: true}, {Name: "age", IsPrimitive: true}},
   331  			},
   332  			outSearch: []*pb.SearchResult{
   333  				{
   334  					Metadata: &pb.MetadataResult{},
   335  					Properties: &pb.PropertiesResult{
   336  						TargetCollection: className,
   337  						NonRefProperties: newStruct(t, map[string]interface{}{
   338  							"word": "word",
   339  							"age":  21,
   340  						}),
   341  						RefProps:          []*pb.RefPropertiesResult{},
   342  						RefPropsRequested: false,
   343  					},
   344  				},
   345  				{
   346  					Metadata: &pb.MetadataResult{},
   347  					Properties: &pb.PropertiesResult{
   348  						TargetCollection: className,
   349  						NonRefProperties: newStruct(t, map[string]interface{}{
   350  							"word": "other",
   351  							"age":  26,
   352  						}),
   353  						RefProps:          []*pb.RefPropertiesResult{},
   354  						RefPropsRequested: false,
   355  					},
   356  				},
   357  			},
   358  		},
   359  		{
   360  			name: "primitive properties",
   361  			res: []interface{}{
   362  				map[string]interface{}{
   363  					"word": "word",
   364  					"age":  float64(21),
   365  				},
   366  				map[string]interface{}{
   367  					"word": "other",
   368  					"age":  float64(26),
   369  				},
   370  			},
   371  			searchParams: dto.GetParams{
   372  				ClassName:  className,
   373  				Properties: search.SelectProperties{{Name: "word", IsPrimitive: true}, {Name: "age", IsPrimitive: true}},
   374  			},
   375  			outSearch: []*pb.SearchResult{
   376  				{
   377  					Metadata: &pb.MetadataResult{},
   378  					Properties: &pb.PropertiesResult{
   379  						TargetCollection: className,
   380  						NonRefProps: &pb.Properties{
   381  							Fields: map[string]*pb.Value{
   382  								"word": {Kind: &pb.Value_StringValue{StringValue: "word"}},
   383  								"age":  {Kind: &pb.Value_IntValue{IntValue: 21}},
   384  							},
   385  						},
   386  						RefProps:          []*pb.RefPropertiesResult{},
   387  						RefPropsRequested: false,
   388  					},
   389  				},
   390  				{
   391  					Metadata: &pb.MetadataResult{},
   392  					Properties: &pb.PropertiesResult{
   393  						TargetCollection: className,
   394  						NonRefProps: &pb.Properties{
   395  							Fields: map[string]*pb.Value{
   396  								"word": {Kind: &pb.Value_StringValue{StringValue: "other"}},
   397  								"age":  {Kind: &pb.Value_IntValue{IntValue: 26}},
   398  							},
   399  						},
   400  						RefProps:          []*pb.RefPropertiesResult{},
   401  						RefPropsRequested: false,
   402  					},
   403  				},
   404  			},
   405  			usesWeaviateStruct: true,
   406  		},
   407  		{
   408  			name: "request property with nil value",
   409  			res: []interface{}{
   410  				map[string]interface{}{
   411  					"word": "word",
   412  				},
   413  			},
   414  			searchParams: dto.GetParams{
   415  				ClassName:  className,
   416  				Properties: search.SelectProperties{{Name: "word", IsPrimitive: true}, {Name: "age", IsPrimitive: true}},
   417  			},
   418  			outSearch: []*pb.SearchResult{
   419  				{
   420  					Metadata: &pb.MetadataResult{},
   421  					Properties: &pb.PropertiesResult{
   422  						TargetCollection: className,
   423  						NonRefProps: &pb.Properties{
   424  							Fields: map[string]*pb.Value{
   425  								"word": {Kind: &pb.Value_StringValue{StringValue: "word"}},
   426  								"age":  {Kind: &pb.Value_NullValue{}},
   427  							},
   428  						},
   429  						RefProps:          []*pb.RefPropertiesResult{},
   430  						RefPropsRequested: false,
   431  					},
   432  				},
   433  			},
   434  			usesWeaviateStruct: true,
   435  		},
   436  		{
   437  			name: "array properties",
   438  			res: []interface{}{
   439  				map[string]interface{}{"nums": []float64{1, 2, 3}}, // ints are encoded as float64 in json
   440  			},
   441  			searchParams: dto.GetParams{
   442  				ClassName:  className,
   443  				Properties: search.SelectProperties{{Name: "nums", IsPrimitive: true}},
   444  			},
   445  			outSearch: []*pb.SearchResult{
   446  				{
   447  					Metadata: &pb.MetadataResult{},
   448  					Properties: &pb.PropertiesResult{
   449  						TargetCollection: className,
   450  						NonRefProps: &pb.Properties{
   451  							Fields: map[string]*pb.Value{
   452  								"nums": {Kind: &pb.Value_ListValue{ListValue: ignoreError(NewPrimitiveList([]float64{1, 2, 3}, schema.DataTypeInt))}},
   453  							},
   454  						},
   455  					},
   456  				},
   457  			},
   458  			usesWeaviateStruct: true,
   459  		},
   460  		{
   461  			name: "array properties deprecated",
   462  			res: []interface{}{
   463  				map[string]interface{}{"nums": []float64{1, 2, 3}}, // ints are encoded as float64 in json
   464  			},
   465  			searchParams: dto.GetParams{
   466  				ClassName:  className,
   467  				Properties: search.SelectProperties{{Name: "nums", IsPrimitive: true}},
   468  			},
   469  			outSearch: []*pb.SearchResult{
   470  				{
   471  					Metadata: &pb.MetadataResult{},
   472  					Properties: &pb.PropertiesResult{
   473  						TargetCollection: className,
   474  						NonRefProperties: newStruct(t, map[string]interface{}{}),
   475  						IntArrayProperties: []*pb.IntArrayProperties{{
   476  							PropName: "nums",
   477  							Values:   []int64{1, 2, 3},
   478  						}},
   479  					},
   480  				},
   481  			},
   482  			usesWeaviateStruct: false,
   483  		},
   484  		{
   485  			name: "nested object properties",
   486  			res: []interface{}{
   487  				map[string]interface{}{
   488  					"something": map[string]interface{}{
   489  						"name":  "Bob",
   490  						"names": []string{"Jo", "Jill"},
   491  						"else": map[string]interface{}{
   492  							"name":  "Bill",
   493  							"names": []string{"Jo", "Jill"},
   494  						},
   495  						"objs": []interface{}{
   496  							map[string]interface{}{"name": "Bill"},
   497  						},
   498  					},
   499  				},
   500  			},
   501  			searchParams: dto.GetParams{
   502  				ClassName: objClass,
   503  				Properties: search.SelectProperties{{
   504  					Name:        "something",
   505  					IsPrimitive: false,
   506  					IsObject:    true,
   507  					Props: []search.SelectProperty{
   508  						{
   509  							Name:        "name",
   510  							IsPrimitive: true,
   511  						},
   512  						{
   513  							Name:        "names",
   514  							IsPrimitive: true,
   515  						},
   516  						{
   517  							Name:        "else",
   518  							IsPrimitive: false,
   519  							IsObject:    true,
   520  							Props: []search.SelectProperty{
   521  								{
   522  									Name:        "name",
   523  									IsPrimitive: true,
   524  								},
   525  								{
   526  									Name:        "names",
   527  									IsPrimitive: true,
   528  								},
   529  							},
   530  						},
   531  						{
   532  							Name:        "objs",
   533  							IsPrimitive: false,
   534  							IsObject:    true,
   535  							Props: []search.SelectProperty{{
   536  								Name:        "name",
   537  								IsPrimitive: true,
   538  							}},
   539  						},
   540  					},
   541  				}},
   542  			},
   543  			outSearch: []*pb.SearchResult{
   544  				{
   545  					Metadata: &pb.MetadataResult{},
   546  					Properties: &pb.PropertiesResult{
   547  						TargetCollection: objClass,
   548  						NonRefProps: &pb.Properties{
   549  							Fields: map[string]*pb.Value{
   550  								"something": {Kind: &pb.Value_ObjectValue{
   551  									ObjectValue: &pb.Properties{
   552  										Fields: map[string]*pb.Value{
   553  											"name":  {Kind: &pb.Value_StringValue{StringValue: "Bob"}},
   554  											"names": {Kind: &pb.Value_ListValue{ListValue: ignoreError(NewPrimitiveList([]string{"Jo", "Jill"}, schema.DataTypeString))}},
   555  											"else": {Kind: &pb.Value_ObjectValue{
   556  												ObjectValue: &pb.Properties{
   557  													Fields: map[string]*pb.Value{
   558  														"name":  {Kind: &pb.Value_StringValue{StringValue: "Bill"}},
   559  														"names": {Kind: &pb.Value_ListValue{ListValue: ignoreError(NewPrimitiveList([]string{"Jo", "Jill"}, schema.DataTypeString))}},
   560  													},
   561  												},
   562  											}},
   563  											"objs": {Kind: &pb.Value_ListValue{ListValue: &pb.ListValue{
   564  												Values: []*pb.Value{{Kind: &pb.Value_ObjectValue{
   565  													ObjectValue: &pb.Properties{
   566  														Fields: map[string]*pb.Value{
   567  															"name": {Kind: &pb.Value_StringValue{StringValue: "Bill"}},
   568  														},
   569  													},
   570  												}}},
   571  											}}},
   572  										},
   573  									},
   574  								}},
   575  							},
   576  						},
   577  					},
   578  				},
   579  			},
   580  			usesWeaviateStruct: true,
   581  		},
   582  		{
   583  			name: "nested object properties with missing values",
   584  			res: []interface{}{
   585  				map[string]interface{}{
   586  					"something": map[string]interface{}{
   587  						"name":  "Bob",
   588  						"names": []string{"Jo", "Jill"},
   589  						"else": map[string]interface{}{
   590  							"names": []string{"Jo", "Jill"},
   591  						},
   592  						"objs": []interface{}{
   593  							map[string]interface{}{"name": "Bill"},
   594  						},
   595  					},
   596  				},
   597  			},
   598  			searchParams: dto.GetParams{
   599  				ClassName: objClass,
   600  				Properties: search.SelectProperties{{
   601  					Name:        "something",
   602  					IsPrimitive: false,
   603  					IsObject:    true,
   604  					Props: []search.SelectProperty{
   605  						{
   606  							Name:        "name",
   607  							IsPrimitive: true,
   608  						},
   609  						{
   610  							Name:        "names",
   611  							IsPrimitive: true,
   612  						},
   613  						{
   614  							Name:        "else",
   615  							IsPrimitive: false,
   616  							IsObject:    true,
   617  							Props: []search.SelectProperty{
   618  								{
   619  									Name:        "name",
   620  									IsPrimitive: true,
   621  								},
   622  								{
   623  									Name:        "names",
   624  									IsPrimitive: true,
   625  								},
   626  							},
   627  						},
   628  						{
   629  							Name:        "objs",
   630  							IsPrimitive: false,
   631  							IsObject:    true,
   632  							Props: []search.SelectProperty{{
   633  								Name:        "name",
   634  								IsPrimitive: true,
   635  							}},
   636  						},
   637  					},
   638  				}},
   639  			},
   640  			outSearch: []*pb.SearchResult{
   641  				{
   642  					Metadata: &pb.MetadataResult{},
   643  					Properties: &pb.PropertiesResult{
   644  						TargetCollection: objClass,
   645  						NonRefProps: &pb.Properties{
   646  							Fields: map[string]*pb.Value{
   647  								"something": {Kind: &pb.Value_ObjectValue{
   648  									ObjectValue: &pb.Properties{
   649  										Fields: map[string]*pb.Value{
   650  											"name":  {Kind: &pb.Value_StringValue{StringValue: "Bob"}},
   651  											"names": {Kind: &pb.Value_ListValue{ListValue: ignoreError(NewPrimitiveList([]string{"Jo", "Jill"}, schema.DataTypeString))}},
   652  											"else": {Kind: &pb.Value_ObjectValue{
   653  												ObjectValue: &pb.Properties{
   654  													Fields: map[string]*pb.Value{
   655  														"names": {Kind: &pb.Value_ListValue{ListValue: ignoreError(NewPrimitiveList([]string{"Jo", "Jill"}, schema.DataTypeString))}},
   656  													},
   657  												},
   658  											}},
   659  											"objs": {Kind: &pb.Value_ListValue{ListValue: &pb.ListValue{
   660  												Values: []*pb.Value{{Kind: &pb.Value_ObjectValue{
   661  													ObjectValue: &pb.Properties{
   662  														Fields: map[string]*pb.Value{
   663  															"name": {Kind: &pb.Value_StringValue{StringValue: "Bill"}},
   664  														},
   665  													},
   666  												}}},
   667  											}}},
   668  										},
   669  									},
   670  								}},
   671  							},
   672  						},
   673  					},
   674  				},
   675  			},
   676  			usesWeaviateStruct: true,
   677  		},
   678  		{
   679  			name: "nested object properties deprecated",
   680  			res: []interface{}{
   681  				map[string]interface{}{
   682  					"something": map[string]interface{}{
   683  						"name":  "Bob",
   684  						"names": []string{"Jo", "Jill"},
   685  						"else": map[string]interface{}{
   686  							"name":  "Bill",
   687  							"names": []string{"Jo", "Jill"},
   688  						},
   689  						"objs": []interface{}{
   690  							map[string]interface{}{"name": "Bill"},
   691  						},
   692  					},
   693  				},
   694  			},
   695  			searchParams: dto.GetParams{
   696  				ClassName: objClass,
   697  				Properties: search.SelectProperties{{
   698  					Name:        "something",
   699  					IsPrimitive: false,
   700  					IsObject:    true,
   701  					Props: []search.SelectProperty{
   702  						{
   703  							Name:        "name",
   704  							IsPrimitive: true,
   705  						},
   706  						{
   707  							Name:        "names",
   708  							IsPrimitive: true,
   709  						},
   710  						{
   711  							Name:        "else",
   712  							IsPrimitive: false,
   713  							IsObject:    true,
   714  							Props: []search.SelectProperty{
   715  								{
   716  									Name:        "name",
   717  									IsPrimitive: true,
   718  								},
   719  								{
   720  									Name:        "names",
   721  									IsPrimitive: true,
   722  								},
   723  							},
   724  						},
   725  						{
   726  							Name:        "objs",
   727  							IsPrimitive: false,
   728  							IsObject:    true,
   729  							Props: []search.SelectProperty{{
   730  								Name:        "name",
   731  								IsPrimitive: true,
   732  							}},
   733  						},
   734  					},
   735  				}},
   736  			},
   737  			outSearch: []*pb.SearchResult{
   738  				{
   739  					Metadata: &pb.MetadataResult{},
   740  					Properties: &pb.PropertiesResult{
   741  						TargetCollection: objClass,
   742  						ObjectProperties: []*pb.ObjectProperties{
   743  							{
   744  								PropName: "something",
   745  								Value: &pb.ObjectPropertiesValue{
   746  									NonRefProperties: newStruct(t, map[string]interface{}{
   747  										"name": "Bob",
   748  									}),
   749  									TextArrayProperties: []*pb.TextArrayProperties{{
   750  										PropName: "names",
   751  										Values:   []string{"Jo", "Jill"},
   752  									}},
   753  									ObjectProperties: []*pb.ObjectProperties{{
   754  										PropName: "else",
   755  										Value: &pb.ObjectPropertiesValue{
   756  											NonRefProperties: newStruct(t, map[string]interface{}{
   757  												"name": "Bill",
   758  											}),
   759  											TextArrayProperties: []*pb.TextArrayProperties{{
   760  												PropName: "names",
   761  												Values:   []string{"Jo", "Jill"},
   762  											}},
   763  										},
   764  									}},
   765  									ObjectArrayProperties: []*pb.ObjectArrayProperties{{
   766  										PropName: "objs",
   767  										Values: []*pb.ObjectPropertiesValue{{
   768  											NonRefProperties: newStruct(t, map[string]interface{}{
   769  												"name": "Bill",
   770  											}),
   771  										}},
   772  									}},
   773  								},
   774  							},
   775  						},
   776  					},
   777  				},
   778  			},
   779  			usesWeaviateStruct: false,
   780  		},
   781  		{
   782  			name: "primitive and ref properties with no references",
   783  			res: []interface{}{
   784  				map[string]interface{}{
   785  					"word": "word",
   786  				},
   787  				map[string]interface{}{
   788  					"word": "other",
   789  				},
   790  			},
   791  			searchParams: dto.GetParams{
   792  				ClassName: className,
   793  				Properties: search.SelectProperties{
   794  					{Name: "word", IsPrimitive: true},
   795  					{Name: "ref", IsPrimitive: false, Refs: []search.SelectClass{
   796  						{
   797  							ClassName:            refClass1,
   798  							RefProperties:        search.SelectProperties{{Name: "something", IsPrimitive: true}},
   799  							AdditionalProperties: additional.Properties{Vector: true},
   800  						},
   801  					}},
   802  				},
   803  			},
   804  			outSearch: []*pb.SearchResult{
   805  				{
   806  					Metadata: &pb.MetadataResult{},
   807  					Properties: &pb.PropertiesResult{
   808  						TargetCollection: className,
   809  						NonRefProps: &pb.Properties{
   810  							Fields: map[string]*pb.Value{
   811  								"word": {Kind: &pb.Value_StringValue{StringValue: "word"}},
   812  							},
   813  						},
   814  						RefProps:          []*pb.RefPropertiesResult{},
   815  						RefPropsRequested: true,
   816  					},
   817  				},
   818  				{
   819  					Metadata: &pb.MetadataResult{},
   820  					Properties: &pb.PropertiesResult{
   821  						TargetCollection: className,
   822  						NonRefProps: &pb.Properties{
   823  							Fields: map[string]*pb.Value{
   824  								"word": {Kind: &pb.Value_StringValue{StringValue: "other"}},
   825  							},
   826  						},
   827  						RefProps:          []*pb.RefPropertiesResult{},
   828  						RefPropsRequested: true,
   829  					},
   830  				},
   831  			},
   832  			usesWeaviateStruct: true,
   833  		},
   834  		{
   835  			name: "primitive and ref properties",
   836  			res: []interface{}{
   837  				map[string]interface{}{
   838  					"word": "word",
   839  					"ref": []interface{}{
   840  						search.LocalRef{
   841  							Class: refClass1,
   842  							Fields: map[string]interface{}{
   843  								"something":   "other",
   844  								"_additional": map[string]interface{}{"vector": []float32{3}},
   845  							},
   846  						},
   847  					},
   848  				},
   849  				map[string]interface{}{
   850  					"word": "other",
   851  					"ref": []interface{}{
   852  						search.LocalRef{
   853  							Class: refClass1,
   854  							Fields: map[string]interface{}{
   855  								"something":   "thing",
   856  								"_additional": map[string]interface{}{"vector": []float32{4}},
   857  							},
   858  						},
   859  					},
   860  				},
   861  			},
   862  			searchParams: dto.GetParams{
   863  				ClassName: className,
   864  				Properties: search.SelectProperties{
   865  					{Name: "word", IsPrimitive: true},
   866  					{Name: "ref", IsPrimitive: false, Refs: []search.SelectClass{
   867  						{
   868  							ClassName:            refClass1,
   869  							RefProperties:        search.SelectProperties{{Name: "something", IsPrimitive: true}},
   870  							AdditionalProperties: additional.Properties{Vector: true},
   871  						},
   872  					}},
   873  				},
   874  			},
   875  			outSearch: []*pb.SearchResult{
   876  				{
   877  					Metadata: &pb.MetadataResult{},
   878  					Properties: &pb.PropertiesResult{
   879  						TargetCollection: className,
   880  						NonRefProps: &pb.Properties{
   881  							Fields: map[string]*pb.Value{
   882  								"word": {Kind: &pb.Value_StringValue{StringValue: "word"}},
   883  							},
   884  						},
   885  						RefProps: []*pb.RefPropertiesResult{{
   886  							PropName: "ref",
   887  							Properties: []*pb.PropertiesResult{
   888  								{
   889  									TargetCollection: refClass1,
   890  									Metadata:         &pb.MetadataResult{Vector: []float32{3}, VectorBytes: byteVector([]float32{3})},
   891  									NonRefProps: &pb.Properties{
   892  										Fields: map[string]*pb.Value{
   893  											"something": {Kind: &pb.Value_StringValue{StringValue: "other"}},
   894  										},
   895  									},
   896  								},
   897  							},
   898  						}},
   899  						RefPropsRequested: true,
   900  					},
   901  				},
   902  				{
   903  					Metadata: &pb.MetadataResult{},
   904  					Properties: &pb.PropertiesResult{
   905  						TargetCollection: className,
   906  						NonRefProps: &pb.Properties{
   907  							Fields: map[string]*pb.Value{
   908  								"word": {Kind: &pb.Value_StringValue{StringValue: "other"}},
   909  							},
   910  						},
   911  						RefProps: []*pb.RefPropertiesResult{{
   912  							PropName: "ref",
   913  							Properties: []*pb.PropertiesResult{
   914  								{
   915  									TargetCollection: refClass1,
   916  									Metadata:         &pb.MetadataResult{Vector: []float32{4}, VectorBytes: byteVector([]float32{4})},
   917  									NonRefProps: &pb.Properties{
   918  										Fields: map[string]*pb.Value{
   919  											"something": {Kind: &pb.Value_StringValue{StringValue: "thing"}},
   920  										},
   921  									},
   922  								},
   923  							},
   924  						}},
   925  						RefPropsRequested: true,
   926  					},
   927  				},
   928  			},
   929  			usesWeaviateStruct: true,
   930  		},
   931  		{
   932  			name: "nested ref properties",
   933  			res: []interface{}{
   934  				map[string]interface{}{
   935  					"word": "word",
   936  					"ref": []interface{}{
   937  						search.LocalRef{
   938  							Class: refClass1,
   939  							Fields: map[string]interface{}{
   940  								"something": "other",
   941  								"ref2": []interface{}{
   942  									search.LocalRef{
   943  										Class: refClass2,
   944  										Fields: map[string]interface{}{
   945  											"else": "thing",
   946  										},
   947  									},
   948  								},
   949  							},
   950  						},
   951  					},
   952  				},
   953  			},
   954  			searchParams: dto.GetParams{
   955  				ClassName: className,
   956  				Properties: search.SelectProperties{
   957  					{Name: "word", IsPrimitive: true},
   958  					{
   959  						Name: "ref", IsPrimitive: false, Refs: []search.SelectClass{
   960  							{
   961  								ClassName: refClass1,
   962  								RefProperties: search.SelectProperties{
   963  									{Name: "something", IsPrimitive: true},
   964  									{
   965  										Name: "ref2", IsPrimitive: false, Refs: []search.SelectClass{{
   966  											ClassName:     refClass2,
   967  											RefProperties: search.SelectProperties{{Name: "else", IsPrimitive: true}},
   968  										}},
   969  									},
   970  								},
   971  							},
   972  						},
   973  					},
   974  				},
   975  			},
   976  			outSearch: []*pb.SearchResult{
   977  				{
   978  					Metadata: &pb.MetadataResult{},
   979  					Properties: &pb.PropertiesResult{
   980  						TargetCollection: className,
   981  						NonRefProps: &pb.Properties{
   982  							Fields: map[string]*pb.Value{
   983  								"word": {Kind: &pb.Value_StringValue{StringValue: "word"}},
   984  							},
   985  						},
   986  						RefProps: []*pb.RefPropertiesResult{{
   987  							PropName: "ref",
   988  							Properties: []*pb.PropertiesResult{
   989  								{
   990  									TargetCollection: refClass1,
   991  									Metadata:         &pb.MetadataResult{},
   992  									NonRefProps: &pb.Properties{
   993  										Fields: map[string]*pb.Value{
   994  											"something": {Kind: &pb.Value_StringValue{StringValue: "other"}},
   995  										},
   996  									},
   997  									RefProps: []*pb.RefPropertiesResult{{
   998  										PropName: "ref2",
   999  										Properties: []*pb.PropertiesResult{{
  1000  											TargetCollection: refClass2,
  1001  											Metadata:         &pb.MetadataResult{},
  1002  											NonRefProps: &pb.Properties{
  1003  												Fields: map[string]*pb.Value{
  1004  													"else": {Kind: &pb.Value_StringValue{StringValue: "thing"}},
  1005  												},
  1006  											},
  1007  											RefProps:          []*pb.RefPropertiesResult{},
  1008  											RefPropsRequested: false,
  1009  										}},
  1010  									}},
  1011  									RefPropsRequested: true,
  1012  								},
  1013  							},
  1014  						}},
  1015  						RefPropsRequested: true,
  1016  					},
  1017  				},
  1018  			},
  1019  			usesWeaviateStruct: true,
  1020  		},
  1021  		{
  1022  			name: "nested ref properties with no references",
  1023  			res: []interface{}{
  1024  				map[string]interface{}{
  1025  					"word": "word",
  1026  					"ref": []interface{}{
  1027  						search.LocalRef{
  1028  							Class: refClass1,
  1029  							Fields: map[string]interface{}{
  1030  								"something": "other",
  1031  							},
  1032  						},
  1033  					},
  1034  				},
  1035  			},
  1036  			searchParams: dto.GetParams{
  1037  				ClassName: className,
  1038  				Properties: search.SelectProperties{
  1039  					{Name: "word", IsPrimitive: true},
  1040  					{
  1041  						Name: "ref", IsPrimitive: false, Refs: []search.SelectClass{
  1042  							{
  1043  								ClassName: refClass1,
  1044  								RefProperties: search.SelectProperties{
  1045  									{Name: "something", IsPrimitive: true},
  1046  									{
  1047  										Name: "ref2", IsPrimitive: false, Refs: []search.SelectClass{{
  1048  											ClassName:     refClass2,
  1049  											RefProperties: search.SelectProperties{{Name: "else", IsPrimitive: true}},
  1050  										}},
  1051  									},
  1052  								},
  1053  							},
  1054  						},
  1055  					},
  1056  				},
  1057  			},
  1058  			outSearch: []*pb.SearchResult{
  1059  				{
  1060  					Metadata: &pb.MetadataResult{},
  1061  					Properties: &pb.PropertiesResult{
  1062  						TargetCollection: className,
  1063  						NonRefProps: &pb.Properties{
  1064  							Fields: map[string]*pb.Value{
  1065  								"word": {Kind: &pb.Value_StringValue{StringValue: "word"}},
  1066  							},
  1067  						},
  1068  						RefProps: []*pb.RefPropertiesResult{{
  1069  							PropName: "ref",
  1070  							Properties: []*pb.PropertiesResult{
  1071  								{
  1072  									TargetCollection: refClass1,
  1073  									Metadata:         &pb.MetadataResult{},
  1074  									NonRefProps: &pb.Properties{
  1075  										Fields: map[string]*pb.Value{
  1076  											"something": {Kind: &pb.Value_StringValue{StringValue: "other"}},
  1077  										},
  1078  									},
  1079  									RefProps:          []*pb.RefPropertiesResult{},
  1080  									RefPropsRequested: true,
  1081  								},
  1082  							},
  1083  						}},
  1084  						RefPropsRequested: true,
  1085  					},
  1086  				},
  1087  			},
  1088  			usesWeaviateStruct: true,
  1089  		},
  1090  		{
  1091  			name: "primitive and ref array properties",
  1092  			res: []interface{}{
  1093  				map[string]interface{}{
  1094  					"word": "word",
  1095  					"ref": []interface{}{
  1096  						search.LocalRef{
  1097  							Class: refClass1,
  1098  							Fields: map[string]interface{}{
  1099  								"nums":        []float64{1, 2, 3}, // ints are encoded as float64 in json
  1100  								"_additional": map[string]interface{}{"vector": []float32{3}},
  1101  							},
  1102  						},
  1103  					},
  1104  				},
  1105  			},
  1106  			searchParams: dto.GetParams{
  1107  				ClassName: className,
  1108  				Properties: search.SelectProperties{
  1109  					{Name: "word", IsPrimitive: true},
  1110  					{Name: "ref", IsPrimitive: false, Refs: []search.SelectClass{
  1111  						{
  1112  							ClassName:            refClass1,
  1113  							RefProperties:        search.SelectProperties{{Name: "nums", IsPrimitive: true}},
  1114  							AdditionalProperties: additional.Properties{Vector: true},
  1115  						},
  1116  					}},
  1117  				},
  1118  			},
  1119  			outSearch: []*pb.SearchResult{
  1120  				{
  1121  					Metadata: &pb.MetadataResult{},
  1122  					Properties: &pb.PropertiesResult{
  1123  						TargetCollection: className,
  1124  						NonRefProps: &pb.Properties{
  1125  							Fields: map[string]*pb.Value{
  1126  								"word": {Kind: &pb.Value_StringValue{StringValue: "word"}},
  1127  							},
  1128  						},
  1129  						RefProps: []*pb.RefPropertiesResult{{
  1130  							PropName: "ref",
  1131  							Properties: []*pb.PropertiesResult{
  1132  								{
  1133  									TargetCollection: refClass1,
  1134  									Metadata:         &pb.MetadataResult{Vector: []float32{3}, VectorBytes: byteVector([]float32{3})},
  1135  									NonRefProps: &pb.Properties{
  1136  										Fields: map[string]*pb.Value{
  1137  											"nums": {Kind: &pb.Value_ListValue{ListValue: ignoreError(NewPrimitiveList([]float64{1, 2, 3}, schema.DataTypeInt))}},
  1138  										},
  1139  									},
  1140  								},
  1141  							},
  1142  						}},
  1143  						RefPropsRequested: true,
  1144  					},
  1145  				},
  1146  			},
  1147  			usesWeaviateStruct: true,
  1148  		},
  1149  		{
  1150  			name: "primitive and ref array properties deprecated",
  1151  			res: []interface{}{
  1152  				map[string]interface{}{
  1153  					"word": "word",
  1154  					"ref": []interface{}{
  1155  						search.LocalRef{
  1156  							Class: refClass1,
  1157  							Fields: map[string]interface{}{
  1158  								"nums":        []float64{1, 2, 3}, // ints are encoded as float64 in json
  1159  								"_additional": map[string]interface{}{"vector": []float32{3}},
  1160  							},
  1161  						},
  1162  					},
  1163  				},
  1164  			},
  1165  			searchParams: dto.GetParams{
  1166  				ClassName: className,
  1167  				Properties: search.SelectProperties{
  1168  					{Name: "word", IsPrimitive: true},
  1169  					{Name: "ref", IsPrimitive: false, Refs: []search.SelectClass{
  1170  						{
  1171  							ClassName:            refClass1,
  1172  							RefProperties:        search.SelectProperties{{Name: "nums", IsPrimitive: true}},
  1173  							AdditionalProperties: additional.Properties{Vector: true},
  1174  						},
  1175  					}},
  1176  				},
  1177  			},
  1178  			outSearch: []*pb.SearchResult{
  1179  				{
  1180  					Metadata: &pb.MetadataResult{},
  1181  					Properties: &pb.PropertiesResult{
  1182  						TargetCollection: className,
  1183  						NonRefProperties: newStruct(t, map[string]interface{}{
  1184  							"word": "word",
  1185  						}),
  1186  						RefProps: []*pb.RefPropertiesResult{{
  1187  							PropName: "ref",
  1188  							Properties: []*pb.PropertiesResult{
  1189  								{
  1190  									TargetCollection: refClass1,
  1191  									Metadata:         &pb.MetadataResult{Vector: []float32{3}, VectorBytes: byteVector([]float32{3})},
  1192  									NonRefProperties: newStruct(t, map[string]interface{}{}),
  1193  									IntArrayProperties: []*pb.IntArrayProperties{{
  1194  										PropName: "nums",
  1195  										Values:   []int64{1, 2, 3},
  1196  									}},
  1197  								},
  1198  							},
  1199  						}},
  1200  					},
  1201  				},
  1202  			},
  1203  			usesWeaviateStruct: false,
  1204  		},
  1205  		{
  1206  			name: "generative single only with ID",
  1207  			res: []interface{}{
  1208  				map[string]interface{}{
  1209  					"_additional": map[string]interface{}{
  1210  						"id":       UUID1,                                               // different place for generative
  1211  						"generate": &addModels.GenerateResult{SingleResult: &refClass1}, // just use some string
  1212  					},
  1213  				},
  1214  				map[string]interface{}{
  1215  					"_additional": map[string]interface{}{
  1216  						"id":       UUID2,
  1217  						"generate": &addModels.GenerateResult{SingleResult: &refClass2},
  1218  					},
  1219  				},
  1220  			},
  1221  			searchParams: dto.GetParams{AdditionalProperties: additional.Properties{
  1222  				ID: true,
  1223  				ModuleParams: map[string]interface{}{
  1224  					"generate": &generate.Params{
  1225  						Prompt: &refClass1,
  1226  					},
  1227  				},
  1228  			}},
  1229  			outSearch: []*pb.SearchResult{
  1230  				{
  1231  					Metadata: &pb.MetadataResult{
  1232  						Id:                string(UUID1),
  1233  						Generative:        refClass1,
  1234  						GenerativePresent: true,
  1235  						IdAsBytes:         idByte(UUID1.String()),
  1236  					},
  1237  					Properties: &pb.PropertiesResult{},
  1238  				},
  1239  				{
  1240  					Metadata: &pb.MetadataResult{
  1241  						Id:                string(UUID2),
  1242  						Generative:        refClass2,
  1243  						GenerativePresent: true,
  1244  						IdAsBytes:         idByte(UUID2.String()),
  1245  					},
  1246  					Properties: &pb.PropertiesResult{},
  1247  				},
  1248  			},
  1249  		},
  1250  		{
  1251  			name: "generative single only without ID",
  1252  			res: []interface{}{
  1253  				map[string]interface{}{
  1254  					"_additional": map[string]interface{}{ // different place for generative
  1255  						"generate": &addModels.GenerateResult{SingleResult: &refClass1}, // just use some string
  1256  					},
  1257  				},
  1258  				map[string]interface{}{
  1259  					"_additional": map[string]interface{}{
  1260  						"generate": &addModels.GenerateResult{SingleResult: &refClass2},
  1261  					},
  1262  				},
  1263  			},
  1264  			searchParams: dto.GetParams{AdditionalProperties: additional.Properties{
  1265  				ModuleParams: map[string]interface{}{
  1266  					"generate": &generate.Params{
  1267  						Prompt: &refClass1,
  1268  					},
  1269  				},
  1270  			}},
  1271  			outSearch: []*pb.SearchResult{
  1272  				{
  1273  					Metadata: &pb.MetadataResult{
  1274  						Generative:        refClass1,
  1275  						GenerativePresent: true,
  1276  					},
  1277  					Properties: &pb.PropertiesResult{},
  1278  				},
  1279  				{
  1280  					Metadata: &pb.MetadataResult{
  1281  						Generative:        refClass2,
  1282  						GenerativePresent: true,
  1283  					},
  1284  					Properties: &pb.PropertiesResult{},
  1285  				},
  1286  			},
  1287  		},
  1288  		{
  1289  			name: "generative with error",
  1290  			res: []interface{}{
  1291  				map[string]interface{}{
  1292  					"_additional": map[string]interface{}{ // different place for generative
  1293  						"generate": &addModels.GenerateResult{Error: errors.New("error")},
  1294  					},
  1295  				},
  1296  			},
  1297  			searchParams: dto.GetParams{AdditionalProperties: additional.Properties{
  1298  				ModuleParams: map[string]interface{}{
  1299  					"generate": &generate.Params{
  1300  						Prompt: &refClass1,
  1301  					},
  1302  				},
  1303  			}},
  1304  			hasError: true,
  1305  		},
  1306  		{
  1307  			name: "generative group only",
  1308  			res: []interface{}{
  1309  				map[string]interface{}{
  1310  					"_additional": map[string]interface{}{
  1311  						"id":       UUID1,                                                // different place for generative
  1312  						"generate": &addModels.GenerateResult{GroupedResult: &refClass1}, // just use some string
  1313  					},
  1314  				},
  1315  				map[string]interface{}{
  1316  					"_additional": map[string]interface{}{
  1317  						"id":       UUID2,
  1318  						"generate": &addModels.GenerateResult{},
  1319  					},
  1320  				},
  1321  			},
  1322  			searchParams: dto.GetParams{AdditionalProperties: additional.Properties{
  1323  				ID: true,
  1324  				ModuleParams: map[string]interface{}{
  1325  					"generate": &generate.Params{
  1326  						Task: &refClass1,
  1327  					},
  1328  				},
  1329  			}},
  1330  			outSearch: []*pb.SearchResult{
  1331  				{
  1332  					Metadata: &pb.MetadataResult{
  1333  						Id:        string(UUID1),
  1334  						IdAsBytes: idByte(UUID1.String()),
  1335  					},
  1336  					Properties: &pb.PropertiesResult{},
  1337  				},
  1338  				{
  1339  					Metadata: &pb.MetadataResult{
  1340  						Id:        string(UUID2),
  1341  						IdAsBytes: idByte(UUID2.String()),
  1342  					},
  1343  					Properties: &pb.PropertiesResult{},
  1344  				},
  1345  			},
  1346  			outGenerative: refClass1,
  1347  		},
  1348  		{
  1349  			name: "group by",
  1350  			res: []interface{}{
  1351  				map[string]interface{}{
  1352  					"_additional": map[string]interface{}{
  1353  						"id": UUID2,
  1354  						"group": &additional.Group{
  1355  							ID:          1,
  1356  							MinDistance: 0.1,
  1357  							MaxDistance: 0.2,
  1358  							Count:       3,
  1359  							GroupedBy:   &additional.GroupedBy{Value: "GroupByValue1", Path: []string{"some_prop"}},
  1360  							Hits: []map[string]interface{}{
  1361  								{
  1362  									"word": "word",
  1363  									"ref": []interface{}{
  1364  										search.LocalRef{
  1365  											Class: refClass1,
  1366  											Fields: map[string]interface{}{
  1367  												"something":   "other",
  1368  												"_additional": map[string]interface{}{"vector": []float32{2}, "id": UUID1},
  1369  											},
  1370  										},
  1371  									},
  1372  									"_additional": &additional.GroupHitAdditional{Vector: []float32{3}, ID: UUID2},
  1373  								},
  1374  								{
  1375  									"word":        "other",
  1376  									"_additional": &additional.GroupHitAdditional{Vector: []float32{4}, ID: UUID1},
  1377  								},
  1378  							},
  1379  						},
  1380  					},
  1381  				},
  1382  			},
  1383  			searchParams: dto.GetParams{AdditionalProperties: additional.Properties{
  1384  				ID:     true,
  1385  				Vector: true,
  1386  			}, GroupBy: &searchparams.GroupBy{Groups: 3, ObjectsPerGroup: 4, Property: "name"}},
  1387  			outGroup: []*pb.GroupByResult{{
  1388  				Name:            "GroupByValue1",
  1389  				MaxDistance:     0.2,
  1390  				MinDistance:     0.1,
  1391  				NumberOfObjects: 3,
  1392  				Objects: []*pb.SearchResult{
  1393  					{
  1394  						Properties: &pb.PropertiesResult{
  1395  							NonRefProperties: newStruct(t, map[string]interface{}{"word": "word"}),
  1396  							RefProps: []*pb.RefPropertiesResult{
  1397  								{
  1398  									PropName: "other",
  1399  									Properties: []*pb.PropertiesResult{
  1400  										{
  1401  											NonRefProperties: newStruct(t, map[string]interface{}{"something": "other"}),
  1402  											Metadata:         &pb.MetadataResult{Vector: []float32{2}, Id: UUID1.String()},
  1403  										},
  1404  									},
  1405  								},
  1406  							},
  1407  							RefPropsRequested: true,
  1408  						},
  1409  						Metadata: &pb.MetadataResult{
  1410  							Id:     string(UUID2),
  1411  							Vector: []float32{3},
  1412  						},
  1413  					},
  1414  					{
  1415  						Properties: &pb.PropertiesResult{
  1416  							NonRefProperties: newStruct(t, map[string]interface{}{"word": "other"}),
  1417  						},
  1418  						Metadata: &pb.MetadataResult{
  1419  							Id:     string(UUID1),
  1420  							Vector: []float32{4},
  1421  						},
  1422  					},
  1423  				},
  1424  			}},
  1425  		},
  1426  		{
  1427  			name: "rerank only",
  1428  			res: []interface{}{
  1429  				map[string]interface{}{
  1430  					"_additional": map[string]interface{}{
  1431  						"id":     UUID1,
  1432  						"rerank": []*addModels.RankResult{{Score: &someFloat64}},
  1433  					},
  1434  				},
  1435  			},
  1436  			searchParams: dto.GetParams{AdditionalProperties: additional.Properties{
  1437  				ID:           true,
  1438  				ModuleParams: map[string]interface{}{"rerank": "must be present for extraction"},
  1439  			}},
  1440  			outSearch: []*pb.SearchResult{
  1441  				{
  1442  					Metadata: &pb.MetadataResult{
  1443  						Id:                 string(UUID1),
  1444  						RerankScore:        someFloat64,
  1445  						RerankScorePresent: true,
  1446  						IdAsBytes:          idByte(UUID1.String()),
  1447  					},
  1448  					Properties: &pb.PropertiesResult{},
  1449  				},
  1450  			},
  1451  		},
  1452  		{
  1453  			name: "generate, group by, & rerank",
  1454  			res: []interface{}{
  1455  				map[string]interface{}{
  1456  					"_additional": map[string]interface{}{
  1457  						"id": UUID2,
  1458  						"generate": &addModels.GenerateResult{
  1459  							SingleResult:  &refClass1,
  1460  							GroupedResult: &refClass2,
  1461  						},
  1462  						"rerank": []*addModels.RankResult{{Score: &someFloat64}},
  1463  						"group": &additional.Group{
  1464  							ID:          1,
  1465  							MinDistance: 0.1,
  1466  							MaxDistance: 0.2,
  1467  							Count:       3,
  1468  							GroupedBy:   &additional.GroupedBy{Value: "GroupByValue1", Path: []string{"some_prop"}},
  1469  							Hits: []map[string]interface{}{
  1470  								{
  1471  									"word": "word",
  1472  									"ref": []interface{}{
  1473  										search.LocalRef{
  1474  											Class: refClass1,
  1475  											Fields: map[string]interface{}{
  1476  												"something":   "other",
  1477  												"_additional": map[string]interface{}{"vector": []float32{2}, "id": UUID1},
  1478  											},
  1479  										},
  1480  									},
  1481  									"_additional": &additional.GroupHitAdditional{Vector: []float32{3}, ID: UUID2},
  1482  								},
  1483  								{
  1484  									"word":        "other",
  1485  									"_additional": &additional.GroupHitAdditional{Vector: []float32{4}, ID: UUID1},
  1486  								},
  1487  							},
  1488  						},
  1489  					},
  1490  				},
  1491  			},
  1492  			searchParams: dto.GetParams{
  1493  				AdditionalProperties: additional.Properties{
  1494  					ID:     true,
  1495  					Vector: true,
  1496  					ModuleParams: map[string]interface{}{
  1497  						"generate": &generate.Params{
  1498  							Prompt: &refClass1,
  1499  							Task:   &refClass2,
  1500  						},
  1501  						"rerank": "must be present for extraction",
  1502  					},
  1503  				},
  1504  				GroupBy: &searchparams.GroupBy{Groups: 3, ObjectsPerGroup: 4, Property: "name"},
  1505  			},
  1506  			outGroup: []*pb.GroupByResult{{
  1507  				Name:            "GroupByValue1",
  1508  				MaxDistance:     0.2,
  1509  				MinDistance:     0.1,
  1510  				NumberOfObjects: 3,
  1511  				Generative:      &pb.GenerativeReply{Result: refClass1},
  1512  				Rerank:          &pb.RerankReply{Score: someFloat64},
  1513  				Objects: []*pb.SearchResult{
  1514  					{
  1515  						Properties: &pb.PropertiesResult{
  1516  							NonRefProperties: newStruct(t, map[string]interface{}{"word": "word"}),
  1517  							RefProps: []*pb.RefPropertiesResult{
  1518  								{
  1519  									PropName: "other",
  1520  									Properties: []*pb.PropertiesResult{
  1521  										{
  1522  											NonRefProperties: newStruct(t, map[string]interface{}{"something": "other"}),
  1523  											Metadata:         &pb.MetadataResult{Vector: []float32{2}, Id: UUID1.String()},
  1524  										},
  1525  									},
  1526  								},
  1527  							},
  1528  							RefPropsRequested: true,
  1529  						},
  1530  						Metadata: &pb.MetadataResult{
  1531  							Id:     string(UUID2),
  1532  							Vector: []float32{3},
  1533  						},
  1534  					},
  1535  					{
  1536  						Properties: &pb.PropertiesResult{
  1537  							NonRefProperties: newStruct(t, map[string]interface{}{"word": "other"}),
  1538  						},
  1539  						Metadata: &pb.MetadataResult{
  1540  							Id:     string(UUID1),
  1541  							Vector: []float32{4},
  1542  						},
  1543  					},
  1544  				},
  1545  			}},
  1546  			outGenerative: refClass2,
  1547  		},
  1548  	}
  1549  
  1550  	for _, tt := range tests {
  1551  		t.Run(tt.name, func(t *testing.T) {
  1552  			out, err := searchResultsToProto(tt.res, time.Now(), tt.searchParams, scheme, tt.usesWeaviateStruct)
  1553  			if tt.hasError {
  1554  				require.NotNil(t, err)
  1555  			} else {
  1556  				require.Nil(t, err)
  1557  				for i := range tt.outSearch {
  1558  					require.Equal(t, tt.outSearch[i].Properties.String(), out.Results[i].Properties.String())
  1559  					// order of the vectors is not guaranteed, doesn't matter for results
  1560  					vectorsOut := out.Results[i].Metadata.Vectors
  1561  					vectorsExpected := tt.outSearch[i].Metadata.Vectors
  1562  					require.ElementsMatch(t, vectorsOut, vectorsExpected)
  1563  
  1564  					out.Results[i].Metadata.Vectors = nil
  1565  					tt.outSearch[i].Metadata.Vectors = nil
  1566  					require.Equal(t, tt.outSearch[i].Metadata.String(), out.Results[i].Metadata.String())
  1567  				}
  1568  				require.Equal(t, tt.outGenerative, *out.GenerativeGroupedResult)
  1569  			}
  1570  		})
  1571  	}
  1572  }