Spaces:
Sleeping
Sleeping
| // _ _ | |
| // __ _____ __ ___ ___ __ _| |_ ___ | |
| // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
| // \ V V / __/ (_| |\ V /| | (_| | || __/ | |
| // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
| // | |
| // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
| // | |
| // CONTACT: [email protected] | |
| // | |
| package filters | |
| import ( | |
| "fmt" | |
| "strings" | |
| "github.com/weaviate/weaviate/entities/schema" | |
| ) | |
| // Represents the path in a filter. | |
| // Either RelationProperty or PrimitiveProperty must be empty (e.g. ""). | |
| type Path struct { | |
| Class schema.ClassName `json:"class"` | |
| Property schema.PropertyName `json:"property"` | |
| // If nil, then this is the property we're interested in. | |
| // If a pointer to another Path, the constraint applies to that one. | |
| Child *Path `json:"child"` | |
| } | |
| // GetInnerMost recursively searches for child paths, only when no more | |
| // children can be found will the path be returned | |
| func (p *Path) GetInnerMost() *Path { | |
| if p.Child == nil { | |
| return p | |
| } | |
| return p.Child.GetInnerMost() | |
| } | |
| // Slice flattens the nested path into a slice of segments | |
| func (p *Path) Slice() []string { | |
| return appendNestedPath(p, true) | |
| } | |
| func (p *Path) SliceInterface() []interface{} { | |
| path := appendNestedPath(p, true) | |
| out := make([]interface{}, len(path)) | |
| for i, element := range path { | |
| out[i] = element | |
| } | |
| return out | |
| } | |
| // TODO: This is now identical with Slice(), so it can be removed once all | |
| // callers have been adopted | |
| func (p *Path) SliceNonTitleized() []string { | |
| return appendNestedPath(p, true) | |
| } | |
| func appendNestedPath(p *Path, omitClass bool) []string { | |
| result := []string{} | |
| if !omitClass { | |
| result = append(result, string(p.Class)) | |
| } | |
| if p.Child != nil { | |
| property := string(p.Property) | |
| result = append(result, property) | |
| result = append(result, appendNestedPath(p.Child, false)...) | |
| } else { | |
| result = append(result, string(p.Property)) | |
| } | |
| return result | |
| } | |
| // ParsePath Parses the path | |
| // It parses an array of strings in this format | |
| // [0] ClassName -> The root class name we're drilling down from | |
| // [1] propertyName -> The property name we're interested in. | |
| func ParsePath(pathElements []interface{}, rootClass string) (*Path, error) { | |
| // we need to manually insert the root class, as that is omitted from the user | |
| pathElements = append([]interface{}{rootClass}, pathElements...) | |
| // The sentinel is used to bootstrap the inlined recursion. | |
| // we return sentinel.Child at the end. | |
| var sentinel Path | |
| // Keep track of where we are in the path (e.g. always points to latest Path segment) | |
| current := &sentinel | |
| // Now go through the path elements, step over it in increments of two. | |
| // Simple case: ClassName -> property | |
| // Nested path case: ClassName -> HasRef -> ClassOfRef -> Property | |
| for i := 0; i < len(pathElements); i += 2 { | |
| lengthRemaining := len(pathElements) - i | |
| if lengthRemaining < 2 { | |
| return nil, fmt.Errorf("missing an argument after '%s'", pathElements[i]) | |
| } | |
| rawClassName, ok := pathElements[i].(string) | |
| if !ok { | |
| return nil, fmt.Errorf("element %v is not a string", i+1) | |
| } | |
| rawPropertyName, ok := pathElements[i+1].(string) | |
| if !ok { | |
| return nil, fmt.Errorf("element %v is not a string", i+2) | |
| } | |
| className, err := schema.ValidateClassName(rawClassName) | |
| if err != nil { | |
| return nil, fmt.Errorf("Expected a valid class name in 'path' field for the filter but got '%s'", rawClassName) | |
| } | |
| var propertyName schema.PropertyName | |
| lengthPropName, isPropLengthFilter := schema.IsPropertyLength(rawPropertyName, 0) | |
| if isPropLengthFilter { | |
| // check if property in len(PROPERTY) is valid | |
| _, err = schema.ValidatePropertyName(lengthPropName) | |
| if err != nil { | |
| return nil, fmt.Errorf("Expected a valid property name in 'path' field for the filter, but got '%s'", lengthPropName) | |
| } | |
| propertyName = schema.PropertyName(rawPropertyName) | |
| } else { | |
| propertyName, err = schema.ValidatePropertyName(rawPropertyName) | |
| // Invalid property name? | |
| // Try to parse it as as a reference or a length. | |
| if err != nil { | |
| untitlizedPropertyName := strings.ToLower(rawPropertyName[0:1]) + rawPropertyName[1:] | |
| propertyName, err = schema.ValidatePropertyName(untitlizedPropertyName) | |
| if err != nil { | |
| return nil, fmt.Errorf("Expected a valid property name in 'path' field for the filter, but got '%s'", rawPropertyName) | |
| } | |
| } | |
| } | |
| current.Child = &Path{ | |
| Class: className, | |
| Property: propertyName, | |
| } | |
| // And down we go. | |
| current = current.Child | |
| } | |
| return sentinel.Child, nil | |
| } | |