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  }