From ac40e5e85b645c5288a10e13aaefd5a9604d4cac Mon Sep 17 00:00:00 2001 From: Alfonso de la Rocha Date: Tue, 17 Aug 2021 08:50:11 +0200 Subject: [PATCH 1/3] stopAt simple testcase --- traversal/stopAt_test.go | 150 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 traversal/stopAt_test.go diff --git a/traversal/stopAt_test.go b/traversal/stopAt_test.go new file mode 100644 index 00000000..a8661968 --- /dev/null +++ b/traversal/stopAt_test.go @@ -0,0 +1,150 @@ +package traversal_test + +import ( + "fmt" + "testing" + + . "github.com/warpfork/go-wish" + + "github.com/ipld/go-ipld-prime" + _ "github.com/ipld/go-ipld-prime/codec/dagjson" + "github.com/ipld/go-ipld-prime/fluent" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/ipld/go-ipld-prime/traversal" + "github.com/ipld/go-ipld-prime/traversal/selector" + "github.com/ipld/go-ipld-prime/traversal/selector/builder" +) + +/* Remember, we've got the following fixtures in scope: +var ( + leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) + leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) + middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 3, func(na fluent.MapAssembler) { + na.AssembleEntry("foo").AssignBool(true) + na.AssembleEntry("bar").AssignBool(false) + na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { + na.AssembleEntry("alink").AssignLink(leafAlphaLnk) + na.AssembleEntry("nonlink").AssignString("zoo") + }) + })) + middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafBetaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + })) + rootNode, rootNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("plain").AssignString("olde string") + na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) + na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) + na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) + })) +) +*/ + +// covers traverse using a variety of selectors. +// all cases here use one already-loaded Node; no link-loading exercised. + +func ExploreRecursiveWithStop(limit selector.RecursionLimit, sequence builder.SelectorSpec, stopLnk ipld.Link) ipld.Node { + np := basicnode.Prototype__Map{} + return fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { + // RecursionLimit + na.AssembleEntry(selector.SelectorKey_ExploreRecursive).CreateMap(3, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) { + switch limit.Mode() { + case selector.RecursionLimit_Depth: + na.AssembleEntry(selector.SelectorKey_LimitDepth).AssignInt(limit.Depth()) + case selector.RecursionLimit_None: + na.AssembleEntry(selector.SelectorKey_LimitNone).CreateMap(0, func(na fluent.MapAssembler) {}) + default: + panic("Unsupported recursion limit type") + } + }) + // Sequence + na.AssembleEntry(selector.SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_ExploreUnion).CreateList(2, func(na fluent.ListAssembler) { + na.AssembleValue().AssignNode(fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {}) + })) + na.AssembleValue().AssignNode(sequence.Node()) + }) + }) + + // Stop condition + if stopLnk != nil { + cond := fluent.MustBuildMap(basicnode.Prototype__Map{}, 1, func(na fluent.MapAssembler) { + na.AssembleEntry(string(selector.ConditionMode_Link)).AssignLink(stopLnk) + }) + na.AssembleEntry(selector.SelectorKey_StopAt).AssignNode(cond) + } + }) + }) + +} + +func TestStopAt(t *testing.T) { + ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype__Any{}) + t.Run("test stop at", func(t *testing.T) { + s, err := selector.CompileSelector(ExploreRecursiveWithStop(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()), leafAlphaLnk)) + + /* NOTE: ExploreRecursive that can be used for testing purposes + s, err := ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreUnion( + ssb.Matcher(), + ssb.ExploreAll(ssb.ExploreRecursiveEdge())), + ).Selector() + */ + var order int + lsys := cidlink.DefaultLinkSystem() + lsys.StorageReadOpener = (&store).OpenRead + err = traversal.Progress{ + Cfg: &traversal.Config{ + LinkSystem: lsys, + LinkTargetNodePrototypeChooser: basicnode.Chooser, + }, + }.WalkMatching(rootNode, s, func(prog traversal.Progress, n ipld.Node) error { + fmt.Println("Order", order, prog.Path.String()) + switch order { + case 0: + // Root + Wish(t, prog.Path.String(), ShouldEqual, "") + case 1: + Wish(t, prog.Path.String(), ShouldEqual, "plain") + Wish(t, n, ShouldEqual, basicnode.NewString("olde string")) + case 2: + Wish(t, prog.Path.String(), ShouldEqual, "linkedString") + /* + case 2: + Wish(t, n, ShouldEqual, basicnode.NewString("beta")) + Wish(t, prog.Path.String(), ShouldEqual, "linkedList/2") + Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedList/2") + Wish(t, prog.LastBlock.Link.String(), ShouldEqual, leafBetaLnk.String()) + case 3: + Wish(t, n, ShouldEqual, basicnode.NewString("alpha")) + Wish(t, prog.Path.String(), ShouldEqual, "linkedList/3") + Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedList/3") + Wish(t, prog.LastBlock.Link.String(), ShouldEqual, leafAlphaLnk.String()) + case 4: + Wish(t, n, ShouldEqual, basicnode.NewBool(true)) + Wish(t, prog.Path.String(), ShouldEqual, "linkedMap/foo") + Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedMap") + Wish(t, prog.LastBlock.Link.String(), ShouldEqual, middleMapNodeLnk.String()) + case 5: + Wish(t, n, ShouldEqual, basicnode.NewString("zoo")) + Wish(t, prog.Path.String(), ShouldEqual, "linkedMap/nested/nonlink") + Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedMap") + Wish(t, prog.LastBlock.Link.String(), ShouldEqual, middleMapNodeLnk.String()) + case 6: + Wish(t, n, ShouldEqual, basicnode.NewString("alpha")) + Wish(t, prog.Path.String(), ShouldEqual, "linkedMap/nested/alink") + Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedMap/nested/alink") + Wish(t, prog.LastBlock.Link.String(), ShouldEqual, leafAlphaLnk.String()) + */ + } + order++ + return nil + }) + Wish(t, err, ShouldEqual, nil) + Wish(t, order, ShouldEqual, 7) + }) +} From 6bb6d8b2afce98c1ff7be36d4ac09d1e885e5058 Mon Sep 17 00:00:00 2001 From: Alfonso de la Rocha Date: Wed, 18 Aug 2021 09:58:04 +0200 Subject: [PATCH 2/3] fixed stopAt in ExploreRecursive. Added tests --- traversal/selector/exploreRecursive.go | 10 +- traversal/stopAt_test.go | 150 ---------------- traversal/walk_with_stop_test.go | 240 +++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 152 deletions(-) delete mode 100644 traversal/stopAt_test.go create mode 100644 traversal/walk_with_stop_test.go diff --git a/traversal/selector/exploreRecursive.go b/traversal/selector/exploreRecursive.go index 83b076e4..713ae5fe 100644 --- a/traversal/selector/exploreRecursive.go +++ b/traversal/selector/exploreRecursive.go @@ -87,8 +87,14 @@ func (s ExploreRecursive) Interests() []ipld.PathSegment { // Explore returns the node's selector for all fields func (s ExploreRecursive) Explore(n ipld.Node, p ipld.PathSegment) Selector { - if s.stopAt != nil && s.stopAt.Match(n) { - return nil + if s.stopAt != nil { + target, err := n.LookupBySegment(p) + if err != nil { + panic(err) // oh dear + } + if s.stopAt.Match(target) { + return nil + } } nextSelector := s.current.Explore(n, p) diff --git a/traversal/stopAt_test.go b/traversal/stopAt_test.go deleted file mode 100644 index a8661968..00000000 --- a/traversal/stopAt_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package traversal_test - -import ( - "fmt" - "testing" - - . "github.com/warpfork/go-wish" - - "github.com/ipld/go-ipld-prime" - _ "github.com/ipld/go-ipld-prime/codec/dagjson" - "github.com/ipld/go-ipld-prime/fluent" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - basicnode "github.com/ipld/go-ipld-prime/node/basic" - "github.com/ipld/go-ipld-prime/traversal" - "github.com/ipld/go-ipld-prime/traversal/selector" - "github.com/ipld/go-ipld-prime/traversal/selector/builder" -) - -/* Remember, we've got the following fixtures in scope: -var ( - leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) - leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) - middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 3, func(na fluent.MapAssembler) { - na.AssembleEntry("foo").AssignBool(true) - na.AssembleEntry("bar").AssignBool(false) - na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { - na.AssembleEntry("alink").AssignLink(leafAlphaLnk) - na.AssembleEntry("nonlink").AssignString("zoo") - }) - })) - middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { - na.AssembleValue().AssignLink(leafAlphaLnk) - na.AssembleValue().AssignLink(leafAlphaLnk) - na.AssembleValue().AssignLink(leafBetaLnk) - na.AssembleValue().AssignLink(leafAlphaLnk) - })) - rootNode, rootNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { - na.AssembleEntry("plain").AssignString("olde string") - na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) - na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) - na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) - })) -) -*/ - -// covers traverse using a variety of selectors. -// all cases here use one already-loaded Node; no link-loading exercised. - -func ExploreRecursiveWithStop(limit selector.RecursionLimit, sequence builder.SelectorSpec, stopLnk ipld.Link) ipld.Node { - np := basicnode.Prototype__Map{} - return fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { - // RecursionLimit - na.AssembleEntry(selector.SelectorKey_ExploreRecursive).CreateMap(3, func(na fluent.MapAssembler) { - na.AssembleEntry(selector.SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) { - switch limit.Mode() { - case selector.RecursionLimit_Depth: - na.AssembleEntry(selector.SelectorKey_LimitDepth).AssignInt(limit.Depth()) - case selector.RecursionLimit_None: - na.AssembleEntry(selector.SelectorKey_LimitNone).CreateMap(0, func(na fluent.MapAssembler) {}) - default: - panic("Unsupported recursion limit type") - } - }) - // Sequence - na.AssembleEntry(selector.SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) { - na.AssembleEntry(selector.SelectorKey_ExploreUnion).CreateList(2, func(na fluent.ListAssembler) { - na.AssembleValue().AssignNode(fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { - na.AssembleEntry(selector.SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {}) - })) - na.AssembleValue().AssignNode(sequence.Node()) - }) - }) - - // Stop condition - if stopLnk != nil { - cond := fluent.MustBuildMap(basicnode.Prototype__Map{}, 1, func(na fluent.MapAssembler) { - na.AssembleEntry(string(selector.ConditionMode_Link)).AssignLink(stopLnk) - }) - na.AssembleEntry(selector.SelectorKey_StopAt).AssignNode(cond) - } - }) - }) - -} - -func TestStopAt(t *testing.T) { - ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype__Any{}) - t.Run("test stop at", func(t *testing.T) { - s, err := selector.CompileSelector(ExploreRecursiveWithStop(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()), leafAlphaLnk)) - - /* NOTE: ExploreRecursive that can be used for testing purposes - s, err := ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreUnion( - ssb.Matcher(), - ssb.ExploreAll(ssb.ExploreRecursiveEdge())), - ).Selector() - */ - var order int - lsys := cidlink.DefaultLinkSystem() - lsys.StorageReadOpener = (&store).OpenRead - err = traversal.Progress{ - Cfg: &traversal.Config{ - LinkSystem: lsys, - LinkTargetNodePrototypeChooser: basicnode.Chooser, - }, - }.WalkMatching(rootNode, s, func(prog traversal.Progress, n ipld.Node) error { - fmt.Println("Order", order, prog.Path.String()) - switch order { - case 0: - // Root - Wish(t, prog.Path.String(), ShouldEqual, "") - case 1: - Wish(t, prog.Path.String(), ShouldEqual, "plain") - Wish(t, n, ShouldEqual, basicnode.NewString("olde string")) - case 2: - Wish(t, prog.Path.String(), ShouldEqual, "linkedString") - /* - case 2: - Wish(t, n, ShouldEqual, basicnode.NewString("beta")) - Wish(t, prog.Path.String(), ShouldEqual, "linkedList/2") - Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedList/2") - Wish(t, prog.LastBlock.Link.String(), ShouldEqual, leafBetaLnk.String()) - case 3: - Wish(t, n, ShouldEqual, basicnode.NewString("alpha")) - Wish(t, prog.Path.String(), ShouldEqual, "linkedList/3") - Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedList/3") - Wish(t, prog.LastBlock.Link.String(), ShouldEqual, leafAlphaLnk.String()) - case 4: - Wish(t, n, ShouldEqual, basicnode.NewBool(true)) - Wish(t, prog.Path.String(), ShouldEqual, "linkedMap/foo") - Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedMap") - Wish(t, prog.LastBlock.Link.String(), ShouldEqual, middleMapNodeLnk.String()) - case 5: - Wish(t, n, ShouldEqual, basicnode.NewString("zoo")) - Wish(t, prog.Path.String(), ShouldEqual, "linkedMap/nested/nonlink") - Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedMap") - Wish(t, prog.LastBlock.Link.String(), ShouldEqual, middleMapNodeLnk.String()) - case 6: - Wish(t, n, ShouldEqual, basicnode.NewString("alpha")) - Wish(t, prog.Path.String(), ShouldEqual, "linkedMap/nested/alink") - Wish(t, prog.LastBlock.Path.String(), ShouldEqual, "linkedMap/nested/alink") - Wish(t, prog.LastBlock.Link.String(), ShouldEqual, leafAlphaLnk.String()) - */ - } - order++ - return nil - }) - Wish(t, err, ShouldEqual, nil) - Wish(t, order, ShouldEqual, 7) - }) -} diff --git a/traversal/walk_with_stop_test.go b/traversal/walk_with_stop_test.go new file mode 100644 index 00000000..1a41dab7 --- /dev/null +++ b/traversal/walk_with_stop_test.go @@ -0,0 +1,240 @@ +package traversal_test + +import ( + "fmt" + "testing" + + . "github.com/warpfork/go-wish" + + "github.com/ipld/go-ipld-prime" + _ "github.com/ipld/go-ipld-prime/codec/dagjson" + "github.com/ipld/go-ipld-prime/fluent" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/ipld/go-ipld-prime/traversal" + "github.com/ipld/go-ipld-prime/traversal/selector" + "github.com/ipld/go-ipld-prime/traversal/selector/builder" +) + +/* Remember, we've got the following fixtures in scope: +var ( + leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) + leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) + middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 3, func(na fluent.MapAssembler) { + na.AssembleEntry("foo").AssignBool(true) + na.AssembleEntry("bar").AssignBool(false) + na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { + na.AssembleEntry("alink").AssignLink(leafAlphaLnk) + na.AssembleEntry("nonlink").AssignString("zoo") + }) + })) + middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafBetaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + })) + rootNode, rootNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("plain").AssignString("olde string") + na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) + na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) + na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) + })) +) +*/ + +// ExploreRecursiveWithStop builds a recursive selector node with a stop condition +func ExploreRecursiveWithStop(limit selector.RecursionLimit, sequence builder.SelectorSpec, stopLnk ipld.Link) ipld.Node { + np := basicnode.Prototype__Map{} + return fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { + // RecursionLimit + na.AssembleEntry(selector.SelectorKey_ExploreRecursive).CreateMap(3, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) { + switch limit.Mode() { + case selector.RecursionLimit_Depth: + na.AssembleEntry(selector.SelectorKey_LimitDepth).AssignInt(limit.Depth()) + case selector.RecursionLimit_None: + na.AssembleEntry(selector.SelectorKey_LimitNone).CreateMap(0, func(na fluent.MapAssembler) {}) + default: + panic("Unsupported recursion limit type") + } + }) + // Sequence + na.AssembleEntry(selector.SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_ExploreUnion).CreateList(2, func(na fluent.ListAssembler) { + na.AssembleValue().AssignNode(fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {}) + })) + na.AssembleValue().AssignNode(sequence.Node()) + }) + }) + + // Stop condition + if stopLnk != nil { + cond := fluent.MustBuildMap(basicnode.Prototype__Map{}, 1, func(na fluent.MapAssembler) { + na.AssembleEntry(string(selector.ConditionMode_Link)).AssignLink(stopLnk) + }) + na.AssembleEntry(selector.SelectorKey_StopAt).AssignNode(cond) + } + }) + }) + +} + +func TestStopAtLink(t *testing.T) { + ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype__Any{}) + t.Run("test ExploreRecursive stopAt with simple node", func(t *testing.T) { + // Selector that passes through the map + s, err := selector.CompileSelector(ExploreRecursiveWithStop( + selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()), + middleMapNodeLnk)) + if err != nil { + t.Fatal(err) + } + var order int + lsys := cidlink.DefaultLinkSystem() + lsys.StorageReadOpener = (&store).OpenRead + err = traversal.Progress{ + Cfg: &traversal.Config{ + LinkSystem: lsys, + LinkTargetNodePrototypeChooser: basicnode.Chooser, + }, + }.WalkMatching(rootNode, s, func(prog traversal.Progress, n ipld.Node) error { + // fmt.Println("Order", order, prog.Path.String()) + switch order { + case 0: + // Root + Wish(t, prog.Path.String(), ShouldEqual, "") + case 1: + Wish(t, prog.Path.String(), ShouldEqual, "plain") + Wish(t, n, ShouldEqual, basicnode.NewString("olde string")) + case 2: + Wish(t, prog.Path.String(), ShouldEqual, "linkedString") + case 3: + Wish(t, prog.Path.String(), ShouldEqual, "linkedList") + // We are starting to traverse the linkedList, we passed through the map already + case 4: + Wish(t, prog.Path.String(), ShouldEqual, "linkedList/0") + } + order++ + return nil + }) + Wish(t, err, ShouldEqual, nil) + Wish(t, order, ShouldEqual, 8) + }) +} + +// mkChain creates a DAG that represent a chain of subDAGs. +// The stopAt condition is extremely appealing for these use cases, as we can +// partially sync a chain using ExploreRecursive without having to sync the +// chain from scratch if we are already partially synced +func mkChain() (ipld.Node, []ipld.Link) { + leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) + leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) + middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 3, func(na fluent.MapAssembler) { + na.AssembleEntry("foo").AssignBool(true) + na.AssembleEntry("bar").AssignBool(false) + na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { + na.AssembleEntry("alink").AssignLink(leafAlphaLnk) + na.AssembleEntry("nonlink").AssignString("zoo") + }) + })) + middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafBetaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + })) + + _, ch1Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) + })) + _, ch2Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) + na.AssembleEntry("ch1").AssignLink(ch1Lnk) + })) + _, ch3Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) + na.AssembleEntry("ch2").AssignLink(ch2Lnk) + })) + + headNode, headLnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("plain").AssignString("olde string") + na.AssembleEntry("ch3").AssignLink(ch3Lnk) + })) + return headNode, []ipld.Link{headLnk, ch3Lnk, ch2Lnk, ch1Lnk} +} + +func TestStopInChain(t *testing.T) { + chainNode, chainLnks := mkChain() + // Stay in head + stopAtInChainTest(t, chainNode, chainLnks[1], 2) + // Get head and following block + stopAtInChainTest(t, chainNode, chainLnks[2], 4) + // One more + stopAtInChainTest(t, chainNode, chainLnks[3], 11) + // Get the full chain + stopAtInChainTest(t, chainNode, nil, 17) +} + +func stopAtInChainTest(t *testing.T, chainNode ipld.Node, stopLnk ipld.Link, numSeen int) { + ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype__Any{}) + t.Run(fmt.Sprintf("test ExploreRecursive stopAt in chain with stoplink: %s", stopLnk), func(t *testing.T) { + s, err := selector.CompileSelector(ExploreRecursiveWithStop( + selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()), + stopLnk)) + if err != nil { + t.Fatal(err) + } + + var order int + lsys := cidlink.DefaultLinkSystem() + lsys.StorageReadOpener = (&store).OpenRead + err = traversal.Progress{ + Cfg: &traversal.Config{ + LinkSystem: lsys, + LinkTargetNodePrototypeChooser: basicnode.Chooser, + }, + }.WalkMatching(chainNode, s, func(prog traversal.Progress, n ipld.Node) error { + //fmt.Println("Order", order, prog.Path.String()) + switch order { + case 0: + // Root + Wish(t, prog.Path.String(), ShouldEqual, "") + case 1: + Wish(t, prog.Path.String(), ShouldEqual, "plain") + Wish(t, n, ShouldEqual, basicnode.NewString("olde string")) + case 2: + Wish(t, prog.Path.String(), ShouldEqual, "ch3") + case 3: + if numSeen > 4 { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2") + } else { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/linkedString") + } + case 4: + if numSeen > 11 { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/ch1") + } else { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/linkedMap") + } + case 5: + if numSeen > 11 { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/ch1/linkedList") + } else { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/linkedMap/bar") + } + case 10: + if numSeen > 11 { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/linkedMap") + } else { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/linkedString") + } + } + order++ + return nil + }) + Wish(t, err, ShouldEqual, nil) + Wish(t, order, ShouldEqual, numSeen) + }) +} From edb58fba48c909b605fa8d2ae69b5f670d16a5bd Mon Sep 17 00:00:00 2001 From: Alfonso de la Rocha Date: Wed, 18 Aug 2021 17:31:54 +0200 Subject: [PATCH 3/3] return error in Explore() of selector interface --- traversal/selector/exploreAll.go | 4 +- traversal/selector/exploreFields.go | 4 +- traversal/selector/exploreIndex.go | 8 +-- traversal/selector/exploreIndex_test.go | 8 +-- traversal/selector/exploreRange.go | 10 ++-- traversal/selector/exploreRange_test.go | 8 +-- traversal/selector/exploreRecursive.go | 18 +++--- traversal/selector/exploreRecursiveEdge.go | 2 +- traversal/selector/exploreRecursive_test.go | 62 ++++++++++----------- traversal/selector/exploreUnion.go | 13 +++-- traversal/selector/exploreUnion_test.go | 6 +- traversal/selector/matcher.go | 4 +- traversal/selector/selector.go | 4 +- traversal/walk.go | 10 +++- 14 files changed, 85 insertions(+), 76 deletions(-) diff --git a/traversal/selector/exploreAll.go b/traversal/selector/exploreAll.go index 55cb0ea5..056a7b57 100644 --- a/traversal/selector/exploreAll.go +++ b/traversal/selector/exploreAll.go @@ -18,8 +18,8 @@ func (s ExploreAll) Interests() []ipld.PathSegment { } // Explore returns the node's selector for all fields -func (s ExploreAll) Explore(n ipld.Node, p ipld.PathSegment) Selector { - return s.next +func (s ExploreAll) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { + return s.next, nil } // Decide always returns false because this is not a matcher diff --git a/traversal/selector/exploreFields.go b/traversal/selector/exploreFields.go index 1705734a..b3884847 100644 --- a/traversal/selector/exploreFields.go +++ b/traversal/selector/exploreFields.go @@ -29,8 +29,8 @@ func (s ExploreFields) Interests() []ipld.PathSegment { // Explore returns the selector for the given path if it is a field in // the selector node or nil if not -func (s ExploreFields) Explore(n ipld.Node, p ipld.PathSegment) Selector { - return s.selections[p.String()] +func (s ExploreFields) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { + return s.selections[p.String()], nil } // Decide always returns false because this is not a matcher diff --git a/traversal/selector/exploreIndex.go b/traversal/selector/exploreIndex.go index 81b40510..fc4db961 100644 --- a/traversal/selector/exploreIndex.go +++ b/traversal/selector/exploreIndex.go @@ -20,16 +20,16 @@ func (s ExploreIndex) Interests() []ipld.PathSegment { // Explore returns the node's selector if // the path matches the index for this selector or nil if not -func (s ExploreIndex) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreIndex) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { if n.Kind() != ipld.Kind_List { - return nil + return nil, nil } expectedIndex, expectedErr := p.Index() actualIndex, actualErr := s.interest[0].Index() if expectedErr != nil || actualErr != nil || expectedIndex != actualIndex { - return nil + return nil, nil } - return s.next + return s.next, nil } // Decide always returns false because this is not a matcher diff --git a/traversal/selector/exploreIndex_test.go b/traversal/selector/exploreIndex_test.go index e665367e..01ceb19b 100644 --- a/traversal/selector/exploreIndex_test.go +++ b/traversal/selector/exploreIndex_test.go @@ -68,7 +68,7 @@ func TestExploreIndexExplore(t *testing.T) { s := ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(3)}} t.Run("exploring should return nil unless node is a list", func(t *testing.T) { n := fluent.MustBuildMap(basicnode.Prototype__Map{}, 0, func(na fluent.MapAssembler) {}) - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, nil) }) n := fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { @@ -78,15 +78,15 @@ func TestExploreIndexExplore(t *testing.T) { na.AssembleValue().AssignInt(3) }) t.Run("exploring should return nil when given a path segment with a different index", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(2)) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("exploring should return nil when given a path segment that isn't an index", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfString("cheese")) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfString("cheese")) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("exploring should return the next selector when given a path segment with the right index", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, Matcher{}) }) } diff --git a/traversal/selector/exploreRange.go b/traversal/selector/exploreRange.go index 17e33054..66bd56fb 100644 --- a/traversal/selector/exploreRange.go +++ b/traversal/selector/exploreRange.go @@ -22,18 +22,18 @@ func (s ExploreRange) Interests() []ipld.PathSegment { // Explore returns the node's selector if // the path matches an index in the range of this selector -func (s ExploreRange) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreRange) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { if n.Kind() != ipld.Kind_List { - return nil + return nil, nil } index, err := p.Index() if err != nil { - return nil + return nil, nil } if index < s.start || index >= s.end { - return nil + return nil, nil } - return s.next + return s.next, nil } // Decide always returns false because this is not a matcher diff --git a/traversal/selector/exploreRange_test.go b/traversal/selector/exploreRange_test.go index c1df770d..17a3cdfa 100644 --- a/traversal/selector/exploreRange_test.go +++ b/traversal/selector/exploreRange_test.go @@ -106,7 +106,7 @@ func TestExploreRangeExplore(t *testing.T) { s := ExploreRange{Matcher{}, 3, 4, []ipld.PathSegment{ipld.PathSegmentOfInt(3)}} t.Run("exploring should return nil unless node is a list", func(t *testing.T) { n := fluent.MustBuildMap(basicnode.Prototype__Map{}, 0, func(na fluent.MapAssembler) {}) - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, nil) }) n := fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { @@ -116,15 +116,15 @@ func TestExploreRangeExplore(t *testing.T) { na.AssembleValue().AssignInt(3) }) t.Run("exploring should return nil when given a path segment out of range", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(2)) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("exploring should return nil when given a path segment that isn't an index", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfString("cheese")) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfString("cheese")) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("exploring should return the next selector when given a path segment with index in range", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, Matcher{}) }) } diff --git a/traversal/selector/exploreRecursive.go b/traversal/selector/exploreRecursive.go index 713ae5fe..6d69aeed 100644 --- a/traversal/selector/exploreRecursive.go +++ b/traversal/selector/exploreRecursive.go @@ -86,34 +86,34 @@ func (s ExploreRecursive) Interests() []ipld.PathSegment { } // Explore returns the node's selector for all fields -func (s ExploreRecursive) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreRecursive) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { if s.stopAt != nil { target, err := n.LookupBySegment(p) if err != nil { - panic(err) // oh dear + return nil, err } if s.stopAt.Match(target) { - return nil + return nil, nil } } - nextSelector := s.current.Explore(n, p) + nextSelector, _ := s.current.Explore(n, p) limit := s.limit if nextSelector == nil { - return nil + return nil, nil } if !s.hasRecursiveEdge(nextSelector) { - return ExploreRecursive{s.sequence, nextSelector, limit, s.stopAt} + return ExploreRecursive{s.sequence, nextSelector, limit, s.stopAt}, nil } switch limit.mode { case RecursionLimit_Depth: if limit.depth < 2 { - return s.replaceRecursiveEdge(nextSelector, nil) + return s.replaceRecursiveEdge(nextSelector, nil), nil } - return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), RecursionLimit{RecursionLimit_Depth, limit.depth - 1}, s.stopAt} + return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), RecursionLimit{RecursionLimit_Depth, limit.depth - 1}, s.stopAt}, nil case RecursionLimit_None: - return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), limit, s.stopAt} + return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), limit, s.stopAt}, nil default: panic("Unsupported recursion limit type") } diff --git a/traversal/selector/exploreRecursiveEdge.go b/traversal/selector/exploreRecursiveEdge.go index 79eb8ce7..dacaeab9 100644 --- a/traversal/selector/exploreRecursiveEdge.go +++ b/traversal/selector/exploreRecursiveEdge.go @@ -22,7 +22,7 @@ func (s ExploreRecursiveEdge) Interests() []ipld.PathSegment { } // Explore should ultimately never get called for an ExploreRecursiveEdge selector -func (s ExploreRecursiveEdge) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreRecursiveEdge) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { panic("Traversed Explore Recursive Edge Node With No Parent") } diff --git a/traversal/selector/exploreRecursive_test.go b/traversal/selector/exploreRecursive_test.go index f8936a1c..9dd6fe4d 100644 --- a/traversal/selector/exploreRecursive_test.go +++ b/traversal/selector/exploreRecursive_test.go @@ -202,28 +202,28 @@ func TestExploreRecursiveExplore(t *testing.T) { err := dagjson.Decode(nb, strings.NewReader(nodeString)) Wish(t, err, ShouldEqual, nil) rn := nb.Build() - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, nil) Wish(t, err, ShouldEqual, nil) @@ -253,31 +253,31 @@ func TestExploreRecursiveExplore(t *testing.T) { err := dagjson.Decode(nb, strings.NewReader(nodeString)) Wish(t, err, ShouldEqual, nil) rn := nb.Build() - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) @@ -296,11 +296,11 @@ func TestExploreRecursiveExplore(t *testing.T) { err := dagjson.Decode(nb, strings.NewReader(nodeString)) Wish(t, err, ShouldEqual, nil) rn := nb.Build() - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) Wish(t, rs, ShouldEqual, nil) }) t.Run("exploring should work when there is nested recursion", func(t *testing.T) { @@ -345,15 +345,15 @@ func TestExploreRecursiveExplore(t *testing.T) { // traverse down Parent nodes rn := n rs = s - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) @@ -361,19 +361,19 @@ func TestExploreRecursiveExplore(t *testing.T) { // traverse down top level Side tree (nested recursion) rn = n rs = s - rs = rs.Explore(rn, ipld.PathSegmentOfString("Side")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Side")) rn, err = rn.LookupByString("Side") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("real")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("real")) rn, err = rn.LookupByString("real") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("apple")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("apple")) rn, err = rn.LookupByString("apple") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("sauce")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("sauce")) rn, err = rn.LookupByString("sauce") Wish(t, rs, ShouldEqual, nil) Wish(t, err, ShouldEqual, nil) @@ -381,23 +381,23 @@ func TestExploreRecursiveExplore(t *testing.T) { // traverse once down Parent (top level recursion) then down Side tree (nested recursion) rn = n rs = s - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Side")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Side")) rn, err = rn.LookupByString("Side") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("cheese")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("cheese")) rn, err = rn.LookupByString("cheese") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("whiz")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("whiz")) rn, err = rn.LookupByString("whiz") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) @@ -426,20 +426,20 @@ func TestExploreRecursiveExplore(t *testing.T) { err := dagjson.Decode(nb, strings.NewReader(nodeString)) Wish(t, err, ShouldEqual, nil) rn := nb.Build() - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreUnion{[]Selector{Matcher{}, subTree}}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreUnion{[]Selector{Matcher{}, subTree}}, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}) Wish(t, err, ShouldEqual, nil) diff --git a/traversal/selector/exploreUnion.go b/traversal/selector/exploreUnion.go index 3918fa73..67a90f60 100644 --- a/traversal/selector/exploreUnion.go +++ b/traversal/selector/exploreUnion.go @@ -42,22 +42,25 @@ func (s ExploreUnion) Interests() []ipld.PathSegment { // - a new union selector if more than one member returns a selector // - if exactly one member returns a selector, that selector // - nil if no members return a selector -func (s ExploreUnion) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreUnion) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { // TODO: memory efficient? nonNilResults := make([]Selector, 0, len(s.Members)) for _, member := range s.Members { - resultSelector := member.Explore(n, p) + resultSelector, err := member.Explore(n, p) + if err != nil { + return nil, err + } if resultSelector != nil { nonNilResults = append(nonNilResults, resultSelector) } } if len(nonNilResults) == 0 { - return nil + return nil, nil } if len(nonNilResults) == 1 { - return nonNilResults[0] + return nonNilResults[0], nil } - return ExploreUnion{nonNilResults} + return ExploreUnion{nonNilResults}, nil } // Decide returns true for a Union selector if any of the member selectors diff --git a/traversal/selector/exploreUnion_test.go b/traversal/selector/exploreUnion_test.go index 8e5c005d..84d7b1df 100644 --- a/traversal/selector/exploreUnion_test.go +++ b/traversal/selector/exploreUnion_test.go @@ -57,14 +57,14 @@ func TestExploreUnionExplore(t *testing.T) { }) t.Run("exploring should return nil if all member selectors return nil when explored", func(t *testing.T) { s := ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}}}} - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("if exactly one member selector returns a non-nil selector when explored, exploring should return that value", func(t *testing.T) { s := ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}}}} - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(2)) Wish(t, returnedSelector, ShouldEqual, Matcher{}) }) t.Run("exploring should return a new union selector if more than one member selector returns a non nil selector when explored", func(t *testing.T) { @@ -75,7 +75,7 @@ func TestExploreUnionExplore(t *testing.T) { ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []ipld.PathSegment{ipld.PathSegmentOfString("applesauce")}}, }} - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(2)) Wish(t, returnedSelector, ShouldEqual, ExploreUnion{[]Selector{Matcher{}, Matcher{}}}) }) } diff --git a/traversal/selector/matcher.go b/traversal/selector/matcher.go index 3c3aa1e2..dcf51ad5 100644 --- a/traversal/selector/matcher.go +++ b/traversal/selector/matcher.go @@ -25,8 +25,8 @@ func (s Matcher) Interests() []ipld.PathSegment { } // Explore will return nil because a matcher is a terminal selector -func (s Matcher) Explore(n ipld.Node, p ipld.PathSegment) Selector { - return nil +func (s Matcher) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { + return nil, nil } // Decide is always true for a match cause it's in the result set diff --git a/traversal/selector/selector.go b/traversal/selector/selector.go index 2f7f4a13..7fd149c7 100644 --- a/traversal/selector/selector.go +++ b/traversal/selector/selector.go @@ -21,8 +21,8 @@ import ( // you'll probably want to approach this be composing the Data Model declaration documents, // not be composing this type, which is only for the "compiled" result. type Selector interface { - Interests() []ipld.PathSegment // returns the segments we're likely interested in **or nil** if we're a high-cardinality or expression based matcher and need all segments proposed to us. - Explore(ipld.Node, ipld.PathSegment) Selector // explore one step -- iteration comes from outside (either whole node, or by following suggestions of Interests). returns nil if no interest. you have to traverse to the next node yourself (the selector doesn't do it for you because you might be considering multiple selection reasons at the same time). + Interests() []ipld.PathSegment // returns the segments we're likely interested in **or nil** if we're a high-cardinality or expression based matcher and need all segments proposed to us. + Explore(ipld.Node, ipld.PathSegment) (Selector, error) // explore one step -- iteration comes from outside (either whole node, or by following suggestions of Interests). returns nil if no interest. you have to traverse to the next node yourself (the selector doesn't do it for you because you might be considering multiple selection reasons at the same time). Decide(ipld.Node) bool } diff --git a/traversal/walk.go b/traversal/walk.go index 30253984..7251f047 100644 --- a/traversal/walk.go +++ b/traversal/walk.go @@ -114,7 +114,10 @@ func (prog Progress) walkAdv_iterateAll(n ipld.Node, s selector.Selector, fn Adv if err != nil { return err } - sNext := s.Explore(n, ps) + sNext, err := s.Explore(n, ps) + if err != nil { + return err + } if sNext != nil { progNext := prog progNext.Path = prog.Path.AppendSegment(ps) @@ -146,7 +149,10 @@ func (prog Progress) walkAdv_iterateSelective(n ipld.Node, attn []ipld.PathSegme if err != nil { continue } - sNext := s.Explore(n, ps) + sNext, err := s.Explore(n, ps) + if err != nil { + return err + } if sNext != nil { progNext := prog progNext.Path = prog.Path.AppendSegment(ps)