// _ _ // __ _____ __ ___ ___ __ _| |_ ___ // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ // \ V V / __/ (_| |\ V /| | (_| | || __/ // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| // // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. // // CONTACT: hello@weaviate.io // package v1 import ( "fmt" "math/big" "strings" "time" "github.com/weaviate/weaviate/usecases/byteops" "github.com/weaviate/weaviate/entities/schema" generative "github.com/weaviate/weaviate/usecases/modulecomponents/additional/generate" "github.com/weaviate/weaviate/usecases/modulecomponents/additional/models" "github.com/go-openapi/strfmt" "github.com/pkg/errors" "github.com/weaviate/weaviate/entities/additional" "github.com/weaviate/weaviate/entities/dto" "github.com/weaviate/weaviate/entities/search" pb "github.com/weaviate/weaviate/grpc/generated/protocol/v1" "google.golang.org/protobuf/types/known/structpb" ) func searchResultsToProto(res []interface{}, start time.Time, searchParams dto.GetParams, scheme schema.Schema, usesPropertiesMessage bool) (*pb.SearchReply, error) { tookSeconds := float64(time.Since(start)) / float64(time.Second) out := &pb.SearchReply{ Took: float32(tookSeconds), GenerativeGroupedResult: new(string), // pointer to empty string } if searchParams.GroupBy != nil { out.GroupByResults = make([]*pb.GroupByResult, len(res)) for i, raw := range res { group, generativeGroupResponse, err := extractGroup(raw, searchParams, scheme, usesPropertiesMessage) if err != nil { return nil, err } if generativeGroupResponse != "" { out.GenerativeGroupedResult = &generativeGroupResponse } out.GroupByResults[i] = group } } else { objects, generativeGroupResponse, err := extractObjectsToResults(res, searchParams, scheme, false, usesPropertiesMessage) if err != nil { return nil, err } out.GenerativeGroupedResult = &generativeGroupResponse out.Results = objects } return out, nil } func extractObjectsToResults(res []interface{}, searchParams dto.GetParams, scheme schema.Schema, fromGroup, usesPropertiesMessage bool) ([]*pb.SearchResult, string, error) { results := make([]*pb.SearchResult, len(res)) generativeGroupResultsReturn := "" for i, raw := range res { asMap, ok := raw.(map[string]interface{}) if !ok { return nil, "", fmt.Errorf("could not parse returns %v", raw) } firstObject := i == 0 var props *pb.PropertiesResult var err error if usesPropertiesMessage { props, err = extractPropertiesAnswer(scheme, asMap, searchParams.Properties, searchParams.ClassName, searchParams.AdditionalProperties) } else { props, err = extractPropertiesAnswerDeprecated(scheme, asMap, searchParams.Properties, searchParams.ClassName, searchParams.AdditionalProperties) } if err != nil { return nil, "", err } additionalProps, generativeGroupResults, err := extractAdditionalProps(asMap, searchParams.AdditionalProperties, firstObject, fromGroup) if err != nil { return nil, "", err } if generativeGroupResultsReturn == "" && generativeGroupResults != "" { generativeGroupResultsReturn = generativeGroupResults } result := &pb.SearchResult{ Properties: props, Metadata: additionalProps, } results[i] = result } return results, generativeGroupResultsReturn, nil } func extractAdditionalProps(asMap map[string]any, additionalPropsParams additional.Properties, firstObject, fromGroup bool) (*pb.MetadataResult, string, error) { generativeSearchRaw, generativeSearchEnabled := additionalPropsParams.ModuleParams["generate"] _, rerankEnabled := additionalPropsParams.ModuleParams["rerank"] metadata := &pb.MetadataResult{} if additionalPropsParams.ID && !generativeSearchEnabled && !rerankEnabled && !fromGroup { idRaw, ok := asMap["id"] if !ok { return nil, "", errors.New("could not extract get id in additional prop") } idStrfmt, ok := idRaw.(strfmt.UUID) if !ok { return nil, "", errors.New("could not extract format id in additional prop") } metadata.Id = idStrfmt.String() hexInteger, success := new(big.Int).SetString(strings.Replace(metadata.Id, "-", "", -1), 16) if !success { return nil, "", fmt.Errorf("failed to parse hex string to integer") } metadata.IdAsBytes = hexInteger.Bytes() } _, ok := asMap["_additional"] if !ok { return metadata, "", nil } var additionalPropertiesMap map[string]interface{} if !fromGroup { additionalPropertiesMap = asMap["_additional"].(map[string]interface{}) } else { addPropertiesGroup := asMap["_additional"].(*additional.GroupHitAdditional) additionalPropertiesMap = make(map[string]interface{}, 3) additionalPropertiesMap["id"] = addPropertiesGroup.ID additionalPropertiesMap["vector"] = addPropertiesGroup.Vector additionalPropertiesMap["distance"] = addPropertiesGroup.Distance } generativeGroupResults := "" // id is part of the _additional map in case of generative search, group, & rerank - don't aks me why if additionalPropsParams.ID && (generativeSearchEnabled || fromGroup || rerankEnabled) { idRaw, ok := additionalPropertiesMap["id"] if !ok { return nil, "", errors.New("could not extract get id generative in additional prop") } idStrfmt, ok := idRaw.(strfmt.UUID) if !ok { return nil, "", errors.New("could not format id generative in additional prop") } metadata.Id = idStrfmt.String() } if generativeSearchEnabled { var generateFmt *models.GenerateResult generate, ok := additionalPropertiesMap["generate"] if !ok { generateFmt = &models.GenerateResult{} } else { generateFmt, ok = generate.(*models.GenerateResult) if !ok { return nil, "", errors.New("could not cast generative result additional prop") } } generativeSearch, ok := generativeSearchRaw.(*generative.Params) if !ok { return nil, "", errors.New("could not cast generative search params") } if generativeSearch.Prompt != nil && generateFmt.SingleResult == nil { return nil, "", errors.New("No results for generative search despite a search request. Is a generative module enabled?") } if generateFmt.Error != nil { return nil, "", generateFmt.Error } if generateFmt.SingleResult != nil && *generateFmt.SingleResult != "" { metadata.Generative = *generateFmt.SingleResult metadata.GenerativePresent = true } // grouped results are only added to the first object for GQL reasons // however, reranking can result in a different order, so we need to check every object // recording the result if it's present assuming that it is at least somewhere and will be caught if generateFmt.GroupedResult != nil && *generateFmt.GroupedResult != "" { generativeGroupResults = *generateFmt.GroupedResult } } if rerankEnabled { rerank, ok := additionalPropertiesMap["rerank"] if !ok { return nil, "", errors.New("No results for rerank despite a search request. Is a the rerank module enabled?") } rerankFmt, ok := rerank.([]*models.RankResult) if !ok { return nil, "", errors.New("could not cast rerank result additional prop") } metadata.RerankScore = *rerankFmt[0].Score metadata.RerankScorePresent = true } // additional properties are only present for certain searches/configs => don't return an error if not available if additionalPropsParams.Vector { vector, ok := additionalPropertiesMap["vector"] if ok { vectorfmt, ok2 := vector.([]float32) if ok2 { metadata.Vector = vectorfmt // deprecated, remove in a bit metadata.VectorBytes = byteops.Float32ToByteVector(vectorfmt) } } } if additionalPropsParams.Certainty { metadata.CertaintyPresent = false certainty, ok := additionalPropertiesMap["certainty"] if ok { certaintyfmt, ok2 := certainty.(float64) if ok2 { metadata.Certainty = float32(certaintyfmt) metadata.CertaintyPresent = true } } } if additionalPropsParams.Distance { metadata.DistancePresent = false distance, ok := additionalPropertiesMap["distance"] if ok { distancefmt, ok2 := distance.(float32) if ok2 { metadata.Distance = distancefmt metadata.DistancePresent = true } } } if additionalPropsParams.CreationTimeUnix { metadata.CreationTimeUnixPresent = false creationtime, ok := additionalPropertiesMap["creationTimeUnix"] if ok { creationtimefmt, ok2 := creationtime.(int64) if ok2 { metadata.CreationTimeUnix = creationtimefmt metadata.CreationTimeUnixPresent = true } } } if additionalPropsParams.LastUpdateTimeUnix { metadata.LastUpdateTimeUnixPresent = false lastUpdateTime, ok := additionalPropertiesMap["lastUpdateTimeUnix"] if ok { lastUpdateTimefmt, ok2 := lastUpdateTime.(int64) if ok2 { metadata.LastUpdateTimeUnix = lastUpdateTimefmt metadata.LastUpdateTimeUnixPresent = true } } } if additionalPropsParams.ExplainScore { metadata.ExplainScorePresent = false explainScore, ok := additionalPropertiesMap["explainScore"] if ok { explainScorefmt, ok2 := explainScore.(string) if ok2 { metadata.ExplainScore = explainScorefmt metadata.ExplainScorePresent = true } } } if additionalPropsParams.Score { metadata.ScorePresent = false score, ok := additionalPropertiesMap["score"] if ok { scorefmt, ok2 := score.(float32) if ok2 { metadata.Score = scorefmt metadata.ScorePresent = true } } } if additionalPropsParams.IsConsistent { isConsistent, ok := additionalPropertiesMap["isConsistent"] if ok { isConsistentfmt, ok2 := isConsistent.(bool) if ok2 { metadata.IsConsistent = &isConsistentfmt metadata.IsConsistentPresent = true } } } return metadata, generativeGroupResults, nil } func extractGroup(raw any, searchParams dto.GetParams, scheme schema.Schema, usesMarshalling bool) (*pb.GroupByResult, string, error) { generativeSearchRaw, generativeSearchEnabled := searchParams.AdditionalProperties.ModuleParams["generate"] _, rerankEnabled := searchParams.AdditionalProperties.ModuleParams["rerank"] asMap, ok := raw.(map[string]interface{}) if !ok { return nil, "", fmt.Errorf("cannot parse result %v", raw) } add, ok := asMap["_additional"] if !ok { return nil, "", fmt.Errorf("_additional is required for groups %v", asMap) } addAsMap, ok := add.(map[string]interface{}) if !ok { return nil, "", fmt.Errorf("cannot parse _additional %v", add) } groupRaw, ok := addAsMap["group"] if !ok { return nil, "", fmt.Errorf("group is not present %v", addAsMap) } group, ok := groupRaw.(*additional.Group) if !ok { return nil, "", fmt.Errorf("cannot parse _additional %v", groupRaw) } ret := &pb.GroupByResult{ Name: group.GroupedBy.Value, MaxDistance: group.MaxDistance, MinDistance: group.MinDistance, NumberOfObjects: int64(group.Count), } groupedGenerativeResults := "" if generativeSearchEnabled { var generateFmt *models.GenerateResult generate, ok := addAsMap["generate"] if !ok { generateFmt = &models.GenerateResult{} } else { generateFmt, ok = generate.(*models.GenerateResult) if !ok { return nil, "", errors.New("could not cast generative result additional prop") } } generativeSearch, ok := generativeSearchRaw.(*generative.Params) if !ok { return nil, "", errors.New("could not cast generative search params") } if generativeSearch.Prompt != nil && generateFmt.SingleResult == nil { return nil, "", errors.New("No results for generative search despite a search request. Is a generative module enabled?") } if generateFmt.Error != nil { return nil, "", generateFmt.Error } if generateFmt.SingleResult != nil && *generateFmt.SingleResult != "" { ret.Generative = &pb.GenerativeReply{Result: *generateFmt.SingleResult} } // grouped results are only added to the first object for GQL reasons // however, reranking can result in a different order, so we need to check every object // recording the result if it's present assuming that it is at least somewhere and will be caught if generateFmt.GroupedResult != nil && *generateFmt.GroupedResult != "" { groupedGenerativeResults = *generateFmt.GroupedResult } } if rerankEnabled { rerankRaw, ok := addAsMap["rerank"] if !ok { return nil, "", fmt.Errorf("rerank is not present %v", addAsMap) } rerank, ok := rerankRaw.([]*models.RankResult) if !ok { return nil, "", fmt.Errorf("cannot parse rerank %v", rerankRaw) } ret.Rerank = &pb.RerankReply{ Score: *rerank[0].Score, } } // group results does not support more additional properties searchParams.AdditionalProperties = additional.Properties{ ID: searchParams.AdditionalProperties.ID, Vector: searchParams.AdditionalProperties.Vector, Distance: searchParams.AdditionalProperties.Distance, } // group objects are returned as a different type than normal results ([]map[string]interface{} vs []interface). As // the normal path is used much more often than groupBy, convert the []map[string]interface{} to []interface{}, even // though we cast it to map[string]interface{} in the extraction function. // This way we only do a copy for groupBy and not for the standard code-path which is used more often returnObjectsUntyped := make([]interface{}, len(group.Hits)) for i := range returnObjectsUntyped { returnObjectsUntyped[i] = group.Hits[i] } objects, _, err := extractObjectsToResults(returnObjectsUntyped, searchParams, scheme, true, usesMarshalling) if err != nil { return nil, "", errors.Wrap(err, "extracting hits from group") } ret.Objects = objects return ret, groupedGenerativeResults, nil } func extractPropertiesAnswerDeprecated(scheme schema.Schema, results map[string]interface{}, properties search.SelectProperties, className string, additionalPropsParams additional.Properties) (*pb.PropertiesResult, error) { nonRefProps := make(map[string]interface{}, 0) refProps := make([]*pb.RefPropertiesResult, 0) objProps := make([]*pb.ObjectProperties, 0) objArrayProps := make([]*pb.ObjectArrayProperties, 0) for _, prop := range properties { propRaw, ok := results[prop.Name] if !ok { continue } if prop.IsPrimitive { nonRefProps[prop.Name] = propRaw continue } if prop.IsObject { nested, err := scheme.GetProperty(schema.ClassName(className), schema.PropertyName(prop.Name)) if err != nil { return nil, errors.Wrap(err, "getting property") } singleObj, ok := propRaw.(map[string]interface{}) if ok { extractedNestedProp, err := extractPropertiesNested(scheme, singleObj, prop, className, &Property{Property: nested}) if err != nil { return nil, errors.Wrap(err, "extracting nested properties") } objProps = append(objProps, &pb.ObjectProperties{ PropName: prop.Name, Value: extractedNestedProp, }) continue } arrayObjs, ok := propRaw.([]interface{}) if ok { extractedNestedProps := make([]*pb.ObjectPropertiesValue, 0, len(arrayObjs)) for _, obj := range arrayObjs { singleObj, ok := obj.(map[string]interface{}) if !ok { continue } extractedNestedProp, err := extractPropertiesNested(scheme, singleObj, prop, className, &Property{Property: nested}) if err != nil { return nil, err } extractedNestedProps = append(extractedNestedProps, extractedNestedProp) } objArrayProps = append(objArrayProps, &pb.ObjectArrayProperties{ PropName: prop.Name, Values: extractedNestedProps, }, ) continue } } refs, ok := propRaw.([]interface{}) if !ok { continue } extractedRefProps := make([]*pb.PropertiesResult, 0, len(refs)) for _, ref := range refs { refLocal, ok := ref.(search.LocalRef) if !ok { continue } extractedRefProp, err := extractPropertiesAnswerDeprecated(scheme, refLocal.Fields, prop.Refs[0].RefProperties, refLocal.Class, additionalPropsParams) if err != nil { continue } additionalProps, _, err := extractAdditionalProps(refLocal.Fields, prop.Refs[0].AdditionalProperties, false, false) if err != nil { return nil, err } extractedRefProp.Metadata = additionalProps extractedRefProps = append(extractedRefProps, extractedRefProp) } refProp := pb.RefPropertiesResult{PropName: prop.Name, Properties: extractedRefProps} refProps = append(refProps, &refProp) } props := pb.PropertiesResult{} if len(nonRefProps) > 0 { outProps := pb.ObjectPropertiesValue{} if err := extractArrayTypesRoot(scheme, className, nonRefProps, &outProps); err != nil { return nil, errors.Wrap(err, "extracting non-primitive types") } newStruct, err := structpb.NewStruct(nonRefProps) if err != nil { return nil, errors.Wrap(err, "creating non-ref-prop struct") } props.NonRefProperties = newStruct props.IntArrayProperties = outProps.IntArrayProperties props.NumberArrayProperties = outProps.NumberArrayProperties props.TextArrayProperties = outProps.TextArrayProperties props.BooleanArrayProperties = outProps.BooleanArrayProperties props.ObjectProperties = outProps.ObjectProperties props.ObjectArrayProperties = outProps.ObjectArrayProperties } if len(refProps) > 0 { props.RefProps = refProps } if len(objProps) > 0 { props.ObjectProperties = objProps } if len(objArrayProps) > 0 { props.ObjectArrayProperties = objArrayProps } props.TargetCollection = className return &props, nil } func extractPropertiesAnswer(scheme schema.Schema, results map[string]interface{}, properties search.SelectProperties, className string, additionalPropsParams additional.Properties) (*pb.PropertiesResult, error) { nonRefProps := &pb.Properties{ Fields: make(map[string]*pb.Value, 0), } refProps := make([]*pb.RefPropertiesResult, 0) class := scheme.GetClass(schema.ClassName(className)) for _, prop := range properties { propRaw, ok := results[prop.Name] if !ok { continue } if prop.IsPrimitive { dataType, err := schema.GetPropertyDataType(class, prop.Name) if err != nil { return nil, errors.Wrap(err, "getting primitive property datatype") } value, err := NewPrimitiveValue(propRaw, *dataType) if err != nil { return nil, errors.Wrapf(err, "creating primitive value for %v", prop.Name) } nonRefProps.Fields[prop.Name] = value continue } if prop.IsObject { nested, err := scheme.GetProperty(schema.ClassName(className), schema.PropertyName(prop.Name)) if err != nil { return nil, errors.Wrap(err, "getting nested property") } value, err := NewNestedValue(propRaw, schema.DataType(nested.DataType[0]), &Property{Property: nested}, prop) if err != nil { return nil, errors.Wrap(err, "creating object value") } nonRefProps.Fields[prop.Name] = value continue } refs, ok := propRaw.([]interface{}) if !ok { continue } extractedRefProps := make([]*pb.PropertiesResult, 0, len(refs)) for _, ref := range refs { refLocal, ok := ref.(search.LocalRef) if !ok { continue } extractedRefProp, err := extractPropertiesAnswer(scheme, refLocal.Fields, prop.Refs[0].RefProperties, refLocal.Class, additionalPropsParams) if err != nil { continue } additionalProps, _, err := extractAdditionalProps(refLocal.Fields, prop.Refs[0].AdditionalProperties, false, false) if err != nil { return nil, err } extractedRefProp.Metadata = additionalProps extractedRefProps = append(extractedRefProps, extractedRefProp) } refProp := pb.RefPropertiesResult{PropName: prop.Name, Properties: extractedRefProps} refProps = append(refProps, &refProp) } props := pb.PropertiesResult{} if len(nonRefProps.Fields) != 0 { props.NonRefProps = nonRefProps } if len(refProps) != 0 { props.RefProps = refProps } props.RefPropsRequested = properties.HasRefs() props.TargetCollection = className return &props, nil } func extractPropertiesNested[P schema.PropertyInterface](scheme schema.Schema, results map[string]interface{}, property search.SelectProperty, className string, parent P) (*pb.ObjectPropertiesValue, error) { primitiveProps := make(map[string]interface{}, 0) objProps := make([]*pb.ObjectProperties, 0) objArrayProps := make([]*pb.ObjectArrayProperties, 0) for _, prop := range property.Props { propRaw, ok := results[prop.Name] if !ok { continue } if prop.IsPrimitive { primitiveProps[prop.Name] = propRaw continue } if prop.IsObject { var err error objProps, objArrayProps, err = extractObjectProperties(scheme, propRaw, prop, className, parent, objProps, objArrayProps) if err != nil { return nil, err } } } props := pb.ObjectPropertiesValue{} if len(primitiveProps) > 0 { if err := extractArrayTypesNested(scheme, className, primitiveProps, &props, parent); err != nil { return nil, errors.Wrap(err, "extracting non-primitive types") } newStruct, err := structpb.NewStruct(primitiveProps) if err != nil { return nil, errors.Wrap(err, "creating non-ref-prop struct") } props.NonRefProperties = newStruct } if len(objProps) > 0 { props.ObjectProperties = objProps } if len(objArrayProps) > 0 { props.ObjectArrayProperties = objArrayProps } return &props, nil } 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) { prop, ok := propRaw.(map[string]interface{}) if ok { objProp, err := extractObjectSingleProperties(scheme, prop, property, className, parent) if err != nil { return objProps, objArrayProps, err } objProps = append(objProps, objProp) } propArray, ok := propRaw.([]interface{}) if ok { objArrayProp, err := extractObjectArrayProperties(scheme, propArray, property, className, parent) if err != nil { return objProps, objArrayProps, err } objArrayProps = append(objArrayProps, objArrayProp) } return objProps, objArrayProps, nil } func extractObjectSingleProperties[P schema.PropertyInterface](scheme schema.Schema, prop map[string]interface{}, property search.SelectProperty, className string, parent P) (*pb.ObjectProperties, error) { nested, err := schema.GetNestedPropertyByName(parent, property.Name) if err != nil { return nil, errors.Wrap(err, "getting property") } extractedNestedProp, err := extractPropertiesNested(scheme, prop, property, className, &NestedProperty{NestedProperty: nested}) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("extracting nested properties from %v", nested)) } return &pb.ObjectProperties{ PropName: property.Name, Value: extractedNestedProp, }, nil } func extractObjectArrayProperties[P schema.PropertyInterface](scheme schema.Schema, propObjs []interface{}, property search.SelectProperty, className string, parent P) (*pb.ObjectArrayProperties, error) { extractedNestedProps := make([]*pb.ObjectPropertiesValue, 0, len(propObjs)) for _, objRaw := range propObjs { nested, err := schema.GetNestedPropertyByName(parent, property.Name) if err != nil { return nil, errors.Wrap(err, "getting property") } obj, ok := objRaw.(map[string]interface{}) if !ok { continue } extractedNestedProp, err := extractPropertiesNested(scheme, obj, property, className, &NestedProperty{NestedProperty: nested}) if err != nil { return nil, errors.Wrap(err, "extracting nested properties") } extractedNestedProps = append(extractedNestedProps, extractedNestedProp) } return &pb.ObjectArrayProperties{ PropName: property.Name, Values: extractedNestedProps, }, nil } func extractArrayTypesRoot(scheme schema.Schema, className string, rawProps map[string]interface{}, props *pb.ObjectPropertiesValue) error { dataTypes := make(map[string]*schema.DataType, 0) for propName := range rawProps { dataType, err := schema.GetPropertyDataType(scheme.GetClass(schema.ClassName(className)), propName) if err != nil { return err } dataTypes[propName] = dataType } return extractArrayTypes(scheme, rawProps, props, dataTypes) } func extractArrayTypesNested[P schema.PropertyInterface](scheme schema.Schema, className string, rawProps map[string]interface{}, props *pb.ObjectPropertiesValue, parent P) error { dataTypes := make(map[string]*schema.DataType, 0) for propName := range rawProps { dataType, err := schema.GetNestedPropertyDataType(parent, propName) if err != nil { return err } dataTypes[propName] = dataType } return extractArrayTypes(scheme, rawProps, props, dataTypes) } // slices cannot be part of a grpc struct, so we need to handle each of them separately func extractArrayTypes(scheme schema.Schema, rawProps map[string]interface{}, props *pb.ObjectPropertiesValue, dataTypes map[string]*schema.DataType) error { for propName, prop := range rawProps { dataType := dataTypes[propName] switch *dataType { case schema.DataTypeIntArray: propIntAsFloat, ok := prop.([]float64) if !ok { emptyArr, ok := prop.([]interface{}) if ok && len(emptyArr) == 0 { continue } return fmt.Errorf("property %v with datatype %v needs to be []float64, got %T", propName, dataType, prop) } propInt := make([]int64, len(propIntAsFloat)) for i := range propIntAsFloat { propInt[i] = int64(propIntAsFloat[i]) } if props.IntArrayProperties == nil { props.IntArrayProperties = make([]*pb.IntArrayProperties, 0) } props.IntArrayProperties = append(props.IntArrayProperties, &pb.IntArrayProperties{PropName: propName, Values: propInt}) delete(rawProps, propName) case schema.DataTypeNumberArray: propFloat, ok := prop.([]float64) if !ok { emptyArr, ok := prop.([]interface{}) if ok && len(emptyArr) == 0 { continue } return fmt.Errorf("property %v with datatype %v needs to be []float64, got %T", propName, dataType, prop) } if props.NumberArrayProperties == nil { props.NumberArrayProperties = make([]*pb.NumberArrayProperties, 0) } props.NumberArrayProperties = append( props.NumberArrayProperties, &pb.NumberArrayProperties{PropName: propName, ValuesBytes: byteops.Float64ToByteVector(propFloat), Values: propFloat}, ) delete(rawProps, propName) case schema.DataTypeStringArray, schema.DataTypeTextArray, schema.DataTypeDateArray, schema.DataTypeUUIDArray: propString, ok := prop.([]string) if !ok { emptyArr, ok := prop.([]interface{}) if ok && len(emptyArr) == 0 { continue } return fmt.Errorf("property %v with datatype %v needs to be []string, got %T", propName, dataType, prop) } if props.TextArrayProperties == nil { props.TextArrayProperties = make([]*pb.TextArrayProperties, 0) } props.TextArrayProperties = append(props.TextArrayProperties, &pb.TextArrayProperties{PropName: propName, Values: propString}) delete(rawProps, propName) case schema.DataTypeBooleanArray: propBool, ok := prop.([]bool) if !ok { emptyArr, ok := prop.([]interface{}) if ok && len(emptyArr) == 0 { continue } return fmt.Errorf("property %v with datatype %v needs to be []bool, got %T", propName, dataType, prop) } if props.BooleanArrayProperties == nil { props.BooleanArrayProperties = make([]*pb.BooleanArrayProperties, 0) } props.BooleanArrayProperties = append(props.BooleanArrayProperties, &pb.BooleanArrayProperties{PropName: propName, Values: propBool}) delete(rawProps, propName) default: _, isArray := schema.IsArrayType(*dataType) if isArray { return fmt.Errorf("property %v with array type not handled %v", propName, dataType) } } } return nil }