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  }