github.com/weaviate/weaviate@v1.24.6/usecases/schema/ref_finder.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 schema
    13  
    14  import (
    15  	"sort"
    16  
    17  	"github.com/weaviate/weaviate/entities/filters"
    18  	"github.com/weaviate/weaviate/entities/models"
    19  	libschema "github.com/weaviate/weaviate/entities/schema"
    20  )
    21  
    22  // RefFinder is a helper that lists classes and their possible paths to to a
    23  // desired target class.
    24  //
    25  // For example if the target class is "car". It might list:
    26  // - Person, drives, Car
    27  // - Person, owns, Car
    28  // - Person, friendsWith, Person, drives, Car
    29  // etc.
    30  //
    31  // It will stop at a preconfigured depth limit, to avoid infinite results, such
    32  // as:
    33  // - Person, friendsWith, Person, friendsWith, Person, ..., drives Car
    34  type RefFinder struct {
    35  	schemaGetter schemaGetterForRefFinder
    36  	depthLimit   int
    37  }
    38  
    39  // NewRefFinder with SchemaGetter and depth limit
    40  func NewRefFinder(getter schemaGetterForRefFinder, depthLimit int) *RefFinder {
    41  	return &RefFinder{
    42  		schemaGetter: getter,
    43  		depthLimit:   depthLimit,
    44  	}
    45  }
    46  
    47  type schemaGetterForRefFinder interface {
    48  	GetSchemaSkipAuth() libschema.Schema
    49  }
    50  
    51  func (r *RefFinder) Find(className libschema.ClassName) []filters.Path {
    52  	schema := r.schemaGetter.GetSchemaSkipAuth()
    53  
    54  	var classes []*models.Class
    55  	if schema.Objects != nil {
    56  		classes = append(classes, schema.Objects.Classes...)
    57  	}
    58  
    59  	return r.findInClassList(className, classes, schema)
    60  }
    61  
    62  func (r *RefFinder) findInClassList(needle libschema.ClassName, classes []*models.Class,
    63  	schema libschema.Schema,
    64  ) []filters.Path {
    65  	var out []filters.Path
    66  
    67  	for _, class := range classes {
    68  		path, ok := r.hasRefTo(needle, class, schema, 1)
    69  		if !ok {
    70  			continue
    71  		}
    72  
    73  		out = append(out, path...)
    74  	}
    75  
    76  	return r.sortByPathLen(out)
    77  }
    78  
    79  func (r *RefFinder) hasRefTo(needle libschema.ClassName, class *models.Class,
    80  	schema libschema.Schema, depth int,
    81  ) ([]filters.Path, bool) {
    82  	var out []filters.Path
    83  
    84  	if depth > r.depthLimit {
    85  		return nil, false
    86  	}
    87  
    88  	for _, prop := range class.Properties {
    89  		dt, err := schema.FindPropertyDataType(prop.DataType)
    90  		if err != nil {
    91  			// silently ignore, maybe the property was deleted in the meantime
    92  			continue
    93  		}
    94  
    95  		if !dt.IsReference() {
    96  			continue
    97  		}
    98  
    99  		for _, haystack := range dt.Classes() {
   100  			refs := r.refsPerClass(needle, class, prop.Name, haystack, schema, depth)
   101  			out = append(out, refs...)
   102  		}
   103  	}
   104  
   105  	return out, len(out) > 0
   106  }
   107  
   108  func (r *RefFinder) refsPerClass(needle libschema.ClassName, class *models.Class,
   109  	propName string, haystack libschema.ClassName, schema libschema.Schema,
   110  	depth int,
   111  ) []filters.Path {
   112  	if haystack == needle {
   113  		// direct match
   114  		return []filters.Path{
   115  			{
   116  				Class:    libschema.ClassName(class.Class),
   117  				Property: libschema.PropertyName(propName),
   118  				Child: &filters.Path{
   119  					Class:    needle,
   120  					Property: "id",
   121  				},
   122  			},
   123  		}
   124  	}
   125  
   126  	// could still be an indirect (recursive) match
   127  	innerClass := schema.FindClassByName(haystack)
   128  	if innerClass == nil {
   129  		return nil
   130  	}
   131  	paths, ok := r.hasRefTo(needle, innerClass, schema, depth+1)
   132  	if !ok {
   133  		return nil
   134  	}
   135  
   136  	var out []filters.Path
   137  	for _, path := range paths {
   138  		out = append(out, filters.Path{
   139  			Class:    libschema.ClassName(class.Class),
   140  			Property: libschema.PropertyName(propName),
   141  			Child:    &path,
   142  		})
   143  	}
   144  
   145  	return out
   146  }
   147  
   148  func (r *RefFinder) sortByPathLen(in []filters.Path) []filters.Path {
   149  	sort.Slice(in, func(i, j int) bool {
   150  		return len(in[i].Slice()) < len(in[j].Slice())
   151  	})
   152  
   153  	return in
   154  }