github.com/weaviate/weaviate@v1.24.6/adapters/handlers/grpc/v1/parse_search_request.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  	"fmt"
    16  
    17  	"github.com/weaviate/weaviate/usecases/config"
    18  
    19  	"github.com/go-openapi/strfmt"
    20  	"github.com/google/uuid"
    21  	"github.com/pkg/errors"
    22  	"github.com/weaviate/weaviate/adapters/handlers/graphql/local/common_filters"
    23  	"github.com/weaviate/weaviate/entities/additional"
    24  	"github.com/weaviate/weaviate/entities/dto"
    25  	"github.com/weaviate/weaviate/entities/filters"
    26  	"github.com/weaviate/weaviate/entities/models"
    27  	"github.com/weaviate/weaviate/entities/schema"
    28  	"github.com/weaviate/weaviate/entities/schema/crossref"
    29  	"github.com/weaviate/weaviate/entities/search"
    30  	"github.com/weaviate/weaviate/entities/searchparams"
    31  	"github.com/weaviate/weaviate/entities/vectorindex/common"
    32  	pb "github.com/weaviate/weaviate/grpc/generated/protocol/v1"
    33  	"github.com/weaviate/weaviate/usecases/byteops"
    34  	"github.com/weaviate/weaviate/usecases/modulecomponents/additional/generate"
    35  	"github.com/weaviate/weaviate/usecases/modulecomponents/additional/rank"
    36  	"github.com/weaviate/weaviate/usecases/modulecomponents/arguments/nearAudio"
    37  	"github.com/weaviate/weaviate/usecases/modulecomponents/arguments/nearDepth"
    38  	"github.com/weaviate/weaviate/usecases/modulecomponents/arguments/nearImage"
    39  	"github.com/weaviate/weaviate/usecases/modulecomponents/arguments/nearImu"
    40  	nearText2 "github.com/weaviate/weaviate/usecases/modulecomponents/arguments/nearText"
    41  	"github.com/weaviate/weaviate/usecases/modulecomponents/arguments/nearThermal"
    42  	"github.com/weaviate/weaviate/usecases/modulecomponents/arguments/nearVideo"
    43  )
    44  
    45  func searchParamsFromProto(req *pb.SearchRequest, scheme schema.Schema, config *config.Config) (dto.GetParams, error) {
    46  	out := dto.GetParams{}
    47  	class, err := schema.GetClassByName(scheme.Objects, req.Collection)
    48  	if err != nil {
    49  		return dto.GetParams{}, err
    50  	}
    51  
    52  	out.ClassName = req.Collection
    53  	out.ReplicationProperties = extractReplicationProperties(req.ConsistencyLevel)
    54  
    55  	out.Tenant = req.Tenant
    56  
    57  	targetVectors, err := extractTargetVectors(req, class)
    58  	if err != nil {
    59  		return dto.GetParams{}, errors.Wrap(err, "extract target vectors")
    60  	}
    61  
    62  	if req.Metadata != nil {
    63  		addProps, err := extractAdditionalPropsFromMetadata(class, req.Metadata, targetVectors)
    64  		if err != nil {
    65  			return dto.GetParams{}, errors.Wrap(err, "extract additional props")
    66  		}
    67  		out.AdditionalProperties = addProps
    68  	}
    69  
    70  	out.Properties, err = extractPropertiesRequest(req.Properties, scheme, req.Collection, req.Uses_123Api, targetVectors)
    71  	if err != nil {
    72  		return dto.GetParams{}, errors.Wrap(err, "extract properties request")
    73  	}
    74  	if len(out.Properties) == 0 {
    75  		out.AdditionalProperties.NoProps = true
    76  	}
    77  
    78  	if hs := req.HybridSearch; hs != nil {
    79  		fusionType := common_filters.HybridFusionDefault
    80  		if hs.FusionType == pb.Hybrid_FUSION_TYPE_RANKED {
    81  			fusionType = common_filters.HybridRankedFusion
    82  		} else if hs.FusionType == pb.Hybrid_FUSION_TYPE_RELATIVE_SCORE {
    83  			fusionType = common_filters.HybridRelativeScoreFusion
    84  		}
    85  
    86  		var vector []float32
    87  		// bytes vector has precedent for being more efficient
    88  		if len(hs.VectorBytes) > 0 {
    89  			vector = byteops.Float32FromByteVector(hs.VectorBytes)
    90  		} else if len(hs.Vector) > 0 {
    91  			vector = hs.Vector
    92  		}
    93  
    94  		out.HybridSearch = &searchparams.HybridSearch{Query: hs.Query, Properties: schema.LowercaseFirstLetterOfStrings(hs.Properties), Vector: vector, Alpha: float64(hs.Alpha), FusionAlgorithm: fusionType, TargetVectors: hs.TargetVectors}
    95  	}
    96  
    97  	if bm25 := req.Bm25Search; bm25 != nil {
    98  		out.KeywordRanking = &searchparams.KeywordRanking{Query: bm25.Query, Properties: schema.LowercaseFirstLetterOfStrings(bm25.Properties), Type: "bm25", AdditionalExplanations: out.AdditionalProperties.ExplainScore}
    99  	}
   100  
   101  	if nv := req.NearVector; nv != nil {
   102  		var vector []float32
   103  		// bytes vector has precedent for being more efficient
   104  		if len(nv.VectorBytes) > 0 {
   105  			vector = byteops.Float32FromByteVector(nv.VectorBytes)
   106  		} else {
   107  			vector = nv.Vector
   108  		}
   109  		out.NearVector = &searchparams.NearVector{
   110  			Vector:        vector,
   111  			TargetVectors: nv.TargetVectors,
   112  		}
   113  
   114  		// The following business logic should not sit in the API. However, it is
   115  		// also part of the GraphQL API, so we need to duplicate it in order to get
   116  		// the same behavior
   117  		if nv.Distance != nil && nv.Certainty != nil {
   118  			return out, fmt.Errorf("near_vector: cannot provide distance and certainty")
   119  		}
   120  
   121  		if nv.Certainty != nil {
   122  			out.NearVector.Certainty = *nv.Certainty
   123  		}
   124  
   125  		if nv.Distance != nil {
   126  			out.NearVector.Distance = *nv.Distance
   127  			out.NearVector.WithDistance = true
   128  		}
   129  	}
   130  
   131  	if no := req.NearObject; no != nil {
   132  		out.NearObject = &searchparams.NearObject{
   133  			ID:            req.NearObject.Id,
   134  			TargetVectors: no.TargetVectors,
   135  		}
   136  
   137  		// The following business logic should not sit in the API. However, it is
   138  		// also part of the GraphQL API, so we need to duplicate it in order to get
   139  		// the same behavior
   140  		if no.Distance != nil && no.Certainty != nil {
   141  			return out, fmt.Errorf("near_object: cannot provide distance and certainty")
   142  		}
   143  
   144  		if no.Certainty != nil {
   145  			out.NearObject.Certainty = *no.Certainty
   146  		}
   147  
   148  		if no.Distance != nil {
   149  			out.NearObject.Distance = *no.Distance
   150  			out.NearObject.WithDistance = true
   151  		}
   152  	}
   153  
   154  	if ni := req.NearImage; ni != nil {
   155  		nearImageOut, err := parseNearImage(ni)
   156  		if err != nil {
   157  			return dto.GetParams{}, err
   158  		}
   159  
   160  		if out.ModuleParams == nil {
   161  			out.ModuleParams = make(map[string]interface{})
   162  		}
   163  		out.ModuleParams["nearImage"] = nearImageOut
   164  	}
   165  
   166  	if na := req.NearAudio; na != nil {
   167  		nearAudioOut, err := parseNearAudio(na)
   168  		if err != nil {
   169  			return dto.GetParams{}, err
   170  		}
   171  
   172  		if out.ModuleParams == nil {
   173  			out.ModuleParams = make(map[string]interface{})
   174  		}
   175  		out.ModuleParams["nearAudio"] = nearAudioOut
   176  	}
   177  
   178  	if nv := req.NearVideo; nv != nil {
   179  		nearVideoOut, err := parseNearVideo(nv)
   180  		if err != nil {
   181  			return dto.GetParams{}, err
   182  		}
   183  
   184  		if out.ModuleParams == nil {
   185  			out.ModuleParams = make(map[string]interface{})
   186  		}
   187  		out.ModuleParams["nearVideo"] = nearVideoOut
   188  	}
   189  
   190  	if nd := req.NearDepth; nd != nil {
   191  		nearDepthOut, err := parseNearDepth(nd)
   192  		if err != nil {
   193  			return dto.GetParams{}, err
   194  		}
   195  
   196  		if out.ModuleParams == nil {
   197  			out.ModuleParams = make(map[string]interface{})
   198  		}
   199  		out.ModuleParams["nearDepth"] = nearDepthOut
   200  	}
   201  
   202  	if nt := req.NearThermal; nt != nil {
   203  		nearThermalOut, err := parseNearThermal(nt)
   204  		if err != nil {
   205  			return dto.GetParams{}, err
   206  		}
   207  
   208  		if out.ModuleParams == nil {
   209  			out.ModuleParams = make(map[string]interface{})
   210  		}
   211  		out.ModuleParams["nearThermal"] = nearThermalOut
   212  	}
   213  
   214  	if ni := req.NearImu; ni != nil {
   215  		nearIMUOut, err := parseNearIMU(ni)
   216  		if err != nil {
   217  			return dto.GetParams{}, err
   218  		}
   219  		if out.ModuleParams == nil {
   220  			out.ModuleParams = make(map[string]interface{})
   221  		}
   222  		out.ModuleParams["nearIMU"] = nearIMUOut
   223  	}
   224  
   225  	out.Pagination = &filters.Pagination{Offset: int(req.Offset), Autocut: int(req.Autocut)}
   226  	if req.Limit > 0 {
   227  		out.Pagination.Limit = int(req.Limit)
   228  	} else {
   229  		out.Pagination.Limit = int(config.QueryDefaults.Limit)
   230  	}
   231  
   232  	if nt := req.NearText; nt != nil {
   233  		moveAwayOut, err := extractNearTextMove(req.Collection, nt.MoveAway)
   234  		if err != nil {
   235  			return dto.GetParams{}, err
   236  		}
   237  		moveToOut, err := extractNearTextMove(req.Collection, nt.MoveTo)
   238  		if err != nil {
   239  			return dto.GetParams{}, err
   240  		}
   241  
   242  		nearText := &nearText2.NearTextParams{
   243  			Values:        nt.Query,
   244  			Limit:         out.Pagination.Limit,
   245  			MoveAwayFrom:  moveAwayOut,
   246  			MoveTo:        moveToOut,
   247  			TargetVectors: nt.TargetVectors,
   248  		}
   249  
   250  		if nt.Certainty != nil {
   251  			nearText.Certainty = *nt.Certainty
   252  		}
   253  		if nt.Distance != nil {
   254  			nearText.Distance = *nt.Distance
   255  			nearText.WithDistance = true
   256  		}
   257  		if out.ModuleParams == nil {
   258  			out.ModuleParams = make(map[string]interface{})
   259  		}
   260  		out.ModuleParams["nearText"] = nearText
   261  	}
   262  
   263  	if req.Generative != nil {
   264  		if out.AdditionalProperties.ModuleParams == nil {
   265  			out.AdditionalProperties.ModuleParams = make(map[string]interface{})
   266  		}
   267  		out.AdditionalProperties.ModuleParams["generate"] = extractGenerative(req)
   268  	}
   269  
   270  	if req.Rerank != nil {
   271  		if out.AdditionalProperties.ModuleParams == nil {
   272  			out.AdditionalProperties.ModuleParams = make(map[string]interface{})
   273  		}
   274  		out.AdditionalProperties.ModuleParams["rerank"] = extractRerank(req)
   275  	}
   276  
   277  	if len(req.After) > 0 {
   278  		out.Cursor = &filters.Cursor{After: req.After, Limit: out.Pagination.Limit}
   279  	}
   280  
   281  	if req.Filters != nil {
   282  		clause, err := extractFilters(req.Filters, scheme, req.Collection)
   283  		if err != nil {
   284  			return dto.GetParams{}, err
   285  		}
   286  		filter := &filters.LocalFilter{Root: &clause}
   287  		if err := filters.ValidateFilters(scheme, filter); err != nil {
   288  			return dto.GetParams{}, err
   289  		}
   290  		out.Filters = filter
   291  	}
   292  
   293  	if len(req.SortBy) > 0 {
   294  		if req.NearText != nil || req.NearVideo != nil || req.NearAudio != nil || req.NearImage != nil || req.NearObject != nil || req.NearVector != nil || req.HybridSearch != nil || req.Bm25Search != nil || req.Generative != nil {
   295  			return dto.GetParams{}, errors.New("sorting cannot be combined with search")
   296  		}
   297  		out.Sort = extractSorting(req.SortBy)
   298  	}
   299  
   300  	if req.GroupBy != nil {
   301  		groupBy, err := extractGroupBy(req.GroupBy, &out)
   302  		if err != nil {
   303  			return dto.GetParams{}, err
   304  		}
   305  		out.AdditionalProperties.Group = true
   306  
   307  		out.GroupBy = groupBy
   308  	}
   309  
   310  	return out, nil
   311  }
   312  
   313  func extractGroupBy(groupIn *pb.GroupBy, out *dto.GetParams) (*searchparams.GroupBy, error) {
   314  	if len(groupIn.Path) != 1 {
   315  		return nil, fmt.Errorf("groupby path can only have one entry, received %v", groupIn.Path)
   316  	}
   317  
   318  	groupOut := &searchparams.GroupBy{
   319  		Property:        groupIn.Path[0],
   320  		ObjectsPerGroup: int(groupIn.ObjectsPerGroup),
   321  		Groups:          int(groupIn.NumberOfGroups),
   322  	}
   323  
   324  	// add the property in case it was not requested as return prop - otherwise it is not resolved
   325  	if out.Properties.FindProperty(groupOut.Property) == nil {
   326  		out.Properties = append(out.Properties, search.SelectProperty{Name: groupOut.Property, IsPrimitive: true})
   327  	}
   328  	out.AdditionalProperties.NoProps = false
   329  
   330  	return groupOut, nil
   331  }
   332  
   333  func extractTargetVectors(req *pb.SearchRequest, class *models.Class) (*[]string, error) {
   334  	var targetVectors *[]string
   335  	if hs := req.HybridSearch; hs != nil {
   336  		targetVectors = &hs.TargetVectors
   337  	}
   338  	if na := req.NearAudio; na != nil {
   339  		targetVectors = &na.TargetVectors
   340  	}
   341  	if nd := req.NearDepth; nd != nil {
   342  		targetVectors = &nd.TargetVectors
   343  	}
   344  	if ni := req.NearImage; ni != nil {
   345  		targetVectors = &ni.TargetVectors
   346  	}
   347  	if ni := req.NearImu; ni != nil {
   348  		targetVectors = &ni.TargetVectors
   349  	}
   350  	if no := req.NearObject; no != nil {
   351  		targetVectors = &no.TargetVectors
   352  	}
   353  	if nt := req.NearText; nt != nil {
   354  		targetVectors = &nt.TargetVectors
   355  	}
   356  	if nt := req.NearThermal; nt != nil {
   357  		targetVectors = &nt.TargetVectors
   358  	}
   359  	if nv := req.NearVector; nv != nil {
   360  		targetVectors = &nv.TargetVectors
   361  	}
   362  	if nv := req.NearVideo; nv != nil {
   363  		targetVectors = &nv.TargetVectors
   364  	}
   365  
   366  	if targetVectors != nil && len(*targetVectors) == 0 && len(class.VectorConfig) > 1 {
   367  		return nil, fmt.Errorf("class %s has multiple vectors, but no target vectors were provided", class.Class)
   368  	}
   369  	if targetVectors != nil && len(*targetVectors) > 1 {
   370  		return nil, fmt.Errorf("cannot provide multiple target vectors when searching, only one is allowed")
   371  	}
   372  	return targetVectors, nil
   373  }
   374  
   375  func extractSorting(sortIn []*pb.SortBy) []filters.Sort {
   376  	sortOut := make([]filters.Sort, len(sortIn))
   377  	for i := range sortIn {
   378  		order := "asc"
   379  		if !sortIn[i].Ascending {
   380  			order = "desc"
   381  		}
   382  		sortOut[i] = filters.Sort{Order: order, Path: sortIn[i].Path}
   383  	}
   384  	return sortOut
   385  }
   386  
   387  func extractGenerative(req *pb.SearchRequest) *generate.Params {
   388  	generative := generate.Params{}
   389  	if req.Generative.SingleResponsePrompt != "" {
   390  		generative.Prompt = &req.Generative.SingleResponsePrompt
   391  	}
   392  	if req.Generative.GroupedResponseTask != "" {
   393  		generative.Task = &req.Generative.GroupedResponseTask
   394  	}
   395  	if len(req.Generative.GroupedProperties) > 0 {
   396  		generative.Properties = req.Generative.GroupedProperties
   397  	}
   398  	return &generative
   399  }
   400  
   401  func extractRerank(req *pb.SearchRequest) *rank.Params {
   402  	rerank := rank.Params{
   403  		Property: &req.Rerank.Property,
   404  	}
   405  	if req.Rerank.Query != nil {
   406  		rerank.Query = req.Rerank.Query
   407  	}
   408  	return &rerank
   409  }
   410  
   411  func extractNearTextMove(classname string, Move *pb.NearTextSearch_Move) (nearText2.ExploreMove, error) {
   412  	var moveAwayOut nearText2.ExploreMove
   413  
   414  	if moveAwayReq := Move; moveAwayReq != nil {
   415  		moveAwayOut.Force = moveAwayReq.Force
   416  		if moveAwayReq.Uuids != nil && len(moveAwayReq.Uuids) > 0 {
   417  			moveAwayOut.Objects = make([]nearText2.ObjectMove, len(moveAwayReq.Uuids))
   418  			for i, objUUid := range moveAwayReq.Uuids {
   419  				uuidFormat, err := uuid.Parse(objUUid)
   420  				if err != nil {
   421  					return moveAwayOut, err
   422  				}
   423  				moveAwayOut.Objects[i] = nearText2.ObjectMove{
   424  					ID:     objUUid,
   425  					Beacon: crossref.NewLocalhost(classname, strfmt.UUID(uuidFormat.String())).String(),
   426  				}
   427  			}
   428  		}
   429  
   430  		moveAwayOut.Values = moveAwayReq.Concepts
   431  	}
   432  	return moveAwayOut, nil
   433  }
   434  
   435  func extractPropertiesRequest(reqProps *pb.PropertiesRequest, scheme schema.Schema, className string, usesNewDefaultLogic bool, targetVectors *[]string) ([]search.SelectProperty, error) {
   436  	props := make([]search.SelectProperty, 0)
   437  
   438  	if reqProps == nil {
   439  		// No properties selected at all, return all non-ref properties.
   440  		// Ignore blobs to not overload the response
   441  		nonRefProps, err := getAllNonRefNonBlobProperties(scheme, className)
   442  		if err != nil {
   443  			return nil, errors.Wrap(err, "get all non ref non blob properties")
   444  		}
   445  		return nonRefProps, nil
   446  	}
   447  
   448  	if !usesNewDefaultLogic {
   449  		// Old stubs being used, use deprecated method
   450  		return extractPropertiesRequestDeprecated(reqProps, scheme, className, targetVectors)
   451  	}
   452  
   453  	if reqProps.ReturnAllNonrefProperties {
   454  		// No non-ref return properties selected, return all non-ref properties.
   455  		// Ignore blobs to not overload the response
   456  		returnProps, err := getAllNonRefNonBlobProperties(scheme, className)
   457  		if err != nil {
   458  			return nil, errors.Wrap(err, "get all non ref non blob properties")
   459  		}
   460  		props = append(props, returnProps...)
   461  	} else if len(reqProps.NonRefProperties) > 0 {
   462  		// Non-ref properties are selected, return only those specified
   463  		// This catches the case where users send an empty list of non ref properties as their request,
   464  		// i.e. they want no non-ref properties
   465  		for _, prop := range reqProps.NonRefProperties {
   466  			props = append(props, search.SelectProperty{
   467  				Name:        schema.LowercaseFirstLetter(prop),
   468  				IsPrimitive: true,
   469  				IsObject:    false,
   470  			})
   471  		}
   472  	}
   473  
   474  	if len(reqProps.RefProperties) > 0 {
   475  		class := scheme.GetClass(schema.ClassName(className))
   476  		for _, prop := range reqProps.RefProperties {
   477  			normalizedRefPropName := schema.LowercaseFirstLetter(prop.ReferenceProperty)
   478  			schemaProp, err := schema.GetPropertyByName(class, normalizedRefPropName)
   479  			if err != nil {
   480  				return nil, err
   481  			}
   482  
   483  			var linkedClassName string
   484  			if len(schemaProp.DataType) == 1 {
   485  				// use datatype of the reference property to get the name of the linked class
   486  				linkedClassName = schemaProp.DataType[0]
   487  			} else {
   488  				linkedClassName = prop.TargetCollection
   489  				if linkedClassName == "" {
   490  					return nil, fmt.Errorf(
   491  						"multi target references from collection %v and property %v with need an explicit"+
   492  							"linked collection. Available linked collections are %v",
   493  						className, prop.ReferenceProperty, schemaProp.DataType)
   494  				}
   495  			}
   496  			var refProperties []search.SelectProperty
   497  			var addProps additional.Properties
   498  			if prop.Properties != nil {
   499  				refProperties, err = extractPropertiesRequest(prop.Properties, scheme, linkedClassName, usesNewDefaultLogic, targetVectors)
   500  				if err != nil {
   501  					return nil, errors.Wrap(err, "extract properties request")
   502  				}
   503  			}
   504  			if prop.Metadata != nil {
   505  				addProps, err = extractAdditionalPropsFromMetadata(class, prop.Metadata, targetVectors)
   506  				if err != nil {
   507  					return nil, errors.Wrap(err, "extract additional props for refs")
   508  				}
   509  			}
   510  
   511  			if prop.Properties == nil {
   512  				refProperties, err = getAllNonRefNonBlobProperties(scheme, linkedClassName)
   513  				if err != nil {
   514  					return nil, errors.Wrap(err, "get all non ref non blob properties")
   515  				}
   516  			}
   517  			if len(refProperties) == 0 && isIdOnlyRequest(prop.Metadata) {
   518  				// This is a pure-ID query without any properties or additional metadata.
   519  				// Indicate this to the DB, so it can optimize accordingly
   520  				addProps.NoProps = true
   521  			}
   522  
   523  			props = append(props, search.SelectProperty{
   524  				Name:        normalizedRefPropName,
   525  				IsPrimitive: false,
   526  				IsObject:    false,
   527  				Refs: []search.SelectClass{{
   528  					ClassName:            linkedClassName,
   529  					RefProperties:        refProperties,
   530  					AdditionalProperties: addProps,
   531  				}},
   532  			})
   533  		}
   534  	}
   535  
   536  	if len(reqProps.ObjectProperties) > 0 {
   537  		props = append(props, extractNestedProperties(reqProps.ObjectProperties)...)
   538  	}
   539  
   540  	return props, nil
   541  }
   542  
   543  func extractPropertiesRequestDeprecated(reqProps *pb.PropertiesRequest, scheme schema.Schema, className string, targetVectors *[]string) ([]search.SelectProperty, error) {
   544  	if reqProps == nil {
   545  		return nil, nil
   546  	}
   547  	props := make([]search.SelectProperty, 0)
   548  	if reqProps.NonRefProperties != nil && len(reqProps.NonRefProperties) > 0 {
   549  		for _, prop := range reqProps.NonRefProperties {
   550  			props = append(props, search.SelectProperty{
   551  				Name:        schema.LowercaseFirstLetter(prop),
   552  				IsPrimitive: true,
   553  				IsObject:    false,
   554  			})
   555  		}
   556  	}
   557  
   558  	if reqProps.RefProperties != nil && len(reqProps.RefProperties) > 0 {
   559  		class := scheme.GetClass(schema.ClassName(className))
   560  		for _, prop := range reqProps.RefProperties {
   561  			normalizedRefPropName := schema.LowercaseFirstLetter(prop.ReferenceProperty)
   562  			schemaProp, err := schema.GetPropertyByName(class, normalizedRefPropName)
   563  			if err != nil {
   564  				return nil, err
   565  			}
   566  
   567  			var linkedClassName string
   568  			if len(schemaProp.DataType) == 1 {
   569  				// use datatype of the reference property to get the name of the linked class
   570  				linkedClassName = schemaProp.DataType[0]
   571  			} else {
   572  				linkedClassName = prop.TargetCollection
   573  				if linkedClassName == "" {
   574  					return nil, fmt.Errorf(
   575  						"multi target references from collection %v and property %v with need an explicit"+
   576  							"linked collection. Available linked collections are %v",
   577  						className, prop.ReferenceProperty, schemaProp.DataType)
   578  				}
   579  			}
   580  			var refProperties []search.SelectProperty
   581  			var addProps additional.Properties
   582  			if prop.Properties != nil {
   583  				refProperties, err = extractPropertiesRequestDeprecated(prop.Properties, scheme, linkedClassName, targetVectors)
   584  				if err != nil {
   585  					return nil, errors.Wrap(err, "extract properties request")
   586  				}
   587  			}
   588  			if prop.Metadata != nil {
   589  				addProps, err = extractAdditionalPropsFromMetadata(class, prop.Metadata, targetVectors)
   590  				if err != nil {
   591  					return nil, errors.Wrap(err, "extract additional props for refs")
   592  				}
   593  			}
   594  
   595  			if prop.Properties == nil {
   596  				refProperties, err = getAllNonRefNonBlobProperties(scheme, linkedClassName)
   597  				if err != nil {
   598  					return nil, errors.Wrap(err, "get all non ref non blob properties")
   599  				}
   600  			}
   601  			if len(refProperties) == 0 && isIdOnlyRequest(prop.Metadata) {
   602  				// This is a pure-ID query without any properties or additional metadata.
   603  				// Indicate this to the DB, so it can optimize accordingly
   604  				addProps.NoProps = true
   605  			}
   606  
   607  			props = append(props, search.SelectProperty{
   608  				Name:        normalizedRefPropName,
   609  				IsPrimitive: false,
   610  				IsObject:    false,
   611  				Refs: []search.SelectClass{{
   612  					ClassName:            linkedClassName,
   613  					RefProperties:        refProperties,
   614  					AdditionalProperties: addProps,
   615  				}},
   616  			})
   617  		}
   618  	}
   619  
   620  	if reqProps.ObjectProperties != nil && len(reqProps.ObjectProperties) > 0 {
   621  		props = append(props, extractNestedProperties(reqProps.ObjectProperties)...)
   622  	}
   623  
   624  	return props, nil
   625  }
   626  
   627  func extractNestedProperties(props []*pb.ObjectPropertiesRequest) []search.SelectProperty {
   628  	selectProps := make([]search.SelectProperty, 0)
   629  	for _, prop := range props {
   630  		nestedProps := make([]search.SelectProperty, 0)
   631  		if prop.PrimitiveProperties != nil && len(prop.PrimitiveProperties) > 0 {
   632  			for _, primitive := range prop.PrimitiveProperties {
   633  				nestedProps = append(nestedProps, search.SelectProperty{
   634  					Name:        schema.LowercaseFirstLetter(primitive),
   635  					IsPrimitive: true,
   636  					IsObject:    false,
   637  				})
   638  			}
   639  		}
   640  		if prop.ObjectProperties != nil && len(prop.ObjectProperties) > 0 {
   641  			nestedProps = append(nestedProps, extractNestedProperties(prop.ObjectProperties)...)
   642  		}
   643  		selectProps = append(selectProps, search.SelectProperty{
   644  			Name:        schema.LowercaseFirstLetter(prop.PropName),
   645  			IsPrimitive: false,
   646  			IsObject:    true,
   647  			Props:       nestedProps,
   648  		})
   649  	}
   650  	return selectProps
   651  }
   652  
   653  func extractAdditionalPropsFromMetadata(class *models.Class, prop *pb.MetadataRequest, targetVectors *[]string) (additional.Properties, error) {
   654  	props := additional.Properties{
   655  		Vector:             prop.Vector,
   656  		ID:                 prop.Uuid,
   657  		CreationTimeUnix:   prop.CreationTimeUnix,
   658  		LastUpdateTimeUnix: prop.LastUpdateTimeUnix,
   659  		Distance:           prop.Distance,
   660  		Score:              prop.Score,
   661  		ExplainScore:       prop.ExplainScore,
   662  		IsConsistent:       prop.IsConsistent,
   663  		Vectors:            prop.Vectors,
   664  	}
   665  
   666  	// return all named vectors if vector is true
   667  	if prop.Vector && len(class.VectorConfig) > 0 {
   668  		props.Vectors = make([]string, 0, len(class.VectorConfig))
   669  		for vectorName := range class.VectorConfig {
   670  			props.Vectors = append(props.Vectors, vectorName)
   671  		}
   672  
   673  	}
   674  
   675  	if targetVectors != nil {
   676  		vectorIndex, err := schema.TypeAssertVectorIndex(class, *targetVectors)
   677  		if err != nil {
   678  			return props, errors.Wrap(err, "get vector index config from class")
   679  		}
   680  
   681  		// certainty is only compatible with cosine distance
   682  		if vectorIndex.DistanceName() == common.DistanceCosine && prop.Certainty {
   683  			props.Certainty = true
   684  		} else {
   685  			props.Certainty = false
   686  		}
   687  	}
   688  
   689  	return props, nil
   690  }
   691  
   692  func isIdOnlyRequest(metadata *pb.MetadataRequest) bool {
   693  	// could also use reflect here but this is more explicit
   694  	return (metadata != nil &&
   695  		metadata.Uuid &&
   696  		!metadata.Vector &&
   697  		!metadata.CreationTimeUnix &&
   698  		!metadata.LastUpdateTimeUnix &&
   699  		!metadata.Distance &&
   700  		!metadata.Certainty &&
   701  		!metadata.Score &&
   702  		!metadata.ExplainScore &&
   703  		!metadata.IsConsistent)
   704  }
   705  
   706  func getAllNonRefNonBlobProperties(scheme schema.Schema, className string) ([]search.SelectProperty, error) {
   707  	var props []search.SelectProperty
   708  	class := scheme.GetClass(schema.ClassName(className))
   709  
   710  	for _, prop := range class.Properties {
   711  		dt, err := schema.GetPropertyDataType(class, prop.Name)
   712  		if err != nil {
   713  			return []search.SelectProperty{}, errors.Wrap(err, "get property data type")
   714  		}
   715  		if *dt == schema.DataTypeCRef || *dt == schema.DataTypeBlob {
   716  			continue
   717  		}
   718  		if *dt == schema.DataTypeObject || *dt == schema.DataTypeObjectArray {
   719  			nested, err := schema.GetPropertyByName(class, prop.Name)
   720  			if err != nil {
   721  				return []search.SelectProperty{}, errors.Wrap(err, "get nested property by name")
   722  			}
   723  			nestedProps, err := getAllNonRefNonBlobNestedProperties(&Property{Property: nested})
   724  			if err != nil {
   725  				return []search.SelectProperty{}, errors.Wrap(err, "get all non ref non blob nested properties")
   726  			}
   727  			props = append(props, search.SelectProperty{
   728  				Name:        prop.Name,
   729  				IsPrimitive: false,
   730  				IsObject:    true,
   731  				Props:       nestedProps,
   732  			})
   733  		} else {
   734  			props = append(props, search.SelectProperty{
   735  				Name:        prop.Name,
   736  				IsPrimitive: true,
   737  			})
   738  		}
   739  	}
   740  	return props, nil
   741  }
   742  
   743  func getAllNonRefNonBlobNestedProperties[P schema.PropertyInterface](property P) ([]search.SelectProperty, error) {
   744  	var props []search.SelectProperty
   745  	for _, prop := range property.GetNestedProperties() {
   746  		dt, err := schema.GetNestedPropertyDataType(property, prop.Name)
   747  		if err != nil {
   748  			return []search.SelectProperty{}, errors.Wrap(err, "get nested property data type")
   749  		}
   750  		if *dt == schema.DataTypeCRef || *dt == schema.DataTypeBlob {
   751  			continue
   752  		}
   753  		if *dt == schema.DataTypeObject || *dt == schema.DataTypeObjectArray {
   754  			nested, err := schema.GetNestedPropertyByName(property, prop.Name)
   755  			if err != nil {
   756  				return []search.SelectProperty{}, errors.Wrap(err, "get nested property by name")
   757  			}
   758  			nestedProps, err := getAllNonRefNonBlobNestedProperties(&NestedProperty{NestedProperty: nested})
   759  			if err != nil {
   760  				return []search.SelectProperty{}, errors.Wrap(err, "get all non ref non blob nested properties")
   761  			}
   762  			props = append(props, search.SelectProperty{
   763  				Name:        prop.Name,
   764  				IsPrimitive: false,
   765  				IsObject:    true,
   766  				Props:       nestedProps,
   767  			})
   768  		} else {
   769  			props = append(props, search.SelectProperty{
   770  				Name:        prop.Name,
   771  				IsPrimitive: true,
   772  			})
   773  		}
   774  	}
   775  	return props, nil
   776  }
   777  
   778  func parseNearImage(n *pb.NearImageSearch) (*nearImage.NearImageParams, error) {
   779  	out := &nearImage.NearImageParams{
   780  		Image:         n.Image,
   781  		TargetVectors: n.TargetVectors,
   782  	}
   783  
   784  	// The following business logic should not sit in the API. However, it is
   785  	// also part of the GraphQL API, so we need to duplicate it in order to get
   786  	// the same behavior
   787  	if n.Distance != nil && n.Certainty != nil {
   788  		return nil, fmt.Errorf("near_image: cannot provide distance and certainty")
   789  	}
   790  
   791  	if n.Certainty != nil {
   792  		out.Certainty = *n.Certainty
   793  	}
   794  
   795  	if n.Distance != nil {
   796  		out.Distance = *n.Distance
   797  		out.WithDistance = true
   798  	}
   799  
   800  	return out, nil
   801  }
   802  
   803  func parseNearAudio(n *pb.NearAudioSearch) (*nearAudio.NearAudioParams, error) {
   804  	out := &nearAudio.NearAudioParams{
   805  		Audio:         n.Audio,
   806  		TargetVectors: n.TargetVectors,
   807  	}
   808  
   809  	// The following business logic should not sit in the API. However, it is
   810  	// also part of the GraphQL API, so we need to duplicate it in order to get
   811  	// the same behavior
   812  	if n.Distance != nil && n.Certainty != nil {
   813  		return nil, fmt.Errorf("near_audio: cannot provide distance and certainty")
   814  	}
   815  
   816  	if n.Certainty != nil {
   817  		out.Certainty = *n.Certainty
   818  	}
   819  
   820  	if n.Distance != nil {
   821  		out.Distance = *n.Distance
   822  		out.WithDistance = true
   823  	}
   824  
   825  	return out, nil
   826  }
   827  
   828  func parseNearVideo(n *pb.NearVideoSearch) (*nearVideo.NearVideoParams, error) {
   829  	out := &nearVideo.NearVideoParams{
   830  		Video:         n.Video,
   831  		TargetVectors: n.TargetVectors,
   832  	}
   833  
   834  	// The following business logic should not sit in the API. However, it is
   835  	// also part of the GraphQL API, so we need to duplicate it in order to get
   836  	// the same behavior
   837  	if n.Distance != nil && n.Certainty != nil {
   838  		return nil, fmt.Errorf("near_video: cannot provide distance and certainty")
   839  	}
   840  
   841  	if n.Certainty != nil {
   842  		out.Certainty = *n.Certainty
   843  	}
   844  
   845  	if n.Distance != nil {
   846  		out.Distance = *n.Distance
   847  		out.WithDistance = true
   848  	}
   849  
   850  	return out, nil
   851  }
   852  
   853  func parseNearDepth(n *pb.NearDepthSearch) (*nearDepth.NearDepthParams, error) {
   854  	out := &nearDepth.NearDepthParams{
   855  		Depth:         n.Depth,
   856  		TargetVectors: n.TargetVectors,
   857  	}
   858  
   859  	// The following business logic should not sit in the API. However, it is
   860  	// also part of the GraphQL API, so we need to duplicate it in order to get
   861  	// the same behavior
   862  	if n.Distance != nil && n.Certainty != nil {
   863  		return nil, fmt.Errorf("near_depth: cannot provide distance and certainty")
   864  	}
   865  
   866  	if n.Certainty != nil {
   867  		out.Certainty = *n.Certainty
   868  	}
   869  
   870  	if n.Distance != nil {
   871  		out.Distance = *n.Distance
   872  		out.WithDistance = true
   873  	}
   874  
   875  	return out, nil
   876  }
   877  
   878  func parseNearThermal(n *pb.NearThermalSearch) (*nearThermal.NearThermalParams, error) {
   879  	out := &nearThermal.NearThermalParams{
   880  		Thermal:       n.Thermal,
   881  		TargetVectors: n.TargetVectors,
   882  	}
   883  
   884  	// The following business logic should not sit in the API. However, it is
   885  	// also part of the GraphQL API, so we need to duplicate it in order to get
   886  	// the same behavior
   887  	if n.Distance != nil && n.Certainty != nil {
   888  		return nil, fmt.Errorf("near_thermal: cannot provide distance and certainty")
   889  	}
   890  
   891  	if n.Certainty != nil {
   892  		out.Certainty = *n.Certainty
   893  	}
   894  
   895  	if n.Distance != nil {
   896  		out.Distance = *n.Distance
   897  		out.WithDistance = true
   898  	}
   899  
   900  	return out, nil
   901  }
   902  
   903  func parseNearIMU(n *pb.NearIMUSearch) (*nearImu.NearIMUParams, error) {
   904  	out := &nearImu.NearIMUParams{
   905  		IMU:           n.Imu,
   906  		TargetVectors: n.TargetVectors,
   907  	}
   908  
   909  	// The following business logic should not sit in the API. However, it is
   910  	// also part of the GraphQL API, so we need to duplicate it in order to get
   911  	// the same behavior
   912  	if n.Distance != nil && n.Certainty != nil {
   913  		return nil, fmt.Errorf("near_imu: cannot provide distance and certainty")
   914  	}
   915  
   916  	if n.Certainty != nil {
   917  		out.Certainty = *n.Certainty
   918  	}
   919  
   920  	if n.Distance != nil {
   921  		out.Distance = *n.Distance
   922  		out.WithDistance = true
   923  	}
   924  
   925  	return out, nil
   926  }