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 }