github.com/weaviate/weaviate@v1.24.6/entities/filters/path.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 filters 13 14 import ( 15 "fmt" 16 "strings" 17 18 "github.com/weaviate/weaviate/entities/schema" 19 ) 20 21 // Represents the path in a filter. 22 // Either RelationProperty or PrimitiveProperty must be empty (e.g. ""). 23 type Path struct { 24 Class schema.ClassName `json:"class"` 25 Property schema.PropertyName `json:"property"` 26 27 // If nil, then this is the property we're interested in. 28 // If a pointer to another Path, the constraint applies to that one. 29 Child *Path `json:"child"` 30 } 31 32 // GetInnerMost recursively searches for child paths, only when no more 33 // children can be found will the path be returned 34 func (p *Path) GetInnerMost() *Path { 35 if p.Child == nil { 36 return p 37 } 38 39 return p.Child.GetInnerMost() 40 } 41 42 // Slice flattens the nested path into a slice of segments 43 func (p *Path) Slice() []string { 44 return appendNestedPath(p, true) 45 } 46 47 func (p *Path) SliceInterface() []interface{} { 48 path := appendNestedPath(p, true) 49 out := make([]interface{}, len(path)) 50 for i, element := range path { 51 out[i] = element 52 } 53 return out 54 } 55 56 // TODO: This is now identical with Slice(), so it can be removed once all 57 // callers have been adopted 58 func (p *Path) SliceNonTitleized() []string { 59 return appendNestedPath(p, true) 60 } 61 62 func appendNestedPath(p *Path, omitClass bool) []string { 63 result := []string{} 64 if !omitClass { 65 result = append(result, string(p.Class)) 66 } 67 68 if p.Child != nil { 69 property := string(p.Property) 70 result = append(result, property) 71 result = append(result, appendNestedPath(p.Child, false)...) 72 } else { 73 result = append(result, string(p.Property)) 74 } 75 76 return result 77 } 78 79 // ParsePath Parses the path 80 // It parses an array of strings in this format 81 // [0] ClassName -> The root class name we're drilling down from 82 // [1] propertyName -> The property name we're interested in. 83 func ParsePath(pathElements []interface{}, rootClass string) (*Path, error) { 84 // we need to manually insert the root class, as that is omitted from the user 85 pathElements = append([]interface{}{rootClass}, pathElements...) 86 87 // The sentinel is used to bootstrap the inlined recursion. 88 // we return sentinel.Child at the end. 89 var sentinel Path 90 91 // Keep track of where we are in the path (e.g. always points to latest Path segment) 92 current := &sentinel 93 94 // Now go through the path elements, step over it in increments of two. 95 // Simple case: ClassName -> property 96 // Nested path case: ClassName -> HasRef -> ClassOfRef -> Property 97 for i := 0; i < len(pathElements); i += 2 { 98 lengthRemaining := len(pathElements) - i 99 if lengthRemaining < 2 { 100 return nil, fmt.Errorf("missing an argument after '%s'", pathElements[i]) 101 } 102 103 rawClassName, ok := pathElements[i].(string) 104 if !ok { 105 return nil, fmt.Errorf("element %v is not a string", i+1) 106 } 107 108 rawPropertyName, ok := pathElements[i+1].(string) 109 if !ok { 110 return nil, fmt.Errorf("element %v is not a string", i+2) 111 } 112 113 className, err := schema.ValidateClassName(rawClassName) 114 if err != nil { 115 return nil, fmt.Errorf("Expected a valid class name in 'path' field for the filter but got '%s'", rawClassName) 116 } 117 118 var propertyName schema.PropertyName 119 lengthPropName, isPropLengthFilter := schema.IsPropertyLength(rawPropertyName, 0) 120 if isPropLengthFilter { 121 // check if property in len(PROPERTY) is valid 122 _, err = schema.ValidatePropertyName(lengthPropName) 123 if err != nil { 124 return nil, fmt.Errorf("Expected a valid property name in 'path' field for the filter, but got '%s'", lengthPropName) 125 } 126 propertyName = schema.PropertyName(rawPropertyName) 127 } else { 128 propertyName, err = schema.ValidatePropertyName(rawPropertyName) 129 // Invalid property name? 130 // Try to parse it as as a reference or a length. 131 if err != nil { 132 untitlizedPropertyName := strings.ToLower(rawPropertyName[0:1]) + rawPropertyName[1:] 133 propertyName, err = schema.ValidatePropertyName(untitlizedPropertyName) 134 if err != nil { 135 return nil, fmt.Errorf("Expected a valid property name in 'path' field for the filter, but got '%s'", rawPropertyName) 136 } 137 } 138 139 } 140 141 current.Child = &Path{ 142 Class: className, 143 Property: propertyName, 144 } 145 146 // And down we go. 147 current = current.Child 148 } 149 150 return sentinel.Child, nil 151 }