github.com/weaviate/weaviate@v1.24.6/adapters/handlers/grpc/v1/prepare_reply.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 "math/big" 17 "strings" 18 "time" 19 20 "github.com/weaviate/weaviate/usecases/byteops" 21 22 "github.com/weaviate/weaviate/entities/schema" 23 generative "github.com/weaviate/weaviate/usecases/modulecomponents/additional/generate" 24 "github.com/weaviate/weaviate/usecases/modulecomponents/additional/models" 25 26 "github.com/go-openapi/strfmt" 27 "github.com/pkg/errors" 28 "github.com/weaviate/weaviate/entities/additional" 29 "github.com/weaviate/weaviate/entities/dto" 30 "github.com/weaviate/weaviate/entities/search" 31 pb "github.com/weaviate/weaviate/grpc/generated/protocol/v1" 32 "google.golang.org/protobuf/types/known/structpb" 33 ) 34 35 func searchResultsToProto(res []interface{}, start time.Time, searchParams dto.GetParams, scheme schema.Schema, usesPropertiesMessage bool) (*pb.SearchReply, error) { 36 tookSeconds := float64(time.Since(start)) / float64(time.Second) 37 out := &pb.SearchReply{ 38 Took: float32(tookSeconds), 39 GenerativeGroupedResult: new(string), // pointer to empty string 40 } 41 42 if searchParams.GroupBy != nil { 43 out.GroupByResults = make([]*pb.GroupByResult, len(res)) 44 for i, raw := range res { 45 group, generativeGroupResponse, err := extractGroup(raw, searchParams, scheme, usesPropertiesMessage) 46 if err != nil { 47 return nil, err 48 } 49 if generativeGroupResponse != "" { 50 out.GenerativeGroupedResult = &generativeGroupResponse 51 } 52 out.GroupByResults[i] = group 53 } 54 } else { 55 objects, generativeGroupResponse, err := extractObjectsToResults(res, searchParams, scheme, false, usesPropertiesMessage) 56 if err != nil { 57 return nil, err 58 } 59 out.GenerativeGroupedResult = &generativeGroupResponse 60 out.Results = objects 61 } 62 return out, nil 63 } 64 65 func extractObjectsToResults(res []interface{}, searchParams dto.GetParams, scheme schema.Schema, fromGroup, usesPropertiesMessage bool) ([]*pb.SearchResult, string, error) { 66 results := make([]*pb.SearchResult, len(res)) 67 generativeGroupResultsReturn := "" 68 for i, raw := range res { 69 asMap, ok := raw.(map[string]interface{}) 70 if !ok { 71 return nil, "", fmt.Errorf("could not parse returns %v", raw) 72 } 73 firstObject := i == 0 74 75 var props *pb.PropertiesResult 76 var err error 77 78 if usesPropertiesMessage { 79 props, err = extractPropertiesAnswer(scheme, asMap, searchParams.Properties, searchParams.ClassName, searchParams.AdditionalProperties) 80 } else { 81 props, err = extractPropertiesAnswerDeprecated(scheme, asMap, searchParams.Properties, searchParams.ClassName, searchParams.AdditionalProperties) 82 } 83 if err != nil { 84 return nil, "", err 85 } 86 87 additionalProps, generativeGroupResults, err := extractAdditionalProps(asMap, searchParams.AdditionalProperties, firstObject, fromGroup) 88 if err != nil { 89 return nil, "", err 90 } 91 92 if generativeGroupResultsReturn == "" && generativeGroupResults != "" { 93 generativeGroupResultsReturn = generativeGroupResults 94 } 95 96 result := &pb.SearchResult{ 97 Properties: props, 98 Metadata: additionalProps, 99 } 100 101 results[i] = result 102 } 103 return results, generativeGroupResultsReturn, nil 104 } 105 106 func idToByte(idRaw interface{}) ([]byte, string, error) { 107 idStrfmt, ok := idRaw.(strfmt.UUID) 108 if !ok { 109 return nil, "", errors.New("could not extract format id in additional prop") 110 } 111 idStrfmtStr := idStrfmt.String() 112 hexInteger, success := new(big.Int).SetString(strings.Replace(idStrfmtStr, "-", "", -1), 16) 113 if !success { 114 return nil, "", fmt.Errorf("failed to parse hex string to integer") 115 } 116 return hexInteger.Bytes(), idStrfmtStr, nil 117 } 118 119 func extractAdditionalProps(asMap map[string]any, additionalPropsParams additional.Properties, firstObject, fromGroup bool) (*pb.MetadataResult, string, error) { 120 generativeSearchRaw, generativeSearchEnabled := additionalPropsParams.ModuleParams["generate"] 121 _, rerankEnabled := additionalPropsParams.ModuleParams["rerank"] 122 123 metadata := &pb.MetadataResult{} 124 if additionalPropsParams.ID && !generativeSearchEnabled && !rerankEnabled && !fromGroup { 125 idRaw, ok := asMap["id"] 126 if !ok { 127 return nil, "", errors.New("could not extract get id in additional prop") 128 } 129 130 idToBytes, idAsString, err := idToByte(idRaw) 131 if err != nil { 132 return nil, "", errors.Wrap(err, "could not extract format id in additional prop") 133 } 134 metadata.Id = idAsString 135 metadata.IdAsBytes = idToBytes 136 } 137 _, ok := asMap["_additional"] 138 if !ok { 139 return metadata, "", nil 140 } 141 142 var additionalPropertiesMap map[string]interface{} 143 if !fromGroup { 144 additionalPropertiesMap = asMap["_additional"].(map[string]interface{}) 145 } else { 146 addPropertiesGroup := asMap["_additional"].(*additional.GroupHitAdditional) 147 additionalPropertiesMap = make(map[string]interface{}, 3) 148 additionalPropertiesMap["id"] = addPropertiesGroup.ID 149 additionalPropertiesMap["vector"] = addPropertiesGroup.Vector 150 additionalPropertiesMap["distance"] = addPropertiesGroup.Distance 151 } 152 generativeGroupResults := "" 153 // id is part of the _additional map in case of generative search, group, & rerank - don't aks me why 154 if additionalPropsParams.ID && (generativeSearchEnabled || fromGroup || rerankEnabled) { 155 idRaw, ok := additionalPropertiesMap["id"] 156 if !ok { 157 return nil, "", errors.New("could not extract get id generative in additional prop") 158 } 159 160 idToBytes, idAsString, err := idToByte(idRaw) 161 if err != nil { 162 return nil, "", errors.Wrap(err, "could not extract format id in additional prop") 163 } 164 metadata.Id = idAsString 165 metadata.IdAsBytes = idToBytes 166 } 167 168 if generativeSearchEnabled { 169 var generateFmt *models.GenerateResult 170 171 generate, ok := additionalPropertiesMap["generate"] 172 if !ok { 173 generateFmt = &models.GenerateResult{} 174 } else { 175 generateFmt, ok = generate.(*models.GenerateResult) 176 if !ok { 177 return nil, "", errors.New("could not cast generative result additional prop") 178 } 179 } 180 181 if generateFmt.Error != nil { 182 return nil, "", generateFmt.Error 183 } 184 185 generativeSearch, ok := generativeSearchRaw.(*generative.Params) 186 if !ok { 187 return nil, "", errors.New("could not cast generative search params") 188 } 189 if generativeSearch.Prompt != nil && generateFmt.SingleResult == nil { 190 return nil, "", errors.New("No results for generative search despite a search request. Is a generative module enabled?") 191 } 192 193 if generateFmt.Error != nil { 194 return nil, "", generateFmt.Error 195 } 196 197 if generateFmt.SingleResult != nil && *generateFmt.SingleResult != "" { 198 metadata.Generative = *generateFmt.SingleResult 199 metadata.GenerativePresent = true 200 } 201 202 // grouped results are only added to the first object for GQL reasons 203 // however, reranking can result in a different order, so we need to check every object 204 // recording the result if it's present assuming that it is at least somewhere and will be caught 205 if generateFmt.GroupedResult != nil && *generateFmt.GroupedResult != "" { 206 generativeGroupResults = *generateFmt.GroupedResult 207 } 208 } 209 210 if rerankEnabled { 211 rerank, ok := additionalPropertiesMap["rerank"] 212 if !ok { 213 return nil, "", errors.New("No results for rerank despite a search request. Is a the rerank module enabled?") 214 } 215 rerankFmt, ok := rerank.([]*models.RankResult) 216 if !ok { 217 return nil, "", errors.New("could not cast rerank result additional prop") 218 } 219 metadata.RerankScore = *rerankFmt[0].Score 220 metadata.RerankScorePresent = true 221 } 222 223 // additional properties are only present for certain searches/configs => don't return an error if not available 224 if additionalPropsParams.Vector { 225 vector, ok := additionalPropertiesMap["vector"] 226 if ok { 227 vectorfmt, ok2 := vector.([]float32) 228 if ok2 { 229 metadata.Vector = vectorfmt // deprecated, remove in a bit 230 metadata.VectorBytes = byteops.Float32ToByteVector(vectorfmt) 231 } 232 } 233 } 234 235 if len(additionalPropsParams.Vectors) > 0 { 236 vectors, ok := additionalPropertiesMap["vectors"] 237 if ok { 238 vectorfmt, ok2 := vectors.(map[string][]float32) 239 if ok2 { 240 metadata.Vectors = make([]*pb.Vectors, 0, len(vectorfmt)) 241 for name, vector := range vectorfmt { 242 metadata.Vectors = append(metadata.Vectors, &pb.Vectors{ 243 VectorBytes: byteops.Float32ToByteVector(vector), 244 Name: name, 245 }) 246 } 247 } 248 249 } 250 } 251 252 if additionalPropsParams.Certainty { 253 metadata.CertaintyPresent = false 254 certainty, ok := additionalPropertiesMap["certainty"] 255 if ok { 256 certaintyfmt, ok2 := certainty.(float64) 257 if ok2 { 258 metadata.Certainty = float32(certaintyfmt) 259 metadata.CertaintyPresent = true 260 } 261 } 262 } 263 264 if additionalPropsParams.Distance { 265 metadata.DistancePresent = false 266 distance, ok := additionalPropertiesMap["distance"] 267 if ok { 268 distancefmt, ok2 := distance.(float32) 269 if ok2 { 270 metadata.Distance = distancefmt 271 metadata.DistancePresent = true 272 } 273 } 274 } 275 276 if additionalPropsParams.CreationTimeUnix { 277 metadata.CreationTimeUnixPresent = false 278 creationtime, ok := additionalPropertiesMap["creationTimeUnix"] 279 if ok { 280 creationtimefmt, ok2 := creationtime.(int64) 281 if ok2 { 282 metadata.CreationTimeUnix = creationtimefmt 283 metadata.CreationTimeUnixPresent = true 284 } 285 } 286 } 287 288 if additionalPropsParams.LastUpdateTimeUnix { 289 metadata.LastUpdateTimeUnixPresent = false 290 lastUpdateTime, ok := additionalPropertiesMap["lastUpdateTimeUnix"] 291 if ok { 292 lastUpdateTimefmt, ok2 := lastUpdateTime.(int64) 293 if ok2 { 294 metadata.LastUpdateTimeUnix = lastUpdateTimefmt 295 metadata.LastUpdateTimeUnixPresent = true 296 } 297 } 298 } 299 300 if additionalPropsParams.ExplainScore { 301 metadata.ExplainScorePresent = false 302 explainScore, ok := additionalPropertiesMap["explainScore"] 303 if ok { 304 explainScorefmt, ok2 := explainScore.(string) 305 if ok2 { 306 metadata.ExplainScore = explainScorefmt 307 metadata.ExplainScorePresent = true 308 } 309 } 310 } 311 312 if additionalPropsParams.Score { 313 metadata.ScorePresent = false 314 score, ok := additionalPropertiesMap["score"] 315 if ok { 316 scorefmt, ok2 := score.(float32) 317 if ok2 { 318 metadata.Score = scorefmt 319 metadata.ScorePresent = true 320 } 321 } 322 } 323 324 if additionalPropsParams.IsConsistent { 325 isConsistent, ok := additionalPropertiesMap["isConsistent"] 326 if ok { 327 isConsistentfmt, ok2 := isConsistent.(bool) 328 if ok2 { 329 metadata.IsConsistent = &isConsistentfmt 330 metadata.IsConsistentPresent = true 331 } 332 } 333 } 334 335 return metadata, generativeGroupResults, nil 336 } 337 338 func extractGroup(raw any, searchParams dto.GetParams, scheme schema.Schema, usesMarshalling bool) (*pb.GroupByResult, string, error) { 339 generativeSearchRaw, generativeSearchEnabled := searchParams.AdditionalProperties.ModuleParams["generate"] 340 _, rerankEnabled := searchParams.AdditionalProperties.ModuleParams["rerank"] 341 asMap, ok := raw.(map[string]interface{}) 342 if !ok { 343 return nil, "", fmt.Errorf("cannot parse result %v", raw) 344 } 345 add, ok := asMap["_additional"] 346 if !ok { 347 return nil, "", fmt.Errorf("_additional is required for groups %v", asMap) 348 } 349 addAsMap, ok := add.(map[string]interface{}) 350 if !ok { 351 return nil, "", fmt.Errorf("cannot parse _additional %v", add) 352 } 353 groupRaw, ok := addAsMap["group"] 354 if !ok { 355 return nil, "", fmt.Errorf("group is not present %v", addAsMap) 356 } 357 group, ok := groupRaw.(*additional.Group) 358 if !ok { 359 return nil, "", fmt.Errorf("cannot parse _additional %v", groupRaw) 360 } 361 362 ret := &pb.GroupByResult{ 363 Name: group.GroupedBy.Value, 364 MaxDistance: group.MaxDistance, 365 MinDistance: group.MinDistance, 366 NumberOfObjects: int64(group.Count), 367 } 368 369 groupedGenerativeResults := "" 370 if generativeSearchEnabled { 371 var generateFmt *models.GenerateResult 372 373 generate, ok := addAsMap["generate"] 374 if !ok { 375 generateFmt = &models.GenerateResult{} 376 } else { 377 generateFmt, ok = generate.(*models.GenerateResult) 378 if !ok { 379 return nil, "", errors.New("could not cast generative result additional prop") 380 } 381 } 382 383 generativeSearch, ok := generativeSearchRaw.(*generative.Params) 384 if !ok { 385 return nil, "", errors.New("could not cast generative search params") 386 } 387 if generativeSearch.Prompt != nil && generateFmt.SingleResult == nil { 388 return nil, "", errors.New("No results for generative search despite a search request. Is a generative module enabled?") 389 } 390 391 if generateFmt.Error != nil { 392 return nil, "", generateFmt.Error 393 } 394 395 if generateFmt.SingleResult != nil && *generateFmt.SingleResult != "" { 396 ret.Generative = &pb.GenerativeReply{Result: *generateFmt.SingleResult} 397 } 398 399 // grouped results are only added to the first object for GQL reasons 400 // however, reranking can result in a different order, so we need to check every object 401 // recording the result if it's present assuming that it is at least somewhere and will be caught 402 if generateFmt.GroupedResult != nil && *generateFmt.GroupedResult != "" { 403 groupedGenerativeResults = *generateFmt.GroupedResult 404 } 405 } 406 407 if rerankEnabled { 408 rerankRaw, ok := addAsMap["rerank"] 409 if !ok { 410 return nil, "", fmt.Errorf("rerank is not present %v", addAsMap) 411 } 412 rerank, ok := rerankRaw.([]*models.RankResult) 413 if !ok { 414 return nil, "", fmt.Errorf("cannot parse rerank %v", rerankRaw) 415 } 416 ret.Rerank = &pb.RerankReply{ 417 Score: *rerank[0].Score, 418 } 419 } 420 421 // group results does not support more additional properties 422 searchParams.AdditionalProperties = additional.Properties{ 423 ID: searchParams.AdditionalProperties.ID, 424 Vector: searchParams.AdditionalProperties.Vector, 425 Distance: searchParams.AdditionalProperties.Distance, 426 } 427 428 // group objects are returned as a different type than normal results ([]map[string]interface{} vs []interface). As 429 // the normal path is used much more often than groupBy, convert the []map[string]interface{} to []interface{}, even 430 // though we cast it to map[string]interface{} in the extraction function. 431 // This way we only do a copy for groupBy and not for the standard code-path which is used more often 432 returnObjectsUntyped := make([]interface{}, len(group.Hits)) 433 for i := range returnObjectsUntyped { 434 returnObjectsUntyped[i] = group.Hits[i] 435 } 436 437 objects, _, err := extractObjectsToResults(returnObjectsUntyped, searchParams, scheme, true, usesMarshalling) 438 if err != nil { 439 return nil, "", errors.Wrap(err, "extracting hits from group") 440 } 441 442 ret.Objects = objects 443 444 return ret, groupedGenerativeResults, nil 445 } 446 447 func extractPropertiesAnswerDeprecated(scheme schema.Schema, results map[string]interface{}, properties search.SelectProperties, className string, additionalPropsParams additional.Properties) (*pb.PropertiesResult, error) { 448 nonRefProps := make(map[string]interface{}, 0) 449 refProps := make([]*pb.RefPropertiesResult, 0) 450 objProps := make([]*pb.ObjectProperties, 0) 451 objArrayProps := make([]*pb.ObjectArrayProperties, 0) 452 for _, prop := range properties { 453 propRaw, ok := results[prop.Name] 454 if !ok { 455 continue 456 } 457 if prop.IsPrimitive { 458 nonRefProps[prop.Name] = propRaw 459 continue 460 } 461 if prop.IsObject { 462 nested, err := scheme.GetProperty(schema.ClassName(className), schema.PropertyName(prop.Name)) 463 if err != nil { 464 return nil, errors.Wrap(err, "getting property") 465 } 466 singleObj, ok := propRaw.(map[string]interface{}) 467 if ok { 468 extractedNestedProp, err := extractPropertiesNested(scheme, singleObj, prop, className, &Property{Property: nested}) 469 if err != nil { 470 return nil, errors.Wrap(err, "extracting nested properties") 471 } 472 objProps = append(objProps, &pb.ObjectProperties{ 473 PropName: prop.Name, 474 Value: extractedNestedProp, 475 }) 476 continue 477 } 478 arrayObjs, ok := propRaw.([]interface{}) 479 if ok { 480 extractedNestedProps := make([]*pb.ObjectPropertiesValue, 0, len(arrayObjs)) 481 for _, obj := range arrayObjs { 482 singleObj, ok := obj.(map[string]interface{}) 483 if !ok { 484 continue 485 } 486 extractedNestedProp, err := extractPropertiesNested(scheme, singleObj, prop, className, &Property{Property: nested}) 487 if err != nil { 488 return nil, err 489 } 490 extractedNestedProps = append(extractedNestedProps, extractedNestedProp) 491 } 492 objArrayProps = append(objArrayProps, 493 &pb.ObjectArrayProperties{ 494 PropName: prop.Name, 495 Values: extractedNestedProps, 496 }, 497 ) 498 continue 499 } 500 } 501 refs, ok := propRaw.([]interface{}) 502 if !ok { 503 continue 504 } 505 extractedRefProps := make([]*pb.PropertiesResult, 0, len(refs)) 506 for _, ref := range refs { 507 refLocal, ok := ref.(search.LocalRef) 508 if !ok { 509 continue 510 } 511 extractedRefProp, err := extractPropertiesAnswerDeprecated(scheme, refLocal.Fields, prop.Refs[0].RefProperties, refLocal.Class, additionalPropsParams) 512 if err != nil { 513 continue 514 } 515 additionalProps, _, err := extractAdditionalProps(refLocal.Fields, prop.Refs[0].AdditionalProperties, false, false) 516 if err != nil { 517 return nil, err 518 } 519 extractedRefProp.Metadata = additionalProps 520 extractedRefProps = append(extractedRefProps, extractedRefProp) 521 } 522 523 refProp := pb.RefPropertiesResult{PropName: prop.Name, Properties: extractedRefProps} 524 refProps = append(refProps, &refProp) 525 } 526 props := pb.PropertiesResult{} 527 if len(nonRefProps) > 0 { 528 outProps := pb.ObjectPropertiesValue{} 529 if err := extractArrayTypesRoot(scheme, className, nonRefProps, &outProps); err != nil { 530 return nil, errors.Wrap(err, "extracting non-primitive types") 531 } 532 newStruct, err := structpb.NewStruct(nonRefProps) 533 if err != nil { 534 return nil, errors.Wrap(err, "creating non-ref-prop struct") 535 } 536 props.NonRefProperties = newStruct 537 props.IntArrayProperties = outProps.IntArrayProperties 538 props.NumberArrayProperties = outProps.NumberArrayProperties 539 props.TextArrayProperties = outProps.TextArrayProperties 540 props.BooleanArrayProperties = outProps.BooleanArrayProperties 541 props.ObjectProperties = outProps.ObjectProperties 542 props.ObjectArrayProperties = outProps.ObjectArrayProperties 543 } 544 if len(refProps) > 0 { 545 props.RefProps = refProps 546 } 547 if len(objProps) > 0 { 548 props.ObjectProperties = objProps 549 } 550 if len(objArrayProps) > 0 { 551 props.ObjectArrayProperties = objArrayProps 552 } 553 554 props.TargetCollection = className 555 return &props, nil 556 } 557 558 func extractPropertiesAnswer(scheme schema.Schema, results map[string]interface{}, properties search.SelectProperties, className string, additionalPropsParams additional.Properties) (*pb.PropertiesResult, error) { 559 nonRefProps := &pb.Properties{ 560 Fields: make(map[string]*pb.Value, 0), 561 } 562 refProps := make([]*pb.RefPropertiesResult, 0) 563 class := scheme.GetClass(schema.ClassName(className)) 564 for _, prop := range properties { 565 propRaw, ok := results[prop.Name] 566 567 if !ok { 568 if prop.IsPrimitive || prop.IsObject { 569 nonRefProps.Fields[prop.Name] = NewNilValue() 570 } 571 continue 572 } 573 if prop.IsPrimitive { 574 dataType, err := schema.GetPropertyDataType(class, prop.Name) 575 if err != nil { 576 return nil, errors.Wrap(err, "getting primitive property datatype") 577 } 578 value, err := NewPrimitiveValue(propRaw, *dataType) 579 if err != nil { 580 return nil, errors.Wrapf(err, "creating primitive value for %v", prop.Name) 581 } 582 nonRefProps.Fields[prop.Name] = value 583 continue 584 } 585 if prop.IsObject { 586 nested, err := scheme.GetProperty(schema.ClassName(className), schema.PropertyName(prop.Name)) 587 if err != nil { 588 return nil, errors.Wrap(err, "getting nested property") 589 } 590 value, err := NewNestedValue(propRaw, schema.DataType(nested.DataType[0]), &Property{Property: nested}, prop) 591 if err != nil { 592 return nil, errors.Wrap(err, "creating object value") 593 } 594 nonRefProps.Fields[prop.Name] = value 595 continue 596 } 597 refs, ok := propRaw.([]interface{}) 598 if !ok { 599 continue 600 } 601 extractedRefProps := make([]*pb.PropertiesResult, 0, len(refs)) 602 for _, ref := range refs { 603 refLocal, ok := ref.(search.LocalRef) 604 if !ok { 605 continue 606 } 607 extractedRefProp, err := extractPropertiesAnswer(scheme, refLocal.Fields, prop.Refs[0].RefProperties, refLocal.Class, additionalPropsParams) 608 if err != nil { 609 continue 610 } 611 additionalProps, _, err := extractAdditionalProps(refLocal.Fields, prop.Refs[0].AdditionalProperties, false, false) 612 if err != nil { 613 return nil, err 614 } 615 extractedRefProp.Metadata = additionalProps 616 extractedRefProps = append(extractedRefProps, extractedRefProp) 617 } 618 619 refProp := pb.RefPropertiesResult{PropName: prop.Name, Properties: extractedRefProps} 620 refProps = append(refProps, &refProp) 621 } 622 props := pb.PropertiesResult{} 623 if len(nonRefProps.Fields) != 0 { 624 props.NonRefProps = nonRefProps 625 } 626 if len(refProps) != 0 { 627 props.RefProps = refProps 628 } 629 props.RefPropsRequested = properties.HasRefs() 630 props.TargetCollection = className 631 return &props, nil 632 } 633 634 func extractPropertiesNested[P schema.PropertyInterface](scheme schema.Schema, results map[string]interface{}, property search.SelectProperty, className string, parent P) (*pb.ObjectPropertiesValue, error) { 635 primitiveProps := make(map[string]interface{}, 0) 636 objProps := make([]*pb.ObjectProperties, 0) 637 objArrayProps := make([]*pb.ObjectArrayProperties, 0) 638 for _, prop := range property.Props { 639 propRaw, ok := results[prop.Name] 640 if !ok { 641 continue 642 } 643 if prop.IsPrimitive { 644 primitiveProps[prop.Name] = propRaw 645 continue 646 } 647 if prop.IsObject { 648 var err error 649 objProps, objArrayProps, err = extractObjectProperties(scheme, propRaw, prop, className, parent, objProps, objArrayProps) 650 if err != nil { 651 return nil, err 652 } 653 } 654 } 655 props := pb.ObjectPropertiesValue{} 656 if len(primitiveProps) > 0 { 657 if err := extractArrayTypesNested(scheme, className, primitiveProps, &props, parent); err != nil { 658 return nil, errors.Wrap(err, "extracting non-primitive types") 659 } 660 newStruct, err := structpb.NewStruct(primitiveProps) 661 if err != nil { 662 return nil, errors.Wrap(err, "creating non-ref-prop struct") 663 } 664 props.NonRefProperties = newStruct 665 } 666 if len(objProps) > 0 { 667 props.ObjectProperties = objProps 668 } 669 if len(objArrayProps) > 0 { 670 props.ObjectArrayProperties = objArrayProps 671 } 672 return &props, nil 673 } 674 675 func extractObjectProperties[P schema.PropertyInterface](scheme schema.Schema, propRaw interface{}, property search.SelectProperty, className string, parent P, objProps []*pb.ObjectProperties, objArrayProps []*pb.ObjectArrayProperties) ([]*pb.ObjectProperties, []*pb.ObjectArrayProperties, error) { 676 prop, ok := propRaw.(map[string]interface{}) 677 if ok { 678 objProp, err := extractObjectSingleProperties(scheme, prop, property, className, parent) 679 if err != nil { 680 return objProps, objArrayProps, err 681 } 682 objProps = append(objProps, objProp) 683 } 684 propArray, ok := propRaw.([]interface{}) 685 if ok { 686 objArrayProp, err := extractObjectArrayProperties(scheme, propArray, property, className, parent) 687 if err != nil { 688 return objProps, objArrayProps, err 689 } 690 objArrayProps = append(objArrayProps, objArrayProp) 691 } 692 return objProps, objArrayProps, nil 693 } 694 695 func extractObjectSingleProperties[P schema.PropertyInterface](scheme schema.Schema, prop map[string]interface{}, property search.SelectProperty, className string, parent P) (*pb.ObjectProperties, error) { 696 nested, err := schema.GetNestedPropertyByName(parent, property.Name) 697 if err != nil { 698 return nil, errors.Wrap(err, "getting property") 699 } 700 extractedNestedProp, err := extractPropertiesNested(scheme, prop, property, className, &NestedProperty{NestedProperty: nested}) 701 if err != nil { 702 return nil, errors.Wrap(err, fmt.Sprintf("extracting nested properties from %v", nested)) 703 } 704 return &pb.ObjectProperties{ 705 PropName: property.Name, 706 Value: extractedNestedProp, 707 }, nil 708 } 709 710 func extractObjectArrayProperties[P schema.PropertyInterface](scheme schema.Schema, propObjs []interface{}, property search.SelectProperty, className string, parent P) (*pb.ObjectArrayProperties, error) { 711 extractedNestedProps := make([]*pb.ObjectPropertiesValue, 0, len(propObjs)) 712 for _, objRaw := range propObjs { 713 nested, err := schema.GetNestedPropertyByName(parent, property.Name) 714 if err != nil { 715 return nil, errors.Wrap(err, "getting property") 716 } 717 obj, ok := objRaw.(map[string]interface{}) 718 if !ok { 719 continue 720 } 721 extractedNestedProp, err := extractPropertiesNested(scheme, obj, property, className, &NestedProperty{NestedProperty: nested}) 722 if err != nil { 723 return nil, errors.Wrap(err, "extracting nested properties") 724 } 725 extractedNestedProps = append(extractedNestedProps, extractedNestedProp) 726 } 727 return &pb.ObjectArrayProperties{ 728 PropName: property.Name, 729 Values: extractedNestedProps, 730 }, nil 731 } 732 733 func extractArrayTypesRoot(scheme schema.Schema, className string, rawProps map[string]interface{}, props *pb.ObjectPropertiesValue) error { 734 dataTypes := make(map[string]*schema.DataType, 0) 735 for propName := range rawProps { 736 dataType, err := schema.GetPropertyDataType(scheme.GetClass(schema.ClassName(className)), propName) 737 if err != nil { 738 return err 739 } 740 dataTypes[propName] = dataType 741 } 742 return extractArrayTypes(scheme, rawProps, props, dataTypes) 743 } 744 745 func extractArrayTypesNested[P schema.PropertyInterface](scheme schema.Schema, className string, rawProps map[string]interface{}, props *pb.ObjectPropertiesValue, parent P) error { 746 dataTypes := make(map[string]*schema.DataType, 0) 747 for propName := range rawProps { 748 dataType, err := schema.GetNestedPropertyDataType(parent, propName) 749 if err != nil { 750 return err 751 } 752 dataTypes[propName] = dataType 753 } 754 return extractArrayTypes(scheme, rawProps, props, dataTypes) 755 } 756 757 // slices cannot be part of a grpc struct, so we need to handle each of them separately 758 func extractArrayTypes(scheme schema.Schema, rawProps map[string]interface{}, props *pb.ObjectPropertiesValue, dataTypes map[string]*schema.DataType) error { 759 for propName, prop := range rawProps { 760 dataType := dataTypes[propName] 761 switch *dataType { 762 case schema.DataTypeIntArray: 763 propIntAsFloat, ok := prop.([]float64) 764 if !ok { 765 emptyArr, ok := prop.([]interface{}) 766 if ok && len(emptyArr) == 0 { 767 continue 768 } 769 return fmt.Errorf("property %v with datatype %v needs to be []float64, got %T", propName, dataType, prop) 770 } 771 propInt := make([]int64, len(propIntAsFloat)) 772 for i := range propIntAsFloat { 773 propInt[i] = int64(propIntAsFloat[i]) 774 } 775 if props.IntArrayProperties == nil { 776 props.IntArrayProperties = make([]*pb.IntArrayProperties, 0) 777 } 778 props.IntArrayProperties = append(props.IntArrayProperties, &pb.IntArrayProperties{PropName: propName, Values: propInt}) 779 delete(rawProps, propName) 780 case schema.DataTypeNumberArray: 781 propFloat, ok := prop.([]float64) 782 if !ok { 783 emptyArr, ok := prop.([]interface{}) 784 if ok && len(emptyArr) == 0 { 785 continue 786 } 787 return fmt.Errorf("property %v with datatype %v needs to be []float64, got %T", propName, dataType, prop) 788 } 789 790 if props.NumberArrayProperties == nil { 791 props.NumberArrayProperties = make([]*pb.NumberArrayProperties, 0) 792 } 793 props.NumberArrayProperties = append( 794 props.NumberArrayProperties, 795 &pb.NumberArrayProperties{PropName: propName, ValuesBytes: byteops.Float64ToByteVector(propFloat), Values: propFloat}, 796 ) 797 delete(rawProps, propName) 798 case schema.DataTypeStringArray, schema.DataTypeTextArray, schema.DataTypeDateArray, schema.DataTypeUUIDArray: 799 propString, ok := prop.([]string) 800 if !ok { 801 emptyArr, ok := prop.([]interface{}) 802 if ok && len(emptyArr) == 0 { 803 continue 804 } 805 return fmt.Errorf("property %v with datatype %v needs to be []string, got %T", propName, dataType, prop) 806 } 807 if props.TextArrayProperties == nil { 808 props.TextArrayProperties = make([]*pb.TextArrayProperties, 0) 809 } 810 props.TextArrayProperties = append(props.TextArrayProperties, &pb.TextArrayProperties{PropName: propName, Values: propString}) 811 delete(rawProps, propName) 812 case schema.DataTypeBooleanArray: 813 propBool, ok := prop.([]bool) 814 if !ok { 815 emptyArr, ok := prop.([]interface{}) 816 if ok && len(emptyArr) == 0 { 817 continue 818 } 819 return fmt.Errorf("property %v with datatype %v needs to be []bool, got %T", propName, dataType, prop) 820 } 821 if props.BooleanArrayProperties == nil { 822 props.BooleanArrayProperties = make([]*pb.BooleanArrayProperties, 0) 823 } 824 props.BooleanArrayProperties = append(props.BooleanArrayProperties, &pb.BooleanArrayProperties{PropName: propName, Values: propBool}) 825 delete(rawProps, propName) 826 default: 827 _, isArray := schema.IsArrayType(*dataType) 828 if isArray { 829 return fmt.Errorf("property %v with array type not handled %v", propName, dataType) 830 } 831 } 832 } 833 return nil 834 }