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 }