github.com/ipld/go-ipld-prime@v0.21.0/traversal/selector/exploreUnion.go (about)

     1  package selector
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/ipld/go-ipld-prime/datamodel"
     7  )
     8  
     9  // ExploreUnion allows selection to continue with two or more distinct selectors
    10  // while exploring the same tree of data.
    11  //
    12  // ExploreUnion can be used to apply a Matcher on one node (causing it to
    13  // be considered part of a (possibly labelled) result set), while simultaneously
    14  // continuing to explore deeper parts of the tree with another selector,
    15  // for example.
    16  type ExploreUnion struct {
    17  	Members []Selector
    18  }
    19  
    20  // Interests for ExploreUnion is:
    21  // - nil (aka all) if any member selector has nil interests
    22  // - the union of values returned by all member selectors otherwise
    23  func (s ExploreUnion) Interests() []datamodel.PathSegment {
    24  	// Check for any high-cardinality selectors first; if so, shortcircuit.
    25  	//  (n.b. we're assuming the 'Interests' method is cheap here.)
    26  	for _, m := range s.Members {
    27  		if m.Interests() == nil {
    28  			return nil
    29  		}
    30  	}
    31  	// Accumulate the whitelist of interesting path segments.
    32  	// TODO: Dedup?
    33  	v := []datamodel.PathSegment{}
    34  	for _, m := range s.Members {
    35  		v = append(v, m.Interests()...)
    36  	}
    37  	return v
    38  }
    39  
    40  // Explore for a Union selector calls explore for each member selector
    41  // and returns:
    42  // - a new union selector if more than one member returns a selector
    43  // - if exactly one member returns a selector, that selector
    44  // - nil if no members return a selector
    45  func (s ExploreUnion) Explore(n datamodel.Node, p datamodel.PathSegment) (Selector, error) {
    46  	// TODO: memory efficient?
    47  	nonNilResults := make([]Selector, 0, len(s.Members))
    48  	for _, member := range s.Members {
    49  		resultSelector, err := member.Explore(n, p)
    50  		if err != nil {
    51  			return nil, err
    52  		}
    53  		if resultSelector != nil {
    54  			nonNilResults = append(nonNilResults, resultSelector)
    55  		}
    56  	}
    57  	if len(nonNilResults) == 0 {
    58  		return nil, nil
    59  	}
    60  	if len(nonNilResults) == 1 {
    61  		return nonNilResults[0], nil
    62  	}
    63  	return ExploreUnion{nonNilResults}, nil
    64  }
    65  
    66  // Decide returns true for a Union selector if any of the member selectors
    67  // return true
    68  func (s ExploreUnion) Decide(n datamodel.Node) bool {
    69  	for _, m := range s.Members {
    70  		if m.Decide(n) {
    71  			return true
    72  		}
    73  	}
    74  	return false
    75  }
    76  
    77  // Match returns true for a Union selector based on the matched union.
    78  func (s ExploreUnion) Match(n datamodel.Node) (datamodel.Node, error) {
    79  	for _, m := range s.Members {
    80  		if mn, err := m.Match(n); mn != nil {
    81  			return mn, nil
    82  		} else if err != nil {
    83  			return nil, err
    84  		}
    85  	}
    86  	return nil, nil
    87  }
    88  
    89  // ParseExploreUnion assembles a Selector
    90  // from an ExploreUnion selector node
    91  func (pc ParseContext) ParseExploreUnion(n datamodel.Node) (Selector, error) {
    92  	if n.Kind() != datamodel.Kind_List {
    93  		return nil, fmt.Errorf("selector spec parse rejected: explore union selector must be a list")
    94  	}
    95  	x := ExploreUnion{
    96  		make([]Selector, 0, n.Length()),
    97  	}
    98  	for itr := n.ListIterator(); !itr.Done(); {
    99  		_, v, err := itr.Next()
   100  		if err != nil {
   101  			return nil, fmt.Errorf("error during selector spec parse: %w", err)
   102  		}
   103  		member, err := pc.ParseSelector(v)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		x.Members = append(x.Members, member)
   108  	}
   109  	return x, nil
   110  }