github.com/ipld/go-ipld-prime@v0.21.0/traversal/selector/exploreRecursive.go (about) 1 package selector 2 3 import ( 4 "fmt" 5 6 "github.com/ipld/go-ipld-prime/datamodel" 7 ) 8 9 // ExploreRecursive traverses some structure recursively. 10 // To guide this exploration, it uses a "sequence", which is another Selector 11 // tree; some leaf node in this sequence should contain an ExploreRecursiveEdge 12 // selector, which denotes the place recursion should occur. 13 // 14 // In implementation, whenever evaluation reaches an ExploreRecursiveEdge marker 15 // in the recursion sequence's Selector tree, the implementation logically 16 // produces another new Selector which is a copy of the original 17 // ExploreRecursive selector, but with a decremented maxDepth parameter, and 18 // continues evaluation thusly. 19 // 20 // It is not valid for an ExploreRecursive selector's sequence to contain 21 // no instances of ExploreRecursiveEdge; it *is* valid for it to contain 22 // more than one ExploreRecursiveEdge. 23 // 24 // ExploreRecursive can contain a nested ExploreRecursive! 25 // This is comparable to a nested for-loop. 26 // In these cases, any ExploreRecursiveEdge instance always refers to the 27 // nearest parent ExploreRecursive (in other words, ExploreRecursiveEdge can 28 // be thought of like the 'continue' statement, or end of a for-loop body; 29 // it is *not* a 'goto' statement). 30 // 31 // Be careful when using ExploreRecursive with a large maxDepth parameter; 32 // it can easily cause very large traversals (especially if used in combination 33 // with selectors like ExploreAll inside the sequence). 34 type ExploreRecursive struct { 35 sequence Selector // selector for element we're interested in 36 current Selector // selector to apply to the current node 37 limit RecursionLimit // the limit for this recursive selector 38 stopAt *Condition // a condition for not exploring the node or children 39 } 40 41 // RecursionLimit_Mode is an enum that represents the type of a recursion limit 42 // -- either "depth" or "none" for now 43 type RecursionLimit_Mode uint8 44 45 const ( 46 // RecursionLimit_None means there is no recursion limit 47 RecursionLimit_None RecursionLimit_Mode = 0 48 // RecursionLimit_Depth mean recursion stops after the recursive selector 49 // is copied to a given depth 50 RecursionLimit_Depth RecursionLimit_Mode = 1 51 ) 52 53 // RecursionLimit is a union type that captures all data about the recursion 54 // limit (both its type and data specific to the type) 55 type RecursionLimit struct { 56 mode RecursionLimit_Mode 57 depth int64 58 } 59 60 // Mode returns the type for this recursion limit 61 func (rl RecursionLimit) Mode() RecursionLimit_Mode { 62 return rl.mode 63 } 64 65 // Depth returns the depth for a depth recursion limit, or 0 otherwise 66 func (rl RecursionLimit) Depth() int64 { 67 if rl.mode != RecursionLimit_Depth { 68 return 0 69 } 70 return rl.depth 71 } 72 73 // RecursionLimitDepth returns a depth limited recursion to the given depth 74 func RecursionLimitDepth(depth int64) RecursionLimit { 75 return RecursionLimit{RecursionLimit_Depth, depth} 76 } 77 78 // RecursionLimitNone return recursion with no limit 79 func RecursionLimitNone() RecursionLimit { 80 return RecursionLimit{RecursionLimit_None, 0} 81 } 82 83 // Interests for ExploreRecursive is empty (meaning traverse everything) 84 func (s ExploreRecursive) Interests() []datamodel.PathSegment { 85 return s.current.Interests() 86 } 87 88 // Explore returns the node's selector for all fields 89 func (s ExploreRecursive) Explore(n datamodel.Node, p datamodel.PathSegment) (Selector, error) { 90 // Check any stopAt conditions right away. 91 if s.stopAt != nil { 92 target, err := n.LookupBySegment(p) 93 if err != nil { 94 return nil, err 95 } 96 if s.stopAt.Match(target) { 97 return nil, nil 98 } 99 } 100 101 // Fence against edge case: if the next selector is a recursion edge, nope, we're out. 102 // (This is only reachable if a recursion contains nothing but an edge -- which is probably somewhat rare, 103 // because it's certainly rather useless -- but it's not explicitly rejected as a malformed selector during compile, either, so it must be handled.) 104 if _, ok := s.current.(ExploreRecursiveEdge); ok { 105 return nil, nil 106 } 107 108 // Apply the current selector clause. (This could be midway through something resembling the initially specified sequence.) 109 nextSelector, _ := s.current.Explore(n, p) 110 111 // We have to wrap the nextSelector yielded by the current clause in recursion information before returning it, 112 // so that future levels of recursion (as well as their limits) can continue to operate correctly. 113 if nextSelector == nil { 114 return nil, nil 115 } 116 limit := s.limit 117 if !s.hasRecursiveEdge(nextSelector) { 118 return ExploreRecursive{s.sequence, nextSelector, limit, s.stopAt}, nil 119 } 120 switch limit.mode { 121 case RecursionLimit_Depth: 122 if limit.depth < 2 { 123 return s.replaceRecursiveEdge(nextSelector, nil), nil 124 } 125 return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), RecursionLimit{RecursionLimit_Depth, limit.depth - 1}, s.stopAt}, nil 126 case RecursionLimit_None: 127 return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), limit, s.stopAt}, nil 128 default: 129 panic("Unsupported recursion limit type") 130 } 131 } 132 133 func (s ExploreRecursive) hasRecursiveEdge(nextSelector Selector) bool { 134 _, isRecursiveEdge := nextSelector.(ExploreRecursiveEdge) 135 if isRecursiveEdge { 136 return true 137 } 138 exploreUnion, isUnion := nextSelector.(ExploreUnion) 139 if isUnion { 140 for _, selector := range exploreUnion.Members { 141 if s.hasRecursiveEdge(selector) { 142 return true 143 } 144 } 145 } 146 return false 147 } 148 149 func (s ExploreRecursive) replaceRecursiveEdge(nextSelector Selector, replacement Selector) Selector { 150 _, isRecursiveEdge := nextSelector.(ExploreRecursiveEdge) 151 if isRecursiveEdge { 152 return replacement 153 } 154 exploreUnion, isUnion := nextSelector.(ExploreUnion) 155 if isUnion { 156 replacementMembers := make([]Selector, 0, len(exploreUnion.Members)) 157 for _, selector := range exploreUnion.Members { 158 newSelector := s.replaceRecursiveEdge(selector, replacement) 159 if newSelector != nil { 160 replacementMembers = append(replacementMembers, newSelector) 161 } 162 } 163 if len(replacementMembers) == 0 { 164 return nil 165 } 166 if len(replacementMembers) == 1 { 167 return replacementMembers[0] 168 } 169 return ExploreUnion{replacementMembers} 170 } 171 return nextSelector 172 } 173 174 // Decide if a node directly matches 175 func (s ExploreRecursive) Decide(n datamodel.Node) bool { 176 return s.current.Decide(n) 177 } 178 179 // Match always returns false because this is not a matcher 180 func (s ExploreRecursive) Match(node datamodel.Node) (datamodel.Node, error) { 181 return s.current.Match(node) 182 } 183 184 type exploreRecursiveContext struct { 185 edgesFound int 186 } 187 188 func (erc *exploreRecursiveContext) Link(s Selector) bool { 189 _, ok := s.(ExploreRecursiveEdge) 190 if ok { 191 erc.edgesFound++ 192 } 193 return ok 194 } 195 196 // ParseExploreRecursive assembles a Selector from a ExploreRecursive selector node 197 func (pc ParseContext) ParseExploreRecursive(n datamodel.Node) (Selector, error) { 198 if n.Kind() != datamodel.Kind_Map { 199 return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map") 200 } 201 202 limitNode, err := n.LookupByString(SelectorKey_Limit) 203 if err != nil { 204 return nil, fmt.Errorf("selector spec parse rejected: limit field must be present in ExploreRecursive selector") 205 } 206 limit, err := parseLimit(limitNode) 207 if err != nil { 208 return nil, err 209 } 210 sequence, err := n.LookupByString(SelectorKey_Sequence) 211 if err != nil { 212 return nil, fmt.Errorf("selector spec parse rejected: sequence field must be present in ExploreRecursive selector") 213 } 214 erc := &exploreRecursiveContext{} 215 selector, err := pc.PushParent(erc).ParseSelector(sequence) 216 if err != nil { 217 return nil, err 218 } 219 if erc.edgesFound == 0 { 220 return nil, fmt.Errorf("selector spec parse rejected: ExploreRecursive must have at least one ExploreRecursiveEdge") 221 } 222 var stopCondition *Condition 223 stop, err := n.LookupByString(SelectorKey_StopAt) 224 if err == nil { 225 condition, err := pc.ParseCondition(stop) 226 if err != nil { 227 return nil, err 228 } 229 stopCondition = &condition 230 } 231 return ExploreRecursive{selector, selector, limit, stopCondition}, nil 232 } 233 234 func parseLimit(n datamodel.Node) (RecursionLimit, error) { 235 if n.Kind() != datamodel.Kind_Map { 236 return RecursionLimit{}, fmt.Errorf("selector spec parse rejected: limit in ExploreRecursive is a keyed union and thus must be a map") 237 } 238 if n.Length() != 1 { 239 return RecursionLimit{}, fmt.Errorf("selector spec parse rejected: limit in ExploreRecursive is a keyed union and thus must be a single-entry map") 240 } 241 kn, v, _ := n.MapIterator().Next() 242 kstr, _ := kn.AsString() 243 switch kstr { 244 case SelectorKey_LimitDepth: 245 maxDepthValue, err := v.AsInt() 246 if err != nil { 247 return RecursionLimit{}, fmt.Errorf("selector spec parse rejected: limit field of type depth must be a number in ExploreRecursive selector") 248 } 249 return RecursionLimit{RecursionLimit_Depth, maxDepthValue}, nil 250 case SelectorKey_LimitNone: 251 return RecursionLimit{RecursionLimit_None, 0}, nil 252 default: 253 return RecursionLimit{}, fmt.Errorf("selector spec parse rejected: %q is not a known member of the limit union in ExploreRecursive", kstr) 254 } 255 }