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  }