github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/refcache/resolver.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 refcache 13 14 import ( 15 "context" 16 "fmt" 17 18 "github.com/pkg/errors" 19 "github.com/weaviate/weaviate/entities/additional" 20 "github.com/weaviate/weaviate/entities/models" 21 "github.com/weaviate/weaviate/entities/multi" 22 "github.com/weaviate/weaviate/entities/schema/crossref" 23 "github.com/weaviate/weaviate/entities/search" 24 ) 25 26 type Resolver struct { 27 cacher cacher 28 // for groupBy feature 29 withGroup bool 30 getGroupSelectProperties func(properties search.SelectProperties) search.SelectProperties 31 } 32 33 type cacher interface { 34 Build(ctx context.Context, objects []search.Result, properties search.SelectProperties, additional additional.Properties) error 35 Get(si multi.Identifier) (search.Result, bool) 36 } 37 38 func NewResolver(cacher cacher) *Resolver { 39 return &Resolver{cacher: cacher} 40 } 41 42 func NewResolverWithGroup(cacher cacher) *Resolver { 43 return &Resolver{ 44 cacher: cacher, 45 // for groupBy feature 46 withGroup: true, 47 getGroupSelectProperties: getGroupSelectProperties, 48 } 49 } 50 51 func (r *Resolver) Do(ctx context.Context, objects []search.Result, 52 properties search.SelectProperties, additional additional.Properties, 53 ) ([]search.Result, error) { 54 if err := r.cacher.Build(ctx, objects, properties, additional); err != nil { 55 return nil, errors.Wrap(err, "build reference cache") 56 } 57 58 return r.parseObjects(objects, properties, additional) 59 } 60 61 func (r *Resolver) parseObjects(objects []search.Result, properties search.SelectProperties, 62 additional additional.Properties, 63 ) ([]search.Result, error) { 64 for i, obj := range objects { 65 parsed, err := r.parseObject(obj, properties, additional) 66 if err != nil { 67 return nil, errors.Wrapf(err, "parse at position %d", i) 68 } 69 70 objects[i] = parsed 71 } 72 73 return objects, nil 74 } 75 76 func (r *Resolver) parseObject(object search.Result, properties search.SelectProperties, 77 additional additional.Properties, 78 ) (search.Result, error) { 79 if object.Schema == nil { 80 return object, nil 81 } 82 83 schemaMap, ok := object.Schema.(map[string]interface{}) 84 if !ok { 85 return object, fmt.Errorf("schema is not a map: %T", object.Schema) 86 } 87 88 schema, err := r.parseSchema(schemaMap, properties) 89 if err != nil { 90 return object, err 91 } 92 93 object.Schema = schema 94 95 if r.withGroup { 96 additionalProperties, err := r.parseAdditionalGroup(object.AdditionalProperties, properties) 97 if err != nil { 98 return object, err 99 } 100 object.AdditionalProperties = additionalProperties 101 } 102 return object, nil 103 } 104 105 func (r *Resolver) parseAdditionalGroup( 106 additionalProperties models.AdditionalProperties, 107 properties search.SelectProperties, 108 ) (models.AdditionalProperties, error) { 109 if additionalProperties != nil && additionalProperties["group"] != nil { 110 if group, ok := additionalProperties["group"].(*additional.Group); ok { 111 for j, hit := range group.Hits { 112 schema, err := r.parseSchema(hit, r.getGroupSelectProperties(properties)) 113 if err != nil { 114 return additionalProperties, fmt.Errorf("resolve group hit: %w", err) 115 } 116 group.Hits[j] = schema 117 } 118 } 119 } 120 return additionalProperties, nil 121 } 122 123 func (r *Resolver) parseSchema(schema map[string]interface{}, 124 properties search.SelectProperties, 125 ) (map[string]interface{}, error) { 126 for propName, value := range schema { 127 refs, ok := value.(models.MultipleRef) 128 if !ok { 129 // not a ref, not interesting for us 130 continue 131 } 132 133 selectProp := properties.FindProperty(propName) 134 if selectProp == nil { 135 // user is not interested in this prop 136 continue 137 } 138 139 parsed, err := r.parseRefs(refs, propName, *selectProp) 140 if err != nil { 141 return schema, errors.Wrapf(err, "parse refs for prop %q", propName) 142 } 143 144 if parsed != nil { 145 schema[propName] = parsed 146 } 147 } 148 149 return schema, nil 150 } 151 152 func (r *Resolver) parseRefs(input models.MultipleRef, prop string, 153 selectProp search.SelectProperty, 154 ) ([]interface{}, error) { 155 var refs []interface{} 156 for _, selectPropRef := range selectProp.Refs { 157 innerProperties := selectPropRef.RefProperties 158 additionalProperties := selectPropRef.AdditionalProperties 159 perClass, err := r.resolveRefs(input, selectPropRef.ClassName, innerProperties, additionalProperties) 160 if err != nil { 161 return nil, errors.Wrap(err, "resolve ref") 162 } 163 164 refs = append(refs, perClass...) 165 } 166 return refs, nil 167 } 168 169 func (r *Resolver) resolveRefs(input models.MultipleRef, desiredClass string, 170 innerProperties search.SelectProperties, 171 additionalProperties additional.Properties, 172 ) ([]interface{}, error) { 173 var output []interface{} 174 for i, item := range input { 175 resolved, err := r.resolveRef(item, desiredClass, innerProperties, additionalProperties) 176 if err != nil { 177 return nil, errors.Wrapf(err, "at position %d", i) 178 } 179 180 if resolved == nil { 181 continue 182 } 183 184 output = append(output, *resolved) 185 } 186 187 return output, nil 188 } 189 190 func (r *Resolver) resolveRef(item *models.SingleRef, desiredClass string, 191 innerProperties search.SelectProperties, 192 additionalProperties additional.Properties, 193 ) (*search.LocalRef, error) { 194 var out search.LocalRef 195 196 ref, err := crossref.Parse(item.Beacon.String()) 197 if err != nil { 198 return nil, err 199 } 200 201 si := multi.Identifier{ 202 ID: ref.TargetID.String(), 203 ClassName: desiredClass, 204 } 205 res, ok := r.cacher.Get(si) 206 if !ok { 207 // silently ignore, could have been deleted in the meantime, or we're 208 // asking for a non-matching selectProperty, for example if we ask for 209 // Article { published { ... on { Magazine { name } ... on { Journal { name } } 210 // we don't know at resolve time if this ID will point to a Magazine or a 211 // Journal, so we will get a few empty responses when trying both for any 212 // given ID. 213 // 214 // In turn this means we need to validate through automated and explorative 215 // tests, that we never skip results that should be contained, as we 216 // wouldn't throw an error, so the user would never notice 217 return nil, nil 218 } 219 220 out.Class = res.ClassName 221 schema := res.Schema.(map[string]interface{}) 222 nested, err := r.parseSchema(schema, innerProperties) 223 if err != nil { 224 return nil, errors.Wrap(err, "resolve nested ref") 225 } 226 227 if additionalProperties.Vector { 228 nested["vector"] = res.Vector 229 } 230 if len(additionalProperties.Vectors) > 0 { 231 vectors := make(map[string][]float32) 232 for _, targetVector := range additionalProperties.Vectors { 233 vectors[targetVector] = res.Vectors[targetVector] 234 } 235 nested["vectors"] = vectors 236 } 237 if additionalProperties.CreationTimeUnix { 238 nested["creationTimeUnix"] = res.Created 239 } 240 if additionalProperties.LastUpdateTimeUnix { 241 nested["lastUpdateTimeUnix"] = res.Updated 242 } 243 out.Fields = nested 244 245 return &out, nil 246 }