github.com/ipld/go-ipld-prime@v0.21.0/traversal/walk.go (about) 1 package traversal 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/ipld/go-ipld-prime/datamodel" 8 "github.com/ipld/go-ipld-prime/linking" 9 "github.com/ipld/go-ipld-prime/linking/preload" 10 "github.com/ipld/go-ipld-prime/traversal/selector" 11 ) 12 13 // phase is an internal enum used to track the current phase of a walk. It's 14 // used to control for a preload pass over a block if one is required. 15 type phase int 16 17 const ( 18 phasePreload phase = iota 19 phaseTraverse phase = iota 20 ) 21 22 // WalkLocal walks a tree of Nodes, visiting each of them, 23 // and calling the given VisitFn on all of them; 24 // it does not traverse any links. 25 // 26 // WalkLocal can skip subtrees if the VisitFn returns SkipMe, 27 // but lacks any other options for controlling or directing the visit; 28 // consider using some of the various Walk functions with Selector parameters if you want more control. 29 func WalkLocal(n datamodel.Node, fn VisitFn) error { 30 return Progress{}.WalkLocal(n, fn) 31 } 32 33 // WalkMatching walks a graph of Nodes, deciding which to visit by applying a Selector, 34 // and calling the given VisitFn on those that the Selector deems a match. 35 // 36 // This function is a helper function which starts a new walk with default configuration. 37 // It cannot cross links automatically (since this requires configuration). 38 // Use the equivalent WalkMatching function on the Progress structure 39 // for more advanced and configurable walks. 40 func WalkMatching(n datamodel.Node, s selector.Selector, fn VisitFn) error { 41 return Progress{}.WalkMatching(n, s, fn) 42 } 43 44 // WalkAdv is identical to WalkMatching, except it is called for *all* nodes 45 // visited (not just matching nodes), together with the reason for the visit. 46 // An AdvVisitFn is used instead of a VisitFn, so that the reason can be provided. 47 // 48 // This function is a helper function which starts a new walk with default configuration. 49 // It cannot cross links automatically (since this requires configuration). 50 // Use the equivalent WalkAdv function on the Progress structure 51 // for more advanced and configurable walks. 52 func WalkAdv(n datamodel.Node, s selector.Selector, fn AdvVisitFn) error { 53 return Progress{}.WalkAdv(n, s, fn) 54 } 55 56 // WalkTransforming walks a graph of Nodes, deciding which to alter by applying a Selector, 57 // and calls the given TransformFn to decide what new node to replace the visited node with. 58 // A new Node tree will be returned (the original is unchanged). 59 // 60 // This function is a helper function which starts a new walk with default configuration. 61 // It cannot cross links automatically (since this requires configuration). 62 // Use the equivalent WalkTransforming function on the Progress structure 63 // for more advanced and configurable walks. 64 func WalkTransforming(n datamodel.Node, s selector.Selector, fn TransformFn) (datamodel.Node, error) { 65 return Progress{}.WalkTransforming(n, s, fn) 66 } 67 68 // WalkMatching walks a graph of Nodes, deciding which to visit by applying a Selector, 69 // and calling the given VisitFn on those that the Selector deems a match. 70 // 71 // WalkMatching is a read-only traversal. 72 // See WalkTransforming if looking for a way to do "updates" to a tree of nodes. 73 // 74 // Provide configuration to this process using the Config field in the Progress object. 75 // 76 // This walk will automatically cross links, but requires some configuration 77 // with link loading functions to do so. 78 // 79 // Traversals are defined as visiting a (node,path) tuple. 80 // This is important to note because when walking DAGs with Links, 81 // it means you may visit the same node multiple times 82 // due to having reached it via a different path. 83 // (You can prevent this by using a LinkLoader function which memoizes a set of 84 // already-visited Links, and returns a SkipMe when encountering them again.) 85 // 86 // WalkMatching (and the other traversal functions) can be used again again inside the VisitFn! 87 // By using the traversal.Progress handed to the VisitFn, 88 // the Path recorded of the traversal so far will continue to be extended, 89 // and thus continued nested uses of Walk and Focus will see the fully contextualized Path. 90 // 91 // WalkMatching can be configured to run with a Preloader. 92 // When a Preloader is configured, the walk will first do a "preload" pass over the initial, 93 // root tree up to link boundaries and report any links encountered to the preloader. 94 // It will then perform a second pass over the tree, calling the VisitFn where necessary as per normal WalkMatching behavior. 95 // This two-pass operation will continue for each block loaded, allowing the preloader to 96 // potentially asynchronously preload any blocks that are going to be encountered at a future point in the walk. 97 func (prog Progress) WalkMatching(n datamodel.Node, s selector.Selector, fn VisitFn) error { 98 prog.init() 99 return prog.walkBlock(n, s, func(prog Progress, n datamodel.Node, tr VisitReason) error { 100 if tr != VisitReason_SelectionMatch { 101 return nil 102 } 103 return fn(prog, n) 104 }) 105 } 106 107 // WalkLocal is the same as the package-scope function of the same name, 108 // but considers an existing Progress state (and any config it might reference). 109 func (prog Progress) WalkLocal(n datamodel.Node, fn VisitFn) error { 110 if err := prog.checkNodeBudget(); err != nil { 111 return err 112 } 113 114 // Visit the current node. 115 if err := fn(prog, n); err != nil { 116 if _, ok := err.(SkipMe); ok { 117 return nil 118 } 119 return err 120 } 121 // Recurse on nodes with a recursive kind; otherwise just return. 122 switch n.Kind() { 123 case datamodel.Kind_Map: 124 for itr := n.MapIterator(); !itr.Done(); { 125 k, v, err := itr.Next() 126 if err != nil { 127 return err 128 } 129 ks, _ := k.AsString() 130 progNext := prog 131 progNext.Path = prog.Path.AppendSegmentString(ks) 132 if err := progNext.WalkLocal(v, fn); err != nil { 133 return err 134 } 135 } 136 return nil 137 case datamodel.Kind_List: 138 for itr := n.ListIterator(); !itr.Done(); { 139 idx, v, err := itr.Next() 140 if err != nil { 141 return err 142 } 143 progNext := prog 144 progNext.Path = prog.Path.AppendSegmentInt(idx) 145 if err := progNext.WalkLocal(v, fn); err != nil { 146 return err 147 } 148 } 149 return nil 150 default: 151 return nil 152 } 153 } 154 155 // WalkAdv is identical to WalkMatching, except it is called for *all* nodes 156 // visited (not just matching nodes), together with the reason for the visit. 157 // An AdvVisitFn is used instead of a VisitFn, so that the reason can be provided. 158 func (prog Progress) WalkAdv(n datamodel.Node, s selector.Selector, fn AdvVisitFn) error { 159 prog.init() 160 return prog.walkBlock(n, s, fn) 161 } 162 163 // walkBlock anchors a walk at the beginning of the traversal and at the 164 // beginning of each new link traversed. This allows us to do a preload phase if 165 // we have a preloader configured. 166 func (prog Progress) walkBlock(n datamodel.Node, s selector.Selector, visitFn AdvVisitFn) error { 167 ph := phaseTraverse 168 var budget *Budget 169 170 if prog.Cfg.Preloader != nil { 171 ph = phasePreload 172 // preserve the budget so we can reset it for the second pass; it will 173 // likely not correctly apply during the preload phase because it 174 // doesn't descend into links first. But we'll use it anyway as a 175 // best-guess because we have nothing better 176 budget = prog.Budget.Clone() 177 } 178 179 // First pass. 180 err := prog.walkAdv(ph, n, s, visitFn) 181 if err != nil && (ph != phasePreload || !errors.Is(&ErrBudgetExceeded{}, err)) { 182 return err 183 } 184 185 if ph == phasePreload { 186 // First past was a preload; now do the _real_ pass. 187 prog.Budget = budget // reset 188 return prog.walkAdv(phaseTraverse, n, s, visitFn) 189 } 190 191 return nil 192 } 193 194 // walkAdv is the main recursive walk function, called to iterate through 195 // recursive nodes (root node, maps, lists and new link root nodes). 196 func (prog Progress) walkAdv(ph phase, n datamodel.Node, s selector.Selector, visitFn AdvVisitFn) error { 197 if err := prog.checkNodeBudget(); err != nil { 198 return err 199 } 200 201 // If we need to interpret this node in an alternative form, reify and replace. 202 if rn, rs, err := prog.reify(n, s); err != nil { 203 return err 204 } else if rn != nil { 205 n = rn 206 s = rs 207 } 208 209 // Call the visit function if necessary. 210 if err := prog.visit(ph, n, s, visitFn); err != nil { 211 return err 212 } 213 214 // If we're handling scalars (e.g. not maps and lists) we can return now. 215 switch n.Kind() { 216 case datamodel.Kind_Map, datamodel.Kind_List: // continue 217 default: 218 return nil 219 } 220 221 // For maps and lists: recurse (in one of two ways, depending on if the selector also states specific interests). 222 223 haveStartAtPath := prog.Cfg.StartAtPath.Len() > 0 224 var reachedStartAtPath bool 225 recurse := func(v datamodel.Node, ps datamodel.PathSegment) error { 226 // First, make sure we're past the start path; if one is specified. 227 if haveStartAtPath { 228 if reachedStartAtPath { 229 prog.PastStartAtPath = reachedStartAtPath 230 } else if !prog.PastStartAtPath && prog.Path.Len() < prog.Cfg.StartAtPath.Len() { 231 if ps.Equals(prog.Cfg.StartAtPath.Segments()[prog.Path.Len()]) { 232 reachedStartAtPath = true 233 } 234 if !reachedStartAtPath { 235 return nil 236 } 237 } 238 } 239 240 if err := prog.explore(ph, s, n, visitFn, v, ps); err != nil { 241 return err 242 } 243 244 return nil 245 } 246 247 attn := s.Interests() 248 249 if attn == nil { // no specific interests; recurse on all children. 250 for itr := selector.NewSegmentIterator(n); !itr.Done(); { 251 ps, v, err := itr.Next() 252 if err != nil { 253 return err 254 } 255 if err := recurse(v, ps); err != nil { 256 return err 257 } 258 } 259 260 return nil 261 } 262 263 if len(attn) == 0 { // nothing to see here 264 return nil 265 } 266 267 // specific interests, recurse on those. 268 for _, ps := range attn { 269 if v, err := n.LookupBySegment(ps); err != nil { 270 continue 271 } else if err := recurse(v, ps); err != nil { 272 return err 273 } 274 } 275 276 return nil 277 } 278 279 func (prog Progress) checkNodeBudget() error { 280 if prog.Budget != nil { 281 if prog.Budget.NodeBudget <= 0 { 282 return &ErrBudgetExceeded{BudgetKind: "node", Path: prog.Path} 283 } 284 prog.Budget.NodeBudget-- 285 } 286 return nil 287 } 288 289 func (prog Progress) checkLinkBudget(lnk datamodel.Link) error { 290 if prog.Budget != nil { 291 if prog.Budget.LinkBudget <= 0 { 292 return &ErrBudgetExceeded{BudgetKind: "link", Path: prog.Path, Link: lnk} 293 } 294 prog.Budget.LinkBudget-- 295 } 296 return nil 297 } 298 299 func (prog Progress) reify(n datamodel.Node, s selector.Selector) (datamodel.Node, selector.Selector, error) { 300 // refiy the node if advised. 301 if rs, ok := s.(selector.Reifiable); ok { 302 adl := rs.NamedReifier() 303 if prog.Cfg.LinkSystem.KnownReifiers == nil { 304 return nil, nil, fmt.Errorf("adl requested but not supported by link system: %q", adl) 305 } 306 307 reifier, ok := prog.Cfg.LinkSystem.KnownReifiers[adl] 308 if !ok { 309 return nil, nil, fmt.Errorf("unregistered adl requested: %q", adl) 310 } 311 312 rn, err := reifier(linking.LinkContext{ 313 Ctx: prog.Cfg.Ctx, 314 LinkPath: prog.Path, 315 }, n, &prog.Cfg.LinkSystem) 316 if err != nil { 317 return nil, nil, fmt.Errorf("failed to reify node as %q: %w", adl, err) 318 } 319 320 // explore into the `InterpretAs` clause to the child selector. 321 s, err = s.Explore(n, datamodel.PathSegment{}) 322 if err != nil { 323 return nil, nil, err 324 } 325 return rn, s, nil 326 } 327 328 return nil, nil, nil 329 } 330 331 // visit calls the visitor if required 332 func (prog Progress) visit(ph phase, n datamodel.Node, s selector.Selector, visitFn AdvVisitFn) error { 333 if ph != phaseTraverse { 334 return nil 335 } 336 337 if !prog.PastStartAtPath && prog.Path.Len() < prog.Cfg.StartAtPath.Len() { 338 return nil 339 } 340 341 // Decide if this node is matched -- do callbacks as appropriate. 342 match, err := s.Match(n) 343 if err != nil { 344 return err 345 } 346 if match != nil { 347 return visitFn(prog, match, VisitReason_SelectionMatch) 348 } 349 return visitFn(prog, n, VisitReason_SelectionCandidate) 350 } 351 352 // explore is called to explore a single node, and recurse into it if necessary, 353 // including loading and recursing into links if the node is a link. 354 func (prog Progress) explore( 355 ph phase, 356 s selector.Selector, 357 n datamodel.Node, 358 visitFn AdvVisitFn, 359 v datamodel.Node, 360 ps datamodel.PathSegment, 361 ) error { 362 sNext, err := s.Explore(n, ps) 363 if err != nil { 364 return err 365 } 366 if sNext == nil { 367 return nil 368 } 369 370 progNext := prog 371 progNext.Path = prog.Path.AppendSegment(ps) 372 373 if v.Kind() != datamodel.Kind_Link { 374 return progNext.walkAdv(ph, v, sNext, visitFn) 375 } 376 377 lnk, _ := v.AsLink() 378 if prog.Cfg.LinkVisitOnlyOnce { 379 if _, seen := prog.SeenLinks[lnk]; seen { 380 return nil 381 } 382 if ph == phaseTraverse { 383 prog.SeenLinks[lnk] = struct{}{} 384 } 385 } 386 387 if ph == phasePreload { 388 if err := prog.checkLinkBudget(lnk); err != nil { 389 return err 390 } 391 pctx := preload.PreloadContext{ 392 Ctx: prog.Cfg.Ctx, 393 BasePath: prog.Path, 394 ParentNode: n, 395 } 396 pl := preload.Link{ 397 Segment: ps, 398 LinkNode: v, 399 Link: lnk, 400 } 401 prog.Cfg.Preloader(pctx, pl) 402 return nil 403 } 404 405 progNext.LastBlock.Path = progNext.Path 406 progNext.LastBlock.Link = lnk 407 408 v, err = progNext.loadLink(lnk, v, n) 409 if err != nil { 410 if _, ok := err.(SkipMe); ok { 411 return nil 412 } 413 return err 414 } 415 416 return progNext.walkBlock(v, sNext, visitFn) 417 } 418 419 // loadLink is called to load a link from the configured LinkSystem with the 420 // appropriate prototype. 421 func (prog Progress) loadLink(lnk datamodel.Link, v datamodel.Node, parent datamodel.Node) (datamodel.Node, error) { 422 if err := prog.checkLinkBudget(lnk); err != nil { 423 return nil, err 424 } 425 // Put together the context info we'll offer to the loader and prototypeChooser. 426 lnkCtx := linking.LinkContext{ 427 Ctx: prog.Cfg.Ctx, 428 LinkPath: prog.Path, 429 LinkNode: v, 430 ParentNode: parent, 431 } 432 // Pick what in-memory format we will build. 433 np, err := prog.Cfg.LinkTargetNodePrototypeChooser(lnk, lnkCtx) 434 if err != nil { 435 return nil, fmt.Errorf("error traversing node at %q: could not load link %q: %w", prog.Path, lnk, err) 436 } 437 // Load link! 438 n, err := prog.Cfg.LinkSystem.Load(lnkCtx, lnk, np) 439 if err != nil { 440 if _, ok := err.(SkipMe); ok { 441 return nil, err 442 } 443 return nil, fmt.Errorf("error traversing node at %q: could not load link %q: %w", prog.Path, lnk, err) 444 } 445 return n, nil 446 } 447 448 // WalkTransforming walks a graph of Nodes, deciding which to alter by applying a Selector, 449 // and calls the given TransformFn to decide what new node to replace the visited node with. 450 // A new Node tree will be returned (the original is unchanged). 451 // 452 // If the TransformFn returns the same Node which it was called with, 453 // then the transform is a no-op; if every visited node is a no-op, 454 // then the root node returned from the walk as a whole will also be 455 // the same as its starting Node (no new memory will be used). 456 // 457 // When a Node is replaced, no further recursion of this walk will occur on its contents. 458 // (You can certainly do a additional traversals, including transforms, 459 // from inside the TransformFn while building the replacement node.) 460 // 461 // The prototype (that is, implementation) of Node returned will be the same as the 462 // prototype of the Nodes at the same positions in the existing tree 463 // (literally, builders used to construct any new needed intermediate nodes 464 // are chosen by asking the existing nodes about their prototype). 465 func (prog Progress) WalkTransforming(n datamodel.Node, s selector.Selector, fn TransformFn) (datamodel.Node, error) { 466 prog.init() 467 return prog.walkTransforming(n, s, fn) 468 } 469 470 func (prog Progress) walkTransforming(n datamodel.Node, s selector.Selector, fn TransformFn) (datamodel.Node, error) { 471 if err := prog.checkNodeBudget(); err != nil { 472 return nil, err 473 } 474 475 if rn, rs, err := prog.reify(n, s); err != nil { 476 return nil, err 477 } else if rn != nil { 478 n = rn 479 s = rs 480 } 481 482 // Decide if this node is matched -- do callbacks as appropriate. 483 if s.Decide(n) { 484 new_n, err := fn(prog, n) 485 if err != nil { 486 return nil, err 487 } 488 if new_n != n { 489 // don't continue on transformed subtrees 490 return new_n, nil 491 } 492 } 493 494 // If we're handling scalars (e.g. not maps and lists) we can return now. 495 nk := n.Kind() 496 switch nk { 497 case datamodel.Kind_List: 498 return prog.walk_transform_iterateList(n, s, fn, s.Interests()) 499 case datamodel.Kind_Map: 500 return prog.walk_transform_iterateMap(n, s, fn, s.Interests()) 501 default: 502 return n, nil 503 } 504 } 505 506 func contains(interest []datamodel.PathSegment, candidate datamodel.PathSegment) bool { 507 for _, i := range interest { 508 if i == candidate { 509 return true 510 } 511 } 512 return false 513 } 514 515 func (prog Progress) walk_transform_iterateList(n datamodel.Node, s selector.Selector, fn TransformFn, attn []datamodel.PathSegment) (datamodel.Node, error) { 516 bldr := n.Prototype().NewBuilder() 517 lstBldr, err := bldr.BeginList(n.Length()) 518 if err != nil { 519 return nil, err 520 } 521 for itr := selector.NewSegmentIterator(n); !itr.Done(); { 522 ps, v, err := itr.Next() 523 if err != nil { 524 return nil, err 525 } 526 if attn == nil || contains(attn, ps) { 527 sNext, err := s.Explore(n, ps) 528 if err != nil { 529 return nil, err 530 } 531 if sNext != nil { 532 progNext := prog 533 progNext.Path = prog.Path.AppendSegment(ps) 534 if v.Kind() == datamodel.Kind_Link { 535 lnk, _ := v.AsLink() 536 if prog.Cfg.LinkVisitOnlyOnce { 537 if _, seen := prog.SeenLinks[lnk]; seen { 538 continue 539 } 540 prog.SeenLinks[lnk] = struct{}{} 541 } 542 progNext.LastBlock.Path = progNext.Path 543 progNext.LastBlock.Link = lnk 544 v, err = progNext.loadLink(lnk, v, n) 545 if err != nil { 546 if _, ok := err.(SkipMe); ok { 547 continue 548 } 549 return nil, err 550 } 551 } 552 553 next, err := progNext.WalkTransforming(v, sNext, fn) 554 if err != nil { 555 return nil, err 556 } 557 if err := lstBldr.AssembleValue().AssignNode(next); err != nil { 558 return nil, err 559 } 560 } else { 561 if err := lstBldr.AssembleValue().AssignNode(v); err != nil { 562 return nil, err 563 } 564 } 565 } else { 566 if err := lstBldr.AssembleValue().AssignNode(v); err != nil { 567 return nil, err 568 } 569 } 570 } 571 if err := lstBldr.Finish(); err != nil { 572 return nil, err 573 } 574 return bldr.Build(), nil 575 } 576 577 func (prog Progress) walk_transform_iterateMap(n datamodel.Node, s selector.Selector, fn TransformFn, attn []datamodel.PathSegment) (datamodel.Node, error) { 578 bldr := n.Prototype().NewBuilder() 579 mapBldr, err := bldr.BeginMap(n.Length()) 580 if err != nil { 581 return nil, err 582 } 583 584 for itr := selector.NewSegmentIterator(n); !itr.Done(); { 585 ps, v, err := itr.Next() 586 if err != nil { 587 return nil, err 588 } 589 if err := mapBldr.AssembleKey().AssignString(ps.String()); err != nil { 590 return nil, err 591 } 592 593 if attn == nil || contains(attn, ps) { 594 sNext, err := s.Explore(n, ps) 595 if err != nil { 596 return nil, err 597 } 598 if sNext != nil { 599 progNext := prog 600 progNext.Path = prog.Path.AppendSegment(ps) 601 if v.Kind() == datamodel.Kind_Link { 602 lnk, _ := v.AsLink() 603 if prog.Cfg.LinkVisitOnlyOnce { 604 if _, seen := prog.SeenLinks[lnk]; seen { 605 continue 606 } 607 prog.SeenLinks[lnk] = struct{}{} 608 } 609 progNext.LastBlock.Path = progNext.Path 610 progNext.LastBlock.Link = lnk 611 v, err = progNext.loadLink(lnk, v, n) 612 if err != nil { 613 if _, ok := err.(SkipMe); ok { 614 continue 615 } 616 return nil, err 617 } 618 } 619 620 next, err := progNext.WalkTransforming(v, sNext, fn) 621 if err != nil { 622 return nil, err 623 } 624 if err := mapBldr.AssembleValue().AssignNode(next); err != nil { 625 return nil, err 626 } 627 } else { 628 if err := mapBldr.AssembleValue().AssignNode(v); err != nil { 629 return nil, err 630 } 631 } 632 } else { 633 if err := mapBldr.AssembleValue().AssignNode(v); err != nil { 634 return nil, err 635 } 636 } 637 } 638 if err := mapBldr.Finish(); err != nil { 639 return nil, err 640 } 641 return bldr.Build(), nil 642 }