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 }